[Pkg-privacy-commits] [libotr] 61/225: 2012-04-30:

Ximin Luo infinity0 at moszumanska.debian.org
Sat Aug 22 12:44:54 UTC 2015


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch master
in repository libotr.

commit c87b3fe6531df4abf0add40f43e7a1cb8b70e3f7
Author: Rob Smits <rdfsmits at cs.uwaterloo.ca>
Date:   Wed May 2 16:44:14 2012 -0400

    2012-04-30:
    
    	* AUTHORS:
    	* README:
    	* test_suite/
    	* toolkit/otr_parse.c:
    	* toolkit/otr_remac.c:
    	* toolkit/parse.c:
    	* toolkit/parse.h:
    	* src/auth.c:
    	* src/auth.h:
    	* src/context.c:
    	* src/context.h:
    	* src/message.c:
    	* src/message.h:
    	* src/privkey.c:
    	* src/privkey.h:
    	* src/proto.c:
    	* src/proto.h:
    	* src/serial.h:
    	* src/tests.c:
    	* src/userstate.c:
    	* src/userstate.h: More changes for instance tags (Rob Smits).
    
    2009-06-11:
    
    	* src/auth.c:
    	* src/auth.h:
    	* src/context.c:
    	* src/context.h:
    	* src/context_priv.h:
    	* src/message.c:
    	* src/message.h:
    	* src/privkey.c:
    	* src/privkey.h:
    	* src/proto.c:
    	* src/proto.h:
    	* src/serial.h:
    	* src/tests.c:
    	* src/userstate.c:
    	* src/userstate.h: Core instance tag functionality (Lisa Du).
---
 AUTHORS                                |    2 +-
 ChangeLog                              |   41 ++
 README                                 |   66 +-
 libotr.pc.in                           |    2 +-
 src/Makefile.am                        |    4 +-
 src/auth.c                             |  227 +++++--
 src/auth.h                             |   34 +-
 src/b64.c                              |   56 +-
 src/b64.h                              |    2 +-
 src/context.c                          |  234 ++++++-
 src/context.h                          |   69 +-
 src/context_priv.c                     |    3 +-
 src/context_priv.h                     |    7 +-
 src/dh.c                               |    5 +-
 src/dh.h                               |    4 +-
 src/instag.c                           |  258 ++++++++
 src/instag.h                           |   89 +++
 src/mem.c                              |    2 +-
 src/mem.h                              |    2 +-
 src/message.c                          |  864 +++++++++++++++++---------
 src/message.h                          |   81 ++-
 src/privkey.c                          |   28 +-
 src/privkey.h                          |    6 +-
 src/proto.c                            |  427 ++++++++-----
 src/proto.h                            |   45 +-
 src/serial.h                           |   25 +-
 src/sm.c                               |  139 +++--
 src/sm.h                               |    2 +-
 src/tests.c                            |   47 +-
 src/tlv.c                              |    2 +-
 src/tlv.h                              |    4 +-
 src/userstate.c                        |    7 +-
 src/userstate.h                        |    6 +-
 src/version.h                          |    2 +-
 test_suite/README                      |   22 +
 test_suite/dummy_im.py                 |  211 +++++++
 test_suite/dummy_im/dummy_client.py    |  122 ++++
 test_suite/instance_tags0.txt          |    3 +
 test_suite/instance_tags1.txt          |    3 +
 test_suite/instance_tags2.txt          |    3 +
 test_suite/instance_tags3.txt          |    3 +
 test_suite/instance_tags4.txt          |    3 +
 test_suite/otr.private_key             |   41 ++
 test_suite/otr_c_client/Makefile       |    8 +
 test_suite/otr_c_client/README         |   10 +
 test_suite/otr_c_client/dummy_client.c | 1071 ++++++++++++++++++++++++++++++++
 test_suite/otr_subprocess.py           |  482 ++++++++++++++
 test_suite/otr_test.py                 |  250 ++++++++
 test_suite/otr_test_general.py         |  162 +++++
 test_suite/otr_test_mixed.py           |  153 +++++
 toolkit/aes.c                          |  482 +++++++-------
 toolkit/ctrmode.c                      |    2 +-
 toolkit/ctrmode.h                      |    2 +-
 toolkit/otr_mackey.c                   |    4 +-
 toolkit/otr_modify.c                   |    6 +-
 toolkit/otr_parse.c                    |   38 +-
 toolkit/otr_readforge.c                |    8 +-
 toolkit/otr_remac.c                    |   48 +-
 toolkit/otr_sesskeys.c                 |    2 +-
 toolkit/otr_toolkit.1                  |   11 +-
 toolkit/parse.c                        |  122 +++-
 toolkit/parse.h                        |   24 +-
 toolkit/readotr.c                      |    2 +-
 toolkit/readotr.h                      |    2 +-
 toolkit/sesskeys.c                     |    2 +-
 toolkit/sesskeys.h                     |    2 +-
 toolkit/sha1hmac.c                     |    2 +-
 toolkit/sha1hmac.h                     |    2 +-
 68 files changed, 5070 insertions(+), 1030 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index ef73f85..515238c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -2,7 +2,7 @@ Off-the-Record Messaging Library and Toolkit
 
 Authors:
 
-    Ian Goldberg, Chris Alexander, Willy Lew, Nikita Borisov
+    Ian Goldberg, Rob Smits, Chris Alexander, Willy Lew, Lisa Du, Nikita Borisov
     <otr at cypherpunks.ca>
 
 See the README file for mailing list information
diff --git a/ChangeLog b/ChangeLog
index 0e210f2..c1e81dd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,44 @@
+2012-04-30:
+
+	* AUTHORS:
+	* README:
+	* toolkit/otr_parse.c:
+	* toolkit/otr_remac.c:
+	* toolkit/parse.c:
+	* toolkit/parse.h:
+	* src/auth.c:
+	* src/auth.h:
+	* src/context.c:
+	* src/context.h:
+	* src/message.c:
+	* src/message.h:
+	* src/privkey.c:
+	* src/privkey.h:
+	* src/proto.c:
+	* src/proto.h:
+	* src/serial.h:
+	* src/tests.c:
+	* src/userstate.c:
+	* src/userstate.h: More changes for instance tags (Rob Smits).
+
+2009-06-11:
+
+	* src/auth.c:
+	* src/auth.h:
+	* src/context.c:
+	* src/context.h:
+	* src/context_priv.h:
+	* src/message.c:
+	* src/message.h:
+	* src/privkey.c:
+	* src/privkey.h:
+	* src/proto.c:
+	* src/proto.h:
+	* src/serial.h:
+	* src/tests.c:
+	* src/userstate.c:
+	* src/userstate.h: Core instance tag functionality (Lisa Du).
+
 2009-09-30:
 
 	* Protocol-v2.html: Edits from Göran Weinholt
diff --git a/README b/README
index 04e5092..ba112d0 100644
--- a/README
+++ b/README
@@ -1,5 +1,5 @@
 	      Off-the-Record Messaging Library and Toolkit
-			  v3.2.0, 15 Jun 2008
+			  v4.0.0, 2012
 
 This is a library and toolkit which implements Off-the-Record (OTR) Messaging.
 
@@ -49,6 +49,10 @@ To read stored private keys:
 
     otrl_privkey_read(userstate, privkeyfilename);
 
+To read stored instance tags:
+
+    otrl_instag_read(userstate, instagfilename);
+
 To read stored fingerprints:
 
     otrl_privkey_read_fingerprints(userstate, fingerprintfilename,
@@ -80,8 +84,14 @@ of the UI functions in message.h.
 3. Sending messages
 
 When you have a message you're about to send, you'll need to know four
-things: you account name, the protocol id, the name of the recipient, and
-the message.
+things: you account name, the protocol id, the name of the recipient, 
+their instance tag, and the message.
+
+OTR protocol version 3 introduces the notion of "instance tags." A
+client may be logged into the same account multiple times from different
+locations. An instance tag is intended to differentiate these clients. 
+When sending a message, you may also specify a particular instance tag, 
+or use meta instance tags like OTRL_INSTAG_MOST_SECURE.
 
 The protocol id is just a unique string that is used to distinguish
 the user foo on AIM from the user foo on MSN, etc.  It can be anything
@@ -108,8 +118,9 @@ next function), simply do this:
     char *newmessage = NULL;
 
     err = otrl_message_sending(userstate, &ui_ops, opdata, accountname,
-	    protocolid, recipient_name, message, tlvs, &newmessage,
-	    add_app_info, add_app_info_data);
+	    protocolid, recipient_name, instag, message, tlvs, 
+	    &newmessage, fragPolicy, contextp, add_app_info, 
+	    add_app_info_data);
 
 add_app_info and add_app_info_data are as above, and may be NULL.
 
@@ -117,6 +128,9 @@ tlvs should usually be NULL.  If it's not, then it points to a chain of
 OtrlTLVs which represent machine-readable data to send along with this
 message.
 
+If contextp is not NULL, it will be set to the context that was used
+for sending the message.
+
 If err is non-zero, then the library tried to encrypt the message,
 but for some reason failed.  DO NOT send the message in the clear in
 that case.
@@ -160,10 +174,13 @@ and the message.
 
     ignore_message = otrl_message_receiving(userstate, &ui_ops, opdata,
 	    accountname, protocolid, sender_name, message, &newmessage,
-	    &tlvs, add_app_info, add_app_info_data);
+	    &tlvs, contextp, add_app_info, add_app_info_data);
 
 add_app_info and add_app_info_data are as above, and may be NULL.
 
+If contextp is not NULL, it will be set to the context that was used
+for receiving the message.
+
 If otrl_message_receiving returns 1, then the message you received was
 an internal protocol message, and no message should be delivered to the
 user.
@@ -235,7 +252,8 @@ Here are the six programs in the toolkit:
      you can still forge messages of your choice using the otr_readforge
      command, above.
 
- - otr_remac mackey flags keyid keyid pubkey counter encdata revealed_mackeys
+ - otr_remac mackey sender_instance receiver_instance flags keyid keyid
+   pubkey counter encdata revealed_mackeys
    - Make a new OTR Data Message, with the given pieces (note that the
      data part is already encrypted).  MAC it with the given mackey.
 
@@ -244,6 +262,30 @@ NOTES
 Please send your bug reports, comments, suggestions, patches, etc. to us
 at the contact address below.
 
+In otrl_message_sending, specifying an instance tag allows you to send a
+message to a particular session of a buddy who is logged in multiple times
+with an otr-enabled client. The OTRL_INSTAG_RECENT_RECEIVED meta-instance
+relies on the time that libotr processed the most recent message. Meta-
+instance tags resolve to actual instance tags before a message is sent. An
+instant messaging network may not agree on which session of the remote party is
+the most recent, e.g., due to underlying network race conditions. If the
+behaviour of an instant messaging network is to only deliver to the most recent,
+and libotr and the network disagree on which session is the most recent, the
+other party will not process the given message. That is, the instant messaging
+network will deliver the message to the session whose actual instance tag does
+not match the addressed instance tag. Also note that OTRL_INSTAG_BEST also
+prefers more recent instance tags in the case of multiple instances with the
+same "best" status (most secure). In this case, the most recent has a
+resolution of one second.
+
+If otrl_message_sending is called with an original_msg that contains the text
+"?OTR?", this is a signal to initiate or refresh an OTR session. There is
+currently no way to indicate if this text was actually typed in by a user and
+part of a conversation (e.g., someone communicating instructions on how to
+refresh OTR). In the future, we may allow a policy to specify whether "?OTR?"
+is a signal to start OTR, or just an ordinary message for encrypted and
+unencrypted conversations.
+
 MAILING LISTS
 
 There are three mailing lists pertaining to Off-the-Record Messaging:
@@ -267,8 +309,8 @@ The Off-the-Record Messaging library (in the src directory) is
 covered by the following (LGPL) license:
 
     Off-the-Record Messaging library
-    Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
-    			     Nikita Borisov
+    Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+    			      Willy Lew, Lisa Du, Nikita Borisov
 			     <otr at cypherpunks.ca>
 
     This library is free software; you can redistribute it and/or
@@ -289,7 +331,8 @@ The Off-the-Record Messaging Toolkit (in the toolkit directory) is covered
 by the following (GPL) license:
 
     Off-the-Record Messaging Toolkit
-    Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+    Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+                             Nikita Borisov
 		             <otr at cypherpunks.ca>
 
     This program is free software; you can redistribute it and/or modify
@@ -311,7 +354,8 @@ CONTACT
 To report problems, comments, suggestions, patches, etc., you can email
 the authors:
 
-Ian Goldberg, Chris Alexander, and Nikita Borisov <otr at cypherpunks.ca>
+Ian Goldberg, Rob Smits, Chris Alexander, Lisa Du, Nikita Borisov
+<otr at cypherpunks.ca>
 
 For more information on Off-the-Record Messaging, visit
 http://otr.cypherpunks.ca/
diff --git a/libotr.pc.in b/libotr.pc.in
index 9538f8e..05b7d43 100644
--- a/libotr.pc.in
+++ b/libotr.pc.in
@@ -5,7 +5,7 @@ includedir=@includedir@
 
 Name: libotr
 Description: Off-the-Record Messaging Library
-Version: 3.1.0
+Version: @VERSION@
 URL: http://otr.cypherpunks.ca/
 Libs: -L${libdir} -lotr
 Cflags: -I${includedir}
diff --git a/src/Makefile.am b/src/Makefile.am
index cc4d26f..96e7ce6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -3,7 +3,7 @@ INCLUDES = @LIBGCRYPT_CFLAGS@
 lib_LTLIBRARIES = libotr.la
 
 libotr_la_SOURCES = privkey.c context.c proto.c b64.c dh.c mem.c message.c \
-		    userstate.c tlv.c auth.c sm.c context_priv.c
+		    userstate.c tlv.c auth.c sm.c context_priv.c instag.c
 
 libotr_la_LDFLAGS = -version-info @LIBOTR_LIBTOOL_VERSION@ @LIBS@ @LIBGCRYPT_LIBS@
 
@@ -11,4 +11,4 @@ otrincdir = $(includedir)/libotr
 
 otrinc_HEADERS = b64.h context.h dh.h mem.h message.h privkey.h proto.h \
 		 version.h userstate.h tlv.h serial.h auth.h sm.h privkey-t.h \
-		 context_priv.h
+		 context_priv.h instag.h
diff --git a/src/auth.c b/src/auth.c
index 36267b5..e3fad2e 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -28,12 +28,15 @@
 #include "privkey.h"
 #include "auth.h"
 #include "serial.h"
+#include "proto.h"
+#include "context.h"
 
 /*
  * Initialize the fields of an OtrlAuthInfo (already allocated).
  */
-void otrl_auth_new(OtrlAuthInfo *auth)
+void otrl_auth_new(struct context *context)
 {
+    OtrlAuthInfo *auth = &(context->auth);
     auth->authstate = OTRL_AUTHSTATE_NONE;
     otrl_dh_keypair_init(&(auth->our_dh));
     auth->our_keyid = 0;
@@ -55,6 +58,7 @@ void otrl_auth_new(OtrlAuthInfo *auth)
     memset(auth->secure_session_id, 0, 20);
     auth->secure_session_id_len = 0;
     auth->lastauthmsg = NULL;
+    auth->context = context;
 }
 
 /*
@@ -95,11 +99,11 @@ void otrl_auth_clear(OtrlAuthInfo *auth)
 }
 
 /*
- * Start a fresh AKE (version 2) using the given OtrlAuthInfo.  Generate
+ * Start a fresh AKE (version 2 or 3) using the given OtrlAuthInfo.  Generate
  * a fresh DH keypair to use.  If no error is returned, the message to
  * transmit will be contained in auth->lastauthmsg.
  */
-gcry_error_t otrl_auth_start_v2(OtrlAuthInfo *auth)
+gcry_error_t otrl_auth_start_v23(OtrlAuthInfo *auth, int version)
 {
     gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
     const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
@@ -112,6 +116,8 @@ gcry_error_t otrl_auth_start_v2(OtrlAuthInfo *auth)
     /* Clear out this OtrlAuthInfo and start over */
     otrl_auth_clear(auth);
     auth->initiated = 1;
+    auth->protocol_version = version;
+    auth->context->protocol_version = version;
 
     otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
     auth->our_keyid = 1;
@@ -152,15 +158,22 @@ gcry_error_t otrl_auth_start_v2(OtrlAuthInfo *auth)
     enc = NULL;
 
     /* Now serialize the message */
-    lenp = 3 + 4 + auth->encgx_len + 4 + 32;
+    lenp = OTRL_HEADER_LEN + (auth->protocol_version == 3 ? 8 : 0) + 4 
+	    + auth->encgx_len + 4 + 32;
     bufp = malloc(lenp);
     if (bufp == NULL) goto memerr;
     buf = bufp;
     buflen = lenp;
 
-    memmove(bufp, "\x00\x02\x02", 3); /* header */
-    debug_data("Header", bufp, 3);
-    bufp += 3; lenp -= 3;
+    /* Header */
+    write_header(auth->protocol_version, '\x02');
+    if (auth->protocol_version == 3) {
+	/* instance tags */
+	write_int(auth->context->our_instance);
+	debug_int("Sender instag", bufp-4);
+	write_int(auth->context->their_instance);
+	debug_int("Recipient instag", bufp-4);
+    }
 
     /* Encrypted g^x */
     write_int(auth->encgx_len);
@@ -206,15 +219,21 @@ static gcry_error_t create_key_message(OtrlAuthInfo *auth)
     size_t npub;
 
     gcry_mpi_print(format, NULL, 0, &npub, auth->our_dh.pub);
-    buflen = 3 + 4 + npub;
+    buflen = OTRL_HEADER_LEN + (auth->protocol_version == 3 ? 8 : 0) + 4 + npub;
     buf = malloc(buflen);
     if (buf == NULL) goto memerr;
     bufp = buf;
     lenp = buflen;
 
-    memmove(bufp, "\x00\x02\x0a", 3); /* header */
-    debug_data("Header", bufp, 3);
-    bufp += 3; lenp -= 3;
+    /* header */
+    write_header(auth->protocol_version, '\x0a');
+    if (auth->protocol_version == 3) {
+	/* instance tags */
+	write_int(auth->context->our_instance);
+	debug_int("Sender instag", bufp-4);
+	write_int(auth->context->their_instance);
+	debug_int("Recipient instag", bufp-4);
+    }
 
     /* g^y */
     write_mpi(auth->our_dh.pub, npub, "g^y");
@@ -239,7 +258,7 @@ memerr:
  * keypair to use.
  */
 gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth,
-	const char *commitmsg)
+	const char *commitmsg, int version)
 {
     gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
     unsigned char *buf = NULL, *bufp = NULL, *encbuf = NULL;
@@ -255,9 +274,14 @@ gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth,
     lenp = buflen;
 
     /* Header */
-    require_len(3);
-    if (memcmp(bufp, "\x00\x02\x02", 3)) goto invval;
-    bufp += 3; lenp -= 3;
+    auth->protocol_version = version;
+    auth->context->protocol_version = version;
+    skip_header('\x02');
+
+    if (version == 3) {
+	require_len(8);
+	bufp += 8; lenp -= 8;
+    }
 
     /* Encrypted g^x */
     read_int(enclen);
@@ -282,10 +306,12 @@ gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth,
 	case OTRL_AUTHSTATE_NONE:
 	case OTRL_AUTHSTATE_AWAITING_SIG:
 	case OTRL_AUTHSTATE_V1_SETUP:
-
 	    /* Store the incoming information */
 	    otrl_auth_clear(auth);
+	    auth->protocol_version = version;
+
 	    otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
+
 	    auth->our_keyid = 1;
 	    auth->encgx = encbuf;
 	    encbuf = NULL;
@@ -296,7 +322,6 @@ gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth,
 	    err = create_key_message(auth);
 	    if (err) goto err;
 	    auth->authstate = OTRL_AUTHSTATE_AWAITING_REVEALSIG;
-
 	    break;
 
 	case OTRL_AUTHSTATE_AWAITING_DHKEY:
@@ -310,6 +335,7 @@ gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth,
 	    } else {
 		/* Ours loses.  Use the incoming parameters instead. */
 		otrl_auth_clear(auth);
+		auth->protocol_version = version;
 		otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
 		auth->our_keyid = 1;
 		auth->encgx = encbuf;
@@ -373,7 +399,7 @@ static gcry_error_t calculate_pubkey_auth(unsigned char **authbufp,
 
     /* How big is the total structure to be MAC'd? */
     totallen = 4 + ourpublen + 4 + theirpublen + 2 + privkey->pubkey_datalen
-	+ 4;
+	    + 4;
     buf = malloc(totallen);
     if (buf == NULL) goto memerr;
 
@@ -582,16 +608,23 @@ static gcry_error_t create_revealsig_message(OtrlAuthInfo *auth,
 	    auth->our_dh.pub, auth->their_pub, privkey, auth->our_keyid);
     if (err) goto err;
 
-    buflen = 3 + 4 + 16 + 4 + authlen + 20;
+    buflen = OTRL_HEADER_LEN + (auth->protocol_version == 3 ? 8 : 0) + 4 + 16 
+	    + 4 + authlen + 20;
     buf = malloc(buflen);
     if (buf == NULL) goto memerr;
 
     bufp = buf;
     lenp = buflen;
 
-    memmove(bufp, "\x00\x02\x11", 3); /* header */
-    debug_data("Header", bufp, 3);
-    bufp += 3; lenp -= 3;
+    /* header */
+    write_header(auth->protocol_version, '\x11');
+    if (auth->protocol_version == 3) {
+	/* instance tags */
+	write_int(auth->context->our_instance);
+	debug_int("Sender instag", bufp-4);
+	write_int(auth->context->their_instance);
+	debug_int("Recipient instag", bufp-4);
+    }
 
     /* r */
     write_int(16);
@@ -654,16 +687,23 @@ static gcry_error_t create_signature_message(OtrlAuthInfo *auth,
 	    auth->our_keyid);
     if (err) goto err;
 
-    buflen = 3 + 4 + authlen + 20;
+    buflen = OTRL_HEADER_LEN + (auth->protocol_version == 3 ? 8 : 0) + 4 
+	    + authlen + 20;
     buf = malloc(buflen);
     if (buf == NULL) goto memerr;
 
     bufp = buf;
     lenp = buflen;
 
-    memmove(bufp, "\x00\x02\x12", 3); /* header */
-    debug_data("Header", bufp, 3);
-    bufp += 3; lenp -= 3;
+    /* header */
+    write_header(auth->protocol_version, '\x12');
+    if (auth->protocol_version == 3) {
+	/* instance tags */
+	write_int(auth->context->our_instance);
+	debug_int("Sender instag", bufp-4);
+	write_int(auth->context->their_instance);
+	debug_int("Recipient instag", bufp-4);
+    }
 
     /* Encrypted authenticator */
     startmac = bufp;
@@ -711,10 +751,12 @@ gcry_error_t otrl_auth_handle_key(OtrlAuthInfo *auth, const char *keymsg,
     unsigned char *buf = NULL, *bufp = NULL;
     size_t buflen, lenp;
     gcry_mpi_t incoming_pub = NULL;
-    int res;
+    int res, msg_version;
 
     *havemsgp = 0;
 
+    msg_version = otrl_proto_message_version(keymsg);
+
     res = otrl_base64_otr_decode(keymsg, &buf, &buflen);
     if (res == -1) goto memerr;
     if (res == -2) goto invval;
@@ -723,8 +765,12 @@ gcry_error_t otrl_auth_handle_key(OtrlAuthInfo *auth, const char *keymsg,
     lenp = buflen;
 
     /* Header */
-    if (memcmp(bufp, "\x00\x02\x0a", 3)) goto invval;
-    bufp += 3; lenp -= 3;
+    skip_header('\x0a');
+
+    if (msg_version == 3) {
+	require_len(8);
+	bufp += 8; lenp -= 8;
+    }
 
     /* g^y */
     read_mpi(incoming_pub);
@@ -735,6 +781,13 @@ gcry_error_t otrl_auth_handle_key(OtrlAuthInfo *auth, const char *keymsg,
 
     switch(auth->authstate) {
 	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	    /* The other party may also be establishing a session with
+	    another instance running a different version. Ignore any
+	    DHKEY messages we aren't expecting. */
+	    if (msg_version != auth->protocol_version) {
+	      goto err;
+	    }
+
 	    /* Store the incoming public key */
 	    gcry_mpi_release(auth->their_pub);
 	    auth->their_pub = incoming_pub;
@@ -808,6 +861,7 @@ gcry_error_t otrl_auth_handle_revealsig(OtrlAuthInfo *auth,
     gcry_mpi_t incoming_pub = NULL;
     unsigned char ctr[16], hashbuf[32];
     int res;
+    unsigned char version;
 
     *havemsgp = 0;
 
@@ -818,9 +872,16 @@ gcry_error_t otrl_auth_handle_revealsig(OtrlAuthInfo *auth,
     bufp = buf;
     lenp = buflen;
 
+    require_len(3);
+    version = bufp[1];
+
     /* Header */
-    if (memcmp(bufp, "\x00\x02\x11", 3)) goto invval;
-    bufp += 3; lenp -= 3;
+    skip_header('\x11');
+
+    if (version == 3) {
+	require_len(8);
+	bufp += 8; lenp -= 8;
+    }
 
     /* r */
     read_int(rlen);
@@ -898,6 +959,7 @@ gcry_error_t otrl_auth_handle_revealsig(OtrlAuthInfo *auth,
 	    /* Check the MAC */
 	    gcry_md_reset(auth->mac_m2);
 	    gcry_md_write(auth->mac_m2, authstart, authend - authstart);
+
 	    if (memcmp(macstart,
 			gcry_md_read(auth->mac_m2, GCRY_MD_SHA256),
 			20)) goto invval;
@@ -921,7 +983,6 @@ gcry_error_t otrl_auth_handle_revealsig(OtrlAuthInfo *auth,
 
 	    /* No error?  Then we've completed our end of the
 	     * authentication. */
-	    auth->protocol_version = 2;
 	    auth->session_id_half = OTRL_SESSIONID_SECOND_HALF_BOLD;
 	    if (auth_succeeded) err = auth_succeeded(auth, asdata);
 	    *havemsgp = 1;
@@ -974,6 +1035,7 @@ gcry_error_t otrl_auth_handle_signature(OtrlAuthInfo *auth,
     unsigned char *authstart, *authend, *macstart;
     size_t buflen, lenp, authlen;
     int res;
+    unsigned char version;
 
     *havemsgp = 0;
 
@@ -984,9 +1046,16 @@ gcry_error_t otrl_auth_handle_signature(OtrlAuthInfo *auth,
     bufp = buf;
     lenp = buflen;
 
+    require_len(3);
+    version = bufp[1];
+
     /* Header */
-    if (memcmp(bufp, "\x00\x02\x12", 3)) goto invval;
-    bufp += 3; lenp -= 3;
+    skip_header('\x12');
+
+    if (version == 3) {
+	require_len(8);
+	bufp += 8; lenp -= 8;
+    }
 
     /* auth */
     authstart = bufp;
@@ -1026,7 +1095,6 @@ gcry_error_t otrl_auth_handle_signature(OtrlAuthInfo *auth,
 
 	    /* No error?  Then we've completed our end of the
 	     * authentication. */
-	    auth->protocol_version = 2;
 	    auth->session_id_half = OTRL_SESSIONID_FIRST_HALF_BOLD;
 	    if (auth_succeeded) err = auth_succeeded(auth, asdata);
 	    free(auth->lastauthmsg);
@@ -1153,6 +1221,7 @@ gcry_error_t otrl_auth_start_v1(OtrlAuthInfo *auth, DH_keypair *our_dh,
     /* Clear out this OtrlAuthInfo and start over */
     otrl_auth_clear(auth);
     auth->initiated = 1;
+    auth->protocol_version = 1;
 
     /* Import the given DH keypair, or else create a fresh one */
     if (our_dh) {
@@ -1248,7 +1317,7 @@ gcry_error_t otrl_auth_handle_v1_key_exchange(OtrlAuthInfo *auth,
     pubs = NULL;
     free(buf);
     buf = NULL;
-    
+
     if (auth->authstate != OTRL_AUTHSTATE_V1_SETUP && received_reply == 0x01) {
 	/* They're replying to something we never sent.  We must be
 	 * logged in more than once; ignore the message. */
@@ -1314,17 +1383,87 @@ err:
     return err;
 }
 
+/*
+ * Copy relevant information from the master OtrlAuthInfo to an
+ * instance OtrlAuthInfo in response to a D-H Commit with a new
+ * instance. The fields copied will depend on the state of the
+ * master auth.
+ */
+gcry_error_t otrl_auth_copy_on_commit(OtrlAuthInfo *m_auth,
+	OtrlAuthInfo *auth)
+{
+    switch(m_auth->authstate) {
+	case OTRL_AUTHSTATE_NONE:
+	case OTRL_AUTHSTATE_AWAITING_REVEALSIG:
+	    auth->authstate = OTRL_AUTHSTATE_NONE;
+	    break;
+	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	    /* We sent a D-H Commit Message, and we also received one.
+	     *  Copy our D_H Commit and auth state */
+	    otrl_dh_keypair_free(&(auth->our_dh));
+	    auth->initiated = m_auth->initiated;
+	    otrl_dh_keypair_copy(&(auth->our_dh), &(m_auth->our_dh));
+	    auth->our_keyid = m_auth->our_keyid;
+	    memmove(auth->r, m_auth->r, 16);
+	    if (auth->encgx) free(auth->encgx);
+	    auth->encgx = malloc(m_auth->encgx_len);
+	    memmove(auth->encgx, m_auth->encgx, m_auth->encgx_len);
+	    memmove(auth->hashgx, m_auth->hashgx, 32);
+
+	    auth->authstate = OTRL_AUTHSTATE_AWAITING_DHKEY;
+	    break;
+
+	default:
+	    /* This bad state will be detected and handled later */
+	    break;
+    }
+}
+
+/*
+ * Copy relevant information from the master OtrlAuthInfo to an
+ * instance OtrlAuthInfo in response to a D-H Key with a new
+ * instance. The fields copied will depend on the state of the
+ * master auth.
+ */
+gcry_error_t otrl_auth_copy_on_key(OtrlAuthInfo *m_auth,
+	OtrlAuthInfo *auth)
+{
+    switch(m_auth->authstate) {
+	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	case OTRL_AUTHSTATE_AWAITING_SIG:
+	    /* Copy our D-H Commit information to the new instance */
+	    otrl_dh_keypair_free(&(auth->our_dh));
+	    auth->initiated = m_auth->initiated;
+	    otrl_dh_keypair_copy(&(auth->our_dh), &(m_auth->our_dh));
+	    auth->our_keyid = m_auth->our_keyid;
+	    memmove(auth->r, m_auth->r, 16);
+	    if (auth->encgx) free(auth->encgx);
+	    auth->encgx = malloc(m_auth->encgx_len);
+	    memmove(auth->encgx, m_auth->encgx, m_auth->encgx_len);
+	    memmove(auth->hashgx, m_auth->hashgx, 32);
+
+	    auth->authstate = OTRL_AUTHSTATE_AWAITING_DHKEY;
+	    break;
+
+	default:
+	    /* This bad state will be detected and handled later */
+	    break;
+    }
+}
+
 #ifdef OTRL_TESTING_AUTH
 #include "mem.h"
 #include "privkey.h"
 
-#define CHECK_ERR if (err) { printf("Error: %s\n", gcry_strerror(err)); return 1; }
+#define CHECK_ERR if (err) { printf("Error: %s\n", gcry_strerror(err)); \
+			return 1; }
 
 static gcry_error_t starting(const OtrlAuthInfo *auth, void *asdata)
 {
     char *name = asdata;
 
-    fprintf(stderr, "\nStarting ENCRYPTED mode for %s (v%d).\n", name, auth->protocol_version);
+    fprintf(stderr, "\nStarting ENCRYPTED mode for %s (v%d).\n",
+	    name, auth->protocol_version);
 
     fprintf(stderr, "\nour_dh (%d):", auth->our_keyid);
     gcry_mpi_dump(auth->our_dh.pub);
@@ -1359,7 +1498,7 @@ int main(int argc, char **argv)
 
     printf("\n\n  ***** V2 *****\n\n");
 
-    err = otrl_auth_start_v2(&bob, NULL, 0);
+    err = otrl_auth_start_v23(&bob, NULL, 0);
     CHECK_ERR
     printf("\nBob: %d\n%s\n\n", strlen(bob.lastauthmsg), bob.lastauthmsg);
     err = otrl_auth_handle_commit(&alice, bob.lastauthmsg, NULL, 0);
@@ -1376,7 +1515,8 @@ int main(int argc, char **argv)
 	    alicepriv, starting, "Alice");
     CHECK_ERR
     if (havemsg) {
-	printf("\nAlice: %d\n%s\n\n", strlen(alice.lastauthmsg), alice.lastauthmsg);
+	printf("\nAlice: %d\n%s\n\n", strlen(alice.lastauthmsg),
+		alice.lastauthmsg);
     } else {
 	printf("\nIGNORE\n\n");
     }
@@ -1393,7 +1533,8 @@ int main(int argc, char **argv)
 	    &havemsg, alicepriv, NULL, 0, starting, "Alice");
     CHECK_ERR
     if (havemsg) {
-	printf("\nAlice: %d\n%s\n\n", strlen(alice.lastauthmsg), alice.lastauthmsg);
+	printf("\nAlice: %d\n%s\n\n", strlen(alice.lastauthmsg),
+		alice.lastauthmsg);
     } else {
 	printf("\nIGNORE\n\n");
     }
diff --git a/src/auth.h b/src/auth.h
index 4ca5aa3..ee69ade 100644
--- a/src/auth.h
+++ b/src/auth.h
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -24,6 +24,7 @@
 #include <gcrypt.h>
 #include "dh.h"
 
+
 typedef enum {
     OTRL_AUTHSTATE_NONE,
     OTRL_AUTHSTATE_AWAITING_DHKEY,
@@ -35,6 +36,8 @@ typedef enum {
 typedef struct {
     OtrlAuthState authstate;              /* Our state */
 
+    struct context *context;              /* The context which points to us */
+
     DH_keypair our_dh;                    /* Our D-H key */
     unsigned int our_keyid;               /* ...and its keyid */
 
@@ -47,6 +50,7 @@ typedef struct {
     gcry_mpi_t their_pub;                 /* Their D-H public key */
     unsigned int their_keyid;             /*  ...and its keyid */
 
+
     gcry_cipher_hd_t enc_c, enc_cp;       /* c and c' encryption keys */
     gcry_md_hd_t mac_m1, mac_m1p;         /* m1 and m1' MAC keys */
     gcry_md_hd_t mac_m2, mac_m2p;         /* m2 and m2' MAC keys */
@@ -78,7 +82,7 @@ typedef struct {
 /*
  * Initialize the fields of an OtrlAuthInfo (already allocated).
  */
-void otrl_auth_new(OtrlAuthInfo *auth);
+void otrl_auth_new(struct context *context);
 
 /*
  * Clear the fields of an OtrlAuthInfo (but leave it allocated).
@@ -86,11 +90,11 @@ void otrl_auth_new(OtrlAuthInfo *auth);
 void otrl_auth_clear(OtrlAuthInfo *auth);
 
 /*
- * Start a fresh AKE (version 2) using the given OtrlAuthInfo.  Generate
+ * Start a fresh AKE (version 2 or 3) using the given OtrlAuthInfo.  Generate
  * a fresh DH keypair to use.  If no error is returned, the message to
  * transmit will be contained in auth->lastauthmsg.
  */
-gcry_error_t otrl_auth_start_v2(OtrlAuthInfo *auth);
+gcry_error_t otrl_auth_start_v23(OtrlAuthInfo *auth, int version);
 
 /*
  * Handle an incoming D-H Commit Message.  If no error is returned, the
@@ -98,7 +102,7 @@ gcry_error_t otrl_auth_start_v2(OtrlAuthInfo *auth);
  * keypair to use.
  */
 gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth,
-	const char *commitmsg);
+	const char *commitmsg, int version);
 
 /*
  * Handle an incoming D-H Key Message.  If no error is returned, and
@@ -155,4 +159,22 @@ gcry_error_t otrl_auth_handle_v1_key_exchange(OtrlAuthInfo *auth,
 	gcry_error_t (*auth_succeeded)(const OtrlAuthInfo *auth, void *asdata),
 	void *asdata);
 
+/*
+ * Copy relevant information from the master OtrlAuthInfo to an
+ * instance OtrlAuthInfo in response to a D-H Commit with a new
+ * instance. The fields copied will depend on the state of the
+ * master auth.
+ */
+gcry_error_t otrl_auth_copy_on_commit(OtrlAuthInfo *m_auth,
+	OtrlAuthInfo *auth);
+
+/*
+ * Copy relevant information from the master OtrlAuthInfo to an
+ * instance OtrlAuthInfo in response to a D-H Key with a new
+ * instance. The fields copied will depend on the state of the
+ * master auth.
+ */
+gcry_error_t otrl_auth_copy_on_key(OtrlAuthInfo *m_auth,
+	OtrlAuthInfo *auth);
+
 #endif
diff --git a/src/b64.c b/src/b64.c
index bfe163a..cc6e7cb 100644
--- a/src/b64.c
+++ b/src/b64.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *  			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
@@ -28,30 +28,30 @@ AUTHOR:         Bob Trower 08/04/01
 
 LICENCE:        Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc.
 
-                Permission is hereby granted, free of charge, to any person
-                obtaining a copy of this software and associated
-                documentation files (the "Software"), to deal in the
-                Software without restriction, including without limitation
-                the rights to use, copy, modify, merge, publish, distribute,
-                sublicense, and/or sell copies of the Software, and to
-                permit persons to whom the Software is furnished to do so,
-                subject to the following conditions:
-
-                The above copyright notice and this permission notice shall
-                be included in all copies or substantial portions of the
-                Software.
-
-                THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-                KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-                WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-                PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-                OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-                OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-                OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-                SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+		Permission is hereby granted, free of charge, to any person
+		obtaining a copy of this software and associated
+		documentation files (the "Software"), to deal in the
+		Software without restriction, including without limitation
+		the rights to use, copy, modify, merge, publish, distribute,
+		sublicense, and/or sell copies of the Software, and to
+		permit persons to whom the Software is furnished to do so,
+		subject to the following conditions:
+
+		The above copyright notice and this permission notice shall
+		be included in all copies or substantial portions of the
+		Software.
+
+		THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+		KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+		WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+		PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+		OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+		OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+		OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+		SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 VERSION HISTORY:
-                Bob Trower 08/04/01 -- Create Version 0.00.00B
+		Bob Trower 08/04/01 -- Create Version 0.00.00B
 
 \******************************************************************* */
 
@@ -88,9 +88,9 @@ static void encodeblock( char *out, const unsigned char *in, size_t len )
     out[0] = cb64[ in0 >> 2 ];
     out[1] = cb64[ ((in0 & 0x03) << 4) | ((in1 & 0xf0) >> 4) ];
     out[2] = len > 1 ? cb64[ ((in1 & 0x0f) << 2) | ((in2 & 0xc0) >> 6) ]
-	             : '=';
+		     : '=';
     out[3] = len > 2 ? cb64[ in2 & 0x3f ]
-	             : '=';
+		     : '=';
 }
 
 /*
@@ -120,7 +120,7 @@ size_t otrl_base64_encode(char *base64data, const unsigned char *data,
 }
 
 static size_t decode(unsigned char *out, const char *in, size_t b64len)
-{   
+{
     size_t written = 0;
     unsigned char c = 0;
 
@@ -228,9 +228,10 @@ int otrl_base64_otr_decode(const char *msg, unsigned char **bufp,
     if (!otrtag) {
 	return -2;
     }
+
     endtag = strchr(otrtag, '.');
     if (endtag) {
-        msglen = endtag-otrtag;
+	msglen = endtag-otrtag;
     } else {
 	return -2;
     }
@@ -241,6 +242,7 @@ int otrl_base64_otr_decode(const char *msg, unsigned char **bufp,
     if (!rawmsg && rawlen > 0) {
 	return -1;
     }
+
     rawlen = otrl_base64_decode(rawmsg, otrtag+5, msglen-5);  /* actual size */
 
     *bufp = rawmsg;
diff --git a/src/b64.h b/src/b64.h
index 9a4e636..96f4f28 100644
--- a/src/b64.h
+++ b/src/b64.h
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *  			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
diff --git a/src/context.c b/src/context.c
index e584769..1a166fb 100644
--- a/src/context.c
+++ b/src/context.c
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -27,6 +27,10 @@
 
 /* libotr headers */
 #include "context.h"
+#include "instag.h"
+
+static void set_instance_fingerprint_next(OtrlUserState us,
+	ConnContext *context);
 
 /* Create a new connection context. */
 static ConnContext * new_context(const char * user, const char * accountname,
@@ -34,19 +38,24 @@ static ConnContext * new_context(const char * user, const char * accountname,
 {
     ConnContext * context;
     OtrlSMState *smstate;
-    context = malloc(sizeof(*context));
+
+    context = malloc(sizeof(ConnContext));
     assert(context != NULL);
+
     context->username = strdup(user);
     context->accountname = strdup(accountname);
     context->protocol = strdup(protocol);
+
     context->msgstate = OTRL_MSGSTATE_PLAINTEXT;
-    otrl_auth_new(&(context->auth));
+    otrl_auth_new(context);
 
     smstate = malloc(sizeof(OtrlSMState));
     assert(smstate != NULL);
     otrl_sm_state_new(smstate);
     context->smstate = smstate;
 
+    context->our_instance = 0;
+    context->their_instance = OTRL_INSTAG_MASTER;
     context->fingerprint_root.fingerprint = NULL;
     context->fingerprint_root.context = context;
     context->fingerprint_root.next = NULL;
@@ -61,39 +70,147 @@ static ConnContext * new_context(const char * user, const char * accountname,
     context->context_priv = context_priv_new();
     assert(context->context_priv != NULL);
     context->next = NULL;
+    context->m_context = context;
+    context->recent_rcvd_child = NULL;
+    context->recent_sent_child = NULL;
+    context->recent_child = NULL;
+
     return context;
 }
 
-/* Look up a connection context by name/account/protocol from the given
+ConnContext * otrl_context_find_recent_instance(ConnContext * context,
+	otrl_instag_t recent_instag) {
+    ConnContext * m_context;
+
+    if (!context) return NULL;
+
+    m_context = context->m_context;
+
+    if (!m_context) return NULL;
+
+    switch(recent_instag) {
+	case OTRL_INSTAG_RECENT:
+	    return m_context->recent_child;
+	case OTRL_INSTAG_RECENT_RECEIVED:
+	    return m_context->recent_rcvd_child;
+	case OTRL_INSTAG_RECENT_SENT:
+	    return m_context->recent_sent_child;
+	default:
+	    return NULL;
+    }
+}
+
+/* Find the instance of this context that has the best security level,
+   and for which we have most recently received a message from. Note that most
+   recent in this case is limited to a one-second resolution. */
+ConnContext * otrl_context_find_recent_secure_instance(ConnContext * context)
+{
+    ConnContext *curp; /* for iteration */
+    ConnContext *m_context; /* master */
+    ConnContext *cresult = context;  /* best so far */
+
+    if (!context) {
+	return cresult;
+    }
+
+    m_context = context->m_context;
+
+    for (curp = m_context; curp && curp->m_context == m_context;
+	    curp = curp->next) {
+	int msgstate_improved = 0; /* 0 == same, 1 == improved   */
+	int trust_improved = 0;    /* (will immediately 'continue' if worse
+				    * than) */
+
+	if (cresult->msgstate == curp->msgstate) {
+	    msgstate_improved = 0;
+	} else if (curp->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+	    msgstate_improved = 1;
+	} else {
+	    continue;
+	}
+
+
+	if (otrl_context_is_fingerprint_trusted(cresult->active_fingerprint) ==
+		otrl_context_is_fingerprint_trusted(curp->active_fingerprint)) {
+
+	    trust_improved = 0;
+	} else if
+		(otrl_context_is_fingerprint_trusted(curp->active_fingerprint)){
+
+	    trust_improved = 1;
+	} else {
+	    continue;
+	}
+
+	if (msgstate_improved || trust_improved ||
+		(!msgstate_improved && !trust_improved &&
+		curp->context_priv->lastrecv >=
+		cresult->context_priv->lastrecv)) {
+	    cresult = curp;
+	}
+    }
+
+    return cresult;
+}
+
+/* Look up a connection context by name/account/protocol/instag from the given
  * OtrlUserState.  If add_if_missing is true, allocate and return a new
  * context if one does not currently exist.  In that event, call
  * add_app_data(data, context) so that app_data and app_data_free can be
- * filled in by the application, and set *addedp to 1. */
+ * filled in by the application, and set *addedp to 1.
+ * In the 'their_instance' field note that you can also specify a 'meta-
+ * instance' value such as OTRL_INSTAG_MASTER, OTRL_INSTAL_RECENT,
+ * OTRL_INSTAG_RECENT_RECEIVED and OTRL_INSTAG_RECENT_SENT. */
 ConnContext * otrl_context_find(OtrlUserState us, const char *user,
-	const char *accountname, const char *protocol, int add_if_missing,
-	int *addedp,
+	const char *accountname, const char *protocol,
+	otrl_instag_t their_instance, int add_if_missing, int *addedp,
 	void (*add_app_data)(void *data, ConnContext *context), void *data)
 {
     ConnContext ** curp;
     int usercmp = 1, acctcmp = 1, protocmp = 1;
     if (addedp) *addedp = 0;
     if (!user || !accountname || !protocol) return NULL;
+
     for (curp = &(us->context_root); *curp; curp = &((*curp)->next)) {
-        if ((usercmp = strcmp((*curp)->username, user)) > 0 ||
+	if ((usercmp = strcmp((*curp)->username, user)) > 0 ||
 		(usercmp == 0 &&
-		  (acctcmp = strcmp((*curp)->accountname, accountname)) > 0) ||
+		(acctcmp = strcmp((*curp)->accountname, accountname)) > 0) ||
 		(usercmp == 0 && acctcmp == 0 &&
-		  (protocmp = strcmp((*curp)->protocol, protocol)) >= 0))
+		(protocmp = strcmp((*curp)->protocol, protocol)) >= 0)
+		&& (their_instance < OTRL_MIN_VALID_INSTAG || 
+		(their_instance == (*curp)->their_instance)))
 	    /* We're at the right place in the list.  We've either found
 	     * it, or gone too far. */
 	    break;
     }
-    if (usercmp == 0 && acctcmp == 0 && protocmp == 0) {
-	/* Found it! */
-	return *curp;
+
+    if (usercmp == 0 && acctcmp == 0 && protocmp == 0 && *curp &&
+	    (their_instance < OTRL_MIN_VALID_INSTAG ||
+	    (their_instance == (*curp)->their_instance))) {
+	/* Found one! */
+	if (their_instance >= OTRL_MIN_VALID_INSTAG ||
+		their_instance == OTRL_INSTAG_MASTER) {
+	    return *curp;
+	}
+
+	/* We need to go back and check more values in the context */
+	switch(their_instance) {
+	    case OTRL_INSTAG_BEST:
+		return otrl_context_find_recent_secure_instance(*curp);
+	    case OTRL_INSTAG_RECENT:
+	    case OTRL_INSTAG_RECENT_RECEIVED:
+	    case OTRL_INSTAG_RECENT_SENT:
+		return otrl_context_find_recent_instance(*curp, their_instance);
+	    default:
+		return NULL;
+	}
     }
+
     if (add_if_missing) {
 	ConnContext *newctx;
+	OtrlInsTag *our_instag = (OtrlInsTag *)otrl_instag_find(us, accountname,
+		protocol);
+
 	if (addedp) *addedp = 1;
 	newctx = new_context(user, accountname, protocol);
 	newctx->next = *curp;
@@ -105,22 +222,71 @@ ConnContext * otrl_context_find(OtrlUserState us, const char *user,
 	if (add_app_data) {
 	    add_app_data(data, *curp);
 	}
+
+	/* Initialize specified instance tags */
+	if (our_instag) {
+	    newctx->our_instance = our_instag->instag;
+	}
+
+	if (their_instance >= OTRL_MIN_VALID_INSTAG ||
+		their_instance == OTRL_INSTAG_MASTER) {
+	    newctx->their_instance = their_instance;
+	}
+
+	if (their_instance >= OTRL_MIN_VALID_INSTAG) {
+	    newctx->m_context = otrl_context_find(us, user, accountname,
+		protocol, OTRL_INSTAG_MASTER, 1, NULL, add_app_data, data);
+	}
+
+	if (their_instance == OTRL_INSTAG_MASTER) {
+	    /* if we're adding a master, there are no children, so the most
+	     * recent context is the one we add. */
+	    newctx->recent_child = newctx;
+	    newctx->recent_rcvd_child = newctx;
+	    newctx->recent_sent_child = newctx;
+	}
+
 	return *curp;
     }
     return NULL;
 }
 
+int otrl_context_is_fingerprint_trusted(Fingerprint *fprint) {
+    return fprint && fprint->trust && fprint->trust[0] != '\0';
+}
+
+void otrl_context_update_recent_child(ConnContext *context,
+	unsigned int sent_msg) {
+    ConnContext *m_context = context->m_context;
+
+    if (sent_msg) {
+	m_context->recent_sent_child = context;
+    } else {
+	m_context->recent_rcvd_child = context;
+    }
+
+    m_context->recent_child = context;
+
+}
+
 /* Find a fingerprint in a given context, perhaps adding it if not
  * present. */
 Fingerprint *otrl_context_find_fingerprint(ConnContext *context,
 	unsigned char fingerprint[20], int add_if_missing, int *addedp)
 {
-    Fingerprint *f = context->fingerprint_root.next;
+    Fingerprint *f;
     if (addedp) *addedp = 0;
+
+    if (!context || !context->m_context) return NULL;
+
+    context = context->m_context;
+
+    f = context->fingerprint_root.next;
     while(f) {
 	if (!memcmp(f->fingerprint, fingerprint, 20)) return f;
 	f = f->next;
     }
+
     /* Didn't find it. */
     if (add_if_missing) {
 	if (addedp) *addedp = 1;
@@ -191,6 +357,7 @@ void otrl_context_forget_fingerprint(Fingerprint *fprint,
     } else {
 	if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT ||
 		context->active_fingerprint != fprint) {
+
 	    free(fprint->fingerprint);
 	    free(fprint->trust);
 	    *(fprint->tous) = fprint->next;
@@ -209,10 +376,33 @@ void otrl_context_forget_fingerprint(Fingerprint *fprint,
     }
 }
 
-/* Forget a whole context, so long as it's PLAINTEXT. */
-void otrl_context_forget(ConnContext *context)
+/* Forget a whole context, so long as it's PLAINTEXT. If a context has child
+ * instances, don't remove this instance unless children are also all in
+ * PLAINTEXT state. In this case, the children will also be removed.
+ * Returns 0 on success, 1 on failure. */
+int otrl_context_forget(ConnContext *context)
 {
-    if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT) return;
+    if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT) return 1;
+
+    if (context->their_instance == OTRL_INSTAG_MASTER) {
+	ConnContext *c_iter;
+
+	for (c_iter = context; c_iter &&
+		c_iter->m_context == context->m_context;
+		c_iter = c_iter->next) {
+	    if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT) return 1;
+	}
+
+	c_iter = context->next;
+	while (c_iter && c_iter->m_context == context->m_context) {
+	    if (!otrl_context_forget(c_iter)) {
+		c_iter = context->next;
+	    } else {
+		return 1;
+	    }
+	}
+
+    }
 
     /* Just to be safe, force to plaintext.  This also frees any
      * extraneous data lying around. */
@@ -245,13 +435,19 @@ void otrl_context_forget(ConnContext *context)
     }
 
     free(context);
+    return 0;
 }
 
 /* Forget all the contexts in a given OtrlUserState. */
 void otrl_context_forget_all(OtrlUserState us)
 {
+    ConnContext *c_iter;
+
+    for (c_iter = us->context_root; c_iter; c_iter = c_iter->next) {
+	otrl_context_force_plaintext(c_iter);
+    }
+
     while (us->context_root) {
-	otrl_context_force_plaintext(us->context_root);
 	otrl_context_forget(us->context_root);
     }
 }
diff --git a/src/context.h b/src/context.h
index 1ae658a..d7595a9 100644
--- a/src/context.h
+++ b/src/context.h
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -29,6 +29,10 @@
 #include "auth.h"
 #include "sm.h"
 
+typedef struct context ConnContext;    /* Forward declare */
+
+#include "instag.h"
+
 typedef enum {
     OTRL_MSGSTATE_PLAINTEXT,           /* Not yet started an encrypted
 					  conversation */
@@ -50,7 +54,7 @@ typedef struct s_fingerprint {
     char *trust;                       /* The trust level of the fingerprint */
 } Fingerprint;
 
-typedef struct context {
+struct context {
     struct context * next;             /* Linked list pointer */
     struct context ** tous;            /* A pointer to the pointer to us */
 
@@ -65,15 +69,39 @@ typedef struct context {
 					  this account... */
     char * protocol;                   /* ... and this protocol */
 
+    struct context *m_context;         /* If this is a child context, this
+					  field will point to the master
+					  context. Otherwise it will point to
+					  itself. */
+    struct context *recent_rcvd_child; /* If this is a master context, this
+					  points to the child context that
+					  has received a message most recently.
+					  By default, it will point to the
+					  master context. In child contexts
+					  this field is NULL. */
+    struct context *recent_sent_child; /* Similar to above, but it points to
+					  the child who has sent most
+					  recently. */
+    struct context *recent_child;      /* Similar to above, but will point to
+					  the most recent of recent_rcvd_child
+					  and recent_sent_child */
+
+    otrl_instag_t our_instance;        /* Our instance tag for this computer*/
+    otrl_instag_t their_instance;      /* The user's instance tag */
+
     OtrlMessageState msgstate;         /* The state of message disposition
 					  with this user */
     OtrlAuthInfo auth;                 /* The state of ongoing
 					  authentication with this user */
 
     Fingerprint fingerprint_root;      /* The root of a linked list of
-					  Fingerprints entries */
+					  Fingerprints entries. This list will
+					  only be populated in master contexts.
+					  For child contexts,
+					  fingerprint_root.next will always
+					  point to NULL. */
     Fingerprint *active_fingerprint;   /* Which fingerprint is in use now?
-                                          A pointer into the above list */
+					  A pointer into the above list */
 
     unsigned char sessionid[20];       /* The sessionid and bold half */
     size_t sessionid_len;              /* determined when this private */
@@ -95,21 +123,29 @@ typedef struct context {
     void (*app_data_free)(void *);
 
     OtrlSMState *smstate;              /* The state of the current
-                                          socialist millionaires exchange */
-} ConnContext;
+					  socialist millionaires exchange */
+};
 
 #include "userstate.h"
 
-/* Look up a connection context by name/account/protocol from the given
- * OtrlUserState.  If add_if_missing is true, allocate and return a new
- * context if one does not currently exist.  In that event, call
+/* Look up a connection context by name/account/protocol/instance from the
+ * given OtrlUserState.  If add_if_missing is true, allocate and return a
+ * new context if one does not currently exist.  In that event, call
  * add_app_data(data, context) so that app_data and app_data_free can be
- * filled in by the application, and set *addedp to 1. */
+ * filled in by the application, and set *addedp to 1.
+ * In the 'their_instance' field note that you can also specify a 'meta-
+ * instance' value such as OTRL_INSTAG_MASTER, OTRL_INSTAL_RECENT,
+ * OTRL_INSTAG_RECENT_RECEIVED and OTRL_INSTAG_RECENT_SENT. */
 ConnContext * otrl_context_find(OtrlUserState us, const char *user,
-	const char *accountname, const char *protocol, int add_if_missing,
-	int *addedp,
+	const char *accountname, const char *protocol,
+	otrl_instag_t their_instance, int add_if_missing, int *addedp,
 	void (*add_app_data)(void *data, ConnContext *context), void *data);
 
+/* This method gets called after sending or receiving a message, to update the
+ * master context's "recent context" pointers. */
+void otrl_context_update_recent_child(ConnContext *context,
+	unsigned int sent_msg);
+
 /* Find a fingerprint in a given context, perhaps adding it if not
  * present. */
 Fingerprint *otrl_context_find_fingerprint(ConnContext *context,
@@ -132,8 +168,11 @@ void otrl_context_force_plaintext(ConnContext *context);
 void otrl_context_forget_fingerprint(Fingerprint *fprint,
 	int and_maybe_context);
 
-/* Forget a whole context, so long as it's PLAINTEXT. */
-void otrl_context_forget(ConnContext *context);
+/* Forget a whole context, so long as it's PLAINTEXT. If a context has child
+ * instances, don't remove this instance unless children are also all in
+ * PLAINTEXT state. In this case, the children will also be removed.
+ * Returns 0 on success, 1 on failure. */
+int otrl_context_forget(ConnContext *context);
 
 /* Forget all the contexts in a given OtrlUserState. */
 void otrl_context_forget_all(OtrlUserState us);
diff --git a/src/context_priv.c b/src/context_priv.c
index 62ea68b..f93136e 100644
--- a/src/context_priv.c
+++ b/src/context_priv.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
@@ -44,6 +44,7 @@ ConnContextPriv *context_priv_new()
 	context_priv->generation = 0;
 	context_priv->lastsent = 0;
 	context_priv->lastmessage = NULL;
+	context_priv->lastrecv = 0;
 	context_priv->may_retransmit = 0;
 	context_priv->their_keyid = 0;
 	context_priv->their_y = NULL;
diff --git a/src/context_priv.h b/src/context_priv.h
index b05b5ff..1bc7e6b 100644
--- a/src/context_priv.h
+++ b/src/context_priv.h
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
+ *			     Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -74,6 +74,9 @@ typedef struct context_priv {
 	/* The last time a Data Message was sent */
 	time_t lastsent;
 
+	/* The last time a Data Message was received */
+	time_t lastrecv;
+
 	/* The plaintext of the last Data Message sent */
 	char *lastmessage;
 
diff --git a/src/dh.c b/src/dh.c
index a28d376..b2caac8 100644
--- a/src/dh.c
+++ b/src/dh.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *  			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
@@ -27,6 +27,7 @@
 /* libotr headers */
 #include "dh.h"
 
+
 static const char* DH1536_MODULUS_S = "0x"
     "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
     "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
@@ -91,7 +92,7 @@ void otrl_dh_keypair_free(DH_keypair *kp)
 
 /*
  * Generate a DH keypair for a specified group.
- */ 
+ */
 gcry_error_t otrl_dh_gen_keypair(unsigned int groupid, DH_keypair *kp)
 {
     unsigned char *secbuf = NULL;
diff --git a/src/dh.h b/src/dh.h
index 788b20f..eedaf06 100644
--- a/src/dh.h
+++ b/src/dh.h
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *  			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
@@ -74,7 +74,7 @@ void otrl_dh_keypair_free(DH_keypair *kp);
 
 /*
  * Generate a DH keypair for a specified group.
- */ 
+ */
 gcry_error_t otrl_dh_gen_keypair(unsigned int groupid, DH_keypair *kp);
 
 /*
diff --git a/src/instag.c b/src/instag.c
new file mode 100644
index 0000000..a17f774
--- /dev/null
+++ b/src/instag.c
@@ -0,0 +1,258 @@
+/*
+ *  Off-the-Record Messaging library
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
+ *                           <otr at cypherpunks.ca>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of version 2.1 of the GNU Lesser General
+ *  Public License as published by the Free Software Foundation.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* system headers */
+#include <stdio.h>
+#include <stdlib.h>
+
+/* libgcrypt headers */
+#include <gcrypt.h>
+
+/* libotr headers */
+#include "instag.h"
+#include "userstate.h"
+
+/* Forget the given instag. */
+void otrl_instag_forget(OtrlInsTag* instag) {
+    if (!instag) return;
+
+    if (instag->accountname) free(instag->accountname);
+    if (instag->protocol) free(instag->protocol);
+
+    /* Re-link the list */
+    *(instag->tous) = instag->next;
+    if (instag->next) {
+	instag->next->tous = instag->tous;
+    }
+
+    free(instag);
+}
+
+/* Forget all instags in a given OtrlUserState. */
+void otrl_instag_forget_all(OtrlUserState us) {
+    while(us->instag_root) {
+	otrl_instag_forget(us->instag_root);
+    }
+}
+
+/* Fetch the instance tag from the given OtrlUserState associated with
+ * the given account */
+OtrlInsTag * otrl_instag_find(OtrlUserState us, const char *accountname,
+	const char *protocol)
+{
+    OtrlInsTag *p;
+
+    for(p=us->instag_root; p; p=p->next) {
+	if (!strcmp(p->accountname, accountname) &&
+		!strcmp(p->protocol, protocol)) {
+	    return p;
+	}
+    }
+    return NULL;
+}
+
+/* Read our instance tag from a file on disk into the given
+ * OtrlUserState. */
+gcry_error_t otrl_instag_read(OtrlUserState us, const char *filename)
+{
+    gcry_error_t err;
+    FILE *instf;
+
+    /* Open the instance tag file. */
+    instf = fopen(filename, "rb");
+    if (!instf) {
+	return gcry_error_from_errno(errno);
+    }
+
+    err = otrl_instag_read_FILEp(us, instf);
+    fclose(instf);
+    return err;
+}
+
+/* Read our instance tag from a file on disk into the given
+ * OtrlUserState. The FILE* must be open for reading. */
+gcry_error_t otrl_instag_read_FILEp(OtrlUserState us, FILE *instf)
+{
+    if (!instf) return gcry_error(GPG_ERR_NO_ERROR);
+
+    OtrlInsTag *p;
+    char storeline[1000];
+    size_t maxsize = sizeof(storeline);
+
+    while(fgets(storeline, maxsize, instf)) {
+	char *prevpos;
+	char *pos;
+	unsigned int instag = 0;
+	int i;
+
+	p = malloc(sizeof(*p));
+	if (!p) {
+	    return gcry_error(GPG_ERR_ENOMEM);
+	}
+
+	/* Parse the line, which should be of the form:
+	 * accountname\tprotocol\t40_hex_nybbles\n          */
+	prevpos = storeline;
+	pos = strchr(prevpos, '\t');
+	if (!pos) {
+	    free(p);
+	    continue;
+	}
+	*pos = '\0';
+	pos++;
+	p->accountname = malloc(pos - prevpos);
+	memmove(p->accountname, prevpos, pos - prevpos);
+
+	prevpos = pos;
+	pos = strchr(prevpos, '\t');
+	if (!pos) {
+	    free(p);
+	    continue;
+	}
+	*pos = '\0';
+	pos++;
+	p->protocol = malloc(pos - prevpos);
+	memmove(p->protocol, prevpos, pos - prevpos);
+
+	prevpos = pos;
+	pos = strchr(prevpos, '\r');
+	if (!pos) pos = strchr(prevpos, '\n');
+	if (!pos) {
+	    free(p);
+	    continue;
+	}
+	*pos = '\0';
+	pos++;
+	/* hex str of length 8 */
+	if (strlen(prevpos) != 8) {
+	    free(p);
+	    continue;
+	}
+
+	sscanf(prevpos, "%08x", &instag);
+
+	if (instag < OTRL_MIN_VALID_INSTAG) {
+	    free(p);
+	    continue;
+	}
+	p->instag = instag;
+
+	/* Link it up */
+	p->next = us->instag_root;
+	if (p->next) {
+	    p->next->tous = &(p->next);
+	}
+	p->tous = &(us->instag_root);
+	us->instag_root = p;
+    }
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/* Generate a new instance tag for the given account and write to file */
+gcry_error_t otrl_instag_generate(OtrlUserState us, const char *filename,
+	const char *accountname, const char *protocol)
+{
+    gcry_error_t err;
+    FILE *instf;
+
+    /* Open the instance tag file. */
+    instf = fopen(filename, "wb");
+    if (!instf) {
+	return gcry_error_from_errno(errno);
+    }
+
+    err = otrl_instag_generate_FILEp(us, instf, accountname, protocol);
+    fclose(instf);
+    return err;
+}
+
+/* Return a new valid instance tag */
+otrl_instag_t otrl_instag_get_new()
+{
+    otrl_instag_t result = 0;
+
+    while(result < OTRL_MIN_VALID_INSTAG) {
+	otrl_instag_t * instag = (otrl_instag_t *)gcry_random_bytes(
+		sizeof(otrl_instag_t), GCRY_STRONG_RANDOM);
+	result = *instag;
+	gcry_free(instag);
+    }
+}
+
+/* Generate a new instance tag for the given account and write to file
+ * The FILE* must be open for writing. */
+gcry_error_t otrl_instag_generate_FILEp(OtrlUserState us, FILE *instf,
+	const char *accountname, const char *protocol)
+{
+    OtrlInsTag *p;
+    if (!accountname || !protocol) return (gcry_error_t)NULL;
+
+    p = (OtrlInsTag *)malloc(sizeof(OtrlInsTag));
+    p->accountname = malloc(strlen(accountname)+1);
+    p->protocol = malloc(strlen(protocol)+1);
+    strcpy(p->accountname, accountname);
+    strcpy(p->protocol, protocol);
+
+    p->instag = otrl_instag_get_new();
+
+    /* Add to our list in OtrlUserState */
+    p->next = us->instag_root;
+    if (p->next) {
+	p->next->tous = &(p->next);
+    }
+    p->tous = &(us->instag_root);
+    us->instag_root = p;
+
+    otrl_instag_write_FILEp(us, instf);
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/* Write our instance tags to a file on disk. */
+gcry_error_t otrl_instag_write(OtrlUserState us, const char *filename)
+{
+    gcry_error_t err;
+    FILE *instf;
+
+    /* Open the instance tag file. */
+    instf = fopen(filename, "wb");
+    if (!instf) {
+	return gcry_error_from_errno(errno);
+    }
+
+    err = otrl_instag_write_FILEp(us, instf);
+    fclose(instf);
+    return err;
+}
+
+/* Write our instance tags to a file on disk.
+ * The FILE* must be open for writing. */
+gcry_error_t otrl_instag_write_FILEp(OtrlUserState us, FILE *instf)
+{
+    OtrlInsTag *p;
+    for(p=us->instag_root; p; p=p->next) {
+	fprintf(instf, "%s\t%s\t%08x\n", p->accountname, p->protocol,
+		p->instag);
+    }
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
diff --git a/src/instag.h b/src/instag.h
new file mode 100644
index 0000000..c3ec036
--- /dev/null
+++ b/src/instag.h
@@ -0,0 +1,89 @@
+/*
+ *  Off-the-Record Messaging library
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
+ *                           <otr at cypherpunks.ca>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of version 2.1 of the GNU Lesser General
+ *  Public License as published by the Free Software Foundation.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __INSTAG_H__
+#define __INSTAG_H__
+
+#include <stdio.h>
+#include <errno.h>
+
+#define OTRL_INSTAG_MASTER 0
+#define OTRL_INSTAG_BEST 1 /* Most secure, based on: conv status,
+			    * then fingerprint status, then most recent. */
+#define OTRL_INSTAG_RECENT 2
+#define OTRL_INSTAG_RECENT_RECEIVED 3
+#define OTRL_INSTAG_RECENT_SENT 4
+
+#define OTRL_MIN_VALID_INSTAG 0x100 /* Instag values below this are reserved
+				     * for meta instags, defined above, */
+
+typedef unsigned int otrl_instag_t;
+
+/* The list of instance tags used for our accounts */
+typedef struct s_OtrlInsTag {
+    struct s_OtrlInsTag *next;
+    struct s_OtrlInsTag **tous;
+
+    char *accountname;
+    char *protocol;
+    otrl_instag_t instag;
+} OtrlInsTag;
+
+#include "userstate.h"
+
+/* Forget the given instag. */
+void otrl_instag_forget(OtrlInsTag* instag);
+
+/* Forget all instags in a given OtrlUserState. */
+void otrl_instag_forget_all(OtrlUserState us);
+
+/* Fetch the instance tag from the given OtrlUserState associated with
+ * the given account */
+OtrlInsTag * otrl_instag_find(OtrlUserState us, const char *accountname,
+	const char *protocol);
+
+/* Read our instance tag from a file on disk into the given
+ * OtrlUserState. */
+gcry_error_t otrl_instag_read(OtrlUserState us, const char *filename);
+
+/* Read our instance tag from a file on disk into the given
+ * OtrlUserState. The FILE* must be open for reading. */
+gcry_error_t otrl_instag_read_FILEp(OtrlUserState us, FILE *instf);
+
+/* Return a new valid instance tag */
+otrl_instag_t otrl_instag_get_new();
+
+/* Get a new instance tag for the given account and write to file*/
+gcry_error_t otrl_instag_generate(OtrlUserState us, const char *filename,
+	const char *accountname, const char *protocol);
+
+/* Get a new instance tag for the given account and write to file
+ * The FILE* must be open for writing. */
+gcry_error_t otrl_instag_generate_FILEp(OtrlUserState us, FILE *instf,
+	const char *accountname, const char *protocol);
+
+/* Write our instance tags to a file on disk. */
+gcry_error_t otrl_instag_write(OtrlUserState us, const char *filename);
+
+/* Write our instance tags to a file on disk.
+ * The FILE* must be open for writing. */
+gcry_error_t otrl_instag_write_FILEp(OtrlUserState us, FILE *instf);
+
+#endif
diff --git a/src/mem.c b/src/mem.c
index 075f3c0..6af3d0f 100644
--- a/src/mem.c
+++ b/src/mem.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *  			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
diff --git a/src/mem.h b/src/mem.h
index e1f5a1e..f128163 100644
--- a/src/mem.h
+++ b/src/mem.h
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *  			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
diff --git a/src/message.c b/src/message.c
index dcc18db..7f6112d 100644
--- a/src/message.c
+++ b/src/message.c
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -28,10 +28,12 @@
 
 /* libotr headers */
 #include "privkey.h"
+#include "userstate.h"
 #include "proto.h"
 #include "auth.h"
 #include "message.h"
 #include "sm.h"
+#include "instag.h"
 
 /* The API version */
 extern unsigned int otrl_api_version;
@@ -51,24 +53,26 @@ static gcry_error_t fragment_and_send(const OtrlMessageAppOps *ops,
 	OtrlFragmentPolicy fragPolicy, char **returnFragment)
 {
     int mms = 0;
+
     if (message && ops->inject_message) {
-    	int msglen;
+	int msglen;
 
-        if (otrl_api_version >= 0x030100 && ops->max_message_size) {
+	if (ops->max_message_size) {
 	    mms = ops->max_message_size(opdata, context);
-        }
-    	msglen = strlen(message);
+	}
+	msglen = strlen(message);
 
 	/* Don't incur overhead of fragmentation unless necessary */
-    	if(mms != 0 && msglen > mms) {
+	if(mms != 0 && msglen > mms) {
 	    char **fragments;
 	    gcry_error_t err;
 	    int i;
-	    int fragment_count = ((msglen - 1) / (mms -19)) + 1;
-		/* like ceil(msglen/(mms - 19)) */
+	    int headerlen = context->protocol_version == 3 ? 37 : 19;
+	    /* Like ceil(msglen/(mms - headerlen)) */
+	    int fragment_count = ((msglen - 1) / (mms - headerlen)) + 1;
 
 	    err = otrl_proto_fragment_create(mms, fragment_count, &fragments,
-		    message);
+		    context, message);
 	    if (err) {
 		return err;
 	    }
@@ -92,7 +96,8 @@ static gcry_error_t fragment_and_send(const OtrlMessageAppOps *ops,
 		*returnFragment = strdup(fragments[fragment_count-1]);
 	    } else {
 		ops->inject_message(opdata, context->accountname,
-			context->protocol, context->username, fragments[fragment_count-1]);
+			context->protocol, context->username,
+			fragments[fragment_count-1]);
 	    }
 	    /* Now free all fragment memory */
 	    otrl_proto_fragment_free(&fragments, fragment_count);
@@ -100,19 +105,37 @@ static gcry_error_t fragment_and_send(const OtrlMessageAppOps *ops,
 	} else {
 	    /* No fragmentation necessary */
 	    if (fragPolicy == OTRL_FRAGMENT_SEND_ALL) {
-	    	ops->inject_message(opdata, context->accountname,
-		        context->protocol, context->username, message);
+		ops->inject_message(opdata, context->accountname,
+			context->protocol, context->username, message);
 	    } else {
 		/* Copy and return the entire given message. */
 		int l = strlen(message) + 1;
-		*returnFragment = malloc(sizeof(char)*l);
-		strcpy(*returnFragment, message);
+		*returnFragment = strdup(message);
 	    }
 	}
     }
+
     return gcry_error(GPG_ERR_NO_ERROR);
 }
 
+static void populate_context_instag(OtrlUserState us, const OtrlMessageAppOps
+	*ops, void *opdata, const char *accountname, const char *protocol,
+	ConnContext *context) {
+    OtrlInsTag *p_instag;
+
+    p_instag = otrl_instag_find(us, accountname, protocol);
+    if ((!p_instag) && ops->create_instag) {
+	ops->create_instag(opdata, accountname, protocol);
+	p_instag = otrl_instag_find(us, accountname, protocol);
+    }
+
+    if (!p_instag || p_instag->instag < OTRL_MIN_VALID_INSTAG) {
+	p_instag->instag = otrl_instag_get_new();
+    }
+
+    context->our_instance = p_instag->instag;
+}
+
 /* Deallocate a message allocated by other otrl_message_* routines. */
 void otrl_message_free(char *message)
 {
@@ -131,6 +154,14 @@ void otrl_message_free(char *message)
  * tlvs is a chain of OtrlTLVs to append to the private message.  It is
  * usually correct to just pass NULL here.
  *
+ * If non-NULL, ops->convert_msg will be called just before encrypting a
+ * message.
+ *
+ * "instag" specifies the instance tag of the buddy (protocol version 3 only).
+ * Meta-instances may also be specified (e.g., OTRL_INSTAG_MOST_SECURE).
+ * If "contextp" is not NULL, it will be set to the ConnContext used for
+ * sending the message.
+ *
  * If no fragmentation or msg injection is wanted, use OTRL_FRAGMENT_SEND_SKIP
  * as the OtrlFragmentPolicy. In this case, this function will assign *messagep
  * with the encrypted msg. If the routine returns non-zero, then the library
@@ -140,47 +171,62 @@ void otrl_message_free(char *message)
  * of *messagep, and send that instead.
  *
  * Other fragmentation policies are OTRL_FRAGMENT_SEND_ALL,
- * OTRL_FRAGMENT_SEND_ALL_BUT_LAST, or OTRL_FRAGMENT_SEND_ALL_BUT_FIRST. In these
- * cases, the appropriate fragments will be automatically sent. For the last two
- * policies, the remaining fragment will be passed in *original_msg.
+ * OTRL_FRAGMENT_SEND_ALL_BUT_LAST, or OTRL_FRAGMENT_SEND_ALL_BUT_FIRST. In
+ * these cases, the appropriate fragments will be automatically sent. For the
+ * last two policies, the remaining fragment will be passed in *original_msg.
  *
- * Call otrl_message_free(*messagep) if you don't need *messagep or if you're
+ * Call otrl_message_free(*messagep) if you don't need *messagep or when you're
  * done with it. */
 gcry_error_t otrl_message_sending(OtrlUserState us,
 	const OtrlMessageAppOps *ops,
 	void *opdata, const char *accountname, const char *protocol,
-	const char *recipient, char **original_msgp, OtrlTLV *tlvs,
-	char **messagep, OtrlFragmentPolicy fragPolicy,
-	void (*convert_msg)(void *convert_data, const char *source, char **target),
-	void *convert_data,
+	const char *recipient, otrl_instag_t their_instag,
+	const char *original_msg, OtrlTLV *tlvs, char **messagep,
+	OtrlFragmentPolicy fragPolicy, ConnContext **contextp,
 	void (*add_appdata)(void *data, ConnContext *context),
 	void *data)
 {
-    struct context * context;
+    ConnContext * context = NULL;
     char * msgtosend;
     const char * err_msg;
     gcry_error_t err_code, err;
     OtrlPolicy policy = OTRL_POLICY_DEFAULT;
     int context_added = 0;
+    int convert_called = 0;
+    char *converted_msg = NULL;
 
     *messagep = NULL;
     err = gcry_error(GPG_ERR_NO_ERROR);	/* Default to no error */
 
+    if (contextp) {
+	*contextp = NULL;
+    }
+
     if (!accountname || !protocol || !recipient ||
-    		!original_msgp || !*original_msgp || !messagep) {
-    	err = gcry_error(GPG_ERR_NO_ERROR);
-    	goto fragment;
+		!original_msg || !messagep) {
+	err = gcry_error(GPG_ERR_NO_ERROR);
+	goto fragment;
     }
 
     /* See if we have a fingerprint for this user */
     context = otrl_context_find(us, recipient, accountname, protocol,
-	    1, &context_added, add_appdata, data);
+	    their_instag, 1, &context_added, add_appdata, data);
 
     /* Update the context list if we added one */
     if (context_added && ops->update_context_list) {
 	ops->update_context_list(opdata);
     }
 
+    /* Find or generate the instance tag if needed */
+    if (!context->our_instance) {
+	populate_context_instag(us, ops, opdata, accountname, protocol,
+	    context);
+    }
+
+    if (contextp) {
+	*contextp = context;
+    }
+
     /* Check the policy */
     if (ops->policy) {
 	policy = ops->policy(opdata, context);
@@ -188,27 +234,27 @@ gcry_error_t otrl_message_sending(OtrlUserState us,
 
     /* Should we go on at all? */
     if ((policy & OTRL_POLICY_VERSION_MASK) == 0) {
-        err =  gcry_error(GPG_ERR_NO_ERROR);
-    	goto fragment;
+	err =  gcry_error(GPG_ERR_NO_ERROR);
+	goto fragment;
     }
 
-    /* XXX: Add flags to the policy specifying whether to treat a typed
-     * "?OTR?" as a signal to start OTR, or just an ordinary message,
-     * for (1) unencrypted conversations, (2) encrypted conversations. */
-
     /* If this is an OTR Query message, don't encrypt it. */
-    if (otrl_proto_message_type(*original_msgp) == OTRL_MSGTYPE_QUERY) {
+    if (otrl_proto_message_type(original_msg) == OTRL_MSGTYPE_QUERY) {
 	/* Replace the "?OTR?" with a custom message */
 	char *bettermsg = otrl_proto_default_query_msg(accountname, policy);
 	if (bettermsg) {
 	    *messagep = bettermsg;
 	}
+	if (context) {
+	    context->otr_offer = OFFER_SENT;
+	}
 	err = gcry_error(GPG_ERR_NO_ERROR);
 	goto fragment;
     }
 
     /* What is the current message disposition? */
     switch(context->msgstate) {
+
 	case OTRL_MSGSTATE_PLAINTEXT:
 	    if ((policy & OTRL_POLICY_REQUIRE_ENCRYPTION)) {
 		/* We're trying to send an unencrypted message with a policy
@@ -221,15 +267,17 @@ gcry_error_t otrl_message_sending(OtrlUserState us,
 		}
 
 		context->context_priv->lastmessage =
-			gcry_malloc_secure(strlen(*original_msgp) + 1);
+			gcry_malloc_secure(strlen(original_msg) + 1);
 		if (context->context_priv->lastmessage) {
 		    char *bettermsg = otrl_proto_default_query_msg(accountname,
 			    policy);
-		    strcpy(context->context_priv->lastmessage, *original_msgp);
+		    strcpy(context->context_priv->lastmessage, original_msg);
 		    context->context_priv->lastsent = time(NULL);
+		    otrl_context_update_recent_child(context, 1);
 		    context->context_priv->may_retransmit = 2;
 		    if (bettermsg) {
 			*messagep = bettermsg;
+			context->otr_offer = OFFER_SENT;
 		    } else {
 			err = gcry_error(GPG_ERR_ENOMEM);
 			goto fragment;
@@ -241,16 +289,18 @@ gcry_error_t otrl_message_sending(OtrlUserState us,
 		    /* See if this user can speak OTR.  Append the
 		     * OTR_MESSAGE_TAG to the plaintext message, and see
 		     * if he responds. */
-		    size_t msglen = strlen(*original_msgp);
+		    size_t msglen = strlen(original_msg);
 		    size_t basetaglen = strlen(OTRL_MESSAGE_TAG_BASE);
 		    size_t v1taglen = (policy & OTRL_POLICY_ALLOW_V1) ?
 			strlen(OTRL_MESSAGE_TAG_V1) : 0;
 		    size_t v2taglen = (policy & OTRL_POLICY_ALLOW_V2) ?
 			strlen(OTRL_MESSAGE_TAG_V2) : 0;
+		    size_t v3taglen = (policy & OTRL_POLICY_ALLOW_V3) ?
+			strlen(OTRL_MESSAGE_TAG_V3) : 0;
 		    char *taggedmsg = malloc(msglen + basetaglen + v1taglen
-			    +v2taglen + 1);
+			    + v2taglen + v3taglen + 1);
 		    if (taggedmsg) {
-			strcpy(taggedmsg, *original_msgp);
+			strcpy(taggedmsg, original_msg);
 			strcpy(taggedmsg + msglen, OTRL_MESSAGE_TAG_BASE);
 			if (v1taglen) {
 			    strcpy(taggedmsg + msglen + basetaglen,
@@ -260,6 +310,10 @@ gcry_error_t otrl_message_sending(OtrlUserState us,
 			    strcpy(taggedmsg + msglen + basetaglen + v1taglen,
 				    OTRL_MESSAGE_TAG_V2);
 			}
+			if (v3taglen) {
+			    strcpy(taggedmsg + msglen + basetaglen + v1taglen
+				    + v2taglen, OTRL_MESSAGE_TAG_V3);
+			}
 			*messagep = taggedmsg;
 			if (context) {
 			    context->otr_offer = OFFER_SENT;
@@ -270,31 +324,44 @@ gcry_error_t otrl_message_sending(OtrlUserState us,
 	    break;
 	case OTRL_MSGSTATE_ENCRYPTED:
 	    /* convert the original message if necessary */
-	    if (convert_msg) {
-	    	/* using msgtosend as a temporary placeholder */
-	        msgtosend = *original_msgp;
-	        convert_msg(convert_data, msgtosend, original_msgp);
-	        free(msgtosend);
-	        msgtosend = NULL;
+	    if (ops->convert_msg) {
+		ops->convert_msg(opdata, context, OTRL_CONVERT_SENDING,
+			&converted_msg, original_msg);
+
+		if (converted_msg) {
+		    convert_called = 1;
+		}
 	    }
 
 	    /* Create the new, encrypted message */
-	    err_code = otrl_proto_create_data(&msgtosend, context,
-		    *original_msgp, tlvs, 0, NULL);
+	    if (convert_called) {
+		err_code = otrl_proto_create_data(&msgtosend, context,
+			converted_msg, tlvs, 0, NULL);
+
+		if (ops->convert_free) {
+		    ops->convert_free(opdata, context, converted_msg);
+		    converted_msg = NULL;
+		}
+	    } else {
+		err_code = otrl_proto_create_data(&msgtosend, context,
+			original_msg, tlvs, 0, NULL);
+	    }
 	    if (!err_code) {
 		context->context_priv->lastsent = time(NULL);
+		otrl_context_update_recent_child(context, 1);
 		*messagep = msgtosend;
 	    } else {
 		/* Uh, oh.  Whatever we do, *don't* send the message in the
 		 * clear. */
 		if (ops->handle_msg_event) {
-		ops->handle_msg_event(opdata, OTRL_MSGEVENT_ENCRYPTION_ERROR,
-		    context, NULL, (gcry_error_t)NULL);
+		    ops->handle_msg_event(opdata,
+			    OTRL_MSGEVENT_ENCRYPTION_ERROR,
+			    context, NULL, (gcry_error_t)NULL);
 		}
 		if (ops->otr_error_message) {
-		   err_msg = ops->otr_error_message(opdata, context,
+		    err_msg = ops->otr_error_message(opdata, context,
 			OTRL_ERRCODE_ENCRYPTION_ERROR);
-		   *messagep = malloc(strlen(OTR_ERROR_PREFIX) +
+		    *messagep = malloc(strlen(OTR_ERROR_PREFIX) +
 			strlen(err_msg) + 1);
 		    if (*messagep) {
 			strcpy(*messagep, OTR_ERROR_PREFIX);
@@ -325,26 +392,17 @@ gcry_error_t otrl_message_sending(OtrlUserState us,
 
 fragment:
     if (fragPolicy == OTRL_FRAGMENT_SEND_SKIP ) {
-    	/* Do not fragment/inject. Default behaviour of libotr3.2.0 */
-    	return err;
+	/* Do not fragment/inject. Default behaviour of libotr3.2.0 */
+	return err;
     } else {
 	/* Fragment and send according to policy */
-    	if (err && *messagep == NULL) {
-	    /* Do not send plaintext */
-	    char *ourm = strdup("");
-	    free(*original_msgp);
-	    *original_msgp = ourm;
-    	} else if (*messagep) {
-	    ConnContext *conncontext = otrl_context_find(us, recipient,
-		    accountname, protocol, 0, NULL, NULL, NULL);
-	    free(*original_msgp);
-	    *original_msgp = NULL;
-	    if (conncontext) {
-		err = fragment_and_send(ops, NULL, conncontext, *messagep,
-			fragPolicy, original_msgp);
+	if (!err && *messagep) {
+	    if (context) {
+		err = fragment_and_send(ops, NULL, context, *messagep,
+			fragPolicy, messagep);
 	    }
-    	}
-    	return err;
+	}
+	return err;
     }
 }
 
@@ -357,15 +415,16 @@ static gcry_error_t send_or_error_auth(const OtrlMessageAppOps *ops,
     if (!err) {
 	const char *msg = context->auth.lastauthmsg;
 	if (msg && *msg) {
- 	    fragment_and_send(ops, opdata, context, msg,
+	    fragment_and_send(ops, opdata, context, msg,
 		    OTRL_FRAGMENT_SEND_ALL, NULL);
 	    context->context_priv->lastsent = time(NULL);
+	    otrl_context_update_recent_child(context, 1);
 	}
     } else {
-     	if (ops->handle_msg_event) {
+	if (ops->handle_msg_event) {
 	    ops->handle_msg_event(opdata, OTRL_MSGEVENT_SETUP_ERROR,
 		    context, NULL, err);
-     	}
+	}
     }
     return err;
 }
@@ -391,12 +450,12 @@ static gcry_error_t go_encrypted(const OtrlAuthInfo *auth, void *asdata)
 
     /* See if we're talking to ourselves */
     if (!gcry_mpi_cmp(auth->their_pub, auth->our_dh.pub)) {
-    	/* Yes, we are. */
-    	if (edata->ops->handle_msg_event) {
+	/* Yes, we are. */
+	if (edata->ops->handle_msg_event) {
 	    edata->ops->handle_msg_event(edata->opdata,
 		    OTRL_MSGEVENT_MSG_REFLECTED, edata->context,
 		    NULL, (gcry_error_t)NULL);
-    	}
+	}
 	edata->ignore_message = 1;
 	return gcry_error(GPG_ERR_NO_ERROR);
     }
@@ -421,7 +480,8 @@ static gcry_error_t go_encrypted(const OtrlAuthInfo *auth, void *asdata)
     /* Is this a new session or just a refresh of an existing one? */
     if (edata->context->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
 	    oldprint == found_print &&
-	    edata->context->context_priv->our_keyid - 1 == edata->context->auth.our_keyid &&
+	    edata->context->context_priv->our_keyid - 1 ==
+	    edata->context->auth.our_keyid &&
 	    !gcry_mpi_cmp(edata->context->context_priv->our_old_dh_key.pub,
 		edata->context->auth.our_dh.pub) &&
 	    ((edata->context->context_priv->their_keyid > 0 &&
@@ -448,38 +508,45 @@ static gcry_error_t go_encrypted(const OtrlAuthInfo *auth, void *asdata)
     memmove(edata->context->sessionid,
 	    edata->context->auth.secure_session_id, 20);
     edata->context->sessionid_len =
-	edata->context->auth.secure_session_id_len;
+	    edata->context->auth.secure_session_id_len;
     edata->context->sessionid_half =
-	edata->context->auth.session_id_half;
+	    edata->context->auth.session_id_half;
     edata->context->protocol_version =
-	edata->context->auth.protocol_version;
+	    edata->context->auth.protocol_version;
 
-    edata->context->context_priv->their_keyid = edata->context->auth.their_keyid;
+    edata->context->context_priv->their_keyid =
+	    edata->context->auth.their_keyid;
     gcry_mpi_release(edata->context->context_priv->their_y);
     gcry_mpi_release(edata->context->context_priv->their_old_y);
-    edata->context->context_priv->their_y = gcry_mpi_copy(edata->context->auth.their_pub);
+    edata->context->context_priv->their_y =
+	    gcry_mpi_copy(edata->context->auth.their_pub);
     edata->context->context_priv->their_old_y = NULL;
 
-    if (edata->context->context_priv->our_keyid - 1 != edata->context->auth.our_keyid ||
+    if (edata->context->context_priv->our_keyid - 1 !=
+	edata->context->auth.our_keyid ||
 	gcry_mpi_cmp(edata->context->context_priv->our_old_dh_key.pub,
-	    edata->context->auth.our_dh.pub)) {
+		edata->context->auth.our_dh.pub)) {
 	otrl_dh_keypair_free(&(edata->context->context_priv->our_dh_key));
 	otrl_dh_keypair_free(&(edata->context->context_priv->our_old_dh_key));
 	otrl_dh_keypair_copy(&(edata->context->context_priv->our_old_dh_key),
 		&(edata->context->auth.our_dh));
-	otrl_dh_gen_keypair(edata->context->context_priv->our_old_dh_key.groupid,
+	otrl_dh_gen_keypair(
+		edata->context->context_priv->our_old_dh_key.groupid,
 		&(edata->context->context_priv->our_dh_key));
-	edata->context->context_priv->our_keyid = edata->context->auth.our_keyid + 1;
+	edata->context->context_priv->our_keyid = edata->context->auth.our_keyid
+		+ 1;
     }
 
     /* Create the session keys from the DH keys */
     otrl_dh_session_free(&(edata->context->context_priv->sesskeys[0][0]));
     err = otrl_dh_session(&(edata->context->context_priv->sesskeys[0][0]),
-	&(edata->context->context_priv->our_dh_key), edata->context->context_priv->their_y);
+	    &(edata->context->context_priv->our_dh_key),
+	    edata->context->context_priv->their_y);
     if (err) return err;
     otrl_dh_session_free(&(edata->context->context_priv->sesskeys[1][0]));
     err = otrl_dh_session(&(edata->context->context_priv->sesskeys[1][0]),
-	&(edata->context->context_priv->our_old_dh_key), edata->context->context_priv->their_y);
+	    &(edata->context->context_priv->our_old_dh_key),
+	    edata->context->context_priv->their_y);
     if (err) return err;
 
     edata->context->context_priv->generation++;
@@ -526,7 +593,7 @@ static void maybe_resend(EncrData *edata)
 	    const char *resent_prefix;
 	    int used_ops_resentmp = 1;
 	    resent_prefix = edata->ops->resent_msg_prefix ?
-			    edata->ops->resent_msg_prefix(edata->opdata,
+				    edata->ops->resent_msg_prefix(edata->opdata,
 				    edata->context) : NULL;
 	    if (!resent_prefix) {
 		resent_prefix = "[resent]"; /* Assign default prefix */
@@ -560,8 +627,9 @@ static void maybe_resend(EncrData *edata)
 		    resendmsg, OTRL_FRAGMENT_SEND_ALL, NULL);
 	    free(resendmsg);
 	    edata->context->context_priv->lastsent = now;
+	    otrl_context_update_recent_child(edata->context, 1);
 	    if (resending) {
-    		/* We're not sending it for the first time; let the user
+		/* We're not sending it for the first time; let the user
 		 * know we resent it */
 		if (edata->ops->handle_msg_event) {
 		    edata->ops->handle_msg_event(edata->opdata,
@@ -660,14 +728,14 @@ static void init_respond_smp(OtrlUserState us, const OtrlMessageAppOps *ops,
 	    : OTRL_TLV_SMP2,
 	    smpmsglen, smpmsg);
     err = otrl_proto_create_data(&sendsmp, context, "", sendtlv,
-            OTRL_MSGFLAGS_IGNORE_UNREADABLE, NULL);
+	    OTRL_MSGFLAGS_IGNORE_UNREADABLE, NULL);
     if (!err) {
-        /*  Send it, and set the next expected message to the
+	/*  Send it, and set the next expected message to the
 	 *  logical response */
-        err = fragment_and_send(ops, opdata, context,
+	err = fragment_and_send(ops, opdata, context,
 		sendsmp, OTRL_FRAGMENT_SEND_ALL, NULL);
-        context->smstate->nextExpected =
-	    initiating ? OTRL_SMP_EXPECT2 : OTRL_SMP_EXPECT3;
+	context->smstate->nextExpected =
+		initiating ? OTRL_SMP_EXPECT2 : OTRL_SMP_EXPECT3;
     }
     free(sendsmp);
     otrl_tlv_free(sendtlv);
@@ -723,6 +791,36 @@ void otrl_message_abort_smp(OtrlUserState us, const OtrlMessageAppOps *ops,
     otrl_tlv_free(sendtlv);
 }
 
+static void message_malformed(const OtrlMessageAppOps *ops,
+	void *opdata, ConnContext *context) {
+    if (ops->handle_msg_event) {
+	ops->handle_msg_event(opdata, OTRL_MSGEVENT_RCVDMSG_MALFORMED, context,
+	    NULL, (gcry_error_t)NULL);
+    }
+
+    if (ops->inject_message && ops->otr_error_message) {
+	const char *err_msg = ops->otr_error_message(opdata, context,
+		OTRL_ERRCODE_MSG_MALFORMED);
+
+	if (err_msg) {
+	    char *buf = malloc(strlen(OTR_ERROR_PREFIX) + strlen(err_msg) + 1);
+
+	    if (buf) {
+		strcpy(buf, OTR_ERROR_PREFIX);
+		strcat(buf, err_msg);
+		ops->inject_message(opdata, context->accountname,
+			context->protocol, context->username, buf);
+		free(buf);
+	    }
+
+	    if (ops->otr_error_message_free) {
+		ops->otr_error_message_free(opdata, err_msg);
+	    }
+	}
+    }
+}
+
+
 /* Handle a message just received from the network.  It is safe to pass
  * all received messages to this routine.  add_appdata is a function
  * that will be called in the event that a new ConnContext is created.
@@ -732,8 +830,11 @@ void otrl_message_abort_smp(OtrlUserState us, const OtrlMessageAppOps *ops,
  * "context->app" field, for example.  If you don't need to do this, you
  * can pass NULL for the last two arguments of otrl_message_receiving.
  *
- * convert_msg is a function that will be called on each msg that is received.
- * You can use it to perform some tweaks on your incoming messages.
+ * If non-NULL, ops->convert_msg will be called after a data message is 
+ * decrypted.
+ *
+ * If "contextp" is not NULL, it will be set to the ConnContext used for 
+ * receiving the message.
  *
  * If otrl_message_receiving returns 1, then the message you received
  * was an internal protocol message, and no message should be delivered
@@ -753,36 +854,51 @@ void otrl_message_abort_smp(OtrlUserState us, const OtrlMessageAppOps *ops,
 int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	void *opdata, const char *accountname, const char *protocol,
 	const char *sender, const char *message, char **newmessagep,
-	OtrlTLV **tlvsp,
-	void (*convert_msg)(void *convert_data, const char *source, char **target),
-	void *convert_data,
+	OtrlTLV **tlvsp, ConnContext **contextp,
 	void (*add_appdata)(void *data, ConnContext *context),
 	void *data)
 {
-    ConnContext *context;
+    ConnContext *context, *m_context, *best_context;
     OtrlMessageType msgtype;
     int context_added = 0;
-    OtrlMessageState msgstate;
     OtrlPolicy policy = OTRL_POLICY_DEFAULT;
     int fragment_assembled = 0;
-    char *unfragmessage = NULL;
+    char *unfragmessage = NULL, *otrtag = NULL;
     EncrData edata;
+    otrl_instag_t our_instance, their_instance;
+    int version;
+    gcry_error_t err;
 
     if (!accountname || !protocol || !sender || !message || !newmessagep)
-        return 0;
+	return 0;
 
     *newmessagep = NULL;
     if (tlvsp) *tlvsp = NULL;
 
-    /* Find our context and state with this correspondent */
-    context = otrl_context_find(us, sender, accountname,
-	    protocol, 1, &context_added, add_appdata, data);
+    if (contextp) {
+	*contextp = NULL;
+    }
+
+    /* Find the master context and state with this correspondent */
+    m_context = otrl_context_find(us, sender, accountname,
+	    protocol, OTRL_INSTAG_MASTER, 1, &context_added, add_appdata, data);
+    context = m_context;
 
     /* Update the context list if we added one */
     if (context_added && ops->update_context_list) {
 	ops->update_context_list(opdata);
     }
 
+    best_context = otrl_context_find(us, sender, accountname,
+	    protocol, OTRL_INSTAG_BEST, 0, NULL, add_appdata, data);
+
+    /* Find or generate the instance tag if needed */
+    if (!context->our_instance) {
+	populate_context_instag(us, ops, opdata, accountname, protocol,
+		context);
+    }
+
+
     /* Check the policy */
     if (ops->policy) {
 	policy = ops->policy(opdata, context);
@@ -790,29 +906,57 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 
     /* Should we go on at all? */
     if ((policy & OTRL_POLICY_VERSION_MASK) == 0) {
-        return 0;
+	return 0;
     }
 
-    /* See if we have a fragment */
-    switch(otrl_proto_fragment_accumulate(&unfragmessage, context, message)) {
-	case OTRL_FRAGMENT_UNFRAGMENTED:
-	    /* Do nothing */
-	    break;
-	case OTRL_FRAGMENT_INCOMPLETE:
-	    /* We've accumulated this fragment, but we don't have a
-	     * complete message yet */
-	    return 1;
-	case OTRL_FRAGMENT_COMPLETE:
-	    /* We've got a new complete message, in unfragmessage. */
-	    fragment_assembled = 1;
-	    message = unfragmessage;
-	    break;
+    otrtag = strstr(message, "?OTR");
+    if (otrtag) {
+	/* See if we have a V3 fragment */
+	if (strstr(message, "?OTR|")) {
+	    /* Get the instance tag from fragment header*/
+	    sscanf(otrtag, "?OTR|%x|%x,", &their_instance, &our_instance);
+	    /* Ignore message if it is intended for a different instance */
+	    if (our_instance && context->our_instance != our_instance) {
+
+		    if (ops->handle_msg_event) {
+			ops->handle_msg_event(opdata,
+				OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE,
+				m_context, NULL, (gcry_error_t)NULL);
+		    }
+		    return 1;
+	    }
+	    /* Get the context for this instance */
+	    if (their_instance >= OTRL_MIN_VALID_INSTAG) {
+		context = otrl_context_find(us, sender, accountname,
+			protocol, their_instance, 1, &context_added,
+			add_appdata, data);
+	    } else {
+		message_malformed(ops, opdata, context);
+		return 1;
+	    }
+	}
+	switch(otrl_proto_fragment_accumulate(&unfragmessage,
+		context, message)) {
+	    case OTRL_FRAGMENT_UNFRAGMENTED:
+		/* Do nothing */
+		break;
+	    case OTRL_FRAGMENT_INCOMPLETE:
+		/* We've accumulated this fragment, but we don't have a
+		 * complete message yet */
+		return 1;
+	    case OTRL_FRAGMENT_COMPLETE:
+		/* We've got a new complete message, in unfragmessage. */
+		fragment_assembled = 1;
+		message = unfragmessage;
+		otrtag = strstr(message, "?OTR");
+		break;
+	}
     }
 
     /* What type of message is it?  Note that this just checks the
      * header; it's not necessarily a _valid_ message of this type. */
     msgtype = otrl_proto_message_type(message);
-    msgstate = context->msgstate;
+    version = otrl_proto_message_version(message);
 
     /* See if they responded to our OTR offer */
     if ((policy & OTRL_POLICY_SEND_WHITESPACE_TAG)) {
@@ -823,6 +967,89 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	}
     }
 
+    /* Check that this version is allowed by the policy */
+    if (((version == 3) && !(policy & OTRL_POLICY_ALLOW_V3))
+	|| ((version == 2) && !(policy & OTRL_POLICY_ALLOW_V2))
+	|| ((version == 1) && !(policy & OTRL_POLICY_ALLOW_V1))) {
+	    return 1;
+    }
+    /* Check the to and from instance tags */
+    if (version == 3) {
+	err = otrl_proto_instance(otrtag, &their_instance, &our_instance);
+	if (!err) {
+	    if (our_instance && context->our_instance != our_instance) {
+		if (ops->handle_msg_event) {
+		    ops->handle_msg_event(opdata,
+			    OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE,
+			    m_context, NULL, (gcry_error_t)NULL);
+		}
+		return 1; /* ignore message intended for a different instance */
+	    }
+
+	    if (their_instance >= OTRL_MIN_VALID_INSTAG) {
+		context = otrl_context_find(us, sender, accountname,
+			protocol, their_instance, 1, &context_added,
+			add_appdata, data);
+	    } else {
+		message_malformed(ops, opdata, context);
+		return 1;
+	    }
+	}
+
+	if (context_added) {
+	    /* Context added because of new instance (either here or when
+	     * accumulating fragments */
+	    /* Copy information from m_context to the new instance context */
+	    context->auth.protocol_version = 3;
+	    context->protocol_version = 3;
+
+	    if (context_added) {
+		context->msgstate = m_context->msgstate;
+	    }
+
+	    if (msgtype == OTRL_MSGTYPE_DH_COMMIT) {
+		otrl_auth_copy_on_commit(&(m_context->auth), &(context->auth));
+	    } else if (msgtype == OTRL_MSGTYPE_DH_KEY) {
+		otrl_auth_copy_on_key(&(m_context->auth), &(context->auth));
+	    } else {
+		return 1;  /* Ignore unexpected message */
+	    }
+
+	    /* Update the context list */
+	    if (ops->update_context_list) {
+		ops->update_context_list(opdata);
+	    }
+	} else if (m_context != context && (context->msgstate !=
+		OTRL_MSGSTATE_ENCRYPTED || context->otr_offer == OFFER_SENT)) {
+	    /* Switching from m_context to existing instance context */
+	    if (msgtype == OTRL_MSGTYPE_DH_KEY && m_context->auth.authstate
+		    == OTRL_AUTHSTATE_AWAITING_DHKEY &&
+		    !(context->auth.authstate ==
+		    OTRL_AUTHSTATE_AWAITING_DHKEY)) {
+		context->msgstate = m_context->msgstate;
+		context->auth.protocol_version = 3;
+		context->protocol_version = 3;
+		otrl_auth_copy_on_key(&(m_context->auth), &(context->auth));
+	    } else if (msgtype == OTRL_MSGTYPE_DH_COMMIT &&
+		    m_context->auth.authstate == OTRL_AUTHSTATE_AWAITING_DHKEY
+		    && !(context->auth.authstate ==
+		    OTRL_AUTHSTATE_AWAITING_DHKEY)) {
+		context->msgstate = m_context->msgstate;
+		context->auth.protocol_version = 3;
+		context->protocol_version = 3;
+		otrl_auth_copy_on_commit(&(m_context->auth), &(context->auth));
+		}
+	    }
+    }
+
+    if (contextp) {
+	*contextp = context;
+    }
+
+    /* update time of last received message */
+    context->context_priv->lastrecv = time(NULL);
+    otrl_context_update_recent_child(context, 0);
+
     edata.gone_encrypted = 0;
     edata.us = us;
     edata.context = context;
@@ -837,8 +1064,8 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	DH_keypair *our_dh;
 	unsigned int our_keyid;
 	OtrlPrivKey *privkey;
-	gcry_error_t err;
 	int haveauthmsg;
+
 	case OTRL_MSGTYPE_QUERY:
 	    /* See if we should use an existing DH keypair, or generate
 	     * a fresh one. */
@@ -852,8 +1079,12 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 
 	    /* Find the best version of OTR that we both speak */
 	    switch(otrl_proto_query_bestversion(message, policy)) {
+		case 3:
+		    err = otrl_auth_start_v23(&(context->auth), 3);
+		    send_or_error_auth(ops, opdata, err, context);
+		    break;
 		case 2:
-		    err = otrl_auth_start_v2(&(context->auth));
+		    err = otrl_auth_start_v23(&(context->auth), 2);
 		    send_or_error_auth(ops, opdata, err, context);
 		    break;
 		case 1:
@@ -884,34 +1115,30 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	    break;
 
 	case OTRL_MSGTYPE_DH_COMMIT:
-	    if ((policy & OTRL_POLICY_ALLOW_V2)) {
-		err = otrl_auth_handle_commit(&(context->auth), message);
-		send_or_error_auth(ops, opdata, err, context);
-	    }
+	    err = otrl_auth_handle_commit(&(context->auth), otrtag, version);
+	    send_or_error_auth(ops, opdata, err, context);
 
 	    if (edata.ignore_message == -1) edata.ignore_message = 1;
 	    break;
 
 	case OTRL_MSGTYPE_DH_KEY:
-	    if ((policy & OTRL_POLICY_ALLOW_V2)) {
-		/* Get our private key */
-		privkey = otrl_privkey_find(us, context->accountname,
-			context->protocol);
-		if (privkey == NULL) {
-		    /* We've got no private key! */
-		    if (ops->create_privkey) {
-			ops->create_privkey(opdata, context->accountname,
-				context->protocol);
-			privkey = otrl_privkey_find(us,
-				context->accountname, context->protocol);
-		    }
+	    /* Get our private key */
+	    privkey = otrl_privkey_find(us, context->accountname,
+		    context->protocol);
+	    if (privkey == NULL) {
+		/* We've got no private key! */
+		if (ops->create_privkey) {
+		    ops->create_privkey(opdata, context->accountname,
+			    context->protocol);
+		    privkey = otrl_privkey_find(us,
+			    context->accountname, context->protocol);
 		}
-		if (privkey) {
-		    err = otrl_auth_handle_key(&(context->auth), message,
-			    &haveauthmsg, privkey);
-		    if (err || haveauthmsg) {
-			send_or_error_auth(ops, opdata, err, context);
-		    }
+	    }
+	    if (privkey) {
+		err = otrl_auth_handle_key(&(context->auth), otrtag,
+			&haveauthmsg, privkey);
+		if (err || haveauthmsg) {
+		    send_or_error_auth(ops, opdata, err, context);
 		}
 	    }
 
@@ -919,27 +1146,25 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	    break;
 
 	case OTRL_MSGTYPE_REVEALSIG:
-	    if ((policy & OTRL_POLICY_ALLOW_V2)) {
-		/* Get our private key */
-		privkey = otrl_privkey_find(us, context->accountname,
-			context->protocol);
-		if (privkey == NULL) {
-		    /* We've got no private key! */
-		    if (ops->create_privkey) {
-			ops->create_privkey(opdata, context->accountname,
-				context->protocol);
-			privkey = otrl_privkey_find(us,
-				context->accountname, context->protocol);
-		    }
+	    /* Get our private key */
+	    privkey = otrl_privkey_find(us, context->accountname,
+		    context->protocol);
+	    if (privkey == NULL) {
+		/* We've got no private key! */
+		if (ops->create_privkey) {
+		    ops->create_privkey(opdata, context->accountname,
+			    context->protocol);
+		    privkey = otrl_privkey_find(us,
+			    context->accountname, context->protocol);
 		}
-		if (privkey) {
-		    err = otrl_auth_handle_revealsig(&(context->auth),
-			    message, &haveauthmsg, privkey, go_encrypted,
-			    &edata);
-		    if (err || haveauthmsg) {
-			send_or_error_auth(ops, opdata, err, context);
-			maybe_resend(&edata);
-		    }
+	    }
+	    if (privkey) {
+		err = otrl_auth_handle_revealsig(&(context->auth),
+			otrtag, &haveauthmsg, privkey, go_encrypted,
+			&edata);
+		if (err || haveauthmsg) {
+		    send_or_error_auth(ops, opdata, err, context);
+		    maybe_resend(&edata);
 		}
 	    }
 
@@ -947,50 +1172,46 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	    break;
 
 	case OTRL_MSGTYPE_SIGNATURE:
-	    if ((policy & OTRL_POLICY_ALLOW_V2)) {
-		err = otrl_auth_handle_signature(&(context->auth),
-			message, &haveauthmsg, go_encrypted, &edata);
-		if (err || haveauthmsg) {
-		    send_or_error_auth(ops, opdata, err, context);
-		    maybe_resend(&edata);
-		}
+	    err = otrl_auth_handle_signature(&(context->auth),
+		    otrtag, &haveauthmsg, go_encrypted, &edata);
+	    if (err || haveauthmsg) {
+		send_or_error_auth(ops, opdata, err, context);
+		maybe_resend(&edata);
 	    }
 
 	    if (edata.ignore_message == -1) edata.ignore_message = 1;
 	    break;
 
 	case OTRL_MSGTYPE_V1_KEYEXCH:
-	    if ((policy & OTRL_POLICY_ALLOW_V1)) {
-		/* See if we should use an existing DH keypair, or generate
-		 * a fresh one. */
-		if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
-		    our_dh = &(context->context_priv->our_old_dh_key);
-		    our_keyid = context->context_priv->our_keyid - 1;
-		} else {
-		    our_dh = NULL;
-		    our_keyid = 0;
-		}
+	    /* See if we should use an existing DH keypair, or generate
+	     * a fresh one. */
+	    if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+		our_dh = &(context->context_priv->our_old_dh_key);
+		our_keyid = context->context_priv->our_keyid - 1;
+	    } else {
+		our_dh = NULL;
+		our_keyid = 0;
+	    }
 
-		/* Get our private key */
-		privkey = otrl_privkey_find(us, context->accountname,
-			context->protocol);
-		if (privkey == NULL) {
-		    /* We've got no private key! */
-		    if (ops->create_privkey) {
-			ops->create_privkey(opdata, context->accountname,
-				context->protocol);
-			privkey = otrl_privkey_find(us,
-				context->accountname, context->protocol);
-		    }
+	    /* Get our private key */
+	    privkey = otrl_privkey_find(us, context->accountname,
+		    context->protocol);
+	    if (privkey == NULL) {
+		/* We've got no private key! */
+		if (ops->create_privkey) {
+		    ops->create_privkey(opdata, context->accountname,
+			    context->protocol);
+		    privkey = otrl_privkey_find(us, context->accountname,
+			    context->protocol);
 		}
-		if (privkey) {
-		    err = otrl_auth_handle_v1_key_exchange(&(context->auth),
-			    message, &haveauthmsg, privkey, our_dh, our_keyid,
-			    go_encrypted, &edata);
-		    if (err || haveauthmsg) {
-			send_or_error_auth(ops, opdata, err, context);
-			maybe_resend(&edata);
-		    }
+	    }
+	    if (privkey) {
+		err = otrl_auth_handle_v1_key_exchange(&(context->auth),
+			message, &haveauthmsg, privkey, our_dh, our_keyid,
+			go_encrypted, &edata);
+		if (err || haveauthmsg) {
+		    send_or_error_auth(ops, opdata, err, context);
+		    maybe_resend(&edata);
 		}
 	    }
 
@@ -1017,28 +1238,25 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 			edata.ignore_message = 1;
 			break;
 		    }
-		    if (ops->handle_msg_event) {
-		    	ops->handle_msg_event(opdata,
-				OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE,
-				context, NULL, (gcry_error_t)NULL);
+
+		    if(best_context && best_context != context &&
+			best_context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+
+			if (ops->handle_msg_event) {
+			    ops->handle_msg_event(opdata,
+				    OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE,
+				    m_context, NULL, (gcry_error_t)NULL);
+			}
+		    } else if (ops->handle_msg_event) {
+			ops->handle_msg_event(opdata,
+				    OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE,
+				    context, NULL, (gcry_error_t)NULL);
 		    }
 		    edata.ignore_message = 1;
-		    if (ops->inject_message && ops->otr_error_message) {
-		    	err_msg = ops->otr_error_message(opdata, context,
-		    			OTRL_ERRCODE_MSG_NOT_IN_PRIVATE);
-		    	buf = malloc(strlen(OTR_ERROR_PREFIX) +
-		    			strlen(err_msg) + 1);
-		    	if (buf) {
-			    strcpy(buf, OTR_ERROR_PREFIX);
-			    strcat(buf, err_msg);
-			    ops->inject_message(opdata, accountname, protocol,
-				    sender, buf);
-			    free(buf);
-		    	}
-		    	if (ops->otr_error_message_free) {
-		    		ops->otr_error_message_free(opdata, err_msg);
-		    	}
-		    }
+
+		    /* We don't actually want to send anything in this case,
+		       since this could just be a message intended for another
+		       v2 instance.  We still notify the local user though */
 		    break;
 
 		case OTRL_MSGSTATE_ENCRYPTED:
@@ -1115,7 +1333,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 		    gcry_free(extrakey);
 		    extrakey = NULL;
 
-                    /* If TLVs contain SMP data, process it */
+		    /* If TLVs contain SMP data, process it */
 		    nextMsg = context->smstate->nextExpected;
 
 		    tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1Q);
@@ -1127,9 +1345,9 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 			    char *question = (char *)tlv->data;
 			    char *qend = memchr(question, '\0', tlv->len - 1);
 			    size_t qlen = qend ? (qend - question + 1) :
-				tlv->len;
+				    tlv->len;
 			    otrl_sm_step2a(context->smstate, tlv->data + qlen,
-				tlv->len - qlen, 1);
+				    tlv->len - qlen, 1);
 
 			    if (context->smstate->sm_prog_state !=
 				    OTRL_SMP_PROG_CHEATED) {
@@ -1145,9 +1363,9 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 					    0, NULL);
 				}
 				context->smstate->nextExpected =
-				    OTRL_SMP_EXPECT1;
+					OTRL_SMP_EXPECT1;
 				context->smstate->sm_prog_state =
-				    OTRL_SMP_PROG_OK;
+					OTRL_SMP_PROG_OK;
 			    }
 			} else {
 			    if (ops->handle_smp_event) {
@@ -1180,9 +1398,9 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 					    context, 0, NULL);
 				}
 				context->smstate->nextExpected =
-				    OTRL_SMP_EXPECT1;
+					OTRL_SMP_EXPECT1;
 				context->smstate->sm_prog_state =
-				    OTRL_SMP_PROG_OK;
+					OTRL_SMP_PROG_OK;
 			    }
 			} else {
 			    if (ops->handle_smp_event) {
@@ -1209,13 +1427,13 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 				sendtlv = otrl_tlv_new(OTRL_TLV_SMP3,
 					nextmsglen, nextmsg);
 				err = otrl_proto_create_data(&sendsmp,
-				    context, "", sendtlv,
-				    OTRL_MSGFLAGS_IGNORE_UNREADABLE,
-				    NULL);
+					context, "", sendtlv,
+					OTRL_MSGFLAGS_IGNORE_UNREADABLE,
+					NULL);
 				if (!err) {
 				err = fragment_and_send(ops,
-				    opdata, context, sendsmp,
-				    OTRL_FRAGMENT_SEND_ALL, NULL);
+					opdata, context, sendsmp,
+					OTRL_FRAGMENT_SEND_ALL, NULL);
 				}
 				free(sendsmp);
 				otrl_tlv_free(sendtlv);
@@ -1226,7 +1444,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 					    context, 60, NULL);
 				}
 				context->smstate->nextExpected =
-				    OTRL_SMP_EXPECT4;
+					OTRL_SMP_EXPECT4;
 			    } else {
 				if (ops->handle_smp_event) {
 				    ops->handle_smp_event(opdata,
@@ -1234,9 +1452,9 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 					    context, 0, NULL);
 				}
 				context->smstate->nextExpected =
-				    OTRL_SMP_EXPECT1;
+					OTRL_SMP_EXPECT1;
 				context->smstate->sm_prog_state =
-				    OTRL_SMP_PROG_OK;
+					OTRL_SMP_PROG_OK;
 			    }
 			    free(nextmsg);
 			} else {
@@ -1260,7 +1478,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 			    /* Set trust level based on result */
 			    if (context->smstate->received_question == 0) {
 				set_smp_trust(ops, opdata, context,
-				    (err == gcry_error(GPG_ERR_NO_ERROR)));
+					(err == gcry_error(GPG_ERR_NO_ERROR)));
 			    }
 
 			    if (context->smstate->sm_prog_state !=
@@ -1269,13 +1487,13 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 				sendtlv = otrl_tlv_new(OTRL_TLV_SMP4,
 					nextmsglen, nextmsg);
 				err = otrl_proto_create_data(&sendsmp,
-				    context, "", sendtlv,
-				    OTRL_MSGFLAGS_IGNORE_UNREADABLE,
-				    NULL);
+					context, "", sendtlv,
+					OTRL_MSGFLAGS_IGNORE_UNREADABLE,
+					NULL);
 				if (!err) {
 				err = fragment_and_send(ops,
-				    opdata, context, sendsmp,
-				    OTRL_FRAGMENT_SEND_ALL, NULL);
+					opdata, context, sendsmp,
+					OTRL_FRAGMENT_SEND_ALL, NULL);
 				}
 				free(sendsmp);
 				otrl_tlv_free(sendtlv);
@@ -1298,9 +1516,9 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 					    context, 0, NULL);
 				}
 				context->smstate->nextExpected =
-				    OTRL_SMP_EXPECT1;
+					OTRL_SMP_EXPECT1;
 				context->smstate->sm_prog_state =
-				    OTRL_SMP_PROG_OK;
+					OTRL_SMP_PROG_OK;
 			    }
 			    free(nextmsg);
 			} else {
@@ -1316,10 +1534,10 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 		    if (tlv) {
 			if (nextMsg == OTRL_SMP_EXPECT4) {
 			    err = otrl_sm_step5(context->smstate, tlv->data,
-				tlv->len);
+				    tlv->len);
 			    /* Set trust level based on result */
 			    set_smp_trust(ops, opdata, context,
-				(err == gcry_error(GPG_ERR_NO_ERROR)));
+				    (err == gcry_error(GPG_ERR_NO_ERROR)));
 
 			    if (context->smstate->sm_prog_state !=
 				    OTRL_SMP_PROG_CHEATED) {
@@ -1333,7 +1551,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 					    context, 100, NULL);
 				}
 				context->smstate->nextExpected =
-				    OTRL_SMP_EXPECT1;
+					OTRL_SMP_EXPECT1;
 			    } else {
 				if (ops->handle_smp_event) {
 				    ops->handle_smp_event(opdata,
@@ -1341,9 +1559,9 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 					    context, 0, NULL);
 				}
 				context->smstate->nextExpected =
-				    OTRL_SMP_EXPECT1;
+					OTRL_SMP_EXPECT1;
 				context->smstate->sm_prog_state =
-				    OTRL_SMP_PROG_OK;
+					OTRL_SMP_PROG_OK;
 			    }
 			} else {
 			    if (ops->handle_smp_event) {
@@ -1362,10 +1580,10 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 				    context, 0, NULL);
 			}
 		    }
-            
+
 		    if (plaintext[0] == '\0') {
 			/* If it's a heartbeat (an empty message), don't
-			 * display it to the user, but log a debug message. */
+			 * display it to the user, but signal an event. */
 			if (ops->handle_msg_event) {
 			    ops->handle_msg_event(opdata,
 				    OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD,
@@ -1388,7 +1606,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 				    OTRL_MSGFLAGS_IGNORE_UNREADABLE,
 				    NULL);
 			    if (!err) {
-				/* Send it, and log a debug message */
+				/* Send it, and inject a debug message */
 				if (ops->inject_message) {
 				    ops->inject_message(opdata, accountname,
 					    protocol, sender, heartbeat);
@@ -1396,12 +1614,13 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 				free(heartbeat);
 
 				context->context_priv->lastsent = now;
+				otrl_context_update_recent_child(context, 1);
 
-				/* Log the heartbeat message */
+				/* Signal an event for the heartbeat message */
 				if (ops->handle_msg_event) {
 				    ops->handle_msg_event(opdata,
-					OTRL_MSGEVENT_LOG_HEARTBEAT_SENT,
-					context, NULL, (gcry_error_t)NULL);
+					    OTRL_MSGEVENT_LOG_HEARTBEAT_SENT,
+					    context, NULL, (gcry_error_t)NULL);
 				}
 			    }
 			}
@@ -1416,17 +1635,28 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 		    }
 
 		    if (edata.ignore_message != 1) {
-		    	*newmessagep = plaintext;
-		    	edata.ignore_message = 0;
-		    	/* convert the original message if necessary */
-		    	if (convert_msg && *newmessagep) {
-			    char *original_msg;
-			    original_msg = *newmessagep;
-			    convert_msg(convert_data, original_msg,
-				    newmessagep);
-			    free(original_msg);
-			    original_msg = NULL;
-		    	}
+			char *converted_msg = NULL;
+
+			*newmessagep = plaintext;
+			edata.ignore_message = 0;
+
+			/* convert the plaintext message if necessary */
+			if (ops->convert_msg) {
+			    ops->convert_msg(opdata, context,
+				    OTRL_CONVERT_RECEIVING, &converted_msg,
+				    plaintext);
+
+			    if (converted_msg) {
+				free(plaintext);
+				plaintext = NULL;
+				*newmessagep = strdup(converted_msg);
+
+				if (ops->convert_free) {
+				    ops->convert_free(opdata, context,
+					    converted_msg);
+				}
+			    }
+			}
 		    } else {
 			free(plaintext);
 		    }
@@ -1446,20 +1676,20 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 		free(msgtosend);
 	    }
 
-	if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
-	    /* Mark the last message we sent as eligible for
-	     * retransmission */
-	    context->context_priv->may_retransmit = 1;
-	}
+	    if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+		/* Mark the last message we sent as eligible for
+		 * retransmission */
+		context->context_priv->may_retransmit = 1;
+	    }
 
-	/* In any event, display the error message, with the
-	 * display_otr_message callback, if possible */
-	if (ops->handle_msg_event) {
-	    /* Remove the OTR error prefix and pass the msg */
-	    const char *just_err_msg = strstr(message, OTR_ERROR_PREFIX);
-	    if (!just_err_msg) {
+	    /* In any event, display the error message, with the
+	     * display_otr_message callback, if possible */
+	    if (ops->handle_msg_event) {
+		/* Remove the OTR error prefix and pass the msg */
+		const char *just_err_msg = strstr(message, OTR_ERROR_PREFIX);
+		if (!just_err_msg) {
 		    just_err_msg = message;
-	    	} else {
+		} else {
 		    just_err_msg += (strlen(OTR_ERROR_PREFIX));
 		    if (*just_err_msg == ' ') {
 			/* Advance pointer to skip the space character */
@@ -1468,9 +1698,9 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 		    ops->handle_msg_event(opdata,
 			    OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR,
 			    context, just_err_msg, (gcry_error_t)NULL);
-	    	}
+		    edata.ignore_message = 1;
+		}
 	    }
-	    edata.ignore_message = 1;
 	    break;
 
 	case OTRL_MSGTYPE_TAGGEDPLAINTEXT:
@@ -1491,8 +1721,12 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	    if (bestversion && context->msgstate != OTRL_MSGSTATE_ENCRYPTED
 		    && (policy & OTRL_POLICY_WHITESPACE_START_AKE)) {
 		switch(bestversion) {
+		    case 3:
+			err = otrl_auth_start_v23(&(context->auth), 3);
+			send_or_error_auth(ops, opdata, err, context);
+			break;
 		    case 2:
-			err = otrl_auth_start_v2(&(context->auth));
+			err = otrl_auth_start_v23(&(context->auth), 2);
 			send_or_error_auth(ops, opdata, err, context);
 			break;
 		    case 1:
@@ -1524,7 +1758,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 
 	    /* FALLTHROUGH */
 	case OTRL_MSGTYPE_NOTOTR:
-	    if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT ||
+	    if (best_context->msgstate != OTRL_MSGSTATE_PLAINTEXT ||
 		    (policy & OTRL_POLICY_REQUIRE_ENCRYPTION)) {
 		/* Not fine.  Let the user know. */
 		const char *plainmsg = (*newmessagep) ? *newmessagep : message;
@@ -1532,16 +1766,16 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 		    ops->handle_msg_event(opdata,
 			    OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED,
 			    context, plainmsg, (gcry_error_t)NULL);
+		    free(*newmessagep);
+		    *newmessagep = NULL;
+		    edata.ignore_message = 1;
 		}
-		free(*newmessagep);
-		*newmessagep = NULL;
-		edata.ignore_message = 1;
 	    }
 	    break;
 
 	case OTRL_MSGTYPE_UNKNOWN:
 	    /* We received an OTR message we didn't recognize.  Ignore
-	     * it, but make a log entry. */
+	     * it, and signal an event. */
 	    if (ops->handle_msg_event) {
 		ops->handle_msg_event(opdata,
 			OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED,
@@ -1563,20 +1797,17 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 
 /* Put a connection into the PLAINTEXT state, first sending the
  * other side a notice that we're doing so if we're currently ENCRYPTED,
- * and we think he's logged in. */
-void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
-	void *opdata, const char *accountname, const char *protocol,
-	const char *username)
+ * and we think he's logged in. Affects only the specified context. */
+static void disconnect_context(OtrlUserState us, const OtrlMessageAppOps *ops,
+	void *opdata, ConnContext *context)
 {
-    ConnContext *context = otrl_context_find(us, username, accountname,
-	    protocol, 0, NULL, NULL, NULL);
-
     if (!context) return;
 
     if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
 	    context->context_priv->their_keyid > 0 &&
 	    ops->is_logged_in &&
-	    ops->is_logged_in(opdata, accountname, protocol, username) == 1) {
+	    ops->is_logged_in(opdata, context->accountname, context->protocol,
+		    context->username) == 1) {
 	if (ops->inject_message) {
 	    char *encmsg = NULL;
 	    gcry_error_t err;
@@ -1585,8 +1816,8 @@ void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
 	    err = otrl_proto_create_data(&encmsg, context, "", tlv,
 		    OTRL_MSGFLAGS_IGNORE_UNREADABLE, NULL);
 	    if (!err) {
-		ops->inject_message(opdata, accountname, protocol,
-			username, encmsg);
+		ops->inject_message(opdata, context->accountname,
+			context->protocol, context->username, encmsg);
 	    }
 	    free(encmsg);
 	}
@@ -1598,6 +1829,45 @@ void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
     }
 }
 
+
+/* Put a connection into the PLAINTEXT state, first sending the
+ * other side a notice that we're doing so if we're currently ENCRYPTED,
+ * and we think he's logged in. Affects only the specified instance. */
+void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
+	void *opdata, const char *accountname, const char *protocol,
+	const char *username, otrl_instag_t instance)
+{
+    ConnContext *context = otrl_context_find(us, username, accountname,
+	    protocol, instance, 0, NULL, NULL, NULL);
+
+    if (!context) return;
+
+    disconnect_context(us, ops, opdata, context);
+}
+
+/* Put a connection into the PLAINTEXT state, first sending the
+ * other side a notice that we're doing so if we're currently ENCRYPTED,
+ * and we think he's logged in. Affects all matching instances. */
+void otrl_message_disconnect_all_instances(OtrlUserState us,
+	const OtrlMessageAppOps *ops, void *opdata, const char *accountname,
+	const char *protocol, const char *username)
+{
+    ConnContext * c_iter;
+    ConnContext *context;
+
+    if (!username || !accountname || !protocol) return;
+    
+    context = otrl_context_find(us, username, accountname,
+	    protocol, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
+
+    if (!context) return;
+
+    for (c_iter = context; c_iter && c_iter->m_context == context->m_context;
+	c_iter = c_iter->next) {
+	disconnect_context(us, ops, opdata, c_iter);
+    }
+}
+
 /* Get the current extra symmetric key (of size OTRL_EXTRAKEY_BYTES
  * bytes) and let the other side know what we're going to use it for.
  * The key is stored in symkey, which must already be allocated
diff --git a/src/message.h b/src/message.h
index f2d1c7a..87927c0 100644
--- a/src/message.h
+++ b/src/message.h
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -61,7 +61,8 @@ typedef enum {
     OTRL_MSGEVENT_LOG_HEARTBEAT_SENT,
     OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR,
     OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED,
-    OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED
+    OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED,
+    OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE
 } OtrlMessageEvent;
 
 typedef enum {
@@ -70,6 +71,11 @@ typedef enum {
     OTRL_NOTIFY_INFO
 } OtrlNotifyLevel;
 
+typedef enum {
+    OTRL_CONVERT_SENDING,
+    OTRL_CONVERT_RECEIVING
+} OtrlConvertType;
+
 typedef struct s_OtrlMessageAppOps {
     /* Return the OTR policy for the given context. */
     OtrlPolicy (*policy)(void *opdata, ConnContext *context);
@@ -148,7 +154,7 @@ typedef struct s_OtrlMessageAppOps {
      * - OTRL_ERRCODE_MSG_MALFORMED
      * 		message sent is malformed */
     const char *(*otr_error_message)(void *opdata, ConnContext *context,
-        OtrlErrorCode err_code);
+	OtrlErrorCode err_code);
 
     /* Deallocate a string returned by otr_error_message */
     void (*otr_error_message_free)(void *opdata, const char *err_msg);
@@ -228,10 +234,27 @@ typedef struct s_OtrlMessageAppOps {
      *      also be passed and it will contain the plaintext message.
      * - OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED
      *      Cannot recognize the type of OTR message received.
-     * */
+     * - OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE
+     *      Received and discarded a message intended for another instance. */
     void (*handle_msg_event)(void *opdata, OtrlMessageEvent msg_event,
-                ConnContext *context, const char *message,
-                gcry_error_t err);
+		ConnContext *context, const char *message,
+		gcry_error_t err);
+
+     /* Create a instance tag for the given accountname/protocol if
+      * desired. */
+    void (*create_instag)(void *opdata, const char *accountname,
+		const char *protocol);
+
+     /* Called immediately before a data message is encrypted, and after a data
+      * message is decrypted. The OtrlConvertType parameter has the value
+      * OTRL_CONVERT_SENDING or OTRL_CONVERT_RECEIVING to differentiate these
+      * cases. */
+    void (*convert_msg)(void *opdata, ConnContext *context,
+		OtrlConvertType convert_type, char ** dest, const char *src);
+
+     /* Deallocate a string returned by convert_msg. */
+    void (*convert_free)(void *opdata, ConnContext *context, char *dest);
+
 } OtrlMessageAppOps;
 
 /* Deallocate a message allocated by other otrl_message_* routines. */
@@ -249,8 +272,13 @@ void otrl_message_free(char *message);
  * tlvs is a chain of OtrlTLVs to append to the private message.  It is
  * usually correct to just pass NULL here.
  *
- * convert_msg is a function that will be called on each msg to be sent. You
- * can use it to perform some tweaks on your outgoing messages.
+ * If non-NULL, ops->convert_msg will be called just before encrypting a
+ * message.
+ *
+ * "instag" specifies the instance tag of the buddy (protocol version 3 only).
+ * Meta-instances may also be specified (e.g., OTRL_INSTAG_MOST_SECURE).
+ * If "contextp" is not NULL, it will be set to the ConnContext used for
+ * sending the message.
  *
  * If no fragmentation or msg injection is wanted, use OTRL_FRAGMENT_SEND_SKIP
  * as the OtrlFragmentPolicy. In this case, this function will assign *messagep
@@ -261,19 +289,18 @@ void otrl_message_free(char *message);
  * of *messagep, and send that instead.
  *
  * Other fragmentation policies are OTRL_FRAGMENT_SEND_ALL,
- * OTRL_FRAGMENT_SEND_ALL_BUT_LAST, or OTRL_FRAGMENT_SEND_ALL_BUT_FIRST. In these
- * cases, the appropriate fragments will be automatically sent. For the last two
- * policies, the remaining fragment will be passed in *original_msg.
+ * OTRL_FRAGMENT_SEND_ALL_BUT_LAST, or OTRL_FRAGMENT_SEND_ALL_BUT_FIRST. In
+ * these cases, the appropriate fragments will be automatically sent. For the
+ * last two policies, the remaining fragment will be passed in *original_msg.
  *
  * Call otrl_message_free(*messagep) if you don't need *messagep or when you're
  * done with it. */
 gcry_error_t otrl_message_sending(OtrlUserState us,
 	const OtrlMessageAppOps *ops,
 	void *opdata, const char *accountname, const char *protocol,
-	const char *recipient, char **original_msgp, OtrlTLV *tlvs,
-	char **messagep, OtrlFragmentPolicy fragPolicy,
-	void (*convert_msg)(void *convert_data, const char *source, char **target),
-	void *convert_data,
+	const char *recipient, otrl_instag_t instag, const char *original_msg,
+	OtrlTLV *tlvs, char **messagep, OtrlFragmentPolicy fragPolicy,
+	ConnContext **contextp,
 	void (*add_appdata)(void *data, ConnContext *context),
 	void *data);
 
@@ -286,8 +313,11 @@ gcry_error_t otrl_message_sending(OtrlUserState us,
  * "context->app" field, for example.  If you don't need to do this, you
  * can pass NULL for the last two arguments of otrl_message_receiving.
  *
- * convert_msg is a function that will be called on each msg that is received.
- * You can use it to perform some tweaks on your incoming messages.
+ * If non-NULL, ops->convert_msg will be called after a data message is 
+ * decrypted.
+ *
+ * If "contextp" is not NULL, it will be set to the ConnContext used for 
+ * receiving the message.
  *
  * If otrl_message_receiving returns 1, then the message you received
  * was an internal protocol message, and no message should be delivered
@@ -307,18 +337,23 @@ gcry_error_t otrl_message_sending(OtrlUserState us,
 int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	void *opdata, const char *accountname, const char *protocol,
 	const char *sender, const char *message, char **newmessagep,
-	OtrlTLV **tlvsp,
-	void (*convert_msg)(void *convert_data, const char *source, char **target),
-	void *convert_data,
+	OtrlTLV **tlvsp, ConnContext **contextp,
 	void (*add_appdata)(void *data, ConnContext *context),
 	void *data);
 
 /* Put a connection into the PLAINTEXT state, first sending the
  * other side a notice that we're doing so if we're currently ENCRYPTED,
- * and we think he's logged in. */
+ * and we think he's logged in. Affects only the specified instance. */
 void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
 	void *opdata, const char *accountname, const char *protocol,
-	const char *username);
+	const char *username, otrl_instag_t instance);
+
+/* Put a connection into the PLAINTEXT state, first sending the
+ * other side a notice that we're doing so if we're currently ENCRYPTED,
+ * and we think he's logged in. Affects all matching instances. */
+void otrl_message_disconnect_all_instances(OtrlUserState us,
+	const OtrlMessageAppOps *ops, void *opdata, const char *accountname,
+	const char *protocol, const char *username);
 
 /* Initiate the Socialist Millionaires' Protocol */
 void otrl_message_initiate_smp(OtrlUserState us, const OtrlMessageAppOps *ops,
diff --git a/src/privkey.c b/src/privkey.c
index 6e58f80..2100585 100644
--- a/src/privkey.c
+++ b/src/privkey.c
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -246,10 +246,10 @@ gcry_error_t otrl_privkey_read_FILEp(OtrlUserState us, FILE *privf)
 	char *name, *proto;
 	gcry_sexp_t accounts;
 	OtrlPrivKey *p;
-	
+
 	/* Get the ith "account" S-exp */
 	accounts = gcry_sexp_nth(allkeys, i);
-	
+
 	/* It's really an "account" S-exp? */
 	token = gcry_sexp_nth_data(accounts, 0, &tokenlen);
 	if (tokenlen != 7 || strncmp(token, "account", 7)) {
@@ -424,7 +424,7 @@ static gcry_error_t sexp_write(FILE *privf, gcry_sexp_t sexp)
 	return gcry_error(GPG_ERR_ENOMEM);
     }
     gcry_sexp_sprint(sexp, GCRYSEXP_FMT_ADVANCED, buf, buflen);
-    
+
     fprintf(privf, "%s", buf);
     free(buf);
 
@@ -500,7 +500,7 @@ gcry_error_t otrl_privkey_generate_start(OtrlUserState us,
 gcry_error_t otrl_privkey_generate_calculate(void *newkey)
 {
     struct s_pending_privkey_calc *ppc =
-	(struct s_pending_privkey_calc *)newkey;
+	    (struct s_pending_privkey_calc *)newkey;
     gcry_error_t err;
     gcry_sexp_t key, parms;
     static const char *parmstr = "(genkey (dsa (nbits 4:1024)))";
@@ -546,10 +546,10 @@ static FILE* privkey_fopen(const char *filename, gcry_error_t *errp)
 /* Call this from the main thread only, in the event that the background
  * thread generating the key is cancelled.  The newkey is deallocated,
  * and must not be used further. */
-void otrl_privkey_generate_cancel(OtrlUserState us, void *newkey)
+void otrl_privkey_generate_cancelled(OtrlUserState us, void *newkey)
 {
     struct s_pending_privkey_calc *ppc =
-	(struct s_pending_privkey_calc *)newkey;
+	    (struct s_pending_privkey_calc *)newkey;
 
     if (us) {
 	pending_forget(pending_find(us, ppc->accountname, ppc->protocol));
@@ -586,7 +586,7 @@ gcry_error_t otrl_privkey_generate_finish_FILEp(OtrlUserState us,
 	void *newkey, FILE *privf)
 {
     struct s_pending_privkey_calc *ppc =
-	(struct s_pending_privkey_calc *)newkey;
+	    (struct s_pending_privkey_calc *)newkey;
     gcry_error_t ret = gcry_error(GPG_ERR_INV_VALUE);
 
     if (ppc && us && privf) {
@@ -612,7 +612,7 @@ gcry_error_t otrl_privkey_generate_finish_FILEp(OtrlUserState us,
 	ret = otrl_privkey_read_FILEp(us, privf);
     }
 
-    otrl_privkey_generate_cancel(us, newkey);
+    otrl_privkey_generate_cancelled(us, newkey);
 
     return ret;
 }
@@ -751,7 +751,7 @@ gcry_error_t otrl_privkey_read_fingerprints_FILEp(OtrlUserState us,
 	}
 	/* Get the context for this user, adding if not yet present */
 	context = otrl_context_find(us, username, accountname, protocol,
-		1, NULL, add_app_data, data);
+		OTRL_INSTAG_MASTER, 1, NULL, add_app_data, data);
 	/* Add the fingerprint if not already there */
 	fng = otrl_context_find_fingerprint(context, fingerprint, 1, NULL);
 	otrl_context_set_trust(fng, trust);
@@ -790,7 +790,10 @@ gcry_error_t otrl_privkey_write_fingerprints_FILEp(OtrlUserState us,
     if (!storef) return gcry_error(GPG_ERR_NO_ERROR);
 
     for(context = us->context_root; context; context = context->next) {
-	/* Don't both with the first (fingerprintless) entry. */
+	/* Fingerprints are only stored in the master contexts */
+	if (context->their_instance != OTRL_INSTAG_MASTER) continue;
+
+	/* Don't bother with the first (fingerprintless) entry. */
 	for (fprint = context->fingerprint_root.next; fprint;
 		fprint = fprint->next) {
 	    int i;
@@ -929,3 +932,4 @@ gcry_error_t otrl_privkey_verify(const unsigned char *sigbuf, size_t siglen,
 
     return err;
 }
+
diff --git a/src/privkey.h b/src/privkey.h
index 200168e..a7e6ed8 100644
--- a/src/privkey.h
+++ b/src/privkey.h
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  			     Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -80,7 +80,7 @@ gcry_error_t otrl_privkey_generate_finish_FILEp(OtrlUserState us,
 /* Call this from the main thread only, in the event that the background
  * thread generating the key is cancelled.  The newkey is deallocated,
  * and must not be used further. */
-void otrl_privkey_generate_cancel(OtrlUserState us, void *newkey);
+void otrl_privkey_generate_cancelled(OtrlUserState us, void *newkey);
 
 /* Generate a private DSA key for a given account, storing it into a
  * file on disk, and loading it into the given OtrlUserState.  Overwrite any
diff --git a/src/proto.c b/src/proto.c
index f5e3980..7fbc83a 100644
--- a/src/proto.c
+++ b/src/proto.c
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -102,19 +102,23 @@ static gcry_error_t reveal_macs(ConnContext *context,
 	return gcry_error(GPG_ERR_ENOMEM);
     }
     if (sess1->rcvmacused) {
-	memmove(newmacs + context->context_priv->numsavedkeys * 20, sess1->rcvmackey, 20);
+	memmove(newmacs + context->context_priv->numsavedkeys * 20,
+		sess1->rcvmackey, 20);
 	context->context_priv->numsavedkeys++;
     }
     if (sess1->sendmacused) {
-	memmove(newmacs + context->context_priv->numsavedkeys * 20, sess1->sendmackey, 20);
+	memmove(newmacs + context->context_priv->numsavedkeys * 20,
+		sess1->sendmackey, 20);
 	context->context_priv->numsavedkeys++;
     }
     if (sess2->rcvmacused) {
-	memmove(newmacs + context->context_priv->numsavedkeys * 20, sess2->rcvmackey, 20);
+	memmove(newmacs + context->context_priv->numsavedkeys * 20,
+		sess2->rcvmackey, 20);
 	context->context_priv->numsavedkeys++;
     }
     if (sess2->sendmacused) {
-	memmove(newmacs + context->context_priv->numsavedkeys * 20, sess2->sendmackey, 20);
+	memmove(newmacs + context->context_priv->numsavedkeys * 20,
+		sess2->sendmackey, 20);
 	context->context_priv->numsavedkeys++;
     }
     context->context_priv->saved_mac_keys = newmacs;
@@ -131,8 +135,8 @@ static gcry_error_t rotate_dh_keys(ConnContext *context)
     /* Rotate the keypair */
     otrl_dh_keypair_free(&(context->context_priv->our_old_dh_key));
     memmove(&(context->context_priv->our_old_dh_key),
-    		&(context->context_priv->our_dh_key),
-    		sizeof(DH_keypair));
+	    &(context->context_priv->our_dh_key),
+	    sizeof(DH_keypair));
 
     /* Rotate the session keys */
     err = reveal_macs(context, &(context->context_priv->sesskeys[1][0]),
@@ -141,11 +145,11 @@ static gcry_error_t rotate_dh_keys(ConnContext *context)
     otrl_dh_session_free(&(context->context_priv->sesskeys[1][0]));
     otrl_dh_session_free(&(context->context_priv->sesskeys[1][1]));
     memmove(&(context->context_priv->sesskeys[1][0]),
-    		&(context->context_priv->sesskeys[0][0]),
-    		sizeof(DH_sesskeys));
+	    &(context->context_priv->sesskeys[0][0]),
+	    sizeof(DH_sesskeys));
     memmove(&(context->context_priv->sesskeys[1][1]),
-    		&(context->context_priv->sesskeys[0][1]),
-    		sizeof(DH_sesskeys));
+	    &(context->context_priv->sesskeys[0][1]),
+	    sizeof(DH_sesskeys));
 
     /* Create a new DH key */
     otrl_dh_gen_keypair(DH1536_GROUP_ID, &(context->context_priv->our_dh_key));
@@ -154,7 +158,8 @@ static gcry_error_t rotate_dh_keys(ConnContext *context)
     /* Make the session keys */
     if (context->context_priv->their_y) {
 	err = otrl_dh_session(&(context->context_priv->sesskeys[0][0]),
-		&(context->context_priv->our_dh_key), context->context_priv->their_y);
+		&(context->context_priv->our_dh_key),
+		context->context_priv->their_y);
 	if (err) return err;
     } else {
 	otrl_dh_session_blank(&(context->context_priv->sesskeys[0][0]));
@@ -187,11 +192,11 @@ static gcry_error_t rotate_y_keys(ConnContext *context, gcry_mpi_t new_y)
     otrl_dh_session_free(&(context->context_priv->sesskeys[0][1]));
     otrl_dh_session_free(&(context->context_priv->sesskeys[1][1]));
     memmove(&(context->context_priv->sesskeys[0][1]),
-    		&(context->context_priv->sesskeys[0][0]),
-    		sizeof(DH_sesskeys));
+	    &(context->context_priv->sesskeys[0][0]),
+	    sizeof(DH_sesskeys));
     memmove(&(context->context_priv->sesskeys[1][1]),
-    		&(context->context_priv->sesskeys[1][0]),
-    		sizeof(DH_sesskeys));
+	    &(context->context_priv->sesskeys[1][0]),
+	    sizeof(DH_sesskeys));
 
     /* Copy in the new public key */
     context->context_priv->their_y = gcry_mpi_copy(new_y);
@@ -199,10 +204,12 @@ static gcry_error_t rotate_y_keys(ConnContext *context, gcry_mpi_t new_y)
 
     /* Make the session keys */
     err = otrl_dh_session(&(context->context_priv->sesskeys[0][0]),
-	    &(context->context_priv->our_dh_key), context->context_priv->their_y);
+	    &(context->context_priv->our_dh_key),
+	    context->context_priv->their_y);
     if (err) return err;
     err = otrl_dh_session(&(context->context_priv->sesskeys[1][0]),
-	    &(context->context_priv->our_old_dh_key), context->context_priv->their_y);
+	    &(context->context_priv->our_old_dh_key),
+	    context->context_priv->their_y);
     if (err) return err;
 
     return gcry_error(GPG_ERR_NO_ERROR);
@@ -214,8 +221,9 @@ static gcry_error_t rotate_y_keys(ConnContext *context, gcry_mpi_t new_y)
 char *otrl_proto_default_query_msg(const char *ourname, OtrlPolicy policy)
 {
     char *msg;
-    int v1_supported, v2_supported;
-    const char *version_tag;
+    int v1_supported, v2_supported, v3_supported;
+    char *version_tag;
+    char *bufp;
     /* Don't use g_strdup_printf here, because someone (not us) is going
      * to free() the *message pointer, not g_free() it.  We can't
      * require that they g_free() it, because this pointer will probably
@@ -230,51 +238,73 @@ char *otrl_proto_default_query_msg(const char *ourname, OtrlPolicy policy)
     /* Figure out the version tag */
     v1_supported = (policy & OTRL_POLICY_ALLOW_V1);
     v2_supported = (policy & OTRL_POLICY_ALLOW_V2);
+    v3_supported = (policy & OTRL_POLICY_ALLOW_V3);
+    version_tag = malloc(8);
+    bufp = version_tag;
     if (v1_supported) {
+	*bufp = '?';
+	bufp++;
+    }
+    if (v2_supported || v3_supported) {
+	*bufp = 'v';
+	bufp++;
 	if (v2_supported) {
-	    version_tag = "?v2?";
-	} else {
-	    version_tag = "?";
+	    *bufp = '2';
+	    bufp++;
 	}
-    } else {
-	if (v2_supported) {
-	    version_tag = "v2?";
-	} else {
-	    version_tag = "v?";
+	if (v3_supported) {
+	    *bufp = '3';
+	    bufp++;
 	}
+	*bufp = '?';
+	bufp++;
     }
+    *bufp = '\0';
 
     /* Remove two "%s", add '\0' */
     msg = malloc(strlen(format) + strlen(version_tag) + strlen(ourname) - 3);
-    if (!msg) return NULL;
+    if (!msg) {
+	free(version_tag);
+	return NULL;
+    }
     sprintf(msg, format, version_tag, ourname);
+    free(version_tag);
     return msg;
 }
 
 /* Return the best version of OTR support by both sides, given an OTR
  * Query Message and the local policy. */
-unsigned int otrl_proto_query_bestversion(const char *querymsg,
+unsigned int otrl_proto_query_bestversion(const char *otrquerymsg,
 	OtrlPolicy policy)
 {
     char *otrtag;
     unsigned int query_versions = 0;
+    unsigned int query_position = 0;
 
-    otrtag = strstr(querymsg, "?OTR");
+
+    otrtag = strstr(otrquerymsg, "?OTR");
     otrtag += 4;
-    if (*otrtag == '?') {
+
+    if (otrtag && *otrtag == '?') {
 	query_versions = (1<<0);
 	++otrtag;
     }
-    if (*otrtag == 'v') {
+    if (otrtag && *otrtag == 'v') {
 	for(++otrtag; *otrtag && *otrtag != '?'; ++otrtag) {
 	    switch(*otrtag) {
 		case '2':
 		    query_versions |= (1<<1);
 		    break;
+		case '3':
+		    query_versions |= (1<<2);
+		    break;
 	    }
 	}
     }
 
+    if ((policy & OTRL_POLICY_ALLOW_V3) && (query_versions & (1<<2))) {
+	return 3;
+    }
     if ((policy & OTRL_POLICY_ALLOW_V2) && (query_versions & (1<<1))) {
 	return 2;
     }
@@ -318,6 +348,9 @@ unsigned int otrl_proto_whitespace_bestversion(const char *msg,
 	    if (!strncmp(endtag, OTRL_MESSAGE_TAG_V2, 8)) {
 		query_versions |= (1<<1);
 	    }
+	    if (!strncmp(endtag, OTRL_MESSAGE_TAG_V3, 8)) {
+		query_versions |= (1<<2);
+	    }
 	    endtag += 8;
 	} else {
 	    break;
@@ -327,6 +360,9 @@ unsigned int otrl_proto_whitespace_bestversion(const char *msg,
     *starttagp = starttag;
     *endtagp = endtag;
 
+    if ((policy & OTRL_POLICY_ALLOW_V3) && (query_versions & (1<<2))) {
+	return 3;
+    }
     if ((policy & OTRL_POLICY_ALLOW_V2) && (query_versions & (1<<1))) {
 	return 2;
     }
@@ -336,7 +372,7 @@ unsigned int otrl_proto_whitespace_bestversion(const char *msg,
     return 0;
 }
 
-/* Return the Message type of the given message. */
+/* Find the message type. */
 OtrlMessageType otrl_proto_message_type(const char *message)
 {
     char *otrtag;
@@ -351,23 +387,78 @@ OtrlMessageType otrl_proto_message_type(const char *message)
 	}
     }
 
-    if (!strncmp(otrtag, "?OTR?", 5)) return OTRL_MSGTYPE_QUERY;
-    if (!strncmp(otrtag, "?OTRv", 5)) return OTRL_MSGTYPE_QUERY;
-    if (!strncmp(otrtag, "?OTR:AAIC", 9)) return OTRL_MSGTYPE_DH_COMMIT;
-    if (!strncmp(otrtag, "?OTR:AAIK", 9)) return OTRL_MSGTYPE_DH_KEY;
-    if (!strncmp(otrtag, "?OTR:AAIR", 9)) return OTRL_MSGTYPE_REVEALSIG;
-    if (!strncmp(otrtag, "?OTR:AAIS", 9)) return OTRL_MSGTYPE_SIGNATURE;
-    if (!strncmp(otrtag, "?OTR:AAEK", 9)) return OTRL_MSGTYPE_V1_KEYEXCH;
-    if (!strncmp(otrtag, "?OTR:AAED", 9)) return OTRL_MSGTYPE_DATA;
-    if (!strncmp(otrtag, "?OTR:AAID", 9)) return OTRL_MSGTYPE_DATA;
-    if (!strncmp(otrtag, "?OTR Error:", 11)) return OTRL_MSGTYPE_ERROR;
-
+    if (!strncmp(otrtag, "?OTR:AAM", 8) || !strncmp(otrtag, "?OTR:AAI", 8)) {
+	switch(*(otrtag + 8)) {
+	    case 'C': return OTRL_MSGTYPE_DH_COMMIT;
+	    case 'K': return OTRL_MSGTYPE_DH_KEY;
+	    case 'R': return OTRL_MSGTYPE_REVEALSIG;
+	    case 'S': return OTRL_MSGTYPE_SIGNATURE;
+	    case 'D': return OTRL_MSGTYPE_DATA;
+	}
+    } else {
+	if (!strncmp(otrtag, "?OTR?", 5)) return OTRL_MSGTYPE_QUERY;
+	if (!strncmp(otrtag, "?OTRv", 5)) return OTRL_MSGTYPE_QUERY;
+	if (!strncmp(otrtag, "?OTR:AAEK", 9)) return OTRL_MSGTYPE_V1_KEYEXCH;
+	if (!strncmp(otrtag, "?OTR:AAED", 9)) return OTRL_MSGTYPE_DATA;
+	if (!strncmp(otrtag, "?OTR Error:", 11)) return OTRL_MSGTYPE_ERROR;
+    }
     return OTRL_MSGTYPE_UNKNOWN;
 }
 
+/* Find the message version. */
+int otrl_proto_message_version(const char *message)
+{
+    char *otrtag;
+
+    otrtag = strstr(message, "?OTR");
+
+    if (!otrtag) {
+	return 0;
+    }
+
+    if (!strncmp(otrtag, "?OTR:AAM", 8))
+	return 3;
+    if (!strncmp(otrtag, "?OTR:AAI", 8))
+	return 2;
+    if (!strncmp(otrtag, "?OTR:AAE", 8))
+	return 1;
+
+    return 0;
+}
+
+/* Find the instance tags in this message */
+gcry_error_t otrl_proto_instance(const char *otrmsg,
+	unsigned int *instance_from, unsigned int *instance_to)
+{
+    gcry_error_t err;
+
+    const char *otrtag = otrmsg;
+    unsigned char *bufp = NULL;
+    unsigned char *bufp_head = NULL;
+    size_t lenp;
+
+    if (strncmp(otrtag, "?OTR:AAM", 8)) {
+	goto invval;
+    }
+
+    if (strlen(otrtag) < 21 ) return err;
+
+    /* Decode and extract instance tag */
+    bufp = malloc(9);
+    bufp_head = bufp;
+    lenp = otrl_base64_decode(bufp, otrtag+9, 12);
+    read_int(*instance_from);
+    read_int(*instance_to);
+    free(bufp_head);
+    return gcry_error(GPG_ERR_NO_ERROR);
+    invval:
+	err = gcry_error(GPG_ERR_INV_VALUE);
+	return err;
+}
+
 /* Create an OTR Data message.  Pass the plaintext as msg, and an
  * optional chain of TLVs.  A newly-allocated string will be returned in
- * *encmessagep.  Put the current extra symmetric key into extrakey
+ * *encmessagep. Put the current extra symmetric key into extrakey
  * (if non-NULL). */
 gcry_error_t otrl_proto_create_data(char **encmessagep, ConnContext *context,
 	const char *msg, const OtrlTLV *tlvs, unsigned char flags,
@@ -406,12 +497,13 @@ gcry_error_t otrl_proto_create_data(char **encmessagep, ConnContext *context,
 
     *encmessagep = NULL;
 
-    /* Header, send keyid, recv keyid, counter, msg len, msg
+    /* Header, msg flags, send keyid, recv keyid, counter, msg len, msg
      * len of revealed mac keys, revealed mac keys, MAC */
-    buflen = 3 + (version == 2 ? 1 : 0) + 4 + 4 + 8 + 4 + msglen +
-	4 + reveallen + 20;
+    buflen = OTRL_HEADER_LEN + (version == 3 ? 8 : 0)
+	+ (version == 2 || version == 3 ? 1 : 0) + 4 + 4
+	+ 8 + 4 + msglen + 4 + reveallen + 20;
     gcry_mpi_print(format, NULL, 0, &pubkeylen,
-    		context->context_priv->our_dh_key.pub);
+	    context->context_priv->our_dh_key.pub);
     buflen += pubkeylen + 4;
     buf = malloc(buflen);
     msgbuf = gcry_malloc_secure(msglen);
@@ -428,18 +520,31 @@ gcry_error_t otrl_proto_create_data(char **encmessagep, ConnContext *context,
     lenp = buflen;
     if (version == 1) {
 	memmove(bufp, "\x00\x01\x03", 3);  /* header */
-    } else {
+    } else if (version == 2) {
 	memmove(bufp, "\x00\x02\x03", 3);  /* header */
+    } else {
+	memmove(bufp, "\x00\x03\x03", 3);  /* header */
     }
+
     debug_data("Header", bufp, 3);
     bufp += 3; lenp -= 3;
-    if (version == 2) {
+
+    if (version == 3) {
+	/* v3 instance tags */
+	write_int(context->our_instance);
+	debug_int("Sender instag", bufp-4);
+	write_int(context->their_instance);
+	debug_int("Recipient instag", bufp-4);
+    }
+
+    if (version == 2 || version == 3) {
 	bufp[0] = flags;
 	bufp += 1; lenp -= 1;
     }
-    write_int(context->context_priv->our_keyid-1);                /* sender keyid */
+
+    write_int(context->context_priv->our_keyid-1); /* sender keyid */
     debug_int("Sender keyid", bufp-4);
-    write_int(context->context_priv->their_keyid);                /* recipient keyid */
+    write_int(context->context_priv->their_keyid); /* recipient keyid */
     debug_int("Recipient keyid", bufp-4);
 
     write_mpi(context->context_priv->our_dh_key.pub, pubkeylen, "Y");  /* Y */
@@ -557,14 +662,15 @@ gcry_error_t otrl_proto_data_read_flags(const char *datamsg,
     lenp = rawlen;
 
     require_len(3);
-    if (memcmp(bufp, "\x00\x01\x03", 3) && memcmp(bufp, "\x00\x02\x03", 3)) {
-	/* Invalid header */
-	goto invval;
-    }
     version = bufp[1];
-    bufp += 3; lenp -= 3;
+    skip_header('\x03');
 
-    if (version == 2) {
+    if (version == 3) {
+	require_len(8);
+	bufp += 8; lenp -= 8;
+    }
+
+    if (version == 2 || version == 3) {
 	require_len(1);
 	if (flagsp) *flagsp = bufp[0];
 	bufp += 1; lenp -= 1;
@@ -630,18 +736,21 @@ gcry_error_t otrl_proto_accept_data(char **plaintextp, OtrlTLV **tlvsp,
 
     macstart = bufp;
     require_len(3);
-    if (memcmp(bufp, "\x00\x01\x03", 3) && memcmp(bufp, "\x00\x02\x03", 3)) {
-	/* Invalid header */
-	goto invval;
-    }
     version = bufp[1];
-    bufp += 3; lenp -= 3;
 
-    if (version == 2) {
+    skip_header('\x03');
+
+    if (version == 3) {
+	require_len(8);
+	bufp += 8; lenp -= 8;
+    }
+
+    if (version == 2 || version == 3) {
 	require_len(1);
 	if (flagsp) *flagsp = bufp[0];
 	bufp += 1; lenp -= 1;
     }
+
     read_int(sender_keyid);
     read_int(recipient_keyid);
     read_mpi(sender_next_y);
@@ -767,55 +876,63 @@ OtrlFragmentResult otrl_proto_fragment_accumulate(char **unfragmessagep,
 {
     OtrlFragmentResult res = OTRL_FRAGMENT_INCOMPLETE;
     const char *tag;
+    unsigned short n = 0, k = 0;
+    int start = 0, end = 0;
 
-    tag = strstr(msg, "?OTR,");
+    tag = strstr(msg, "?OTR|");
     if (tag) {
-	unsigned short n = 0, k = 0;
-	int start = 0, end = 0;
-
+	sscanf(tag, "?OTR|%*x|%*x,%hu,%hu,%n%*[^,],%n", &k, &n, &start, &end);
+    } else if (tag = strstr(msg, "?OTR,")) {
 	sscanf(tag, "?OTR,%hu,%hu,%n%*[^,],%n", &k, &n, &start, &end);
-	if (k > 0 && n > 0 && k <= n && start > 0 && end > 0 && start < end) {
-	    if (k == 1) {
-		int fraglen = end - start - 1;
-		size_t newsize = fraglen + 1;
-		free(context->context_priv->fragment);
-		context->context_priv->fragment = NULL;
-		if (newsize > fraglen) {  /* Check for overflow */
-		    context->context_priv->fragment = malloc(newsize);
-		}
-		if (context->context_priv->fragment) {
-		    memmove(context->context_priv->fragment, tag + start, fraglen);
-		    context->context_priv->fragment_len = fraglen;
-		    context->context_priv->fragment[context->context_priv->fragment_len] = '\0';
-		    context->context_priv->fragment_n = n;
-		    context->context_priv->fragment_k = k;
-		} else {
-		    context->context_priv->fragment_len = 0;
-		    context->context_priv->fragment_n = 0;
-		    context->context_priv->fragment_k = 0;
-		}
-	    } else if (n == context->context_priv->fragment_n &&
-		    k == context->context_priv->fragment_k + 1) {
-		int fraglen = end - start - 1;
-		char *newfrag = NULL;
-		size_t newsize = context->context_priv->fragment_len + fraglen + 1;
-		if (newsize > fraglen) {  /* Check for overflow */
-		    newfrag = realloc(context->context_priv->fragment, newsize);
-		}
-		if (newfrag) {
-		    context->context_priv->fragment = newfrag;
-		    memmove(context->context_priv->fragment + context->context_priv->fragment_len,
-			    tag + start, fraglen);
-		    context->context_priv->fragment_len += fraglen;
-		    context->context_priv->fragment[context->context_priv->fragment_len] = '\0';
-		    context->context_priv->fragment_k = k;
-		} else {
-		    free(context->context_priv->fragment);
-		    context->context_priv->fragment = NULL;
-		    context->context_priv->fragment_len = 0;
-		    context->context_priv->fragment_n = 0;
-		    context->context_priv->fragment_k = 0;
-		}
+    } else {
+	/* Unfragmented message, so discard any fragment we may have */
+	free(context->context_priv->fragment);
+	context->context_priv->fragment = NULL;
+	context->context_priv->fragment_len = 0;
+	context->context_priv->fragment_n = 0;
+	context->context_priv->fragment_k = 0;
+	res = OTRL_FRAGMENT_UNFRAGMENTED;
+	return res;
+    }
+
+    if (k > 0 && n > 0 && k <= n && start > 0 && end > 0 && start < end) {
+	if (k == 1) {
+	    int fraglen = end - start - 1;
+	    size_t newsize = fraglen + 1;
+	    free(context->context_priv->fragment);
+	    context->context_priv->fragment = NULL;
+	    if (newsize > fraglen) {  /* Check for overflow */
+		context->context_priv->fragment = malloc(newsize);
+	    }
+	    if (context->context_priv->fragment) {
+		memmove(context->context_priv->fragment, tag + start, fraglen);
+		context->context_priv->fragment_len = fraglen;
+		context->context_priv->fragment[
+			context->context_priv->fragment_len] = '\0';
+		context->context_priv->fragment_n = n;
+		context->context_priv->fragment_k = k;
+	    } else {
+		context->context_priv->fragment_len = 0;
+		context->context_priv->fragment_n = 0;
+		context->context_priv->fragment_k = 0;
+	    }
+	} else if (n == context->context_priv->fragment_n &&
+		k == context->context_priv->fragment_k + 1) {
+	    int fraglen = end - start - 1;
+	    char *newfrag = NULL;
+	    size_t newsize = context->context_priv->fragment_len + fraglen + 1;
+	    if (newsize > fraglen) {  /* Check for overflow */
+		newfrag = realloc(context->context_priv->fragment, newsize);
+	    }
+	    if (newfrag) {
+		context->context_priv->fragment = newfrag;
+		memmove(context->context_priv->fragment +
+			context->context_priv->fragment_len,
+			tag + start, fraglen);
+		context->context_priv->fragment_len += fraglen;
+		context->context_priv->fragment[
+			context->context_priv->fragment_len] = '\0';
+		context->context_priv->fragment_k = k;
 	    } else {
 		free(context->context_priv->fragment);
 		context->context_priv->fragment = NULL;
@@ -823,26 +940,25 @@ OtrlFragmentResult otrl_proto_fragment_accumulate(char **unfragmessagep,
 		context->context_priv->fragment_n = 0;
 		context->context_priv->fragment_k = 0;
 	    }
-	}
-
-	if (context->context_priv->fragment_n > 0 &&
-		context->context_priv->fragment_n == context->context_priv->fragment_k) {
-	    /* We've got a complete message */
-	    *unfragmessagep = context->context_priv->fragment;
+	} else {
+	    free(context->context_priv->fragment);
 	    context->context_priv->fragment = NULL;
 	    context->context_priv->fragment_len = 0;
 	    context->context_priv->fragment_n = 0;
 	    context->context_priv->fragment_k = 0;
-	    res = OTRL_FRAGMENT_COMPLETE;
 	}
-    } else {
-	/* Unfragmented message, so discard any fragment we may have */
-	free(context->context_priv->fragment);
+    }
+
+    if (context->context_priv->fragment_n > 0 &&
+	    context->context_priv->fragment_n ==
+	    context->context_priv->fragment_k) {
+	/* We've got a complete message */
+	*unfragmessagep = context->context_priv->fragment;
 	context->context_priv->fragment = NULL;
 	context->context_priv->fragment_len = 0;
 	context->context_priv->fragment_n = 0;
 	context->context_priv->fragment_k = 0;
-	res = OTRL_FRAGMENT_UNFRAGMENTED;
+	res = OTRL_FRAGMENT_COMPLETE;
     }
 
     return res;
@@ -850,14 +966,15 @@ OtrlFragmentResult otrl_proto_fragment_accumulate(char **unfragmessagep,
 
 /* Create a fragmented message. */
 gcry_error_t otrl_proto_fragment_create(int mms, int fragment_count,
-	char ***fragments, const char *message)
+	char ***fragments, ConnContext *context, const char *message)
 {
     char *fragdata;
     int fragdatalen = 0;
     unsigned short curfrag = 0;
     int index = 0;
     int msglen = strlen(message);
-    int headerlen = 19; /* Should vary by number of msgs */
+    /* Should vary by number of msgs */
+    int headerlen = context->protocol_version == 3 ? 37 : 19;
 
     char **fragmentarray = malloc(fragment_count * sizeof(char*));
     if(!fragmentarray) return gcry_error(GPG_ERR_ENOMEM);
@@ -867,40 +984,50 @@ gcry_error_t otrl_proto_fragment_create(int mms, int fragment_count,
      */
     for(curfrag = 1; curfrag <= fragment_count; curfrag++) {
 	int i;
-    	char *fragmentmsg;
+	char *fragmentmsg;
 
 	if (msglen - index < mms - headerlen) {
-    	    fragdatalen = msglen - index;
+	    fragdatalen = msglen - index;
 	} else {
 	    fragdatalen = mms - headerlen;
 	}
+
 	fragdata = malloc(fragdatalen + 1);
-    	if(!fragdata) {
+	if(!fragdata) {
 		for (i=0; i<curfrag-1; free(fragmentarray[i++])) {}
-    		free(fragmentarray);
-    		return gcry_error(GPG_ERR_ENOMEM);
-    	}
-    	strncpy(fragdata, message, fragdatalen);
-    	fragdata[fragdatalen] = 0;
-
-    	fragmentmsg = malloc(fragdatalen+headerlen+1);
-    	if(!fragmentmsg) {
+		free(fragmentarray);
+		return gcry_error(GPG_ERR_ENOMEM);
+	}
+	strncpy(fragdata, message, fragdatalen);
+	fragdata[fragdatalen] = 0;
+
+	fragmentmsg = malloc(fragdatalen+headerlen+1);
+	if(!fragmentmsg) {
 	    for (i=0; i<curfrag-1; free(fragmentarray[i++])) {}
-    	    free(fragmentarray);
-    	    free(fragdata);
-    	    return gcry_error(GPG_ERR_ENOMEM);
-    	}
+	    free(fragmentarray);
+	    free(fragdata);
+	    return gcry_error(GPG_ERR_ENOMEM);
+	}
 
-    	/*
-    	 * Create the actual fragment and store it in the array
-    	 */
-    	snprintf(fragmentmsg, fragdatalen + headerlen, "?OTR,%05hu,%05hu,%s,", curfrag, fragment_count, fragdata);
-    	fragmentmsg[fragdatalen + headerlen] = 0;
+	/*
+	 * Create the actual fragment and store it in the array
+	 */
+	if (context->auth.protocol_version != 3) {
+	    snprintf(fragmentmsg, fragdatalen + headerlen,
+		    "?OTR,%05hu,%05hu,%s,", curfrag, fragment_count, fragdata);
+	} else {
+	    /* V3 messages require instance tags in the header */
+	    snprintf(fragmentmsg, fragdatalen + headerlen,
+		    "?OTR|%08x|%08x,%05hu,%05hu,%s,",
+		    context->our_instance, context->their_instance, curfrag,
+		    fragment_count, fragdata);
+	}
+	fragmentmsg[fragdatalen + headerlen] = 0;
 
-    	fragmentarray[curfrag-1] = fragmentmsg;
+	fragmentarray[curfrag-1] = fragmentmsg;
 
-    	free(fragdata);
-    	index += fragdatalen;
+	free(fragdata);
+	index += fragdatalen;
 	message += fragdatalen;
     }
 
@@ -917,7 +1044,7 @@ void otrl_proto_fragment_free(char ***fragments, unsigned short arraylen)
 	for(i = 0; i < arraylen; i++)
 	{
 	    if(fragmentarray[i]) {
-	    	free(fragmentarray[i]);
+		free(fragmentarray[i]);
 	    }
 	}
 	free(fragmentarray);
diff --git a/src/proto.h b/src/proto.h
index afd74fe..7f98999 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -31,6 +31,7 @@
 /* The following must each be of length 8 */
 #define OTRL_MESSAGE_TAG_V1 " \t \t  \t "
 #define OTRL_MESSAGE_TAG_V2 "  \t\t  \t "
+#define OTRL_MESSAGE_TAG_V3 "  \t\t  \t\t"
 
 /* The possible flags contained in a Data Message */
 #define OTRL_MSGFLAGS_IGNORE_UNREADABLE		0x01
@@ -39,27 +40,36 @@ typedef unsigned int OtrlPolicy;
 
 #define OTRL_POLICY_ALLOW_V1			0x01
 #define OTRL_POLICY_ALLOW_V2			0x02
-#define OTRL_POLICY_REQUIRE_ENCRYPTION		0x04
-#define OTRL_POLICY_SEND_WHITESPACE_TAG		0x08
-#define OTRL_POLICY_WHITESPACE_START_AKE	0x10
-#define OTRL_POLICY_ERROR_START_AKE		0x20
+#define OTRL_POLICY_ALLOW_V3			0x04
+#define OTRL_POLICY_REQUIRE_ENCRYPTION		0x08
+#define OTRL_POLICY_SEND_WHITESPACE_TAG		0x10
+#define OTRL_POLICY_WHITESPACE_START_AKE	0x20
+#define OTRL_POLICY_ERROR_START_AKE		0x40
 
-#define OTRL_POLICY_VERSION_MASK (OTRL_POLICY_ALLOW_V1 | OTRL_POLICY_ALLOW_V2)
+#define OTRL_POLICY_VERSION_MASK (OTRL_POLICY_ALLOW_V1 | OTRL_POLICY_ALLOW_V2 |\
+	OTRL_POLICY_ALLOW_V3)
+
+/* Length of OTR message headers */
+#define OTRL_HEADER_LEN		3
+#define OTRL_B64_HEADER_LEN	4
 
 /* For v1 compatibility */
 #define OTRL_POLICY_NEVER			0x00
 #define OTRL_POLICY_OPPORTUNISTIC \
 	    ( OTRL_POLICY_ALLOW_V1 | \
 	    OTRL_POLICY_ALLOW_V2 | \
+	    OTRL_POLICY_ALLOW_V3 | \
 	    OTRL_POLICY_SEND_WHITESPACE_TAG | \
 	    OTRL_POLICY_WHITESPACE_START_AKE | \
 	    OTRL_POLICY_ERROR_START_AKE )
 #define OTRL_POLICY_MANUAL \
 	    ( OTRL_POLICY_ALLOW_V1 | \
-	    OTRL_POLICY_ALLOW_V2 )
+	    OTRL_POLICY_ALLOW_V2 | \
+	    OTRL_POLICY_ALLOW_V3)
 #define OTRL_POLICY_ALWAYS \
 	    ( OTRL_POLICY_ALLOW_V1 | \
 	    OTRL_POLICY_ALLOW_V2 | \
+	    OTRL_POLICY_ALLOW_V3 | \
 	    OTRL_POLICY_REQUIRE_ENCRYPTION | \
 	    OTRL_POLICY_WHITESPACE_START_AKE | \
 	    OTRL_POLICY_ERROR_START_AKE )
@@ -86,10 +96,11 @@ typedef enum {
 } OtrlFragmentResult;
 
 typedef enum {
+    OTRL_FRAGMENT_SEND_SKIP, /* Return new message back to caller,
+			      * but don't inject. */
     OTRL_FRAGMENT_SEND_ALL,
     OTRL_FRAGMENT_SEND_ALL_BUT_FIRST,
-    OTRL_FRAGMENT_SEND_ALL_BUT_LAST,
-    OTRL_FRAGMENT_SEND_SKIP
+    OTRL_FRAGMENT_SEND_ALL_BUT_LAST
 } OtrlFragmentPolicy;
 
 /* Initialize the OTR library.  Pass the version of the API you are
@@ -122,12 +133,20 @@ unsigned int otrl_proto_query_bestversion(const char *querymsg,
 unsigned int otrl_proto_whitespace_bestversion(const char *msg,
 	const char **starttagp, const char **endtagp, OtrlPolicy policy);
 
-/* Return the Message type of the given message. */
+/* Find the message type. */
 OtrlMessageType otrl_proto_message_type(const char *message);
 
+/* Find the message version. */
+int otrl_proto_message_version(const char *message);
+
+/* Find the instance tags in this message. */
+gcry_error_t otrl_proto_instance(const char *otrmsg,
+	unsigned int *instance_from, unsigned int *instance_to);
+
 /* Create an OTR Data message.  Pass the plaintext as msg, and an
  * optional chain of TLVs.  A newly-allocated string will be returned in
- * *encmessagep. */
+ * *encmessagep. Put the current extra symmetric key into extrakey
+ * (if non-NULL). */
 gcry_error_t otrl_proto_create_data(char **encmessagep, ConnContext *context,
 	const char *msg, const OtrlTLV *tlvs, unsigned char flags,
 	unsigned char *extrakey);
@@ -149,7 +168,7 @@ OtrlFragmentResult otrl_proto_fragment_accumulate(char **unfragmessagep,
 	ConnContext *context, const char *msg);
 
 gcry_error_t otrl_proto_fragment_create(int mms, int fragment_count,
-	char ***fragments, const char *message);
+	char ***fragments, ConnContext *context, const char *message);
 
 void otrl_proto_fragment_free(char ***fragments, unsigned short arraylen);
 #endif
diff --git a/src/serial.h b/src/serial.h
index 439a271..ed8fef9 100644
--- a/src/serial.h
+++ b/src/serial.h
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -83,4 +83,25 @@
 	bufp += mpilen; lenp -= mpilen; \
     } while(0)
 
+/* Write version and msg type into bufp*/
+#define write_header(version, msgtype) do { \
+	bufp[0] = 0x00; \
+        bufp[1] = version & 0xff; \
+        bufp[2] = msgtype; \
+        debug_data("Header", bufp, 3); \
+        bufp += 3; lenp -= 3; \
+    } while(0)
+
+/* Verify msg header is v1, v2 or v3 and has type x,
+*  increment bufp past msg header */
+#define skip_header(x) do { \
+        require_len(3); \
+        if ((bufp[0] != 0x00) || (bufp[2] != x)) \
+	    goto invval; \
+        if ((bufp[1] == 0x01) || (bufp[1] == 0x02) || \
+                (bufp[1] == 0x03)) { \
+	    bufp += 3; lenp -= 3; \
+	} else goto invval; \
+    } while(0)
+
 #endif
diff --git a/src/sm.c b/src/sm.c
index f3bcbd0..fd8588a 100644
--- a/src/sm.c
+++ b/src/sm.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *  			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
@@ -21,7 +21,7 @@
 /* system headers */
 #include <stdlib.h>
 #include <stdio.h>
-#include <sys/types.h> 
+#include <sys/types.h>
 
 /* libgcrypt headers */
 #include <gcrypt.h>
@@ -157,7 +157,7 @@ void otrl_sm_msg2_init(gcry_mpi_t **msg2)
     msg[7] = gcry_mpi_new(SM_MOD_LEN_BITS);
     msg[8] = NULL;
     msg[9] = gcry_mpi_new(SM_MOD_LEN_BITS);
-    msg[10] = gcry_mpi_new(SM_MOD_LEN_BITS); 
+    msg[10] = gcry_mpi_new(SM_MOD_LEN_BITS);
 
     *msg2 = msg;
 }
@@ -224,7 +224,7 @@ void otrl_sm_msg_free(gcry_mpi_t **message, int msglen)
     gcry_mpi_t *msg = *message;
     int i;
     for (i=0; i<msglen; i++) {
-        gcry_mpi_release(msg[i]);
+	gcry_mpi_release(msg[i]);
     }
     free(msg);
     *message = NULL;
@@ -256,7 +256,7 @@ static gcry_error_t otrl_sm_hash(gcry_mpi_t* hash, int version,
     size_t totalsize;
     unsigned char* dataa;
     unsigned char* datab;
-    
+
     gcry_mpi_aprint(GCRYMPI_FMT_USG, &dataa, &sizea, a);
     totalsize = 1 + 4 + sizea;
     if (b) {
@@ -307,9 +307,9 @@ static gcry_error_t serialize_mpi_array(unsigned char **buffer, int *buflen,
     unsigned char *bufp;
 
     for (i=0; i<count; i++) {
-        gcry_mpi_aprint(GCRYMPI_FMT_USG, &(tempbuffer[i]), &(list_sizes[i]),
+	gcry_mpi_aprint(GCRYMPI_FMT_USG, &(tempbuffer[i]), &(list_sizes[i]),
 		mpis[i]);
-        totalsize += list_sizes[i];
+	totalsize += list_sizes[i];
     }
 
     *buflen = (count+1)*4 + totalsize;
@@ -321,15 +321,15 @@ static gcry_error_t serialize_mpi_array(unsigned char **buffer, int *buflen,
     write_int(count);
     for(i=0; i<count; i++)
     {
-        nextsize = list_sizes[i];
-        write_int(nextsize);
-        
-        for(j=0; j<nextsize; j++)
-            bufp[j] = tempbuffer[i][j];
-        
+	nextsize = list_sizes[i];
+	write_int(nextsize);
+
+	for(j=0; j<nextsize; j++)
+	    bufp[j] = tempbuffer[i][j];
+
 	bufp += nextsize;
-        lenp -= nextsize;
-        gcry_free(tempbuffer[i]);
+	lenp -= nextsize;
+	gcry_free(tempbuffer[i]);
     }
     free(tempbuffer);
     free(list_sizes);
@@ -401,7 +401,8 @@ static int check_expon(gcry_mpi_t x)
 /*
  * Proof of knowledge of a discrete logarithm
  */
-static gcry_error_t otrl_sm_proof_know_log(gcry_mpi_t *c, gcry_mpi_t *d, const gcry_mpi_t g, const gcry_mpi_t x, int version)
+static gcry_error_t otrl_sm_proof_know_log(gcry_mpi_t *c, gcry_mpi_t *d,
+	const gcry_mpi_t g, const gcry_mpi_t x, int version)
 {
     gcry_mpi_t r = randomExponent();
     gcry_mpi_t temp = gcry_mpi_new(SM_MOD_LEN_BITS);
@@ -416,9 +417,11 @@ static gcry_error_t otrl_sm_proof_know_log(gcry_mpi_t *c, gcry_mpi_t *d, const g
 }
 
 /*
- * Verify a proof of knowledge of a discrete logarithm.  Checks that c = h(g^d x^c)
+ * Verify a proof of knowledge of a discrete logarithm. 
+ * Checks that c = h(g^d x^c)
  */
-static int otrl_sm_check_know_log(const gcry_mpi_t c, const gcry_mpi_t d, const gcry_mpi_t g, const gcry_mpi_t x, int version)
+static int otrl_sm_check_know_log(const gcry_mpi_t c, const gcry_mpi_t d,
+	const gcry_mpi_t g, const gcry_mpi_t x, int version)
 {
     int comp;
 
@@ -431,7 +434,7 @@ static int otrl_sm_check_know_log(const gcry_mpi_t c, const gcry_mpi_t d, const
     gcry_mpi_powm(xc, x, c, SM_MODULUS);
     gcry_mpi_mulm(gdxc, gd, xc, SM_MODULUS);
     otrl_sm_hash(&hgdxc, version, gdxc, NULL);
-    
+
     comp = gcry_mpi_cmp(hgdxc, c);
     gcry_mpi_release(gd);
     gcry_mpi_release(xc);
@@ -444,7 +447,9 @@ static int otrl_sm_check_know_log(const gcry_mpi_t c, const gcry_mpi_t d, const
 /*
  * Proof of knowledge of coordinates with first components being equal
  */
-static gcry_error_t otrl_sm_proof_equal_coords(gcry_mpi_t *c, gcry_mpi_t *d1, gcry_mpi_t *d2, const OtrlSMState *state, const gcry_mpi_t r, int version)
+static gcry_error_t otrl_sm_proof_equal_coords(gcry_mpi_t *c, gcry_mpi_t *d1,
+	gcry_mpi_t *d2, const OtrlSMState *state, const gcry_mpi_t r,
+	int version)
 {
     gcry_mpi_t r1 = randomExponent();
     gcry_mpi_t r2 = randomExponent();
@@ -476,7 +481,9 @@ static gcry_error_t otrl_sm_proof_equal_coords(gcry_mpi_t *c, gcry_mpi_t *d1, gc
 /*
  * Verify a proof of knowledge of coordinates with first components being equal
  */
-static gcry_error_t otrl_sm_check_equal_coords(const gcry_mpi_t c, const gcry_mpi_t d1, const gcry_mpi_t d2, const gcry_mpi_t p, const gcry_mpi_t q, const OtrlSMState *state, int version)
+static gcry_error_t otrl_sm_check_equal_coords(const gcry_mpi_t c,
+	const gcry_mpi_t d1, const gcry_mpi_t d2, const gcry_mpi_t p,
+	const gcry_mpi_t q, const OtrlSMState *state, int version)
 {
     int comp;
 
@@ -519,7 +526,8 @@ static gcry_error_t otrl_sm_check_equal_coords(const gcry_mpi_t c, const gcry_mp
 /*
  * Proof of knowledge of logs with exponents being equal
  */
-static gcry_error_t otrl_sm_proof_equal_logs(gcry_mpi_t *c, gcry_mpi_t *d, OtrlSMState *state, int version)
+static gcry_error_t otrl_sm_proof_equal_logs(gcry_mpi_t *c, gcry_mpi_t *d,
+	OtrlSMState *state, int version)
 {
     gcry_mpi_t r = randomExponent();
     gcry_mpi_t temp1 = gcry_mpi_new(SM_MOD_LEN_BITS);
@@ -544,7 +552,9 @@ static gcry_error_t otrl_sm_proof_equal_logs(gcry_mpi_t *c, gcry_mpi_t *d, OtrlS
 /*
  * Verify a proof of knowledge of logs with exponents being equal
  */
-static gcry_error_t otrl_sm_check_equal_logs(const gcry_mpi_t c, const gcry_mpi_t d, const gcry_mpi_t r, const OtrlSMState *state, int version)
+static gcry_error_t otrl_sm_check_equal_logs(const gcry_mpi_t c,
+	const gcry_mpi_t d, const gcry_mpi_t r, const OtrlSMState *state,
+	int version)
 {
     int comp;
 
@@ -556,7 +566,7 @@ static gcry_error_t otrl_sm_check_equal_logs(const gcry_mpi_t c, const gcry_mpi_
     /* Here, we recall the exponents used to create g3.
      * If we have previously seen g3o = g1^x where x is unknown
      * during the DH exchange to produce g3, then we may proceed with:
-     * 
+     *
      * To verify, we test that hash(g1^d * g3o^c, qab^d * r^c) = c
      * If indeed c = hash(g1^r1, qab^r1), d = r1- x * c
      * And if indeed r = qab^x
@@ -602,11 +612,11 @@ gcry_error_t otrl_sm_step1(OtrlSMAliceState *astate,
 
     *output = NULL;
     *outputlen = 0;
-    
+
     gcry_mpi_scan(&secret_mpi, GCRYMPI_FMT_USG, secret, secretlen, NULL);
 
     if (! astate->g1) {
-        otrl_sm_state_init(astate);
+	otrl_sm_state_init(astate);
     }
     gcry_mpi_set(astate->secret, secret_mpi);
     gcry_mpi_release(secret_mpi);
@@ -632,7 +642,8 @@ gcry_error_t otrl_sm_step1(OtrlSMAliceState *astate,
 /* Receive the first message in SMP exchange, which was generated by
  * otrl_sm_step1.  Input is saved until the user inputs their secret
  * information.  No output. */
-gcry_error_t otrl_sm_step2a(OtrlSMBobState *bstate, const unsigned char* input, const int inputlen, int received_question)
+gcry_error_t otrl_sm_step2a(OtrlSMBobState *bstate, const unsigned char* input,
+	const int inputlen, int received_question)
 {
     gcry_mpi_t *msg1;
     gcry_error_t err;
@@ -646,21 +657,21 @@ gcry_error_t otrl_sm_step2a(OtrlSMBobState *bstate, const unsigned char* input,
 
     /* Read from input to find the mpis */
     err = unserialize_mpi_array(&msg1, SM_MSG1_LEN, input, inputlen);
-    
+
     if (err != gcry_error(GPG_ERR_NO_ERROR)) return err;
 
     if (check_group_elem(msg1[0]) || check_expon(msg1[2]) ||
 	    check_group_elem(msg1[3]) || check_expon(msg1[5])) {
-        return gcry_error(GPG_ERR_INV_VALUE);
+	return gcry_error(GPG_ERR_INV_VALUE);
     }
 
     /* Store Alice's g3a value for later in the protocol */
     gcry_mpi_set(bstate->g3o, msg1[3]);
-    
+
     /* Verify Alice's proofs */
-    if (otrl_sm_check_know_log(msg1[1], msg1[2], bstate->g1, msg1[0], 1) || 
-        otrl_sm_check_know_log(msg1[4], msg1[5], bstate->g1, msg1[3], 2)) {
-        return gcry_error(GPG_ERR_INV_VALUE);
+    if (otrl_sm_check_know_log(msg1[1], msg1[2], bstate->g1, msg1[0], 1) ||
+	otrl_sm_check_know_log(msg1[4], msg1[5], bstate->g1, msg1[3], 2)) {
+	return gcry_error(GPG_ERR_INV_VALUE);
     }
 
     /* Create Bob's half of the generators g2 and g3 */
@@ -685,7 +696,8 @@ gcry_error_t otrl_sm_step2a(OtrlSMBobState *bstate, const unsigned char* input,
  * [4] = c3, [5] = d3, Bob's ZK proof of knowledge of g3b exponent
  * [6] = pb, [7] = qb, Bob's halves of the (Pa/Pb) and (Qa/Qb) values
  * [8] = cp, [9] = d5, [10] = d6, Bob's ZK proof that pb, qb formed correctly */
-gcry_error_t otrl_sm_step2b(OtrlSMBobState *bstate, const unsigned char* secret, int secretlen, unsigned char **output, int* outputlen)
+gcry_error_t otrl_sm_step2b(OtrlSMBobState *bstate, const unsigned char* secret,
+	int secretlen, unsigned char **output, int* outputlen)
 {
     /* Convert the given secret to the proper form and store it */
     gcry_mpi_t r, qb1, qb2;
@@ -694,7 +706,7 @@ gcry_error_t otrl_sm_step2b(OtrlSMBobState *bstate, const unsigned char* secret,
 
     *output = NULL;
     *outputlen = 0;
-    
+
     gcry_mpi_scan(&secret_mpi, GCRYMPI_FMT_USG, secret, secretlen, NULL);
     gcry_mpi_set(bstate->secret, secret_mpi);
     gcry_mpi_release(secret_mpi);
@@ -718,7 +730,8 @@ gcry_error_t otrl_sm_step2b(OtrlSMBobState *bstate, const unsigned char* secret,
     gcry_mpi_mulm(bstate->q, qb1, qb2, SM_MODULUS);
     gcry_mpi_set(msg2[7], bstate->q);
 
-    otrl_sm_proof_equal_coords(&(msg2[8]), &(msg2[9]), &(msg2[10]), bstate, r, 5);
+    otrl_sm_proof_equal_coords(&(msg2[8]), &(msg2[9]),
+	    &(msg2[10]), bstate, r, 5);
 
     /* Convert to serialized form */
     serialize_mpi_array(output, outputlen, SM_MSG2_LEN, msg2);
@@ -739,18 +752,19 @@ gcry_error_t otrl_sm_step2b(OtrlSMBobState *bstate, const unsigned char* secret,
  * [2] = cp, [3] = d5, [4] = d6, Alice's ZK proof that pa, qa formed correctly
  * [5] = ra, calculated as (Qa/Qb)^x3 where x3 is the exponent used in g3a
  * [6] = cr, [7] = d7, Alice's ZK proof that ra is formed correctly */
-gcry_error_t otrl_sm_step3(OtrlSMAliceState *astate, const unsigned char* input, const int inputlen, unsigned char **output, int* outputlen)
+gcry_error_t otrl_sm_step3(OtrlSMAliceState *astate, const unsigned char* input,
+	const int inputlen, unsigned char **output, int* outputlen)
 {
     /* Read from input to find the mpis */
     gcry_mpi_t r, qa1, qa2, inv;
     gcry_mpi_t *msg2;
     gcry_mpi_t *msg3;
     gcry_error_t err;
-    
+
     *output = NULL;
     *outputlen = 0;
     astate->sm_prog_state = OTRL_SMP_PROG_CHEATED;
-    
+
     err = unserialize_mpi_array(&msg2, SM_MSG2_LEN, input, inputlen);
     if (err != gcry_error(GPG_ERR_NO_ERROR)) return err;
 
@@ -758,7 +772,7 @@ gcry_error_t otrl_sm_step3(OtrlSMAliceState *astate, const unsigned char* input,
 	    check_group_elem(msg2[6]) || check_group_elem(msg2[7]) ||
 	    check_expon(msg2[2]) || check_expon(msg2[5]) ||
 	    check_expon(msg2[9]) || check_expon(msg2[10])) {
-        return gcry_error(GPG_ERR_INV_VALUE);
+	return gcry_error(GPG_ERR_INV_VALUE);
     }
 
     otrl_sm_msg3_init(&msg3);
@@ -767,9 +781,9 @@ gcry_error_t otrl_sm_step3(OtrlSMAliceState *astate, const unsigned char* input,
     gcry_mpi_set(astate->g3o, msg2[3]);
 
     /* Verify Bob's knowledge of discreet log proofs */
-    if (otrl_sm_check_know_log(msg2[1], msg2[2], astate->g1, msg2[0], 3) || 
-        otrl_sm_check_know_log(msg2[4], msg2[5], astate->g1, msg2[3], 4)) {
-        return gcry_error(GPG_ERR_INV_VALUE);
+    if (otrl_sm_check_know_log(msg2[1], msg2[2], astate->g1, msg2[0], 3) ||
+	otrl_sm_check_know_log(msg2[4], msg2[5], astate->g1, msg2[3], 4)) {
+	return gcry_error(GPG_ERR_INV_VALUE);
     }
 
     /* Combine the two halves from Bob and Alice and determine g2 and g3 */
@@ -777,8 +791,9 @@ gcry_error_t otrl_sm_step3(OtrlSMAliceState *astate, const unsigned char* input,
     gcry_mpi_powm(astate->g3, msg2[3], astate->x3, SM_MODULUS);
 
     /* Verify Bob's coordinate equality proof */
-    if (otrl_sm_check_equal_coords(msg2[8], msg2[9], msg2[10], msg2[6], msg2[7], astate, 5))
-        return gcry_error(GPG_ERR_INV_VALUE);
+    if (otrl_sm_check_equal_coords(msg2[8], msg2[9], msg2[10], msg2[6], msg2[7],
+	    astate, 5))
+	return gcry_error(GPG_ERR_INV_VALUE);
 
     /* Calculate P and Q values for Alice */
     r = randomExponent();
@@ -791,7 +806,8 @@ gcry_error_t otrl_sm_step3(OtrlSMAliceState *astate, const unsigned char* input,
     gcry_mpi_mulm(astate->q, qa1, qa2, SM_MODULUS);
     gcry_mpi_set(msg3[1], astate->q);
 
-    otrl_sm_proof_equal_coords(&(msg3[2]), &(msg3[3]), &(msg3[4]), astate, r, 6);
+    otrl_sm_proof_equal_coords(&(msg3[2]), &(msg3[3]), &(msg3[4]), astate,
+	    r, 6);
 
     /* Calculate Ra and proof */
     inv = gcry_mpi_new(SM_MOD_LEN_BITS);
@@ -823,7 +839,8 @@ gcry_error_t otrl_sm_step3(OtrlSMAliceState *astate, const unsigned char* input,
  * This method also checks if Alice and Bob's secrets were the same.  If
  * so, it returns NO_ERROR.  If the secrets differ, an INV_VALUE error is
  * returned instead. */
-gcry_error_t otrl_sm_step4(OtrlSMBobState *bstate, const unsigned char* input, const int inputlen, unsigned char **output, int* outputlen)
+gcry_error_t otrl_sm_step4(OtrlSMBobState *bstate, const unsigned char* input,
+	const int inputlen, unsigned char **output, int* outputlen)
 {
     /* Read from input to find the mpis */
     int comp;
@@ -836,7 +853,7 @@ gcry_error_t otrl_sm_step4(OtrlSMBobState *bstate, const unsigned char* input, c
     *output = NULL;
     *outputlen = 0;
     bstate->sm_prog_state = OTRL_SMP_PROG_CHEATED;
-    
+
     if (err != gcry_error(GPG_ERR_NO_ERROR)) return err;
 
     otrl_sm_msg4_init(&msg4);
@@ -844,12 +861,13 @@ gcry_error_t otrl_sm_step4(OtrlSMBobState *bstate, const unsigned char* input, c
     if (check_group_elem(msg3[0]) || check_group_elem(msg3[1]) ||
 	    check_group_elem(msg3[5]) || check_expon(msg3[3]) ||
 	    check_expon(msg3[4]) || check_expon(msg3[7]))  {
-        return gcry_error(GPG_ERR_INV_VALUE);
+	return gcry_error(GPG_ERR_INV_VALUE);
     }
 
     /* Verify Alice's coordinate equality proof */
-    if (otrl_sm_check_equal_coords(msg3[2], msg3[3], msg3[4], msg3[0], msg3[1], bstate, 6))
-        return gcry_error(GPG_ERR_INV_VALUE);
+    if (otrl_sm_check_equal_coords(msg3[2], msg3[3], msg3[4], msg3[0], msg3[1],
+	    bstate, 6))
+	return gcry_error(GPG_ERR_INV_VALUE);
 
     /* Find Pa/Pb and Qa/Qb */
     inv = gcry_mpi_new(SM_MOD_LEN_BITS);
@@ -860,7 +878,7 @@ gcry_error_t otrl_sm_step4(OtrlSMBobState *bstate, const unsigned char* input, c
 
     /* Verify Alice's log equality proof */
     if (otrl_sm_check_equal_logs(msg3[6], msg3[7], msg3[5], bstate, 7))
-        return gcry_error(GPG_ERR_INV_VALUE);
+	return gcry_error(GPG_ERR_INV_VALUE);
 
     /* Calculate Rb and proof */
     gcry_mpi_powm(msg4[0], bstate->qab, bstate->x3, SM_MODULUS);
@@ -883,16 +901,17 @@ gcry_error_t otrl_sm_step4(OtrlSMBobState *bstate, const unsigned char* input, c
 	OTRL_SMP_PROG_SUCCEEDED;
 
     if (comp)
-        return gcry_error(GPG_ERR_INV_VALUE);
+	return gcry_error(GPG_ERR_INV_VALUE);
     else
-        return gcry_error(GPG_ERR_NO_ERROR);
+	return gcry_error(GPG_ERR_NO_ERROR);
 }
 
 /* Receives the final SMP message, which was generated in otrl_sm_step.
  * This method checks if Alice and Bob's secrets were the same.  If
  * so, it returns NO_ERROR.  If the secrets differ, an INV_VALUE error is
  * returned instead. */
-gcry_error_t otrl_sm_step5(OtrlSMAliceState *astate, const unsigned char* input, const int inputlen)
+gcry_error_t otrl_sm_step5(OtrlSMAliceState *astate, const unsigned char* input,
+	const int inputlen)
 {
     /* Read from input to find the mpis */
     int comp;
@@ -901,16 +920,16 @@ gcry_error_t otrl_sm_step5(OtrlSMAliceState *astate, const unsigned char* input,
     gcry_error_t err;
     err = unserialize_mpi_array(&msg4, SM_MSG4_LEN, input, inputlen);
     astate->sm_prog_state = OTRL_SMP_PROG_CHEATED;
-    
+
     if (err != gcry_error(GPG_ERR_NO_ERROR)) return err;
 
     if (check_group_elem(msg4[0]) || check_expon(msg4[2])) {
-        return gcry_error(GPG_ERR_INV_VALUE);
+	return gcry_error(GPG_ERR_INV_VALUE);
     }
 
     /* Verify Bob's log equality proof */
     if (otrl_sm_check_equal_logs(msg4[1], msg4[2], msg4[0], astate, 8))
-        return gcry_error(GPG_ERR_INV_VALUE);
+	return gcry_error(GPG_ERR_INV_VALUE);
 
     /* Calculate Rab and verify that secrets match */
     rab = gcry_mpi_new(SM_MOD_LEN_BITS);
@@ -924,7 +943,7 @@ gcry_error_t otrl_sm_step5(OtrlSMAliceState *astate, const unsigned char* input,
 	OTRL_SMP_PROG_SUCCEEDED;
 
     if (comp)
-        return gcry_error(GPG_ERR_INV_VALUE);
+	return gcry_error(GPG_ERR_INV_VALUE);
     else
-        return gcry_error(GPG_ERR_NO_ERROR);
+	return gcry_error(GPG_ERR_NO_ERROR);
 }
diff --git a/src/sm.h b/src/sm.h
index 2e44b46..eae1241 100644
--- a/src/sm.h
+++ b/src/sm.h
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *  			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
diff --git a/src/tests.c b/src/tests.c
index ba429b3..d64ac35 100644
--- a/src/tests.c
+++ b/src/tests.c
@@ -4,6 +4,7 @@
 #include "proto.h"
 #include "privkey.h"
 #include "message.h"
+#include "instag.h"
 
 #define ALICE "alice"
 #define BOB "bob"
@@ -11,7 +12,6 @@
 
 static OtrlPolicy ALICEPOLICY = OTRL_POLICY_DEFAULT &~ OTRL_POLICY_ALLOW_V1;
 static OtrlPolicy BOBPOLICY = OTRL_POLICY_DEFAULT;
-
 void receiving(const char *from, const char *to, const char *msg);
 
 typedef struct s_node {
@@ -22,6 +22,18 @@ typedef struct s_node {
 static MsgNode *noderoot = NULL;
 static MsgNode **nodeend = &noderoot;
 
+void otrl_sm_init(void) {}
+void otrl_sm_state_new(OtrlSMState *smst) {}
+void otrl_sm_state_init(OtrlSMState *smst) {}
+void otrl_sm_state_free(OtrlSMState *smst) {}
+gcry_error_t otrl_sm_step1(OtrlSMAliceState *astate, const unsigned char* secret, int secretlen, unsigned char** output, int* outputlen) {return gcry_error(GPG_ERR_NO_ERROR);}
+gcry_error_t otrl_sm_step2a(OtrlSMBobState *bstate, const unsigned char* input, const int inputlen, int received_question) {return gcry_error(GPG_ERR_NO_ERROR);}
+gcry_error_t otrl_sm_step2b(OtrlSMBobState *bstate, const unsigned char* secret, int secretlen, unsigned char **output, int* outputlen) {return gcry_error(GPG_ERR_NO_ERROR);}
+gcry_error_t otrl_sm_step3(OtrlSMAliceState *astate, const unsigned char* input, const int inputlen, unsigned char **output, int* outputlen) {return gcry_error(GPG_ERR_NO_ERROR);}
+gcry_error_t otrl_sm_step4(OtrlSMBobState *bstate, const unsigned char* input, const int inputlen, unsigned char **output, int* outputlen) {return gcry_error(GPG_ERR_NO_ERROR);}
+gcry_error_t otrl_sm_step5(OtrlSMAliceState *astate, const unsigned char* input, const int inputlen) {return gcry_error(GPG_ERR_NO_ERROR);}
+
+
 static void dispatch(void)
 {
     while(noderoot) {
@@ -136,7 +148,7 @@ static void sending(const char *from, const char *to, const char *msg)
     char *newmsg;
 
     err = otrl_message_sending(us, &ops, NULL, from, PROTO, to, msg,
-	    tlvs, &newmsg, NULL, NULL);
+	    tlvs, &newmsg, NULL, NULL, NULL);
 
     if (!err) {
 	inject(from, to, newmsg ? newmsg : msg);
@@ -146,14 +158,18 @@ static void sending(const char *from, const char *to, const char *msg)
     otrl_tlv_free(tlvs);
 }
 
-void test(int vers, int both)
+static void test(int vers, int both)
 {
     printf("\n\n*** Testing version %d, %s ***\n\n", vers,
 	    both ? "simultaneous start" : "Alice start");
 
     otrl_context_forget_all(us);
-    ALICEPOLICY = vers == 1 ? (OTRL_POLICY_DEFAULT &~ OTRL_POLICY_ALLOW_V2) :
-	OTRL_POLICY_DEFAULT;
+    if (vers == 1)
+	ALICEPOLICY = OTRL_POLICY_ALLOW_V1;
+    else if (vers == 2)
+	ALICEPOLICY = OTRL_POLICY_ALLOW_V2;
+    else
+	ALICEPOLICY = OTRL_POLICY_DEFAULT;
     sending(ALICE, BOB, "?OTR?");
     if (both) {
 	sending(BOB, ALICE, "?OTR?");
@@ -170,7 +186,7 @@ void test_unreadable(void)
     printf("\n\n*** Testing Bob receiving unreadable messages from "
 	    "Alice ***\n\n");
 
-    bobcontext = otrl_context_find(us, ALICE, BOB, PROTO, 0, NULL, NULL, NULL);
+    bobcontext = otrl_context_find(us, ALICE, BOB, PROTO, 0, 0, NULL, NULL, NULL);
     otrl_context_force_plaintext(bobcontext);
     sending(ALICE, BOB, "unreadable text");
     dispatch();
@@ -188,8 +204,8 @@ void test_crash1(void)
     sending(ALICE, BOB, "?OTR?");
     dispatch();
 
-    alicecontext = otrl_context_find(us, BOB, ALICE, PROTO, 0, NULL, NULL, NULL);
-    bobcontext = otrl_context_find(us, ALICE, BOB, PROTO, 0, NULL, NULL, NULL);
+    alicecontext = otrl_context_find(us, BOB, ALICE, PROTO, 0, 0, NULL, NULL, NULL);
+    bobcontext = otrl_context_find(us, ALICE, BOB, PROTO, 0, 0, NULL, NULL, NULL);
 
     sending(ALICE, BOB, "Hi!"); dispatch();
     sending(BOB, ALICE, "There!"); dispatch();
@@ -213,12 +229,16 @@ void test_refresh(int vers)
     printf("\n\n*** Testing refresh ***\n\n");
 
     otrl_context_forget_all(us);
-    ALICEPOLICY = vers == 1 ? (OTRL_POLICY_DEFAULT &~ OTRL_POLICY_ALLOW_V2) :
-	OTRL_POLICY_DEFAULT;
+    if (vers == 1)
+	ALICEPOLICY = OTRL_POLICY_ALLOW_V1;
+    else if (vers == 2)
+	ALICEPOLICY = OTRL_POLICY_ALLOW_V2;
+    else
+	ALICEPOLICY = OTRL_POLICY_DEFAULT;
     sending(ALICE, BOB, "?OTR?"); dispatch();
 
-    alicecontext = otrl_context_find(us, BOB, ALICE, PROTO, 0, NULL, NULL, NULL);
-    bobcontext = otrl_context_find(us, ALICE, BOB, PROTO, 0, NULL, NULL, NULL);
+    alicecontext = otrl_context_find(us, BOB, ALICE, PROTO, 0, 0, NULL, NULL, NULL);
+    bobcontext = otrl_context_find(us, ALICE, BOB, PROTO, 0, 0, NULL, NULL, NULL);
     printf("%p %p\n", alicecontext, bobcontext);
 
     sending(ALICE, BOB, "Hi!"); dispatch();
@@ -236,13 +256,16 @@ int main(int argc, char **argv)
     us = otrl_userstate_create();
 
     otrl_privkey_read(us, "/home/iang/.gaim/otr.private_key");
+    otrl_instag_read(us, "inst.txt");
 
     test(1,0);
     test(2,0);
+    test(3,0);
     test(1,1);
     test(2,1);
     test_unreadable();
     test_crash1();
+    test_refresh(3);
     test_refresh(2);
     test_refresh(1);
 
diff --git a/src/tlv.c b/src/tlv.c
index 687051b..5e87c3d 100644
--- a/src/tlv.c
+++ b/src/tlv.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *  			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
diff --git a/src/tlv.h b/src/tlv.h
index f1c3436..256d80b 100644
--- a/src/tlv.h
+++ b/src/tlv.h
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *  			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
@@ -36,7 +36,7 @@ typedef struct s_OtrlTLV {
 /* The sender has thrown away his OTR session keys with you */
 #define OTRL_TLV_DISCONNECTED    0x0001
 
-/* The message contains a step in the Socialist Millionaires' Protocol. */ 
+/* The message contains a step in the Socialist Millionaires' Protocol. */
 #define OTRL_TLV_SMP1            0x0002
 #define OTRL_TLV_SMP2            0x0003
 #define OTRL_TLV_SMP3            0x0004
diff --git a/src/userstate.c b/src/userstate.c
index 51d543c..b0f46b0 100644
--- a/src/userstate.c
+++ b/src/userstate.c
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -39,8 +39,8 @@ OtrlUserState otrl_userstate_create(void)
     if (!us) return NULL;
     us->context_root = NULL;
     us->privkey_root = NULL;
+    us->instag_root = NULL;
     us->pending_root = NULL;
-
     return us;
 }
 
@@ -50,5 +50,6 @@ void otrl_userstate_free(OtrlUserState us)
     otrl_context_forget_all(us);
     otrl_privkey_forget_all(us);
     otrl_privkey_pending_forget_all(us);
+    otrl_instag_forget_all(us);
     free(us);
 }
diff --git a/src/userstate.h b/src/userstate.h
index 2f0106a..258376a 100644
--- a/src/userstate.h
+++ b/src/userstate.h
@@ -1,7 +1,7 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
- *  			     Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This library is free software; you can redistribute it and/or
@@ -23,12 +23,14 @@
 
 typedef struct s_OtrlUserState* OtrlUserState;
 
+#include "instag.h"
 #include "context.h"
 #include "privkey-t.h"
 
 struct s_OtrlUserState {
     ConnContext *context_root;
     OtrlPrivKey *privkey_root;
+    OtrlInsTag *instag_root;
     OtrlPendingPrivKey *pending_root;
 };
 
diff --git a/src/version.h b/src/version.h
index d755018..ffb52f6 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging library
- *  Copyright (C) 2004-2009  Ian Goldberg, Chris Alexander, Willy Lew,
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Willy Lew,
  *  			     Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
diff --git a/test_suite/README b/test_suite/README
new file mode 100644
index 0000000..dd4788c
--- /dev/null
+++ b/test_suite/README
@@ -0,0 +1,22 @@
+OTR TEST SUITE
+
+BEFORE RUNNING
+This test suite is known to run with Python 2.7.1. The OTR C client first needs to compiled and statically linked with
+various versions of libotr.  See the README file in "otr_c_client" for more information about this.
+
+TESTS
+Two tests are currently included. 
+
+otr_test_general.py
+This test establishes OTR sessions between pairs of all 3.X versions of OTR clients. It also tests multiple 4.0 clients
+corresponding to one account establishing OTR sessions with another account with multiple 4.0 clients. 
+
+otr_test_mixed.py
+This test establishes OTR sessions with multple 4.0 clients corresponding to one account with another accout with 
+multiple 4.0 clients.  This test also includes another 3.X with one of these accounts.  All 3.X versions are tested.
+
+RUNNING
+Executing one of the above files directly will execute the corresponding test. The underlying instant messaging server
+will be started and stopped as necessary.
+
+After each test, "Test succeeded" or "Test failed" will be printed.
diff --git a/test_suite/dummy_im.py b/test_suite/dummy_im.py
new file mode 100644
index 0000000..3d098fe
--- /dev/null
+++ b/test_suite/dummy_im.py
@@ -0,0 +1,211 @@
+#!/usr/bin/python
+
+import time
+import sys
+import threading
+import socket
+from struct import *
+
+
+class client_context():
+  def __init__(self, accountname, protocol, client_thread):
+    self.accountname = accountname
+    self.protocol = protocol
+    self.thread = client_thread
+
+class client_thread(threading.Thread):
+  def __init__(self, server, socket, debug=False):
+    threading.Thread.__init__(self)
+    self.server = server
+    self.sock = socket
+    self.done = False
+    self.done_sem = threading.Semaphore()
+    self.send_sem = threading.Semaphore()
+    self.ctx = None
+    self.debug = debug
+
+  def run(self):
+    #Registration: 1 byte accountname len, accountname, 1 byte protocol len, protocol
+    accountname_len = self.read_1b_val()
+    accountname = self.read_bytes(accountname_len)
+    
+    protocol_len = self.read_1b_val()
+    protocol = self.read_bytes(protocol_len)
+
+    if self.debug:
+      print('Client accountname: ' + accountname + ' protocol: ' + protocol)
+
+    #create client_context and add to table
+    self.ctx = client_context(accountname, protocol, self)
+    self.server.add_client(self.ctx)
+
+    while not self.check_client_done():
+      try:         #Message: 1 byte accountname len, accountname, 1 byte protocol len, protocol, 4 byte msg len, msg
+        accountname_len = self.read_1b_val()
+        accountname = self.read_bytes(accountname_len)
+      
+        protocol_len = self.read_1b_val()
+        protocol = self.read_bytes(protocol_len)
+      
+        msg_len = self.read_4b_val()
+        msg = self.read_bytes(msg_len)
+      
+        if not self.check_client_done():
+          self.server.deliver_msg(self.ctx.accountname, self.ctx.protocol, accountname, protocol, msg)
+
+      except:
+        if self.debug:
+          print('removing client due to error')
+        break
+
+    self.server.remove_client(self.ctx)
+    self.sock.close()
+
+  def send_msg(self, msg):
+    self.send_sem.acquire()
+    totalsent = 0
+    while not self.check_client_done() and totalsent < len(msg):
+      sent = self.sock.send(msg[totalsent:])
+      if sent == 0:
+        self.set_client_done()
+      totalsent = totalsent + sent
+    self.send_sem.release()
+    if self.debug:
+      print 'sent: ', totalsent
+
+  def set_client_done(self):
+    self.done_sem.acquire()
+    self.done = True
+    self.done_sem.release()
+    if self.ctx is not None:
+      if self.debug:
+        print('Client accountname: ' + self.ctx.accountname + ' protocol: ' + self.ctx.protocol + ' is marked as done')
+    else: 
+      if self.debug:
+        print('Uninitialized client disconnected')
+
+  def check_client_done(self):
+    done_val = False
+    self.done_sem.acquire()
+    done_val = self.done
+    self.done_sem.release()
+    return done_val
+
+  def read_bytes(self, num_bytes):
+    msg = ''
+    while not self.check_client_done() and len(msg) < num_bytes:
+      chunk = self.sock.recv(num_bytes-len(msg))
+      if chunk == '':
+        self.set_client_done()
+        break
+      msg = msg + chunk
+    return msg
+
+  def read_1b_val(self):
+    byte = self.read_bytes(1)
+    if len(byte) == 1:
+      return ord(byte)
+    else: return
+
+  def read_4b_val(self):
+    bytes = self.read_bytes(4)
+    if len(bytes) == 4:
+      val = unpack('!I',bytes)
+      return int(val[0])
+    else: return
+
+
+class im_server(threading.Thread):
+  def __init__(self, port, debug=False):
+    threading.Thread.__init__(self)
+    self.port = port
+    self.finished = False
+    self.clients_sem = threading.Semaphore()
+    self.clients = dict()
+    self.debug=debug
+
+  def run(self):
+    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    serversocket.bind(('', self.port))
+    serversocket.listen(50)
+    
+    if self.debug:
+      print('Server listening...')
+
+    while not self.finished:
+      (clientsocket, address) = serversocket.accept()
+      if self.debug:
+        print('Client connected: ' + str(address))
+      cl_thread = client_thread(self, clientsocket, self.debug)
+      cl_thread.daemon = True
+      cl_thread.start()
+    
+    serversocket.close()
+
+  def set_finished(self):
+    self.finished = True
+
+  def add_client(self, ctx):
+    self.clients_sem.acquire()
+
+    if self.debug:
+      print('Adding client to dict: ' + str(ctx))
+    entry_list = self.clients.get((ctx.accountname, ctx.protocol))
+    if entry_list is None:
+      entry_list = [ctx]
+      self.clients[(ctx.accountname, ctx.protocol)] = entry_list
+    else: 
+      entry_list.append(ctx)
+
+    self.clients_sem.release()
+
+  def remove_client(self, ctx):
+    self.clients_sem.acquire()
+
+    if self.debug:
+      print('Removing client from dict: ' + str(ctx))
+    entry_list = self.clients.get((ctx.accountname, ctx.protocol))
+    if entry_list is not None and entry_list.count(ctx) > 0:
+      entry_list.remove(ctx)
+      if len(entry_list) == 0:
+        del self.clients[(ctx.accountname, ctx.protocol)]
+
+    self.clients_sem.release()
+
+  def deliver_msg(self, src_accountname, src_protocol, dst_accountname, dst_protocol, msg):
+    self.clients_sem.acquire()
+    new_msg = bytearray()
+    new_msg.append(len(src_accountname))
+    new_msg.extend(src_accountname)
+    new_msg.append(len(src_protocol))
+    new_msg.extend(src_protocol)
+
+    for b in pack('!I', len(msg)):
+      new_msg.append(ord(b))
+
+    new_msg.extend(msg)
+  
+    entry_list = self.clients.get((dst_accountname, dst_protocol))
+    if entry_list is not None:
+      for c in entry_list:
+        if self.debug:
+          print('Delivering msg from accountname: ' + src_accountname + 
+            ' protocol: ' + src_protocol + ' to accountname: ' + dst_accountname + 
+            ' protocol: ' + dst_protocol)
+        c.thread.send_msg(new_msg)
+
+    self.clients_sem.release()
+
+def main(argv):
+  port = 1536
+  
+  if len(argv) > 0:
+    port = int(argv[1])
+
+  server = im_server(port, debug=True)
+  #server.daemon = True
+  server.start()
+
+if __name__ == "__main__":
+  main(sys.argv)
+
diff --git a/test_suite/dummy_im/dummy_client.py b/test_suite/dummy_im/dummy_client.py
new file mode 100755
index 0000000..e675b5c
--- /dev/null
+++ b/test_suite/dummy_im/dummy_client.py
@@ -0,0 +1,122 @@
+#!/usr/bin/python
+
+import sys
+import socket
+import threading
+from struct import *
+
+class kb_thread(threading.Thread):
+  def __init__(self, socket):
+    threading.Thread.__init__(self)
+    self.sock = socket
+
+  def run(self):
+    while True:
+      msg = getline(sys.stdin)
+
+      account_end = msg.find(' ')
+      if account_end < 0: continue
+      accountname = msg[:account_end]
+
+      proto_end = msg[account_end+1:].find(' ') + account_end + 1
+      if proto_end < 0: continue
+      protocol = msg[account_end+1:proto_end]
+
+      msg = msg[proto_end+1:]
+
+      print("Sending msg -- account: " + accountname + " protocol: " + protocol + " msg: " + msg)
+
+      send_msg(self.sock, accountname, protocol, msg)
+
+def getline(stream, delimiter="\n"):
+  def _gen():
+    while 1:
+      line = stream.readline()
+      if delimiter in line:
+        yield line[0:line.index(delimiter)]
+        break
+      else:
+        yield line
+  return "".join(_gen())
+
+def send(sock, msg, length=0):
+  if length == 0: length = len(msg)
+  totalsent = 0
+  while totalsent < length:
+    sent = sock.send(msg[totalsent:])
+    if sent == 0:
+      return
+    totalsent = totalsent + sent
+
+def read_bytes(sock, num_bytes):
+  msg = ''
+  while len(msg) < num_bytes:
+    chunk = sock.recv(num_bytes-len(msg))
+    if chunk == '':
+      return
+    msg = msg + chunk
+  return msg
+
+def read_1b_val(sock):
+  byte = read_bytes(sock, 1)
+  if len(byte) == 1:
+    return ord(byte)
+  else: return
+
+def read_4b_val(sock):
+  bytes = read_bytes(sock, 4)
+  if len(bytes) == 4:
+    val = unpack('!I',bytes)
+    return int(val[0])
+  else: return
+
+def recv_msg(sock):
+  recv_acc_len = read_1b_val(sock)
+  recv_acc = read_bytes(sock, recv_acc_len)
+  recv_proto_len = read_1b_val(sock)
+  recv_proto = read_bytes(sock, recv_proto_len)
+  recv_msg_len = read_4b_val(sock)
+  recv_msg = read_bytes(sock, recv_msg_len)
+  print("Received msg: " + recv_msg)
+  return recv_msg
+
+def send_msg(sock, accountname, protocol, msg):
+  new_msg = bytearray()
+  new_msg.append(len(accountname))
+  new_msg.extend(accountname)
+  new_msg.append(len(protocol))
+  new_msg.extend(protocol)
+  for b in pack('!I', len(msg)):
+    new_msg.append(ord(b))  
+  new_msg.extend(msg)
+  send(sock, new_msg)
+
+def main(argv):
+  if len(argv) < 3:
+    print("Usage: " + argv[0] + " <accountname> <protocol>")
+    return
+
+  accountname = argv[1]
+  protocol = argv[2]
+
+  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+  s.connect(("localhost", 1536))
+
+  acc_len = len(accountname)
+  packed_acc_len = pack('B', acc_len)
+
+  #init
+  send(s, packed_acc_len, 1)
+  send(s, accountname)
+  send(s, pack('B', len(protocol)), 1)
+  send(s, protocol)
+
+  input_handler = kb_thread(s)
+  input_handler.start()
+
+  while True:
+    recv_msg(s)
+
+if __name__ == "__main__":
+  main(sys.argv)
+
diff --git a/test_suite/instance_tags0.txt b/test_suite/instance_tags0.txt
new file mode 100644
index 0000000..04aa50e
--- /dev/null
+++ b/test_suite/instance_tags0.txt
@@ -0,0 +1,3 @@
+otrtest1	prpl-aim	27e31597
+otrtest2	prpl-aim	27e31598
+otrtest3	prpl-aim	27e31599
diff --git a/test_suite/instance_tags1.txt b/test_suite/instance_tags1.txt
new file mode 100644
index 0000000..ac822fc
--- /dev/null
+++ b/test_suite/instance_tags1.txt
@@ -0,0 +1,3 @@
+otrtest1	prpl-aim	27e32500
+otrtest2	prpl-aim	27e32501
+otrtest3	prpl-aim	27e32502
diff --git a/test_suite/instance_tags2.txt b/test_suite/instance_tags2.txt
new file mode 100644
index 0000000..80c0e28
--- /dev/null
+++ b/test_suite/instance_tags2.txt
@@ -0,0 +1,3 @@
+otrtest1	prpl-aim	27e32603
+otrtest2	prpl-aim	27e32604
+otrtest3	prpl-aim	27e32605
diff --git a/test_suite/instance_tags3.txt b/test_suite/instance_tags3.txt
new file mode 100644
index 0000000..9d579dd
--- /dev/null
+++ b/test_suite/instance_tags3.txt
@@ -0,0 +1,3 @@
+otrtest1	prpl-aim	27e33506
+otrtest2	prpl-aim	27e33507
+otrtest3	prpl-aim	27e33508
diff --git a/test_suite/instance_tags4.txt b/test_suite/instance_tags4.txt
new file mode 100644
index 0000000..e772568
--- /dev/null
+++ b/test_suite/instance_tags4.txt
@@ -0,0 +1,3 @@
+otrtest1	prpl-aim	27e42509
+otrtest2	prpl-aim	27e42500
+otrtest3	prpl-aim	27e42501
diff --git a/test_suite/otr.private_key b/test_suite/otr.private_key
new file mode 100644
index 0000000..ca67d84
--- /dev/null
+++ b/test_suite/otr.private_key
@@ -0,0 +1,41 @@
+(privkeys
+ (account
+(name otrtest2)
+(protocol prpl-aim)
+(private-key 
+ (dsa 
+  (p #00E4240AE9740D5FB1DF076A3270143BBD5B66DEB9BFD601CF3C3DC3E8D76BF1EA0D1A66E209516600E915B79552887EE9810D613C63EA6D2B6E69321C3C81F71F533F02430D20AF65C05C15BA46EF10C78EFF3BE687C33A4F6EA90877AB30E6423070AAFD3F22B5939D0DC8FBE5145A68F38EBA98ADCA7E06EFBC5E345E9AEDEF#)
+  (q "\x00�ջ�*��i#�b�U~�{U��q")
+  (g #00D3A15721AEF3A49DF26E875EC8C5FF4096B0B1717AF89485A015245176FD4AE3065D9F7B2D185B1E02CEAFB53E6984B1D341E62EBB4A5C189EB40959CFED0C48DAE73F3391C5E1482372E9DE539CD6911C54583DC0A3179547A38E19549F81A6DBFA8036C471C2A358D4B3AB86C7424556DFD61C35CC1EC08304B0102A0EEE08#)
+  (y #6E3996E07B6CB54DC443BD83033293956E5B7CCC511D19DCAC8735496B061D6EB6DF3D5540548D3C97201393E669EF9066BCA6A72E3AC68710188BEDF06FDF22548D8709BD4E9E1AD02DB0E193399BEB39DE5218D70C999D9856D3205DB79BA8DFC188726E646A1BE1722843F5E56A51C499C5BE5F63FACE7802ECD565E99F39#)
+  (x #2C5887BA7EC5F426B6BE4438E2FAE4589DBF2CB6#)
+  )
+ )
+ )
+ (account
+(name otrtest1)
+(protocol prpl-aim)
+(private-key 
+ (dsa 
+  (p #00BD114F05B275A8A94954047983C5CD96ED95C782D2ED65A18E78C98E8EAFBAF58BBD046BE9895AD55FD0FF95907E7EBD6ACA2688D24779BDE9F0AAB13924CE65F597F9C9B9953DDBACF51DA7113FBAB9BE1DF6C6EA836DEB48983CCDCFC4125B5013D0CE52F890D0C391A035D30BCD5169A3451FD7023685274576DCB5F8FA47#)
+  (q #00D1DA3915346A704EB2D2F2A48CD48F3DCC4CF25D#)
+  (g #501BCFB989AD2C346BBD7782CA0230551F976B1A07EE3AEE27E4B63B7B00B1ACA712AD85784986411278163156D4DBA9DF75C8560F9C2E02C02AEC830EC403A56B6F64432869D6CA9314A648076511343507629BF4FC96F8FDBB9797258DDF11F437B1450BA23F1AA7E885EC6A33D37B7D7EC384A004420DB238E140B94AAAFE#)
+  (y #7C9CB7732164787DD1931BB58257665EB60D6AA72B8D64D634530A61BE93D5AF01427962646542F18401B73032B12B9CBCAE8E3CF080DAD55C6612A97D6D8776CF2CBDD3AAC75D302B60E6956E5B3C60B39E171A2D5F150A924C6E22981EFDF052D5C6507B2DEC15E96CB6CAF7B260D5386BBDD7D7F69B4BF14451D64D847AEB#)
+  (x #00AB1E941176D94505911118AC799A504ADCCE88F8#)
+  )
+ )
+ )
+ (account
+(name otrtest3)
+(protocol prpl-aim)
+(private-key 
+ (dsa 
+  (p #00BB4C57669E50E4C35F8E4CA84855CF2C83EE75C4F44B4BB4A7E88590D394D7A738E82EE97892E5051CE45E200741E18D423137AA8E6679B1CFAB4FF11D45D8C9CBDE388D30FC800B4879713E3C57BA48A92FE135BB9AF265F770B706FB9A04802244D12CBFFD97ACE5C73FCE88C2B716B4B22B994CD6429A7E16D9B6D1874137#)
+  (q #00C40DA63B679A80FC31BF49A68503BB39754D0A45#)
+  (g #6C0A48BEA859587D6677306D1777A2A0635470F149A86EB64EA62EAAA4C21ECE4375ACD016B776E3AD3411C18BB3FF37F963FCEBB8820FF8838AFA6FCD1B39558DAB78450AE2ED9457DEDBDCE13DF5A6B20A738D2973D375D360C044AF7F0204CCC372098F0B6460963274B1EA0B5FEC93571A15F5C03DCDF54EE83BB198F363#)
+  (y #00AB2C8A82F020DB99EF5B7A8330EC43E0D5EBD623FEB67D1B046D88FACA01D8E31E4D7865DC62D4DA58CF8BC7FF4B57C203A9F7F5C85DAB1B63D63299EF13AD89AAA7E6638C9DBC42D096408936C9F0382224CFB5C1528DCC8C7F2554CB4CA2FF3C3239BC921F1C690295DD9AE69C8EF5BBD8E58A8FAA8BB9D5F88463CAECEE7B#)
+  (x #7824B713A4E5FA6D6C69172196648CD4657A1ED1#)
+  )
+ )
+ )
+)
diff --git a/test_suite/otr_c_client/Makefile b/test_suite/otr_c_client/Makefile
new file mode 100644
index 0000000..d8229c7
--- /dev/null
+++ b/test_suite/otr_c_client/Makefile
@@ -0,0 +1,8 @@
+all:
+	gcc dummy_client.c -DOTR30  -g -o dummy_client_30  -Wl,-Bstatic -I/home/rdfsmits/otr/cvs-3.0/libotr/src -L./libs -lotr3.0 -Wl,-Bdynamic -lpthread  -lgcrypt -lgpg-error
+	gcc dummy_client.c -DOTR31  -g -o dummy_client_31  -Wl,-Bstatic -I/home/rdfsmits/otr/cvs-3.1/libotr/src -L./libs -lotr3.1 -Wl,-Bdynamic -lpthread  -lgcrypt -lgpg-error
+	gcc dummy_client.c -DOTR32  -g -o dummy_client_32  -Wl,-Bstatic -I/home/rdfsmits/otr/cvs-3.2/libotr/src -L./libs -lotr3.2 -Wl,-Bdynamic -lpthread  -lgcrypt -lgpg-error
+	gcc dummy_client.c -DOTR40  -g -o dummy_client_40  -Wl,-Bstatic -I/home/rdfsmits/otr/git/otr/libotr/src -L./libs -lotr4.0 -Wl,-Bdynamic -lpthread  -lgcrypt -lgpg-error
+clean:
+	rm dummy_client_30 dummy_client_31 dummy_client_32 dummy_client_40 
+
diff --git a/test_suite/otr_c_client/README b/test_suite/otr_c_client/README
new file mode 100644
index 0000000..7f3187f
--- /dev/null
+++ b/test_suite/otr_c_client/README
@@ -0,0 +1,10 @@
+The Makefile is currently set up to find pre-compiled libotr static libraries in a ./libs directory. 
+You will need to change the header include directories as appropriate.
+
+It expects the following files (copied and renamed from the appropriate versions):
+
+libotr3.0.a
+libotr3.1.a
+libotr3.2.a
+libotr4.0.a
+
diff --git a/test_suite/otr_c_client/dummy_client.c b/test_suite/otr_c_client/dummy_client.c
new file mode 100644
index 0000000..d42d793
--- /dev/null
+++ b/test_suite/otr_c_client/dummy_client.c
@@ -0,0 +1,1071 @@
+#include <pthread.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <errno.h>
+
+#include <gcrypt.h>
+
+#include "proto.h"
+#include "privkey.h"
+#include "message.h"
+#include "context.h"
+
+#ifdef OTR40
+#include "instag.h"
+#endif
+
+#define PROCESSING_DONE "DONE"
+
+#define DEFAULT_IP "127.0.0.1"
+#define DEFAULT_PORT 1536
+
+#define Q_ID_RECEIVED 1
+#define Q_ID_RECEIVED_OTR 2
+#define Q_ID_ERR 3
+#define Q_ID_GONE_SECURE 4
+
+int sockfd = -1;
+
+FILE *logfd = NULL;
+
+OtrlUserState us;
+static char* our_account = "default_account";
+static char* our_protocol = "default_protocol";
+
+pthread_mutex_t stdout_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+
+static OtrlPolicy op_policy(void *opdata, ConnContext *context) {
+    return OTRL_POLICY_DEFAULT;
+}
+
+static void op_inject(void *opdata, const char *accountname,
+	const char *protocol, const char *recipient, const char *message);
+
+
+char* pass_otr_in_msg(char* account, char* protocol, char* msg);
+
+void write_query_response(uint32_t id, const char* msg);
+void write_query_response_s(uint32_t id, unsigned char* buf, uint32_t msg_size);
+
+#ifdef  OTR40
+const char* otr_error_message(void *opdata, ConnContext *context, OtrlErrorCode err_code);
+void otr_error_message_free(void *opdata, const char *err_msg);
+void handle_msg_event(void *opdata, OtrlMessageEvent msg_event, ConnContext *context, const char *message, gcry_error_t err);
+#endif
+
+#if defined OTR30 || defined OTR31 || defined OTR32
+int display_otr_message(void *opdata, const char *accountname, const char *protocol, const char *username, const char *msg);
+#endif
+
+uint32_t all_contexts_to_buf(unsigned char** buf_p);
+
+uint32_t context_to_buf(unsigned char** buf_p, uint32_t protocol_version, char* username, char * accountname, 
+                        char * protocol, uint32_t otr_offer, uint32_t msg_state, uint32_t auth_state, 
+                        uint32_t our_instance, uint32_t their_instance);
+
+void gone_secure(void *opdata, ConnContext *context);
+
+int max_message_size(void *opdata, ConnContext *context);
+
+#ifdef OTR30
+static OtrlMessageAppOps ops = {
+    op_policy,
+    NULL,
+    NULL,
+    op_inject,
+    NULL,
+    display_otr_message,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    gone_secure,
+    NULL,
+    NULL,
+    NULL
+};
+#endif
+
+#if defined OTR31 || defined OTR32
+static OtrlMessageAppOps ops = {
+    op_policy,
+    NULL,
+    NULL,
+    op_inject,
+    NULL,
+    display_otr_message,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    gone_secure,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL
+};
+#endif
+
+#ifdef OTR40
+static OtrlMessageAppOps ops = {
+    op_policy,
+    NULL,
+    NULL,
+    op_inject,
+    NULL,
+    NULL,
+    NULL,
+    gone_secure,
+    NULL,
+    NULL,
+    max_message_size,
+    NULL,
+    NULL,
+    NULL,
+    otr_error_message,
+    otr_error_message_free,
+    NULL,
+    NULL,
+    NULL,
+    handle_msg_event,
+    NULL,
+    NULL,
+    NULL
+};
+#endif
+
+int max_message_size(void *opdata, ConnContext *context) {
+#ifdef FRAG40
+  return 100;
+#else
+  return 0;
+#endif
+}
+
+static void read_fingerprint(char *file) {
+  if (otrl_privkey_read_fingerprints(us, file, NULL, NULL)) {
+    fprintf(stderr, "Error reading fingerprints");
+  }
+}
+
+static void read_privkey(char *file) {
+  if (otrl_privkey_read(us, file)) {
+    fprintf(stderr, "Error reading private key");
+  }
+}
+
+#ifdef OTR40
+static void read_instag(char *file) {
+  pthread_mutex_lock(&log_mutex);
+  fprintf(logfd, "About to read instance tag: %s \n", file);
+  fflush(logfd);
+  pthread_mutex_unlock(&log_mutex);
+  if (otrl_instag_read(us, file)) {
+    fprintf(stderr, "Error reading instance tags");
+  }
+}
+#endif
+
+void waitForEofClear(FILE *f) {
+   while (feof(f)) {
+      clearerr(f);
+      sleep(1);
+   }
+}
+
+uint32_t read_stdin(char* buf, uint32_t num) {
+  uint32_t n_read = 0;
+  uint32_t i = 0;
+  
+  for (i = 0; i < num; i++) {
+    unsigned char c = EOF;
+
+    waitForEofClear(stdin);
+    c = fgetc(stdin);
+  
+    if (c == EOF) break;
+  
+    buf[i] = c;
+    n_read++;
+  }
+  /*waitForEofClear(stdin);
+  n_read = read(0, buf, num);*/
+
+  return n_read;
+}
+
+uint32_t write_stdout(char* buf, uint32_t num) {
+  uint32_t i = 0;
+  pthread_mutex_lock(&stdout_mutex);
+  
+  
+  while (i < num) {
+    int wrote = fputc((int)buf[i], stdout);
+    fflush(stdout);
+
+    if (((unsigned char)wrote) != (unsigned char)(buf[i])) {
+      pthread_mutex_lock(&log_mutex);
+      fprintf(logfd, "Expected to write %x, returned %x\n", (unsigned char)buf[i], wrote);
+      fflush(logfd);
+      pthread_mutex_unlock(&log_mutex);
+      sleep(1);
+      continue;
+    }
+    
+    i++;
+  }
+  /*i = write(1, buf, num);*/
+
+  pthread_mutex_unlock(&stdout_mutex);
+  return i;
+}
+
+char* pass_otr_out_msg(uint32_t id, char* account, char* protocol, char* message) {
+    char *new_message = NULL;    
+    gcry_error_t err;
+
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, "Passing to OTR message_sending %s to account %s protocol %s\n", message, account, protocol);
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+
+#ifdef OTR40
+#ifdef FRAG40
+    err = otrl_message_sending(us, &ops, NULL,
+	    our_account, protocol, account, OTRL_INSTAG_BEST, message, NULL, &new_message,
+	    OTRL_FRAGMENT_SEND_ALL_BUT_LAST, NULL, NULL, NULL);
+#else
+    err = otrl_message_sending(us, &ops, NULL,
+	    our_account, protocol, account, OTRL_INSTAG_BEST, message, NULL, &new_message,
+	    OTRL_FRAGMENT_SEND_SKIP, NULL, NULL, NULL);
+#endif
+#endif
+
+#if defined OTR30 || defined OTR31 || defined OTR32
+    err = otrl_message_sending(us, &ops, NULL,
+	    our_account, protocol, account, message, NULL, &new_message,
+	    NULL, NULL);
+#endif
+
+    if (new_message) {
+      char *ourm = strdup(new_message);
+
+      write_query_response(id, new_message); /* send modified message back */ 
+      
+      otrl_message_free(new_message);
+      new_message = ourm;
+    }
+
+  if (err) {
+    	/* Do not send out plain text */
+    	char *ourm = strdup("");
+    	new_message = ourm;
+  }
+
+  return new_message;
+}
+
+void parse_outgoing_msg_otr(uint32_t id, uint32_t *size, unsigned char** buf_ptr) {
+  unsigned char* buf = *buf_ptr;
+  unsigned char account_len;
+  char* account;
+  unsigned char protocol_len;
+  char* protocol;
+  uint32_t msg_len;
+  char* msg;
+  char* new_msg;
+
+  pthread_mutex_lock(&log_mutex);
+  fprintf(logfd, "About to parse outgoing payload for otr\n");
+  fflush(logfd);
+  pthread_mutex_unlock(&log_mutex);
+
+  account_len = buf[0];
+  buf++;
+  account = malloc(account_len+1);
+  strncpy(account, buf, account_len);
+  account[account_len] = '\0';
+  buf += account_len;
+
+  protocol_len = buf[0];
+  buf++;
+  protocol = malloc(protocol_len+1);
+  strncpy(protocol, buf, protocol_len);
+  protocol[protocol_len] = '\0';
+  buf += protocol_len;
+
+  msg_len = *((uint32_t*)buf);
+  msg_len = ntohl(msg_len);
+  buf += 4;
+
+  if (msg_len) {
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, "Outgoing payload contained msg length %u\n", msg_len);
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+    
+    msg = malloc(msg_len+1);
+    strncpy(msg, buf, msg_len);
+    msg[msg_len] = '\0';
+    
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, "Outgoing msg %s\n", msg);
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+    
+    new_msg = pass_otr_out_msg(id, account, protocol, msg);
+    if (new_msg) {
+      pthread_mutex_lock(&log_mutex);
+      fprintf(logfd, "New msg from OTR: %s len: %u\n", new_msg, strlen(new_msg));
+      fflush(logfd);
+      pthread_mutex_unlock(&log_mutex);
+    }
+  }
+
+  if (new_msg) {
+    uint32_t new_msg_len = strlen(new_msg);
+    uint32_t new_buf_len = 1 + account_len + 1 + protocol_len + 4 + new_msg_len;
+    unsigned char* new_buf = malloc(new_buf_len);
+    unsigned char* new_buf_head = new_buf;
+      
+    *size = new_buf_len;
+    memcpy(new_buf, *buf_ptr, 1 + account_len + 1 + protocol_len);
+    new_buf += 1 + account_len + 1 + protocol_len;
+    *((uint32_t*)new_buf) = htonl(new_msg_len);
+    new_buf += 4;
+    strncpy(new_buf, new_msg, new_msg_len);
+    free(new_msg);
+    free(*buf_ptr); 
+    *buf_ptr = new_buf_head;
+    if (msg) free(msg);
+  } else {
+      *size = strlen(msg);
+      *buf_ptr = msg;
+  }
+
+  free(account);
+  free(protocol);
+}
+
+
+
+int send_msg(uint32_t size, char* payload) {
+  int sent = -1;
+  if (sent = send(sockfd, payload, size, 0) != size) {
+    fprintf(stderr, "Error sending data (send_msg)\n");
+  }
+  return sent;
+}
+
+int send_msg_otr(uint32_t id, uint32_t size, char** payload) {
+  int sent = -1;
+
+  if (!size || !*payload) return sent;
+
+  parse_outgoing_msg_otr(id, &size, (unsigned char**) payload);
+  
+  if (!size || !*payload) return sent;
+  
+  sent = send_msg(size, *payload);
+
+  return sent;
+}
+
+process_command_privkey(uint32_t size, char* payload) {
+  char path[size+1];
+  strncpy(path, payload, size);
+  path[size] = '\0';
+  read_privkey(path);
+}
+
+#ifdef OTR40
+process_command_instag(uint32_t size, char* payload) {
+  char path[size+1];
+  strncpy(path, payload, size);
+  path[size] = '\0';
+  read_instag(path);
+}
+#endif
+
+void finish_up() {
+  close((int)logfd);
+  close(sockfd);
+  exit(0);
+}
+
+void check_finished(uint16_t command) {
+  if (command == 0xFFFF) {
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, "%s", "Read command 0xFFFF, assuming parent is finished\n");
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+    finish_up();
+  }
+}
+
+void write_all_contexts(uint32_t id) {
+  unsigned char* buf = NULL;
+  uint32_t buf_size = all_contexts_to_buf(&buf);
+  
+  pthread_mutex_lock(&log_mutex);
+  fprintf(logfd, "%s", "***************** dumping all contexts ********************\n");
+  fwrite(buf, 1, buf_size, logfd);
+  fprintf(logfd, "%s", "\n***************** done dumping contexts ********************\n");
+  fflush(logfd);
+  pthread_mutex_unlock(&log_mutex);
+  
+  write_query_response_s(id, buf, buf_size);
+  free(buf);
+}
+
+void gone_secure(void *opdata, ConnContext *context) {
+  unsigned char* buf = NULL;
+  uint32_t buf_size = 0;
+  
+  if (!context) return;
+  
+  #if defined OTR30 || defined OTR31 || defined OTR32
+  buf_size = context_to_buf(&buf, context->protocol_version, context->username, 
+                              context->accountname, context->protocol, context->otr_offer, context->msgstate, 
+                              context->auth.authstate, 0, 0);
+  #endif
+    
+  #if defined OTR40
+  buf_size = context_to_buf(&buf, context->protocol_version, context->username, 
+                              context->accountname, context->protocol, context->otr_offer, context->msgstate, 
+                              context->auth.authstate, context->our_instance, context->their_instance);
+  #endif
+  
+  pthread_mutex_lock(&log_mutex);
+  fprintf(logfd, "Context %s %s %s has gone secure (printing data below)\n", context->username, context->accountname, context->protocol);
+  fwrite(buf, 1, buf_size, logfd);
+  fprintf(logfd, "%s", "\n");
+  fflush(logfd);
+  pthread_mutex_unlock(&log_mutex);
+  
+  write_query_response_s(Q_ID_GONE_SECURE, buf, buf_size);
+  free(buf); 
+}
+
+void process_command(uint16_t command, 
+		     uint32_t id, uint32_t size, char** payload) {
+  switch (command) {
+  case 0x0000: /* send msg (no OTR) */
+    send_msg(size, *payload);
+    break;
+  case 0x0001: /* send msg */
+    #ifdef __DEBUGGING
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, "%s", "Sending message...\n");
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+    #endif
+    
+    send_msg_otr(id, size, payload);
+    break;
+  case 0x0002: /* read privkey */ 
+    process_command_privkey(size, *payload);
+    break;
+  case 0x0003: /* write all contexts */
+    write_all_contexts(id);
+    break;
+  #ifdef OTR40
+  case 0x004:
+    process_command_instag(size, *payload);
+    break;
+  #endif
+  }
+  write_query_response(id, PROCESSING_DONE);
+}
+
+process_and_write_msg(unsigned char account_len, char* account, 
+	    unsigned char protocol_len, char* protocol, 
+	    uint32_t msg_len, char* msg, uint32_t q_id) {
+  /* re-assemble message as single buffer */
+  unsigned char* buf;
+  unsigned char* buf_head;
+  
+  if (msg == NULL || msg_len == 0) return;
+
+  uint32_t total_len = 4 + 4 + 1 + account_len + 1 + protocol_len + 4 + msg_len;
+  buf = malloc(total_len);
+  buf_head = buf;
+
+  *((uint32_t*)buf) = htonl(q_id);
+  buf += 4;
+
+  *((uint32_t*)buf) = htonl(total_len - 8);
+  buf += 4;
+
+  buf[0] = account_len;
+  buf++;
+  strncpy(buf, account, account_len);
+  buf += account_len;
+
+  buf[0] = protocol_len;
+  buf++;
+  strncpy(buf, protocol, protocol_len);
+  buf += protocol_len;
+
+  *((uint32_t*)buf) = htonl(msg_len);
+  buf += 4;
+  strncpy(buf, msg, msg_len);
+
+  uint32_t written = write_stdout(buf_head, total_len);
+  
+  if (written == total_len) {
+    
+    #ifdef __DEBUGGING
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, "%s", "Msg written to stdout\n");
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+    #endif
+    
+  } else {
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, "Only wrote %u bytes to stdout, expected %u\n", written, total_len);
+    fprintf(logfd, "Failed on message from account %s protocol %s contents %s\n", account, protocol, msg);
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+    fprintf(stderr, "Only wrote %u bytes to stdout, expected %u\n", written, total_len);
+  }
+  
+  free(buf_head);
+}
+
+void* receive_msgs(void* data) {
+  int recvd = 0;
+  int finished = 0;
+
+  while (1) {
+    unsigned char account_len;
+    char* account = 0;
+    unsigned char protocol_len;
+    char* protocol = 0;
+    uint32_t msg_len;
+    char* msg = 0;
+    uint32_t new_msg_len;
+    char* new_msg = NULL;
+    #ifdef __DEBUGGING
+    char print_buf[1024];
+    #endif
+
+    if (recvd = recv(sockfd, &account_len, 1, 0) != 1) {
+      fprintf(stderr, "account_len: Failed to receive from socket\n");
+      break;
+    }
+
+    account = malloc(account_len+1);
+
+    if (recvd = recv(sockfd, account, account_len, 0) != account_len) {
+      fprintf(stderr, "account: Failed to receive from socket\n");
+      break;
+    }
+
+    if (recvd = recv(sockfd, &protocol_len, 1, 0) != 1) {
+      fprintf(stderr, "protocol_len: Failed to receive from socket\n");
+      break;
+    }
+
+    protocol = malloc(protocol_len+1);
+
+    if (recvd = recv(sockfd, protocol, protocol_len, 0) != protocol_len) {
+      fprintf(stderr, "protocol: Failed to receive from socket\n");
+      break;
+    }
+
+    if (recvd = recv(sockfd, &msg_len, 4, 0) != 4) {
+      fprintf(stderr, "msg_len: Failed to receive from socket\n");
+      break;
+    }
+
+    msg_len = ntohl(msg_len);
+    msg = malloc(msg_len+1);
+
+    if (recvd = recv(sockfd, msg, msg_len, 0) != msg_len) {
+      fprintf(stderr, "msg: Failed to receive from socket. length: %u, recvd: %u, %s\n", msg_len, recvd, strerror( errno ));
+      break;
+    }
+    
+    #ifdef __DEBUGGING
+    sprintf(print_buf, "Received msg: %u %%.%us, from account %u %%.%us protocol %u %%.%us\n", msg_len, msg_len, account_len, account_len, protocol_len, protocol_len);
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, print_buf, msg, account, protocol);
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+    #endif
+    
+    account[account_len] = '\0';
+    protocol[protocol_len] = '\0';
+    msg[msg_len] = '\0';
+
+    process_and_write_msg(account_len, account, protocol_len, protocol, msg_len, msg, Q_ID_RECEIVED);
+
+    new_msg = pass_otr_in_msg(account, protocol, msg);
+
+    if (new_msg) {
+      new_msg_len = strlen(new_msg);
+      process_and_write_msg(account_len, account, protocol_len, protocol, new_msg_len, new_msg, Q_ID_RECEIVED_OTR);
+      free(new_msg);
+    }
+    
+    free(msg);
+    free(account);
+    free(protocol);
+  }
+}
+
+uint32_t context_to_buf(unsigned char** buf_p, uint32_t protocol_version, char* username, char * accountname, 
+                        char * protocol, uint32_t otr_offer, uint32_t msg_state, uint32_t auth_state, 
+                        uint32_t our_instance, uint32_t their_instance) {
+  uint32_t username_len = strlen(username);
+  uint32_t accountname_len = strlen(accountname);
+  uint32_t protocol_len = strlen(protocol);
+  uint32_t buf_size = 4 + 1 + username_len + 1 + accountname_len + 1 + protocol_len + 4 + 4 + 4 + 4 + 4;
+  unsigned char* buf = malloc(buf_size);
+  *buf_p = buf;
+  
+  *((uint32_t*)buf) = htonl(protocol_version);
+  buf += 4;
+  
+  *buf = username_len;
+  buf++;
+  
+  strncpy(buf, username, username_len);
+  buf += username_len;
+  
+  *buf = accountname_len;
+  buf++;
+  
+  strncpy(buf, accountname, accountname_len);
+  buf += accountname_len;
+  
+  *buf = protocol_len;
+  buf++;
+  
+  strncpy(buf, protocol, protocol_len);
+  buf += accountname_len;
+  
+  *((uint32_t*)buf) = htonl(otr_offer);
+  buf += 4;
+  
+  *((uint32_t*)buf) = htonl(msg_state);
+  buf += 4;
+  
+  *((uint32_t*)buf) = htonl(auth_state);
+  buf += 4;
+  
+  *((uint32_t*)buf) = htonl(our_instance);
+  buf += 4;
+  
+  *((uint32_t*)buf) = htonl(their_instance);
+  buf += 4;
+  
+  
+  return buf_size;
+}
+
+uint32_t all_contexts_to_buf(unsigned char** buf_p) {
+  ConnContext *context = us->context_root;
+  unsigned char* buf = NULL;
+  uint32_t buf_size = 0;
+  uint32_t num_contexts = 0;
+  
+  while (context != NULL) {
+    unsigned char* temp_buf;
+    
+    uint32_t temp_buf_size = 0;
+    
+    #if defined OTR30 || defined OTR31 || defined OTR32
+    temp_buf_size = context_to_buf(&temp_buf, context->protocol_version, context->username, 
+                                context->accountname, context->protocol, context->otr_offer, context->msgstate, 
+                                context->auth.authstate, 0, 0);
+    #endif
+    
+    #if defined OTR40
+    temp_buf_size = context_to_buf(&temp_buf, context->protocol_version, context->username, 
+                                context->accountname, context->protocol, context->otr_offer, context->msgstate, 
+                                context->auth.authstate, context->our_instance, context->their_instance);
+    #endif
+
+    unsigned char* new_buf = malloc(buf_size + temp_buf_size);
+    if (buf != NULL) memcpy(new_buf, buf, buf_size);
+    memcpy(new_buf + buf_size, temp_buf, temp_buf_size);
+
+    free(buf);
+    free(temp_buf);
+    buf = new_buf;
+    buf_size += temp_buf_size;
+    
+    context = context->next;
+    num_contexts++;
+  }
+  
+  /* precede serialized contexts by number of contexts */
+  if (num_contexts) {
+    unsigned char* new_buf = malloc(4 + buf_size);
+    *((uint32_t *)new_buf) = htonl(num_contexts);
+    memcpy(new_buf + 4, buf, buf_size);
+    free(buf);
+    buf = new_buf;
+    buf_size += 4;
+  }
+  
+  *buf_p = buf;
+  return buf_size;
+}
+
+void op_inject(void *opdata, const char *accountname,
+	const char *protocol, const char *recipient, const char *message) {
+  unsigned char recipient_len = strlen(recipient);
+  unsigned char protocol_len = strlen(protocol);
+  uint32_t message_len = strlen(message);
+  uint32_t total_len = 1 + recipient_len + 1 + protocol_len + 4 + message_len;
+  unsigned char *buf = malloc(total_len);
+  unsigned char *buf_head = buf;
+  
+  pthread_mutex_lock(&log_mutex);
+  fprintf(logfd, "Inject called with message %s for account %s protocol %s from account %s\n", message, recipient, protocol, accountname);
+  fflush(logfd);
+  pthread_mutex_unlock(&log_mutex);
+
+  buf[0] = recipient_len;
+  buf++;
+  strncpy(buf, recipient, recipient_len);
+  buf += recipient_len;
+  
+  buf[0] = protocol_len;
+  buf++;
+  strncpy(buf, protocol, protocol_len);
+  buf += protocol_len;
+
+  *((uint32_t*)buf) = htonl(message_len);
+  buf += 4;
+  strncpy(buf, message, message_len);
+
+  send_msg(total_len, buf_head);
+  free(buf_head);
+}
+
+#ifdef OTR40
+const char* otr_error_message(void *opdata, ConnContext *context, OtrlErrorCode err_code) {
+  char* msg = "";
+  char* result;
+
+  pthread_mutex_lock(&log_mutex);
+  fprintf(logfd, "otr_error_message called with err_code %u\n", err_code);
+  fflush(logfd);
+  pthread_mutex_unlock(&log_mutex);
+
+  switch(err_code) {
+  case OTRL_ERRCODE_ENCRYPTION_ERROR:
+    msg = "OTRL_ERRCODE_ENCRYPTION_ERROR";
+    break;
+  case OTRL_ERRCODE_MSG_NOT_IN_PRIVATE:
+    msg = "OTRL_ERRCODE_MSG_NOT_IN_PRIVATE";
+    break;
+  case OTRL_ERRCODE_MSG_UNREADABLE:
+    msg = "OTRL_ERRCODE_MSG_UNREADABLE";
+    break;
+  case OTRL_ERRCODE_MSG_MALFORMED:
+    msg = "OTRL_ERRCODE_MSG_MALFORMED";
+    break;
+  default:
+    break;
+  }
+
+  /* copy any info from context? */
+
+  result = malloc(strlen(msg)+1);
+  strcpy(result, msg);
+  
+  return result;
+}
+#endif
+
+void otr_error_message_free(void *opdata, const char *err_msg) {
+  free((char*)err_msg);
+}
+
+/* msg should be a null-terminated string */
+void write_query_response(uint32_t id, const char* msg) {
+  write_query_response_s(id, (unsigned char*)msg, strlen(msg));
+}
+
+void write_query_response_s(uint32_t id, unsigned char* msg, uint32_t msg_size) {
+  uint32_t buf_size = 0;
+  unsigned char* buf;
+  unsigned char* buf_head;
+  
+  buf_size = 4 + 4 + msg_size;
+  buf = malloc(buf_size);
+  buf_head = buf;
+  
+  pthread_mutex_lock(&log_mutex);
+  fprintf(logfd, "Writing query response to stdout -- id %u size %u msg size %u msg %s\n", id, buf_size, msg_size, msg);
+  fflush(logfd);
+  pthread_mutex_unlock(&log_mutex);
+  
+  *((uint32_t*)buf) = htonl(id);
+  buf += 4;
+  
+  *((uint32_t*)buf) = htonl((uint32_t)msg_size);
+  buf += 4;
+  
+  memcpy(buf, msg, msg_size);
+  
+  uint32_t written = buf_size;
+  written = write_stdout(buf_head, buf_size);
+  
+  if (written == buf_size) {
+    
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, "%s", "Msg written to stdout\n");
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+    
+  } else {
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, "Only wrote %u bytes to stdout, expected %u\n", written, buf_size);
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+    fprintf(stderr, "Only wrote %u bytes to stdout, expected %u\n", written, buf_size);
+  }  
+  
+  free(buf_head);
+}
+
+#if defined OTR30 || defined OTR31 || defined OTR32
+int display_otr_message(void *opdata, const char *accountname, const char *protocol, const char *username, const char *msg) {
+  pthread_mutex_lock(&log_mutex);
+  fprintf(logfd, "display_otr_message called with msg %s\n", msg);
+  fflush(logfd);
+  pthread_mutex_unlock(&log_mutex);
+  write_query_response(Q_ID_ERR, msg);
+}
+#endif
+
+#ifdef OTR40
+void handle_msg_event(void *opdata, OtrlMessageEvent msg_event, ConnContext *context, const char *message, gcry_error_t err) {
+  char* msg = "";
+
+  switch(msg_event) {
+    case OTRL_MSGEVENT_NONE:
+      msg = "OTRL_MSGEVENT_NONE";
+      break;
+    case OTRL_MSGEVENT_ENCRYPTION_REQUIRED:
+      msg = "OTRL_MSGEVENT_ENCRYPTION_REQUIRED";
+      break;
+    case OTRL_MSGEVENT_ENCRYPTION_ERROR:
+      msg = "OTRL_MSGEVENT_ENCRYPTION_ERROR";
+      break;
+    case OTRL_MSGEVENT_CONNECTION_ENDED:
+      msg = "OTRL_MSGEVENT_CONNECTION_ENDED";
+      break;
+    case OTRL_MSGEVENT_SETUP_ERROR:
+      msg = "OTRL_MSGEVENT_SETUP_ERROR";
+      break;
+    case OTRL_MSGEVENT_MSG_REFLECTED:
+      msg = "OTRL_MSGEVENT_MSG_REFLECTED";
+      break;
+    case OTRL_MSGEVENT_MSG_RESENT:
+      msg = "OTRL_MSGEVENT_MSG_RESENT";
+      break;
+    case OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE:
+      msg = "OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE";
+      break;
+    case OTRL_MSGEVENT_RCVDMSG_UNREADABLE:
+      msg = "OTRL_MSGEVENT_RCVDMSG_UNREADABLE";
+      break;
+    case OTRL_MSGEVENT_RCVDMSG_MALFORMED:
+      msg = "OTRL_MSGEVENT_RCVDMSG_MALFORMED";
+      break;
+    case OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD:
+      msg = "OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD";
+      break;
+    case OTRL_MSGEVENT_LOG_HEARTBEAT_SENT:
+      msg = "OTRL_MSGEVENT_LOG_HEARTBEAT_SENT";
+      break;
+    case OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR:
+      msg = "OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR";
+      break;
+    case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED:
+      msg = "OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED";
+      break;
+    case OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED:
+      msg = "OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED";
+      break;
+    case OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE:
+      msg = "OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE";
+      break;
+  }
+  
+  pthread_mutex_lock(&log_mutex);
+  fprintf(logfd, "Handle_msg_event called with msg_event %s errcode %i message %s \n", msg, gcry_err_code(err), message);
+  fflush(logfd);
+  pthread_mutex_unlock(&log_mutex);
+  
+  write_query_response(Q_ID_ERR, msg);
+  
+}
+#endif
+
+char* pass_otr_in_msg(char* account, char* protocol, char* msg)  {
+  char *new_message = NULL;
+  OtrlTLV *tlvs = NULL;
+  uint32_t ignore = 0;
+
+  pthread_mutex_lock(&log_mutex);
+  fprintf(logfd, "Passing incoming msg to OTR from account %s protocol %s payload %s\n", account, protocol, msg);
+  fflush(logfd);
+  pthread_mutex_unlock(&log_mutex);
+
+  #if defined OTR40
+  ignore = otrl_message_receiving(us, &ops, NULL,
+    our_account, protocol, account, msg,
+    &new_message, &tlvs, NULL, NULL, NULL);
+  #endif
+
+
+  #if defined OTR30 || defined OTR31 || defined OTR32
+  ignore = otrl_message_receiving(us, &ops, NULL,
+    our_account, protocol, account, msg,
+    &new_message, &tlvs, NULL, NULL);
+  #endif
+
+  if (new_message) {
+    char *ourm = malloc(strlen(new_message) + 1);
+    if (ourm) {
+      strcpy(ourm, new_message);
+    }
+
+    otrl_message_free(new_message);
+    new_message = ourm;
+    
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, "New message from OTR message_receiving %s\n", new_message);
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+  }
+
+  if (ignore) {
+    free(new_message);
+    new_message = NULL;
+  }
+
+  return new_message;
+}
+
+int main(int argc, char *argv[]) {
+  char* remote_ip = DEFAULT_IP;
+  uint32_t remote_port = DEFAULT_PORT;
+  struct sockaddr_in addr;
+
+  OTRL_INIT;
+
+  us = otrl_userstate_create();
+  
+  if (argc > 1) {
+    our_account = argv[1];
+  }
+
+  if (argc > 2) {
+    our_protocol = argv[2];
+  }
+
+  if (argc > 3) {
+    remote_ip = argv[3];
+  }
+
+  if (argc > 4) {
+    remote_port = atoi(argv[4]);
+  }
+
+  if (argc > 5) {
+    logfd = fopen(argv[5], "w");
+  } else {
+    logfd = fopen(our_account, "w");
+  }
+
+  pthread_mutex_lock(&log_mutex);
+  fprintf(logfd, "Connecting to dummy_im: %s:%u\n", remote_ip, remote_port);
+  fflush(logfd);
+  pthread_mutex_unlock(&log_mutex);
+  
+  if ((sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+    fprintf(stderr, "Cannot create socket\n");
+    exit(1);
+  }
+
+  memset(&addr, 0, sizeof(addr));
+  addr.sin_family = AF_INET;
+  addr.sin_addr.s_addr = inet_addr(remote_ip);
+  addr.sin_port = htons(remote_port);
+
+  if (connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+    fprintf(stderr, "Cannot connect to server\n");
+    exit(1);
+  }
+
+  pthread_mutex_init(&stdout_mutex, NULL);
+  pthread_mutex_init(&log_mutex, NULL);
+  
+
+  /* pthread launch thread that receives from socket and wrties to stdout */
+  pthread_t t_socker_reader;
+  pthread_create(&t_socker_reader, NULL, receive_msgs, (void*) &sockfd);
+
+  /* the main thread will receive commands from stdin and write to socket */
+  while(1) {
+    unsigned char buf[10];
+    uint16_t command = 0;
+    uint32_t id = 0;
+    uint32_t size = 0;
+    char* payload = 0;
+
+    if (read_stdin(buf, 10) != 10) {
+      fprintf(stderr, "Error reading from stdin\n");
+      return;
+    }
+
+    command = ntohs(*((uint16_t*)(buf)));
+    id = ntohl(*((uint32_t*)(buf+2)));
+    size = ntohl(*((uint32_t*)(buf+6)));
+    
+    pthread_mutex_lock(&log_mutex);
+    fprintf(logfd, "Received command msg: %X id: %u size: %u\n", command, id, size);
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+
+    check_finished(command);
+    payload = malloc(size);
+    
+    if (read_stdin(payload, size) != size) {
+      fprintf(stderr, "Error reading from stdin\n");
+      return;
+    }
+    
+    pthread_mutex_lock(&log_mutex);
+    
+    fprintf(logfd, "Received payload\n");
+    fflush(logfd);
+    pthread_mutex_unlock(&log_mutex);
+    
+    process_command(command, id, size, &payload);
+
+    free(payload);
+  }
+
+}
diff --git a/test_suite/otr_subprocess.py b/test_suite/otr_subprocess.py
new file mode 100644
index 0000000..1161ccc
--- /dev/null
+++ b/test_suite/otr_subprocess.py
@@ -0,0 +1,482 @@
+#!/usr/bin/python
+
+import re
+import sys
+import threading
+import signal
+import select
+import time
+import Queue
+from subprocess import *
+from struct import *
+
+otr_tab_tag = " \t  \t\t\t\t \t \t \t  "
+
+otr_query = "?OTR?"
+
+otr_key_prefix_regex = ".*\\?OTR:...K"
+otr_commit_prefix_regex = ".*\\?OTR:...C"
+otr_sign_prefix_regex = ".*\\?OTR:...S"
+otr_reveal_prefix_regex = ".*\\?OTR:...R"
+
+otr_auth_regexs = [otr_key_prefix_regex, otr_commit_prefix_regex, otr_sign_prefix_regex, otr_reveal_prefix_regex]
+
+otr_data_prefix_regex = ".*\\?OTR:...D"
+
+im_ip = "127.0.0.1"
+im_port = "1536"
+
+q_raw_msg = 1       #The queue id for received messages that have not gone through OTR
+q_otr_msg = 2       #The queue id for received messages that have passed through OTR
+q_err = 3           #The queue id for error-type messages from OTR
+q_gone_secure = 4   #The queue id for signal of contexts that have gone secure
+q_raw_auth_msg = 5
+q_raw_data_msg = 6
+
+class otr_context:
+  def __init__(self):
+    self.protocol_version = None
+    self.username = None
+    self.accountname = None
+    self.protocol = None
+    self.offer_state = None
+    self.msg_state = None
+    self.auth_state = None
+    self.our_instance = None
+    self.their_instance = None
+
+  def __init__(self, protocol_version, username, accountname, protocol, offer_state, msg_state, auth_state, our_instance, their_instance):
+    self.protocol_version = protocol_version
+    self.username = username
+    self.accountname = accountname
+    self.protocol = protocol
+    self.offer_state = offer_state
+    self.msg_state = msg_state
+    self.auth_state = auth_state
+    self.our_instance = our_instance
+    self.their_instance = their_instance
+
+  def __str__(self):
+    return "Protocol version: " + str(self.protocol_version) + "\nUsername: " + str(self.username) + "\nAccount name: "+ \
+           str(self.accountname) + "\nProtocol: " + str(self.protocol) + "\nOffer state: " + str(self.offer_state) + \
+           "\nMsg state: " + str(self.msg_state) + "\nAuth state: " + str(self.auth_state) + "\nOur instance: " + \
+           str(self.our_instance) + "\nTheir instance: " + str(self.their_instance)
+
+class subprocess_exception(Exception):
+  def __init__(self, value):
+    self.value = value
+
+  def __str__(self):
+    return repr(self.value)
+
+class otr_subprocess_read_thread(threading.Thread):
+  def __init__(self, otr_subprocess):
+    threading.Thread.__init__(self)
+    self.subprocess = otr_subprocess
+    
+  def run(self):
+    while True:
+      recv_msg_id = read_4b_val(self.subprocess.stdout)
+      recv_msg_len = read_4b_val(self.subprocess.stdout)
+      recv_msg = self.subprocess.stdout.read(int(recv_msg_len))
+      
+      if recv_msg_id == q_raw_msg:
+        msg = deserialize_msg(recv_msg)
+        if re.match(otr_data_prefix_regex, msg[2]) != None:
+          self.subprocess.write_query(q_raw_data_msg, recv_msg)
+        else:
+          for auth_regex in otr_auth_regexs:
+            if re.match(auth_regex, msg[2]) != None:
+              self.subprocess.write_query(q_raw_auth_msg, recv_msg)
+              break
+       
+      
+      #print("Msg len " + str(recv_msg_len) + " contents " + recv_msg)
+      
+      self.subprocess.write_query(recv_msg_id, recv_msg)
+
+class otr_subprocess(Popen):
+  def __init__(self, args, offset=0):
+    #The test client takes parameters like accountname, and protocol, and will be found
+    #in the args list. 'offset' points to the the location in the list just before the
+    #accountname and protocol are listed (and should itself point to where the actual
+    #client_location is) 
+    
+    self.client_location = args[offset]
+    self.accountname = args[offset+1]
+    self.protocol = args[offset+2]
+    
+    print(args)
+    Popen.__init__(self, args, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True, universal_newlines=True, bufsize=16384)
+    
+    self.querycount = 16 # Unique IDs for async queries to C
+    self.querycount_lock = threading.Semaphore()
+    
+    self.querymap = {} # Query ID --> Queues
+    self.querymap_lock = threading.Semaphore() # Locks the above map (intended to make the map operations safe, not the underlying queues)
+    
+    self.querymap[q_raw_msg] = Queue.Queue() # For incoming messages to this process. This data structure is synchronized
+    self.querymap[q_otr_msg] = Queue.Queue() # For incoming messages that were process by OTR
+    self.querymap[q_err] = Queue.Queue()     # For OTR error messages
+    self.querymap[q_gone_secure] = Queue.Queue()
+    self.querymap[q_raw_auth_msg] = Queue.Queue()
+    self.querymap[q_raw_data_msg] = Queue.Queue()
+    
+    self.read_thread = otr_subprocess_read_thread(self)
+    self.read_thread.daemon = True # Python will exit when only daemonic threads are left
+    self.read_thread.start()
+  
+  def get_query_id(self):
+    result = 0
+    self.querycount_lock.acquire()
+    result = self.querycount
+    self.querycount += 1
+    self.querycount_lock.release()
+    return result
+
+  def reset(self):
+    while not self.querymap[q_raw_msg].empty():
+      self.querymap[q_raw_msg].get(False)
+    while not self.querymap[q_otr_msg].empty():
+      self.querymap[q_otr_msg].get(False)
+    while not self.querymap[q_err].empty():
+      self.querymap[q_err].get(False)
+
+  def write_query(self, id, contents):
+    self.querymap_lock.acquire()
+    
+    queue = self.querymap.get(id)
+    
+    if queue is None:
+      queue = Queue.Queue()
+      self.querymap[id] = queue
+    
+    queue.put(contents)
+    
+    self.querymap_lock.release()
+
+  def get_query_blocking(self, id):
+    self.querymap_lock.acquire()
+    
+    result = None
+    queue = self.querymap.get(id)
+    
+    if queue is None:
+      queue = Queue.Queue()
+      self.querymap[id] = queue
+      
+    self.querymap_lock.release()  
+    result = queue.get()
+
+    return result
+
+  def check_error(self):
+    inputready,outputready,exceptready = select.select([self.stderr],[],[], 0) #non-blocking poll 
+    
+    for s in inputready:
+      raise subprocess_exception(s.readline())
+    
+    if self.poll():
+      raise subprocess_exception('Subprocess terminated with return code: ' + str(self.returncode))
+
+  def send_init(self):
+    new_msg = bytearray()
+    acc_len = len(self.accountname)
+    proto_len = len(self.protocol)
+    full_len = 1 + acc_len + 1 + proto_len
+
+    cmd_type = 0 #cmd type 0 (init)
+    
+    for b in pack('!H', cmd_type):
+      new_msg.append(ord(b))
+      
+    msg_id = self.get_query_id()
+    for b in pack('!I', msg_id):
+      new_msg.append(ord(b))
+  
+    for b in pack('!I', full_len):
+      new_msg.append(ord(b))
+
+    new_msg.append(acc_len)
+    new_msg.extend(self.accountname)
+  
+    new_msg.append(proto_len)
+    new_msg.extend(self.protocol)
+  
+    self.stdin.write(new_msg)
+    self.stdin.flush()
+    time.sleep(0.1)
+    self.check_error()
+    
+    return msg_id
+  
+  def send_msg(self, dst_acc, dst_proto, msg):
+    new_msg = bytearray()
+    acc_len = len(dst_acc)
+    proto_len = len(dst_proto)
+    msg_len = len(msg)
+    full_len = 1 + acc_len + 1 + proto_len + 4 + msg_len
+  
+    cmd_type = 1 #cmd type 1 (message)
+    for b in pack('!H', cmd_type):
+      new_msg.append(ord(b))
+
+    msg_id = self.get_query_id()
+    for b in pack('!I', msg_id):
+      new_msg.append(ord(b))
+
+    for b in pack('!I', full_len):
+      new_msg.append(ord(b))
+
+    new_msg.append(acc_len)
+    new_msg.extend(dst_acc)
+  
+    new_msg.append(proto_len)
+    new_msg.extend(dst_proto)
+  
+    for b in pack('!I', msg_len):
+      new_msg.append(ord(b))
+    new_msg.extend(msg)
+  
+    self.stdin.write(new_msg)
+    self.stdin.flush()
+    
+    time.sleep(0.1)
+    self.check_error()
+    
+    return msg_id
+
+  def send_read_privkey(self, path):
+    new_msg = bytearray()
+
+    cmd_type = 2 #cmd type 2 (read privkey)
+    
+    for b in pack('!H', cmd_type):
+      new_msg.append(ord(b))
+    
+    msg_id = self.get_query_id()
+    for b in pack('!I', msg_id):
+      new_msg.append(ord(b))
+    
+    full_len = len(path)
+
+    for b in pack('!I', full_len):
+      new_msg.append(ord(b))
+
+    new_msg.extend(path)
+    self.stdin.write(new_msg)
+    self.stdin.flush()
+    
+    time.sleep(0.1)
+    self.check_error()
+    
+    return msg_id
+
+  def send_read_instag(self, path):
+    new_msg = bytearray()
+
+    cmd_type = 4 #cmd type 4 (read instag)
+    
+    for b in pack('!H', cmd_type):
+      new_msg.append(ord(b))
+    
+    msg_id = self.get_query_id()
+    for b in pack('!I', msg_id):
+      new_msg.append(ord(b))
+    
+    full_len = len(path)
+
+    for b in pack('!I', full_len):
+      new_msg.append(ord(b))
+
+    new_msg.extend(path)
+    self.stdin.write(new_msg)
+    self.stdin.flush()
+    
+    time.sleep(0.1)
+    self.check_error()
+    
+    return msg_id
+
+
+  def send_get_contexts(self):
+    new_msg = bytearray()
+
+    cmd_type = 3 #cmd type 3 (get contexts)
+    
+    for b in pack('!H', cmd_type):
+      new_msg.append(ord(b))
+    
+    msg_id = self.get_query_id()
+    for b in pack('!I', msg_id):
+      new_msg.append(ord(b))
+    
+    full_len = 1
+
+    for b in pack('!I', full_len):
+      new_msg.append(ord(b))
+
+    new_msg.append(ord('\0')) #We don't support zero len so this is dummy data
+    self.stdin.write(new_msg)
+    self.stdin.flush()
+    
+    time.sleep(0.1)
+    self.check_error()
+    
+    return msg_id
+
+
+  def sigint(self):
+    self.send_signal(signal.SIGINT)
+
+#TODO: convert following methods to class methods or static methods?
+
+def deserialize_msg(raw_msg):
+  i = 0
+  
+  account_len = (unpack('B', raw_msg[i:i+1]))[0]
+  i += 1
+  
+  account = raw_msg[i:i+account_len]
+  i += account_len
+  
+  protocol_len = (unpack('B', raw_msg[i:i+1]))[0]
+  i += 1
+  
+  protocol = raw_msg[i:i+protocol_len]
+  i += protocol_len
+  
+  msg_len = (unpack('!I',raw_msg[i:i+4]))[0]
+  i += 4
+  
+  msg = raw_msg[i:i+msg_len]
+  
+  return [account, protocol, msg]
+
+def deserialize_context(contexts, serialized_context):
+  i = 0
+  
+  protocol_version = (unpack('!I',serialized_context[i:i+4]))[0]
+  i += 4
+  
+  username_len = ord(serialized_context[i])
+  i += 1
+  
+  username = serialized_context[i:i+username_len]
+  i += username_len
+  
+  accountname_len = ord(serialized_context[i])
+  i += 1
+  
+  accountname = serialized_context[i:i+accountname_len]
+  i += accountname_len
+  
+  protocol_len = ord(serialized_context[i])
+  i += 1
+  
+  protocol = serialized_context[i:i+protocol_len]
+  i += protocol_len
+  
+  otr_offer = (unpack('!I',serialized_context[i:i+4]))[0]
+  i += 4
+  
+  msg_state = (unpack('!I',serialized_context[i:i+4]))[0]
+  i += 4
+  
+  auth_state = (unpack('!I',serialized_context[i:i+4]))[0]
+  i += 4
+  
+  our_instance = (unpack('!I',serialized_context[i:i+4]))[0]
+  i += 4
+  
+  their_instance = (unpack('!I',serialized_context[i:i+4]))[0]
+  i += 4
+  
+  #if protocol_version < 3:
+  #  our_instance = None
+  #  their_instance = None
+  
+  context = otr_context(protocol_version, username, accountname, protocol, otr_offer, msg_state, auth_state, our_instance, their_instance)
+  contexts.append(context)
+  
+  return serialized_context[i:]
+
+def deserialize_contexts(serialized_contexts):
+  num_contexts = (unpack('!I',serialized_contexts[0:4]))[0]
+  
+  serialized_contexts = serialized_contexts[4:]
+  contexts = []
+  
+  for i in range(num_contexts):
+    serialized_contexts = deserialize_context(contexts, serialized_contexts)
+  
+  return contexts  
+
+def get_n_messages_blocking(queue, n):
+  result = []
+  
+  for i in range(n):
+    result.append(queue.get())
+  
+  return result
+
+def read_bytes(stream, num_bytes):
+  msg = ''
+  while len(msg) < num_bytes:
+    chunk = stream.read(num_bytes-len(msg))
+    if chunk is None:
+      return
+    msg = msg + chunk
+  return msg
+
+def read_1b_val(stream):
+  byte = read_bytes(stream, 1)
+  
+  if len(byte) == 1:
+    return ord(byte)
+  else:
+    print("Read unexpected length: " + str(len(byte)))
+    return
+
+def read_4b_val(stream):
+  bytes = read_bytes(stream, 4)
+  if len(bytes) == 4:
+    val = unpack('!I',bytes) #network byte order
+    return int(val[0])
+  else: 
+    print("Read unexpected length: " + str(len(byte)))
+    return
+
+def dump_msg_queue(queue):
+  while not queue.empty():
+    item = queue.get(False)
+    if item is not None:
+      item = deserialize_msg(item)
+    print('###############')
+    print(item)
+    print('###############')
+
+def dump_queue(queue):
+  while not queue.empty():
+    item = queue.get(False)
+    print('###############')
+    print(str(item))
+    print('###############')
+
+def dump_contexts(contexts):
+  for context in contexts:
+    print('***************')
+    print(str(context))
+    print('***************')
+
+
+def get_many_instances(location, account, protocol, num, log_tag):
+  result = []
+  for i in range(num):
+    #result.append(otr_subprocess(['/usr/bin/valgrind', '--tool=memcheck', '--leak-check=yes', '--show-reachable=yes', '--num-callers=20', '--track-fds=yes', '--log-file=valgrind_log' + account + protocol + str(i) + '.txt',  location, account, protocol, im_ip, im_port, log_tag+str(i)+".txt"], 7))
+    result.append(otr_subprocess([location, account, protocol, im_ip, im_port, log_tag+str(i)+".txt"], 0))
+
+  return result
+
+if __name__ == "__main__":
+  main(sys.argv)
diff --git a/test_suite/otr_test.py b/test_suite/otr_test.py
new file mode 100644
index 0000000..483952d
--- /dev/null
+++ b/test_suite/otr_test.py
@@ -0,0 +1,250 @@
+#!/usr/bin/python
+
+import sys
+import itertools
+import re
+import Queue
+
+from dummy_im import *
+from otr_subprocess import *
+
+client_location_30 = "./otr_c_client/dummy_client_30"
+client_location_31 = "./otr_c_client/dummy_client_31"
+client_location_32 = "./otr_c_client/dummy_client_32"
+client_location_40 = "./otr_c_client/dummy_client_40"
+
+client_locations = [client_location_30, client_location_31, client_location_32, client_location_40]
+
+
+class otr_test_failed_exception(Exception):    
+  def __init__(self, value, p_list=None):
+    self.value = value
+    self.p_list = p_list
+    if p_list is not None:
+      for p in p_list:
+        q_id = p.send_get_contexts()
+        serialized_contexts = p.get_query_blocking(q_id)
+        p_contexts = deserialize_contexts(serialized_contexts)
+        print("Dumping contexts:")
+        dump_contexts(p_contexts)
+        print("Dumping raw messages:")
+        dump_msg_queue(p.querymap[q_raw_msg])
+        print("Dumping otr-processed messages:")
+        dump_msg_queue(p.querymap[q_otr_msg])
+        print("Dumping error messages:")
+        dump_queue(p.querymap[q_err])
+
+  def __str__(self):
+    return "Test Failed: " + repr(self.value)
+
+class otr_test:
+  def __init__(self, alices, bobs):
+    
+    self.alices = alices
+    self.bobs = bobs
+    self.subprocesses = alices + bobs
+    
+  #no default tests -- always defined by child class
+  def run_test(self, options={}):
+    return
+
+  def reset_processes(self):
+    for p in self.subprocesses:
+      p.sigint()
+    
+    #Give them a moment to terminate on their own if they are willing and able  
+    time.sleep(0.5)
+      
+    for p in self.subprocesses:
+      p.reset()    
+  
+  def check_error_all(self, allowed_events=[]): 
+    for p in self.subprocesses:
+      p.check_error()
+      if not p.querymap[q_err].empty():
+        tmp_stack = []
+        
+        while not p.querymap[q_err].empty():
+          item = p.querymap[q_err].get(False)
+          if allowed_events.count(item) < 1:
+            raise otr_test_failed_exception(item, [p])
+          tmp_stack.append(item)
+        
+        while len(tmp_stack) > 0:
+          p.querymap[q_err].put(tmp_stack.pop())
+    
+        
+  def wait_all_gone_encrypted(self):
+    for p in self.subprocesses:
+      p.get_query_blocking(q_gone_secure) #Wait for "gone encrypted" signal
+      print(p.accountname + " " + p.protocol + " went encrypted")
+      
+  def send_encrypted_and_check(self, senders, receivers, out_msg, options={}):
+    for p in list(set(senders)):
+      print("About to send encrypted message")
+      msg_id = p.send_msg(receivers[0].accountname, receivers[0].protocol, out_msg)
+      modified_msg = p.get_query_blocking(msg_id)
+      print("Encrypted message sent")
+
+      if modified_msg.find(out_msg) >= 0:
+        raise otr_test_failed_exception("Sent message not encrypted", self.subprocesses)
+      
+    receiver_msgs = []
+
+    for p in list(set(receivers)): #remove duplicates
+      print("About to receive raw encrypted message")
+      receiver_msgs.append(get_n_messages_blocking(p.querymap[q_raw_data_msg], len(list(set(senders)))))
+      
+    for msg in receiver_msgs:
+      check_contains_message_matches_ex(msg, otr_data_prefix_regex, len(list(set(senders))), self.subprocesses)   
+
+    for i in range(len(list(set(senders)))):      
+      print("About to receive decrypted message")
+      receiver_recvd = find_first_msg_blocking(receivers, q_otr_msg)
+        
+      if not check_message_matches(receiver_recvd, re.escape(out_msg)):
+        raise otr_test_failed_exception("Received message not decrypted", self.subprocesses)
+        
+  def check_all_contexts_encrypted(self, options={}):
+    for p in self.alices:
+      c_id = p.send_get_contexts()
+      serialized_contexts = p.get_query_blocking(c_id)
+      p_contexts = deserialize_contexts(serialized_contexts)
+      check_contexts_for_encrypted(p_contexts, self.num_bob, p)
+      
+    for p in self.bobs:
+      c_id = p.send_get_contexts()
+      serialized_contexts = p.get_query_blocking(c_id)
+      p_contexts = deserialize_contexts(serialized_contexts)
+      check_contexts_for_encrypted(p_contexts, self.num_alice, p)
+      
+  def init_processes(self, options={}):
+    for p in self.subprocesses:
+        p.check_error()
+    
+    privkey = options.get('privkey', 'otr.private_key')
+    
+    for p in self.subprocesses:
+      c_id = p.send_read_privkey(privkey)
+      p.get_query_blocking(c_id)
+        
+    for i, p in enumerate(self.alices):  
+      c_id = p.send_read_instag("instance_tags" + str(i) + ".txt")
+      p.get_query_blocking(c_id)
+      
+    for i, p in enumerate(self.bobs):  
+      c_id = p.send_read_instag("instance_tags" + str(i) + ".txt")
+      p.get_query_blocking(c_id)
+        
+    for p in self.subprocesses:
+      c_id = p.send_init()
+      p.get_query_blocking(c_id)
+      
+    for p in self.subprocesses:
+      p.check_error()
+    
+  def otr_init_msg(self, options={}):
+    alice_init_idx = options.get('alice_init_idx', 0)
+    msg_id = self.alices[alice_init_idx].send_msg(self.bob_account, self.bob_proto, self.msg1)
+    modified_msg = self.alices[alice_init_idx].get_query_blocking(msg_id)
+    done = self.alices[alice_init_idx].get_query_blocking(msg_id)
+    
+    for p in self.bobs:
+      msg = p.get_query_blocking(q_otr_msg)
+      if check_message_matches(msg, re.escape(self.msg1) + otr_tab_tag):
+        raise otr_test_failed_exception("Query tabs not removed", self.subprocesses)
+        
+      check_message_matches_ex(msg, re.escape(self.msg1), self.subprocesses)
+
+  def otr_init_query(self, options={}):
+    msg_id = self.alices[0].send_msg(self.bob_account, self.bob_proto, otr_query)
+    modified_msg = self.alices[0].get_query_blocking(msg_id)
+    done = ""
+    while done.find("DONE") < 0:
+      done = self.alices[0].get_query_blocking(msg_id)
+
+    #More to check?
+    
+#TODO: Make these methods class methods / static methods?
+def chomp_auth_msgs(ps):
+  for p in ps:
+    msgs = get_n_messages_blocking(p.querymap[q_raw_msg], p.querymap[q_raw_msg].qsize())
+    for msg in msgs:
+      parsed_msg = deserialize_msg(msg)[2]
+      if parsed_msg.find("?OTR") != 0:
+        p.querymap[q_raw_msg].put(msg)
+    
+def chomp_msgs(ps, q_idx):
+  for p in ps:
+    get_n_messages_blocking(p.querymap[q_idx], p.querymap[q_idx].qsize())
+    
+
+def wait_gone_encrypted(ps):
+  for p in ps:
+    p.get_query_blocking(q_gone_secure) #Wait for "gone encrypted" signal
+
+def find_first_msg_blocking(ps, q_idx):
+  #We have to spin here until this is implemented: 
+  # http://bugs.python.org/issue3831
+  result = None
+  
+  while result is None:
+    for p in ps:
+      try:
+        result = p.querymap[q_idx].get(True, 0.05) #block for 50ms
+        break
+      except (Queue.Empty) as e:
+        continue
+  
+  return result
+
+def check_message_matches(msg, regex):
+  msg = deserialize_msg(msg)
+  return re.match(regex, msg[2]) is not None
+
+def check_message_matches_ex(msg, regex, p_list):
+  if not check_message_matches(msg, regex):
+    raise otr_test_failed_exception("msg failed to match regex -- regex: " + regex + " msg: " + msg[2], p_list)
+
+def check_contains_message_matches_ex(msgs, regex, n, p_list):
+  matches = 0
+  for msg in msgs:
+    if check_message_matches(msg, regex):
+      matches += 1
+      
+  if matches < n:
+    raise otr_test_failed_exception("msg failed to match regex " + str(n) + " times -- regex: " + regex + " matches: " + str(matches) + " msgs: " + str(msgs), p_list)
+
+
+#Given a process's list of contexts, check that num_expected_encrypted are encrypted
+def check_contexts_for_encrypted(contexts, num_expected_encrypted, p):
+  num_good = 0
+
+  if num_expected_encrypted > 1 and p.client_location != client_location_40: #XXX: support future versions
+    raise otr_test_failed_exception("Expected > 1 good contexts on protocol version < 2", [p])
+  
+  if len(contexts) == 0:
+    raise otr_test_failed_exception("Failed: No contexts", [p])
+    
+  if p.client_location != client_location_40 and num_expected_encrypted == 1 and len(contexts) == 1:
+    if not check_context_encrypted(contexts[0], p):
+      raise otr_test_failed_exception("Failed: Not encrypted", [p])
+    
+  else:
+    for context in contexts:
+      if check_context_encrypted(context, p):
+        num_good += 1
+        
+    if num_good < num_expected_encrypted:
+      msg = "Failed: fewer than expected encrypted contexts. Found " + str(num_good) + " Expected " + str(num_expected_encrypted)
+      raise otr_test_failed_exception(msg, [p])
+
+def check_offer_accepted(context):
+  if context.offer_state != 3:
+        raise otr_test_failed_exception("Failed: Offer not given or not accepted")
+  
+def check_context_encrypted(context, p):
+  if context.msg_state == 1:
+    return True
+  else: 
+    return False
diff --git a/test_suite/otr_test_general.py b/test_suite/otr_test_general.py
new file mode 100644
index 0000000..9928c10
--- /dev/null
+++ b/test_suite/otr_test_general.py
@@ -0,0 +1,162 @@
+#!/usr/bin/python
+
+import sys
+import itertools
+import re
+import Queue
+
+from otr_test import *
+
+class otr_test_general(otr_test):
+  #Supports multiple 4.0 clients or single 3.X clients
+
+  def __init__(self, alices, bobs):
+    otr_test.__init__(self, alices, bobs)
+    
+    self.alice_account = alices[0].accountname
+    self.alice_proto = alices[0].protocol
+      
+    self.bob_account = bobs[0].accountname
+    self.bob_proto = bobs[0].protocol
+    
+    self.num_alice = len(self.alices)
+    self.num_bob = len(self.bobs)
+  
+  def run_test(self, options={}):
+    try:
+      self.msg1 = options.get('msg1', '%$sup?')
+      self.msg2 = options.get('msg2', '^&not much')
+      self.msg3 = options.get('msg3', '*(cool')
+      self.init_processes(options)
+      time.sleep(1)
+      print("Processes initialized")
+      if options.get('otr_init_method', 'msg') == 'msg':
+        self.otr_init_msg(options)
+      elif options.get('otr_init_method', 'msg') == 'query':
+        self.otr_init_query()
+      print("About to wait for all to go encrypted")
+      self.wait_all_gone_encrypted()
+      print("All went encrypted")
+      #self.analyze_otr_init(options) #hasn't been updated to support fragments
+      time.sleep(5)
+      self.check_all_contexts_encrypted(options)
+      print("Verified encrypted contexts")
+      self.send_encrypted_and_check(self.bobs, self.alices, self.msg2, options)
+      print("Sent and verified encrypted message from bobs to alices")
+      self.send_encrypted_and_check(self.alices, self.bobs, self.msg3, options)
+      print("Sent and verified encrypted message from alices to bobs")
+      self.check_error_all(options.get('allowed_msg_events', []))
+      print("Test succeeded")
+      self.reset_processes()
+
+    except (subprocess_exception, otr_test_failed_exception) as e:
+      print '***Exception: ', e, e.value, sys.exc_info()
+      self.reset_processes()
+      print("Test failed")
+
+  def analyze_otr_init(self, options={}):
+    alice_msgs = []
+    bob_msgs = []
+    
+    for p in self.bobs:
+      init_msg = p.querymap[q_raw_msg].get()
+      
+      if options.get('otr_init_method', 'msg') == 'msg':
+        check_contains_message_matches_ex([init_msg], re.escape(self.msg1) + otr_tab_tag, 1, self.subprocesses)
+      elif options.get('otr_init_method', 'msg') == 'query':
+        check_contains_message_matches_ex([init_msg], re.escape(otr_query), 1, self.subprocesses)
+      
+      bob_msgs.append(get_n_messages_blocking(p.querymap[q_raw_auth_msg], self.num_alice*self.num_bob*2))
+      
+    for p in self.alices:  
+      alice_msgs.append(get_n_messages_blocking(p.querymap[q_raw_auth_msg], self.num_bob + self.num_bob*self.num_alice))
+
+    for msg in bob_msgs:
+      check_contains_message_matches_ex(msg, otr_key_prefix_regex, self.num_alice*self.num_bob, self.subprocesses)
+      check_contains_message_matches_ex(msg, otr_sign_prefix_regex, self.num_alice*self.num_bob, self.subprocesses)
+
+    for msg in alice_msgs:
+      check_contains_message_matches_ex(msg, otr_commit_prefix_regex, self.num_bob, self.subprocesses)
+      check_contains_message_matches_ex(msg, otr_reveal_prefix_regex, self.num_bob*self.num_alice, self.subprocesses)
+
+   
+def test_all_vers_1_1():
+  alice_accountname = "otrtest3"
+  bob_accountname = "otrtest1"
+  protocol = "prpl-aim"
+  
+  test_combos = []
+  
+  for loc1, loc2 in itertools.product(client_locations, repeat=2):
+    test_combos.append((loc1, loc2))
+  
+  for i, (loc1, loc2) in enumerate(test_combos): #otr_subprocess([location, account, protocol, im_ip, im_port, log_tag+str(i)+".txt"], 0)
+    alice = otr_subprocess([loc1, alice_accountname, protocol, im_ip, im_port, "alice"+str(i)+".txt"], 0)
+    bob = otr_subprocess([loc2, bob_accountname, protocol, im_ip, im_port, "bob"+str(i)+".txt"], 0)
+    
+    print('Testing ' + loc1 + ' and ' + loc2)
+    
+    the_test = otr_test_general([alice], [bob])
+    options={}
+    options['otr_init_method'] = 'msg'
+    the_test.run_test(options)
+    time.sleep(1)
+  
+  print('Test complete!')
+
+def test_basic_40():
+  #tests single 4.0s
+  alice_accountname = "otrtest3"
+  bob_accountname = "otrtest1"
+  protocol = "prpl-aim"
+  
+  alices = get_many_instances(client_location_40, alice_accountname, protocol, 1, "alice")
+  bobs = get_many_instances(client_location_40, bob_accountname, protocol, 1, "bob")
+  
+  the_test = otr_test_general(alices, bobs)
+  options={}
+  options['otr_init_method'] = 'query'
+  options['allowed_msg_events'] = ["OTRL_MSGEVENT_LOG_HEARTBEAT_SENT", "OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD"]
+  the_test.run_test(options)
+  
+  print('Test complete!')
+  
+
+def test_multi_40():
+  #tests multiple 4.0 versions
+  alice_accountname = "otrtest3"
+  bob_accountname = "otrtest1"
+  protocol = "prpl-aim"
+  
+  alices = get_many_instances(client_location_40, alice_accountname, protocol, 3, "alice")
+  bobs = get_many_instances(client_location_40, bob_accountname, protocol, 2, "bob")
+  
+  the_test = otr_test_general(alices, bobs)
+  options={}
+  options['otr_init_method'] = 'query'
+  options['allowed_msg_events'] = ["OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE", "OTRL_MSGEVENT_LOG_HEARTBEAT_SENT", "OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD"]
+  the_test.run_test(options)
+  
+  print('Test complete!')
+  
+
+def main(args):
+  server = im_server(int(im_port))
+  server.daemon = True
+  server.start()
+  
+  print('Testing basic 4.0 to 4.0')
+  test_basic_40()
+  
+  print('Testing all versions 1 client to 1 client')
+  test_all_vers_1_1()
+  
+  print('Testing multi 4.0 to multi 4.0')
+  test_multi_40()
+  
+  server.set_finished()
+  print("Shutting down...")
+  
+if __name__ == "__main__":
+  main(sys.argv)   
+   
diff --git a/test_suite/otr_test_mixed.py b/test_suite/otr_test_mixed.py
new file mode 100644
index 0000000..7bef2c3
--- /dev/null
+++ b/test_suite/otr_test_mixed.py
@@ -0,0 +1,153 @@
+#!/usr/bin/python
+
+import sys
+import itertools
+import re
+import Queue
+
+from otr_test import *
+
+class otr_test_mixed(otr_test):
+  #Supports multiple 4.0 clients or single 3.X clients
+
+  def __init__(self, alices, bobs):
+    otr_test.__init__(self, alices, bobs)
+    
+    self.alice_account = alices[0].accountname
+    self.alice_proto = alices[0].protocol
+      
+    self.bob_account = bobs[0].accountname
+    self.bob_proto = bobs[0].protocol
+    
+    self.num_alice = len(self.alices)
+    self.num_bob = len(self.bobs)
+  
+  def run_test(self, options={}):
+    try:
+      self.msg1 = options.get('msg1', '%$sup?')
+      self.msg2 = options.get('msg2', '^&not much')
+      self.msg3 = options.get('msg3', '*(cool')
+      
+      self.init_processes(options)
+      
+      if options.get('otr_init_method', 'msg') == 'msg':
+        self.otr_init_msg(options)
+      elif options.get('otr_init_method', 'msg') == 'query':
+        self.otr_init_query()
+      
+      alice_encrypted_idx = options.get('alice_expected_encrypted_idx', range(len(self.alices)))
+      bob_encrypted_idx = options.get('bob_expected_encrypted_idx', range(len(self.bobs)))
+      
+      alice_encrypted = []
+      for i in alice_encrypted_idx:
+        alice_encrypted.append(self.alices[i])
+      
+      bob_encrypted = []
+      for i in bob_encrypted_idx:
+        bob_encrypted.append(self.bobs[i])
+      
+
+      wait_gone_encrypted(alice_encrypted + bob_encrypted)
+      time.sleep(1) #Ensure all messages received and processed
+
+      chomp_msgs(alice_encrypted + bob_encrypted, q_raw_msg)
+
+      #Check contexts
+      for p in list(set(alice_encrypted)):
+        c_id = p.send_get_contexts()
+        serialized_contexts = p.get_query_blocking(c_id)
+        p_contexts = deserialize_contexts(serialized_contexts)
+        check_contexts_for_encrypted(p_contexts, alice_encrypted.count(p), p)
+        
+
+      for p in list(set(bob_encrypted)):
+        c_id = p.send_get_contexts()
+        serialized_contexts = p.get_query_blocking(c_id)
+        p_contexts = deserialize_contexts(serialized_contexts)
+        check_contexts_for_encrypted(p_contexts, bob_encrypted.count(p), p)
+        
+
+      #We only send messages between the 4.0s because the 3.X will be paired with only one partner, and we don't know 
+      #for sure which one.
+      self.send_encrypted_and_check(return_only_40(bob_encrypted), return_only_40(alice_encrypted), self.msg2, options)
+
+      self.send_encrypted_and_check(return_only_40(alice_encrypted), return_only_40(bob_encrypted), self.msg3, options)
+
+      #Non-4.0 clients will error from unexpected messages, but we are expecting this
+      for p in self.subprocesses:
+        p.check_error()
+        if not p.querymap[q_err].empty() and p.client_location == client_location_40:
+          errors = get_n_messages_blocking(p.querymap[q_err], p.querymap[q_err].qsize())
+          for error in errors: 
+            print("Warning: setup errors detected: " + error)
+
+      print("Test succeeded")
+      self.reset_processes()
+      
+    except (subprocess_exception, otr_test_failed_exception) as e:
+      print '***Exception: ', e.value, sys.exc_info()
+      self.reset_processes()
+      print("Test failed")
+
+def return_only_40(processes):
+  result = []
+  for p in processes:
+    if p.client_location == client_location_40:
+      result.append(p)
+  
+  return result
+
+def test_40_mixed(num_alice_40, num_bob_40, alice_extra_location=None, bob_extra_location=None, options={}):
+  alice_accountname = "otrtest3"
+  bob_accountname = "otrtest1"
+  protocol = "prpl-aim"
+  
+  alices = get_many_instances(client_location_40, alice_accountname, protocol, num_alice_40, "alice")
+  bobs = get_many_instances(client_location_40, bob_accountname, protocol, num_bob_40, "bob")
+  
+  if alice_extra_location is not None:
+    alices.append(otr_subprocess([alice_extra_location, alice_accountname, protocol, im_ip, im_port, "alice" + str(num_alice_40) + ".txt"], 0))
+  
+  if bob_extra_location is not None:
+    bobs.append(otr_subprocess([bob_extra_location, bob_accountname, protocol, im_ip, im_port, "bob" + str(num_bob_40) + ".txt"], 0))
+  
+  the_test = otr_test_mixed(alices, bobs)
+  
+  options['otr_init_method'] = 'msg'
+  options['allowed_msg_events'] = ["OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE", "OTRL_MSGEVENT_LOG_HEARTBEAT_SENT", "OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD"]
+  the_test.run_test(options)
+  
+
+def main(args):
+  print('Testing 4.0 and other to 4.0')
+  server = im_server(int(im_port))
+  server.daemon = True
+  server.start()
+  
+  options = {}
+  options['alice_init_idx'] = 0
+  options['alice_expected_encrypted_idx'] = [0, 0, 1, 1, 2, 2]
+  options['bob_expected_encrypted_idx'] = [0, 0, 0, 1, 1, 1, 2]
+  
+  for location in client_locations:
+    if location == client_location_40: continue
+    print("Testing with extra Bob location: " + location)
+    test_40_mixed(3, 2, None, location, options)
+    time.sleep(1)
+  
+  
+  options['alice_expected_encrypted_idx'] = [0, 1]
+  options['bob_expected_encrypted_idx'] = [0, 0, 1, 1]
+  
+  for location in client_locations:
+    if location == client_location_40: continue
+    print("Testing with extra Alice location: " + location)
+    test_40_mixed(2, 2, location, None, options)
+    time.sleep(1)
+  
+  server.set_finished()
+  print("Shutting down...")
+  
+if __name__ == "__main__":
+  main(sys.argv)   
+   
diff --git a/toolkit/aes.c b/toolkit/aes.c
index 24b9b56..046eed7 100644
--- a/toolkit/aes.c
+++ b/toolkit/aes.c
@@ -36,10 +36,10 @@
 /* forward S-box & tables */
 
 uint32 FSb[256];
-uint32 FT0[256]; 
-uint32 FT1[256]; 
-uint32 FT2[256]; 
-uint32 FT3[256]; 
+uint32 FT0[256];
+uint32 FT1[256];
+uint32 FT2[256];
+uint32 FT3[256];
 
 /* reverse S-box & tables */
 
@@ -60,7 +60,7 @@ int do_init = 1;
 /* tables generation routine */
 
 #define ROTR8(x) ( ( ( x << 24 ) & 0xFFFFFFFF ) | \
-                   ( ( x & 0xFFFFFFFF ) >>  8 ) )
+		   ( ( x & 0xFFFFFFFF ) >>  8 ) )
 
 #define XTIME(x) ( ( x <<  1 ) ^ ( ( x & 0x80 ) ? 0x1B : 0x00 ) )
 #define MUL(x,y) ( ( x &&  y ) ? pow[(log[x] + log[y]) % 255] : 0 )
@@ -76,15 +76,15 @@ void aes_gen_tables( void )
 
     for( i = 0, x = 1; i < 256; i++, x ^= XTIME( x ) )
     {
-        pow[i] = x;
-        log[x] = i;
+	pow[i] = x;
+	log[x] = i;
     }
 
     /* calculate the round constants */
 
     for( i = 0, x = 1; i < 10; i++, x = XTIME( x ) )
     {
-        RCON[i] = (uint32) x << 24;
+	RCON[i] = (uint32) x << 24;
     }
 
     /* generate the forward and reverse S-boxes */
@@ -94,47 +94,47 @@ void aes_gen_tables( void )
 
     for( i = 1; i < 256; i++ )
     {
-        x = pow[255 - log[i]];
+	x = pow[255 - log[i]];
 
-        y = x;  y = ( y << 1 ) | ( y >> 7 );
-        x ^= y; y = ( y << 1 ) | ( y >> 7 );
-        x ^= y; y = ( y << 1 ) | ( y >> 7 );
-        x ^= y; y = ( y << 1 ) | ( y >> 7 );
-        x ^= y ^ 0x63;
+	y = x;  y = ( y << 1 ) | ( y >> 7 );
+	x ^= y; y = ( y << 1 ) | ( y >> 7 );
+	x ^= y; y = ( y << 1 ) | ( y >> 7 );
+	x ^= y; y = ( y << 1 ) | ( y >> 7 );
+	x ^= y ^ 0x63;
 
-        FSb[i] = x;
-        RSb[x] = i;
+	FSb[i] = x;
+	RSb[x] = i;
     }
 
     /* generate the forward and reverse tables */
 
     for( i = 0; i < 256; i++ )
     {
-        x = (unsigned char) FSb[i]; y = XTIME( x );
+	x = (unsigned char) FSb[i]; y = XTIME( x );
 
-        FT0[i] =   (uint32) ( x ^ y ) ^
-                 ( (uint32) x <<  8 ) ^
-                 ( (uint32) x << 16 ) ^
-                 ( (uint32) y << 24 );
+	FT0[i] =   (uint32) ( x ^ y ) ^
+		 ( (uint32) x <<  8 ) ^
+		 ( (uint32) x << 16 ) ^
+		 ( (uint32) y << 24 );
 
-        FT0[i] &= 0xFFFFFFFF;
+	FT0[i] &= 0xFFFFFFFF;
 
-        FT1[i] = ROTR8( FT0[i] );
-        FT2[i] = ROTR8( FT1[i] );
-        FT3[i] = ROTR8( FT2[i] );
+	FT1[i] = ROTR8( FT0[i] );
+	FT2[i] = ROTR8( FT1[i] );
+	FT3[i] = ROTR8( FT2[i] );
 
-        y = (unsigned char) RSb[i];
+	y = (unsigned char) RSb[i];
 
-        RT0[i] = ( (uint32) MUL( 0x0B, y )       ) ^
-                 ( (uint32) MUL( 0x0D, y ) <<  8 ) ^
-                 ( (uint32) MUL( 0x09, y ) << 16 ) ^
-                 ( (uint32) MUL( 0x0E, y ) << 24 );
+	RT0[i] = ( (uint32) MUL( 0x0B, y )       ) ^
+		 ( (uint32) MUL( 0x0D, y ) <<  8 ) ^
+		 ( (uint32) MUL( 0x09, y ) << 16 ) ^
+		 ( (uint32) MUL( 0x0E, y ) << 24 );
 
-        RT0[i] &= 0xFFFFFFFF;
+	RT0[i] &= 0xFFFFFFFF;
 
-        RT1[i] = ROTR8( RT0[i] );
-        RT2[i] = ROTR8( RT1[i] );
-        RT3[i] = ROTR8( RT2[i] );
+	RT1[i] = ROTR8( RT0[i] );
+	RT2[i] = ROTR8( RT1[i] );
+	RT3[i] = ROTR8( RT2[i] );
     }
 }
 
@@ -412,9 +412,9 @@ void aes_gen_tables( void )
 #define GET_UINT32(n,b,i)                       \
 {                                               \
     (n) = ( (uint32) (b)[(i)    ] << 24 )       \
-        | ( (uint32) (b)[(i) + 1] << 16 )       \
-        | ( (uint32) (b)[(i) + 2] <<  8 )       \
-        | ( (uint32) (b)[(i) + 3]       );      \
+	| ( (uint32) (b)[(i) + 1] << 16 )       \
+	| ( (uint32) (b)[(i) + 2] <<  8 )       \
+	| ( (uint32) (b)[(i) + 3]       );      \
 }
 
 #define PUT_UINT32(n,b,i)                       \
@@ -443,24 +443,24 @@ int aes_set_key( aes_context *ctx, uint8 *key, int nbits )
 
     if( do_init )
     {
-        aes_gen_tables();
+	aes_gen_tables();
 
-        do_init = 0;
+	do_init = 0;
     }
 
     switch( nbits )
     {
-        case 128: ctx->nr = 10; break;
-        case 192: ctx->nr = 12; break;
-        case 256: ctx->nr = 14; break;
-        default : return( 1 );
+	case 128: ctx->nr = 10; break;
+	case 192: ctx->nr = 12; break;
+	case 256: ctx->nr = 14; break;
+	default : return( 1 );
     }
 
     RK = ctx->erk;
 
     for( i = 0; i < (nbits >> 5); i++ )
     {
-        GET_UINT32( RK[i], key, i * 4 );
+	GET_UINT32( RK[i], key, i * 4 );
     }
 
     /* setup encryption round keys */
@@ -469,78 +469,78 @@ int aes_set_key( aes_context *ctx, uint8 *key, int nbits )
     {
     case 128:
 
-        for( i = 0; i < 10; i++, RK += 4 )
-        {
-            RK[4]  = RK[0] ^ RCON[i] ^
-                        ( FSb[ (uint8) ( RK[3] >> 16 ) ] << 24 ) ^
-                        ( FSb[ (uint8) ( RK[3] >>  8 ) ] << 16 ) ^
-                        ( FSb[ (uint8) ( RK[3]       ) ] <<  8 ) ^
-                        ( FSb[ (uint8) ( RK[3] >> 24 ) ]       );
+	for( i = 0; i < 10; i++, RK += 4 )
+	{
+	    RK[4]  = RK[0] ^ RCON[i] ^
+			( FSb[ (uint8) ( RK[3] >> 16 ) ] << 24 ) ^
+			( FSb[ (uint8) ( RK[3] >>  8 ) ] << 16 ) ^
+			( FSb[ (uint8) ( RK[3]       ) ] <<  8 ) ^
+			( FSb[ (uint8) ( RK[3] >> 24 ) ]       );
 
-            RK[5]  = RK[1] ^ RK[4];
-            RK[6]  = RK[2] ^ RK[5];
-            RK[7]  = RK[3] ^ RK[6];
-        }
-        break;
+	    RK[5]  = RK[1] ^ RK[4];
+	    RK[6]  = RK[2] ^ RK[5];
+	    RK[7]  = RK[3] ^ RK[6];
+	}
+	break;
 
     case 192:
 
-        for( i = 0; i < 8; i++, RK += 6 )
-        {
-            RK[6]  = RK[0] ^ RCON[i] ^
-                        ( FSb[ (uint8) ( RK[5] >> 16 ) ] << 24 ) ^
-                        ( FSb[ (uint8) ( RK[5] >>  8 ) ] << 16 ) ^
-                        ( FSb[ (uint8) ( RK[5]       ) ] <<  8 ) ^
-                        ( FSb[ (uint8) ( RK[5] >> 24 ) ]       );
-
-            RK[7]  = RK[1] ^ RK[6];
-            RK[8]  = RK[2] ^ RK[7];
-            RK[9]  = RK[3] ^ RK[8];
-            RK[10] = RK[4] ^ RK[9];
-            RK[11] = RK[5] ^ RK[10];
-        }
-        break;
+	for( i = 0; i < 8; i++, RK += 6 )
+	{
+	    RK[6]  = RK[0] ^ RCON[i] ^
+			( FSb[ (uint8) ( RK[5] >> 16 ) ] << 24 ) ^
+			( FSb[ (uint8) ( RK[5] >>  8 ) ] << 16 ) ^
+			( FSb[ (uint8) ( RK[5]       ) ] <<  8 ) ^
+			( FSb[ (uint8) ( RK[5] >> 24 ) ]       );
+
+	    RK[7]  = RK[1] ^ RK[6];
+	    RK[8]  = RK[2] ^ RK[7];
+	    RK[9]  = RK[3] ^ RK[8];
+	    RK[10] = RK[4] ^ RK[9];
+	    RK[11] = RK[5] ^ RK[10];
+	}
+	break;
 
     case 256:
 
-        for( i = 0; i < 7; i++, RK += 8 )
-        {
-            RK[8]  = RK[0] ^ RCON[i] ^
-                        ( FSb[ (uint8) ( RK[7] >> 16 ) ] << 24 ) ^
-                        ( FSb[ (uint8) ( RK[7] >>  8 ) ] << 16 ) ^
-                        ( FSb[ (uint8) ( RK[7]       ) ] <<  8 ) ^
-                        ( FSb[ (uint8) ( RK[7] >> 24 ) ]       );
-
-            RK[9]  = RK[1] ^ RK[8];
-            RK[10] = RK[2] ^ RK[9];
-            RK[11] = RK[3] ^ RK[10];
-
-            RK[12] = RK[4] ^
-                        ( FSb[ (uint8) ( RK[11] >> 24 ) ] << 24 ) ^
-                        ( FSb[ (uint8) ( RK[11] >> 16 ) ] << 16 ) ^
-                        ( FSb[ (uint8) ( RK[11] >>  8 ) ] <<  8 ) ^
-                        ( FSb[ (uint8) ( RK[11]       ) ]       );
-
-            RK[13] = RK[5] ^ RK[12];
-            RK[14] = RK[6] ^ RK[13];
-            RK[15] = RK[7] ^ RK[14];
-        }
-        break;
+	for( i = 0; i < 7; i++, RK += 8 )
+	{
+	    RK[8]  = RK[0] ^ RCON[i] ^
+			( FSb[ (uint8) ( RK[7] >> 16 ) ] << 24 ) ^
+			( FSb[ (uint8) ( RK[7] >>  8 ) ] << 16 ) ^
+			( FSb[ (uint8) ( RK[7]       ) ] <<  8 ) ^
+			( FSb[ (uint8) ( RK[7] >> 24 ) ]       );
+
+	    RK[9]  = RK[1] ^ RK[8];
+	    RK[10] = RK[2] ^ RK[9];
+	    RK[11] = RK[3] ^ RK[10];
+
+	    RK[12] = RK[4] ^
+			( FSb[ (uint8) ( RK[11] >> 24 ) ] << 24 ) ^
+			( FSb[ (uint8) ( RK[11] >> 16 ) ] << 16 ) ^
+			( FSb[ (uint8) ( RK[11] >>  8 ) ] <<  8 ) ^
+			( FSb[ (uint8) ( RK[11]       ) ]       );
+
+	    RK[13] = RK[5] ^ RK[12];
+	    RK[14] = RK[6] ^ RK[13];
+	    RK[15] = RK[7] ^ RK[14];
+	}
+	break;
     }
 
     /* setup decryption round keys */
 
     if( KT_init )
     {
-        for( i = 0; i < 256; i++ )
-        {
-            KT0[i] = RT0[ FSb[i] ];
-            KT1[i] = RT1[ FSb[i] ];
-            KT2[i] = RT2[ FSb[i] ];
-            KT3[i] = RT3[ FSb[i] ];
-        }
-
-        KT_init = 0;
+	for( i = 0; i < 256; i++ )
+	{
+	    KT0[i] = RT0[ FSb[i] ];
+	    KT1[i] = RT1[ FSb[i] ];
+	    KT2[i] = RT2[ FSb[i] ];
+	    KT3[i] = RT3[ FSb[i] ];
+	}
+
+	KT_init = 0;
     }
 
     SK = ctx->drk;
@@ -552,27 +552,27 @@ int aes_set_key( aes_context *ctx, uint8 *key, int nbits )
 
     for( i = 1; i < ctx->nr; i++ )
     {
-        RK -= 8;
-
-        *SK++ = KT0[ (uint8) ( *RK >> 24 ) ] ^
-                KT1[ (uint8) ( *RK >> 16 ) ] ^
-                KT2[ (uint8) ( *RK >>  8 ) ] ^
-                KT3[ (uint8) ( *RK       ) ]; RK++;
-
-        *SK++ = KT0[ (uint8) ( *RK >> 24 ) ] ^
-                KT1[ (uint8) ( *RK >> 16 ) ] ^
-                KT2[ (uint8) ( *RK >>  8 ) ] ^
-                KT3[ (uint8) ( *RK       ) ]; RK++;
-
-        *SK++ = KT0[ (uint8) ( *RK >> 24 ) ] ^
-                KT1[ (uint8) ( *RK >> 16 ) ] ^
-                KT2[ (uint8) ( *RK >>  8 ) ] ^
-                KT3[ (uint8) ( *RK       ) ]; RK++;
-
-        *SK++ = KT0[ (uint8) ( *RK >> 24 ) ] ^
-                KT1[ (uint8) ( *RK >> 16 ) ] ^
-                KT2[ (uint8) ( *RK >>  8 ) ] ^
-                KT3[ (uint8) ( *RK       ) ]; RK++;
+	RK -= 8;
+
+	*SK++ = KT0[ (uint8) ( *RK >> 24 ) ] ^
+		KT1[ (uint8) ( *RK >> 16 ) ] ^
+		KT2[ (uint8) ( *RK >>  8 ) ] ^
+		KT3[ (uint8) ( *RK       ) ]; RK++;
+
+	*SK++ = KT0[ (uint8) ( *RK >> 24 ) ] ^
+		KT1[ (uint8) ( *RK >> 16 ) ] ^
+		KT2[ (uint8) ( *RK >>  8 ) ] ^
+		KT3[ (uint8) ( *RK       ) ]; RK++;
+
+	*SK++ = KT0[ (uint8) ( *RK >> 24 ) ] ^
+		KT1[ (uint8) ( *RK >> 16 ) ] ^
+		KT2[ (uint8) ( *RK >>  8 ) ] ^
+		KT3[ (uint8) ( *RK       ) ]; RK++;
+
+	*SK++ = KT0[ (uint8) ( *RK >> 24 ) ] ^
+		KT1[ (uint8) ( *RK >> 16 ) ] ^
+		KT2[ (uint8) ( *RK >>  8 ) ] ^
+		KT3[ (uint8) ( *RK       ) ]; RK++;
     }
 
     RK -= 8;
@@ -601,26 +601,26 @@ void aes_encrypt( aes_context *ctx, uint8 input[16], uint8 output[16] )
 #define AES_FROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3)     \
 {                                               \
     RK += 4;                                    \
-                                                \
+						\
     X0 = RK[0] ^ FT0[ (uint8) ( Y0 >> 24 ) ] ^  \
-                 FT1[ (uint8) ( Y1 >> 16 ) ] ^  \
-                 FT2[ (uint8) ( Y2 >>  8 ) ] ^  \
-                 FT3[ (uint8) ( Y3       ) ];   \
-                                                \
+		 FT1[ (uint8) ( Y1 >> 16 ) ] ^  \
+		 FT2[ (uint8) ( Y2 >>  8 ) ] ^  \
+		 FT3[ (uint8) ( Y3       ) ];   \
+						\
     X1 = RK[1] ^ FT0[ (uint8) ( Y1 >> 24 ) ] ^  \
-                 FT1[ (uint8) ( Y2 >> 16 ) ] ^  \
-                 FT2[ (uint8) ( Y3 >>  8 ) ] ^  \
-                 FT3[ (uint8) ( Y0       ) ];   \
-                                                \
+		 FT1[ (uint8) ( Y2 >> 16 ) ] ^  \
+		 FT2[ (uint8) ( Y3 >>  8 ) ] ^  \
+		 FT3[ (uint8) ( Y0       ) ];   \
+						\
     X2 = RK[2] ^ FT0[ (uint8) ( Y2 >> 24 ) ] ^  \
-                 FT1[ (uint8) ( Y3 >> 16 ) ] ^  \
-                 FT2[ (uint8) ( Y0 >>  8 ) ] ^  \
-                 FT3[ (uint8) ( Y1       ) ];   \
-                                                \
+		 FT1[ (uint8) ( Y3 >> 16 ) ] ^  \
+		 FT2[ (uint8) ( Y0 >>  8 ) ] ^  \
+		 FT3[ (uint8) ( Y1       ) ];   \
+						\
     X3 = RK[3] ^ FT0[ (uint8) ( Y3 >> 24 ) ] ^  \
-                 FT1[ (uint8) ( Y0 >> 16 ) ] ^  \
-                 FT2[ (uint8) ( Y1 >>  8 ) ] ^  \
-                 FT3[ (uint8) ( Y2       ) ];   \
+		 FT1[ (uint8) ( Y0 >> 16 ) ] ^  \
+		 FT2[ (uint8) ( Y1 >>  8 ) ] ^  \
+		 FT3[ (uint8) ( Y2       ) ];   \
 }
 
     AES_FROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );       /* round 1 */
@@ -635,14 +635,14 @@ void aes_encrypt( aes_context *ctx, uint8 input[16], uint8 output[16] )
 
     if( ctx->nr > 10 )
     {
-        AES_FROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );   /* round 10 */
-        AES_FROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );   /* round 11 */
+	AES_FROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );   /* round 10 */
+	AES_FROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );   /* round 11 */
     }
 
     if( ctx->nr > 12 )
     {
-        AES_FROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );   /* round 12 */
-        AES_FROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );   /* round 13 */
+	AES_FROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );   /* round 12 */
+	AES_FROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );   /* round 13 */
     }
 
     /* last round */
@@ -650,24 +650,24 @@ void aes_encrypt( aes_context *ctx, uint8 input[16], uint8 output[16] )
     RK += 4;
 
     X0 = RK[0] ^ ( FSb[ (uint8) ( Y0 >> 24 ) ] << 24 ) ^
-                 ( FSb[ (uint8) ( Y1 >> 16 ) ] << 16 ) ^
-                 ( FSb[ (uint8) ( Y2 >>  8 ) ] <<  8 ) ^
-                 ( FSb[ (uint8) ( Y3       ) ]       );
+		 ( FSb[ (uint8) ( Y1 >> 16 ) ] << 16 ) ^
+		 ( FSb[ (uint8) ( Y2 >>  8 ) ] <<  8 ) ^
+		 ( FSb[ (uint8) ( Y3       ) ]       );
 
     X1 = RK[1] ^ ( FSb[ (uint8) ( Y1 >> 24 ) ] << 24 ) ^
-                 ( FSb[ (uint8) ( Y2 >> 16 ) ] << 16 ) ^
-                 ( FSb[ (uint8) ( Y3 >>  8 ) ] <<  8 ) ^
-                 ( FSb[ (uint8) ( Y0       ) ]       );
+		 ( FSb[ (uint8) ( Y2 >> 16 ) ] << 16 ) ^
+		 ( FSb[ (uint8) ( Y3 >>  8 ) ] <<  8 ) ^
+		 ( FSb[ (uint8) ( Y0       ) ]       );
 
     X2 = RK[2] ^ ( FSb[ (uint8) ( Y2 >> 24 ) ] << 24 ) ^
-                 ( FSb[ (uint8) ( Y3 >> 16 ) ] << 16 ) ^
-                 ( FSb[ (uint8) ( Y0 >>  8 ) ] <<  8 ) ^
-                 ( FSb[ (uint8) ( Y1       ) ]       );
+		 ( FSb[ (uint8) ( Y3 >> 16 ) ] << 16 ) ^
+		 ( FSb[ (uint8) ( Y0 >>  8 ) ] <<  8 ) ^
+		 ( FSb[ (uint8) ( Y1       ) ]       );
 
     X3 = RK[3] ^ ( FSb[ (uint8) ( Y3 >> 24 ) ] << 24 ) ^
-                 ( FSb[ (uint8) ( Y0 >> 16 ) ] << 16 ) ^
-                 ( FSb[ (uint8) ( Y1 >>  8 ) ] <<  8 ) ^
-                 ( FSb[ (uint8) ( Y2       ) ]       );
+		 ( FSb[ (uint8) ( Y0 >> 16 ) ] << 16 ) ^
+		 ( FSb[ (uint8) ( Y1 >>  8 ) ] <<  8 ) ^
+		 ( FSb[ (uint8) ( Y2       ) ]       );
 
     PUT_UINT32( X0, output,  0 );
     PUT_UINT32( X1, output,  4 );
@@ -691,26 +691,26 @@ void aes_decrypt( aes_context *ctx, uint8 input[16], uint8 output[16] )
 #define AES_RROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3)     \
 {                                               \
     RK += 4;                                    \
-                                                \
+						\
     X0 = RK[0] ^ RT0[ (uint8) ( Y0 >> 24 ) ] ^  \
-                 RT1[ (uint8) ( Y3 >> 16 ) ] ^  \
-                 RT2[ (uint8) ( Y2 >>  8 ) ] ^  \
-                 RT3[ (uint8) ( Y1       ) ];   \
-                                                \
+		 RT1[ (uint8) ( Y3 >> 16 ) ] ^  \
+		 RT2[ (uint8) ( Y2 >>  8 ) ] ^  \
+		 RT3[ (uint8) ( Y1       ) ];   \
+						\
     X1 = RK[1] ^ RT0[ (uint8) ( Y1 >> 24 ) ] ^  \
-                 RT1[ (uint8) ( Y0 >> 16 ) ] ^  \
-                 RT2[ (uint8) ( Y3 >>  8 ) ] ^  \
-                 RT3[ (uint8) ( Y2       ) ];   \
-                                                \
+		 RT1[ (uint8) ( Y0 >> 16 ) ] ^  \
+		 RT2[ (uint8) ( Y3 >>  8 ) ] ^  \
+		 RT3[ (uint8) ( Y2       ) ];   \
+						\
     X2 = RK[2] ^ RT0[ (uint8) ( Y2 >> 24 ) ] ^  \
-                 RT1[ (uint8) ( Y1 >> 16 ) ] ^  \
-                 RT2[ (uint8) ( Y0 >>  8 ) ] ^  \
-                 RT3[ (uint8) ( Y3       ) ];   \
-                                                \
+		 RT1[ (uint8) ( Y1 >> 16 ) ] ^  \
+		 RT2[ (uint8) ( Y0 >>  8 ) ] ^  \
+		 RT3[ (uint8) ( Y3       ) ];   \
+						\
     X3 = RK[3] ^ RT0[ (uint8) ( Y3 >> 24 ) ] ^  \
-                 RT1[ (uint8) ( Y2 >> 16 ) ] ^  \
-                 RT2[ (uint8) ( Y1 >>  8 ) ] ^  \
-                 RT3[ (uint8) ( Y0       ) ];   \
+		 RT1[ (uint8) ( Y2 >> 16 ) ] ^  \
+		 RT2[ (uint8) ( Y1 >>  8 ) ] ^  \
+		 RT3[ (uint8) ( Y0       ) ];   \
 }
 
     AES_RROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );       /* round 1 */
@@ -725,14 +725,14 @@ void aes_decrypt( aes_context *ctx, uint8 input[16], uint8 output[16] )
 
     if( ctx->nr > 10 )
     {
-        AES_RROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );   /* round 10 */
-        AES_RROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );   /* round 11 */
+	AES_RROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );   /* round 10 */
+	AES_RROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );   /* round 11 */
     }
 
     if( ctx->nr > 12 )
     {
-        AES_RROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );   /* round 12 */
-        AES_RROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );   /* round 13 */
+	AES_RROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );   /* round 12 */
+	AES_RROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );   /* round 13 */
     }
 
     /* last round */
@@ -740,24 +740,24 @@ void aes_decrypt( aes_context *ctx, uint8 input[16], uint8 output[16] )
     RK += 4;
 
     X0 = RK[0] ^ ( RSb[ (uint8) ( Y0 >> 24 ) ] << 24 ) ^
-                 ( RSb[ (uint8) ( Y3 >> 16 ) ] << 16 ) ^
-                 ( RSb[ (uint8) ( Y2 >>  8 ) ] <<  8 ) ^
-                 ( RSb[ (uint8) ( Y1       ) ]       );
+		 ( RSb[ (uint8) ( Y3 >> 16 ) ] << 16 ) ^
+		 ( RSb[ (uint8) ( Y2 >>  8 ) ] <<  8 ) ^
+		 ( RSb[ (uint8) ( Y1       ) ]       );
 
     X1 = RK[1] ^ ( RSb[ (uint8) ( Y1 >> 24 ) ] << 24 ) ^
-                 ( RSb[ (uint8) ( Y0 >> 16 ) ] << 16 ) ^
-                 ( RSb[ (uint8) ( Y3 >>  8 ) ] <<  8 ) ^
-                 ( RSb[ (uint8) ( Y2       ) ]       );
+		 ( RSb[ (uint8) ( Y0 >> 16 ) ] << 16 ) ^
+		 ( RSb[ (uint8) ( Y3 >>  8 ) ] <<  8 ) ^
+		 ( RSb[ (uint8) ( Y2       ) ]       );
 
     X2 = RK[2] ^ ( RSb[ (uint8) ( Y2 >> 24 ) ] << 24 ) ^
-                 ( RSb[ (uint8) ( Y1 >> 16 ) ] << 16 ) ^
-                 ( RSb[ (uint8) ( Y0 >>  8 ) ] <<  8 ) ^
-                 ( RSb[ (uint8) ( Y3       ) ]       );
+		 ( RSb[ (uint8) ( Y1 >> 16 ) ] << 16 ) ^
+		 ( RSb[ (uint8) ( Y0 >>  8 ) ] <<  8 ) ^
+		 ( RSb[ (uint8) ( Y3       ) ]       );
 
     X3 = RK[3] ^ ( RSb[ (uint8) ( Y3 >> 24 ) ] << 24 ) ^
-                 ( RSb[ (uint8) ( Y2 >> 16 ) ] << 16 ) ^
-                 ( RSb[ (uint8) ( Y1 >>  8 ) ] <<  8 ) ^
-                 ( RSb[ (uint8) ( Y0       ) ]       );
+		 ( RSb[ (uint8) ( Y2 >> 16 ) ] << 16 ) ^
+		 ( RSb[ (uint8) ( Y1 >>  8 ) ] <<  8 ) ^
+		 ( RSb[ (uint8) ( Y0       ) ]       );
 
     PUT_UINT32( X0, output,  0 );
     PUT_UINT32( X1, output,  4 );
@@ -784,7 +784,7 @@ static unsigned char AES_enc_test[3][16] =
     { 0x1F, 0x67, 0x63, 0xDF, 0x80, 0x7A, 0x7E, 0x70,
       0x96, 0x0D, 0x4C, 0xD3, 0x11, 0x8E, 0x60, 0x1A }
 };
-    
+
 static unsigned char AES_dec_test[3][16] =
 {
     { 0xF5, 0xBF, 0x8B, 0x37, 0x13, 0x6F, 0x2E, 0x1F,
@@ -794,7 +794,7 @@ static unsigned char AES_dec_test[3][16] =
     { 0x4D, 0xE0, 0xC6, 0xDF, 0x7C, 0xB1, 0x69, 0x72,
       0x84, 0x60, 0x4D, 0x60, 0x27, 0x1B, 0xC5, 0x9A }
 };
-    
+
 int main( void )
 {
     int m, n, i, j;
@@ -804,57 +804,57 @@ int main( void )
 
     for( m = 0; m < 2; m++ )
     {
-        printf( "\n Rijndael Monte Carlo Test (ECB mode) - " );
-
-        if( m == 0 ) printf( "encryption\n\n" );
-        if( m == 1 ) printf( "decryption\n\n" );
-
-        for( n = 0; n < 3; n++ )
-        {
-            printf( " Test %d, key size = %3d bits: ",
-                    n + 1, 128 + n * 64 );
-
-            fflush( stdout );
-
-            memset( buf, 0, 16 );
-            memset( key, 0, 16 + n * 8 );
-
-            for( i = 0; i < 400; i++ )
-            {
-                aes_set_key( &ctx, key, 128 + n * 64 );
-
-                for( j = 0; j < 9999; j++ )
-                {
-                    if( m == 0 ) aes_encrypt( &ctx, buf, buf );
-                    if( m == 1 ) aes_decrypt( &ctx, buf, buf );
-                }
-
-                if( n > 0 )
-                {
-                    for( j = 0; j < (n << 3); j++ )
-                    {
-                        key[j] ^= buf[j + 16 - (n << 3)];
-                    }
-                }
-
-                if( m == 0 ) aes_encrypt( &ctx, buf, buf );
-                if( m == 1 ) aes_decrypt( &ctx, buf, buf );
-
-                for( j = 0; j < 16; j++ )
-                {
-                    key[j + (n << 3)] ^= buf[j];
-                }
-            }
-
-            if( ( m == 0 && memcmp( buf, AES_enc_test[n], 16 ) != 0 ) ||
-                ( m == 1 && memcmp( buf, AES_dec_test[n], 16 ) != 0 ) )
-            {
-                printf( "failed!\n" );
-                return( 1 );
-            }
-
-            printf( "passed.\n" );
-        }
+	printf( "\n Rijndael Monte Carlo Test (ECB mode) - " );
+
+	if( m == 0 ) printf( "encryption\n\n" );
+	if( m == 1 ) printf( "decryption\n\n" );
+
+	for( n = 0; n < 3; n++ )
+	{
+	    printf( " Test %d, key size = %3d bits: ",
+		    n + 1, 128 + n * 64 );
+
+	    fflush( stdout );
+
+	    memset( buf, 0, 16 );
+	    memset( key, 0, 16 + n * 8 );
+
+	    for( i = 0; i < 400; i++ )
+	    {
+		aes_set_key( &ctx, key, 128 + n * 64 );
+
+		for( j = 0; j < 9999; j++ )
+		{
+		    if( m == 0 ) aes_encrypt( &ctx, buf, buf );
+		    if( m == 1 ) aes_decrypt( &ctx, buf, buf );
+		}
+
+		if( n > 0 )
+		{
+		    for( j = 0; j < (n << 3); j++ )
+		    {
+			key[j] ^= buf[j + 16 - (n << 3)];
+		    }
+		}
+
+		if( m == 0 ) aes_encrypt( &ctx, buf, buf );
+		if( m == 1 ) aes_decrypt( &ctx, buf, buf );
+
+		for( j = 0; j < 16; j++ )
+		{
+		    key[j + (n << 3)] ^= buf[j];
+		}
+	    }
+
+	    if( ( m == 0 && memcmp( buf, AES_enc_test[n], 16 ) != 0 ) ||
+		( m == 1 && memcmp( buf, AES_dec_test[n], 16 ) != 0 ) )
+	    {
+		printf( "failed!\n" );
+		return( 1 );
+	    }
+
+	    printf( "passed.\n" );
+	}
     }
 
     printf( "\n" );
diff --git a/toolkit/ctrmode.c b/toolkit/ctrmode.c
index f89cfbd..443fd27 100644
--- a/toolkit/ctrmode.c
+++ b/toolkit/ctrmode.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
diff --git a/toolkit/ctrmode.h b/toolkit/ctrmode.h
index a1e049e..fae8558 100644
--- a/toolkit/ctrmode.h
+++ b/toolkit/ctrmode.h
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
diff --git a/toolkit/otr_mackey.c b/toolkit/otr_mackey.c
index 214d59b..5417c0d 100644
--- a/toolkit/otr_mackey.c
+++ b/toolkit/otr_mackey.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -48,7 +48,7 @@ int main(int argc, char **argv)
     if (!argbuf) {
 	usage(argv[0]);
     }
-    
+
     if (argbuflen != 16) {
 	fprintf(stderr, "The AES key must be 32 hex chars long.\n");
 	usage(argv[0]);
diff --git a/toolkit/otr_modify.c b/toolkit/otr_modify.c
index c6d045f..acb1071 100644
--- a/toolkit/otr_modify.c
+++ b/toolkit/otr_modify.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -63,7 +63,7 @@ int main(int argc, char **argv)
     if (!mackey) {
 	usage(argv[0]);
     }
-    
+
     if (mackeylen != 20) {
 	fprintf(stderr, "The MAC key must be 40 hex chars long.\n");
 	usage(argv[0]);
@@ -88,7 +88,7 @@ int main(int argc, char **argv)
 	fprintf(stderr, "No OTR Data Message found on stdin.\n");
 	exit(1);
     }
-    
+
     if (otrl_proto_message_type(otrmsg) != OTRL_MSGTYPE_DATA) {
 	fprintf(stderr, "OTR Non-Data Message found on stdin.\n");
 	exit(1);
diff --git a/toolkit/otr_parse.c b/toolkit/otr_parse.c
index f3bcee4..0112437 100644
--- a/toolkit/otr_parse.c
+++ b/toolkit/otr_parse.c
@@ -1,6 +1,7 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *                           Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -48,7 +49,15 @@ static void parse(const char *msg)
 		printf("Invalid D-H Commit Message\n\n");
 		break;
 	    }
+
 	    printf("D-H Commit Message:\n");
+
+	    dump_data(stdout, "\tVersion", &(cmsg->version), 1);
+	    if (cmsg->version == 3) {
+		dump_int(stdout, "\tSender instance", cmsg->sender_instance);
+		dump_int(stdout, "\tReceiver instance",
+			cmsg->receiver_instance);
+	    }
 	    dump_data(stdout, "\tEncrypted Key", cmsg->enckey,
 		    cmsg->enckeylen);
 	    dump_data(stdout, "\tHashed Key", cmsg->hashkey,
@@ -63,6 +72,12 @@ static void parse(const char *msg)
 		break;
 	    }
 	    printf("D-H Key Message:\n");
+	    dump_data(stdout, "\tVersion", &(kmsg->version), 1);
+	    if (kmsg->version == 3) {
+		dump_int(stdout, "\tSender instance", kmsg->sender_instance);
+		dump_int(stdout, "\tReceiver instance",
+			kmsg->receiver_instance);
+	    }
 	    dump_mpi(stdout, "\tD-H Key", kmsg->y);
 	    printf("\n");
 	    free_key(kmsg);
@@ -74,6 +89,12 @@ static void parse(const char *msg)
 		break;
 	    }
 	    printf("Reveal Signature Message:\n");
+	    dump_data(stdout, "\tVersion", &(rmsg->version), 1);
+	    if (rmsg->version == 3) {
+		dump_int(stdout, "\tSender instance", rmsg->sender_instance);
+		dump_int(stdout, "\tReceiver instance",
+			rmsg->receiver_instance);
+	    }
 	    dump_data(stdout, "\tKey", rmsg->key, rmsg->keylen);
 	    dump_data(stdout, "\tEncrypted Signature",
 		    rmsg->encsig, rmsg->encsiglen);
@@ -88,6 +109,12 @@ static void parse(const char *msg)
 		break;
 	    }
 	    printf("Signature Message:\n");
+	    dump_data(stdout, "\tVersion", &(smsg->version), 1);
+	    if (smsg->version == 3) {
+		dump_int(stdout, "\tSender instance", smsg->sender_instance);
+		dump_int(stdout, "\tReceiver instance",
+			smsg->receiver_instance);
+	    }
 	    dump_data(stdout, "\tEncrypted Signature",
 		    smsg->encsig, smsg->encsiglen);
 	    dump_data(stdout, "\tMAC", smsg->mac, 20);
@@ -120,9 +147,18 @@ static void parse(const char *msg)
 		break;
 	    }
 	    printf("Data Message:\n");
+
+	    dump_data(stdout, "\tVersion", &(datamsg->version), 1);
 	    if (datamsg->flags >= 0) {
 		dump_int(stdout, "\tFlags", datamsg->flags);
 	    }
+
+	    if (datamsg->version == 3) {
+		dump_int(stdout, "\tSender instance", datamsg->sender_instance);
+		dump_int(stdout, "\tReceiver instance",
+			datamsg->receiver_instance);
+	    }
+
 	    dump_int(stdout, "\tSender keyid", datamsg->sender_keyid);
 	    dump_int(stdout, "\tRcpt keyid", datamsg->rcpt_keyid);
 	    dump_mpi(stdout, "\tDH y", datamsg->y);
diff --git a/toolkit/otr_readforge.c b/toolkit/otr_readforge.c
index 203001c..6c54a1d 100644
--- a/toolkit/otr_readforge.c
+++ b/toolkit/otr_readforge.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -59,7 +59,7 @@ int main(int argc, char **argv)
     if (!aeskey) {
 	usage(argv[0]);
     }
-    
+
     if (aeskeylen != 16) {
 	fprintf(stderr, "The AES key must be 32 hex chars long.\n");
 	usage(argv[0]);
@@ -70,7 +70,7 @@ int main(int argc, char **argv)
 	fprintf(stderr, "No OTR Data Message found on stdin.\n");
 	exit(1);
     }
-    
+
     if (otrl_proto_message_type(otrmsg) != OTRL_MSGTYPE_DATA) {
 	fprintf(stderr, "OTR Non-Data Message found on stdin.\n");
 	exit(1);
@@ -119,7 +119,7 @@ int main(int argc, char **argv)
 	free(datamsg->encmsg);
 	datamsg->encmsg = ciphertext;
 	datamsg->encmsglen = newlen;
-	
+
 	newdatamsg = remac_datamsg(datamsg, mackey);
 
 	printf("%s\n", newdatamsg);
diff --git a/toolkit/otr_remac.c b/toolkit/otr_remac.c
index 4ae04fc..fbad4d4 100644
--- a/toolkit/otr_remac.c
+++ b/toolkit/otr_remac.c
@@ -1,6 +1,7 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *                           Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -30,12 +31,13 @@
 
 static void usage(const char *progname)
 {
-    fprintf(stderr, "Usage: %s mackey flags snd_keyid rcp_keyid pubkey "
-	    "counter encdata revealed_mackeys\n"
+    fprintf(stderr, "Usage: %s mackey sender_instance receiver_instance "
+	"flags snd_keyid rcp_keyid pubkey counter encdata revealed_mackeys\n"
 "Make a new Data message, with the given pieces (note that the\n"
 "data part is already encrypted).  MAC it with the given mackey.\n"
 "mackey, pubkey, counter, encdata, and revealed_mackeys are given\n"
-"as strings of hex chars.  snd_keyid and rcp_keyid are decimal integers.\n", progname);
+"as strings of hex chars.  snd_keyid and rcp_keyid are decimal integers.\n",
+	progname);
     exit(1);
 }
 
@@ -45,6 +47,9 @@ int main(int argc, char **argv)
     size_t mackeylen;
     unsigned int snd_keyid, rcp_keyid;
     int flags;
+    unsigned char version = 3;
+    unsigned int sender_instance;
+    unsigned int receiver_instance;
     unsigned char *pubkey;
     size_t pubkeylen;
     gcry_mpi_t pubv;
@@ -56,7 +61,7 @@ int main(int argc, char **argv)
     size_t mackeyslen;
     char *newdatamsg;
 
-    if (argc != 9) {
+    if (argc != 11) {
 	usage(argv[0]);
     }
 
@@ -64,35 +69,45 @@ int main(int argc, char **argv)
     if (!mackey) {
 	usage(argv[0]);
     }
-    
+
     if (mackeylen != 20) {
 	fprintf(stderr, "The MAC key must be 40 hex chars long.\n");
 	usage(argv[0]);
     }
 
-    if (sscanf(argv[2], "%d", &flags) != 1) {
+    if (sscanf(argv[2], "%u", &sender_instance) != 1) {
+	fprintf(stderr, "Unparseable sender_instance given.\n");
+	usage(argv[0]);
+    }
+
+    if (sscanf(argv[3], "%u", &receiver_instance) != 1) {
+	fprintf(stderr, "Unparseable receiver_instance given.\n");
+	usage(argv[0]);
+    }
+
+    if (sscanf(argv[4], "%d", &flags) != 1) {
 	fprintf(stderr, "Unparseable flags given.\n");
 	usage(argv[0]);
     }
 
-    if (sscanf(argv[3], "%u", &snd_keyid) != 1) {
+    if (sscanf(argv[5], "%u", &snd_keyid) != 1) {
 	fprintf(stderr, "Unparseable snd_keyid given.\n");
 	usage(argv[0]);
     }
 
-    if (sscanf(argv[4], "%u", &rcp_keyid) != 1) {
+    if (sscanf(argv[6], "%u", &rcp_keyid) != 1) {
 	fprintf(stderr, "Unparseable rcp_keyid given.\n");
 	usage(argv[0]);
     }
 
-    argv_to_buf(&pubkey, &pubkeylen, argv[5]);
+    argv_to_buf(&pubkey, &pubkeylen, argv[7]);
     if (!pubkey) {
 	usage(argv[0]);
     }
     gcry_mpi_scan(&pubv, GCRYMPI_FMT_USG, pubkey, pubkeylen, NULL);
     free(pubkey);
-    
-    argv_to_buf(&ctr, &ctrlen, argv[6]);
+
+    argv_to_buf(&ctr, &ctrlen, argv[8]);
     if (!ctr) {
 	usage(argv[0]);
     }
@@ -102,18 +117,19 @@ int main(int argc, char **argv)
 	usage(argv[0]);
     }
 
-    argv_to_buf(&encdata, &encdatalen, argv[7]);
+    argv_to_buf(&encdata, &encdatalen, argv[9]);
     if (!encdata) {
 	usage(argv[0]);
     }
 
-    argv_to_buf(&mackeys, &mackeyslen, argv[8]);
+    argv_to_buf(&mackeys, &mackeyslen, argv[10]);
     if (!mackeys) {
 	usage(argv[0]);
     }
 
-    newdatamsg = assemble_datamsg(mackey, flags, snd_keyid, rcp_keyid,
-	    pubv, ctr, encdata, encdatalen, mackeys, mackeyslen);
+    newdatamsg = assemble_datamsg(mackey, version, sender_instance,
+	    receiver_instance, flags, snd_keyid, rcp_keyid, pubv, ctr, encdata,
+	    encdatalen, mackeys, mackeyslen);
     printf("%s\n", newdatamsg);
     free(newdatamsg);
 
diff --git a/toolkit/otr_sesskeys.c b/toolkit/otr_sesskeys.c
index 5c5583f..1cb262d 100644
--- a/toolkit/otr_sesskeys.c
+++ b/toolkit/otr_sesskeys.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
diff --git a/toolkit/otr_toolkit.1 b/toolkit/otr_toolkit.1
index 45a8e72..9b074f9 100644
--- a/toolkit/otr_toolkit.1
+++ b/toolkit/otr_toolkit.1
@@ -2,7 +2,7 @@
 .\" First parameter, NAME, should be all caps
 .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
 .\" other parameters are allowed: see man(7), man(1)
-.TH OTR_PARSE 1 "October 27, 2005"
+.TH OTR_PARSE 1 "March 14, 2012"
 .\" Please adjust this date whenever revising the manpage.
 .\"
 .\" Some roff macros, for reference:
@@ -33,7 +33,7 @@ otr_parse, otr_sesskeys, otr_mackey, otr_readforge, otr_modify, otr_remac \- Pro
 .I mackey old_text new_text offset
 .br
 .B otr_remac
-.I mackey flags snd_keyid rcv_keyid pubkey counter encdata revealed_mackeys
+.I mackey sender_instance receiver_instance flags snd_keyid rcv_keyid pubkey counter encdata revealed_mackeys
 .SH DESCRIPTION
 Off-the-Record (OTR) Messaging allows you to have private conversations
 over IM by providing:
@@ -95,9 +95,10 @@ Here are the six programs in the toolkit:
      you can still forge messages of your choice using the
      otr_readforge command, above.
 
- - otr_remac mackey flags snd_keyid rcv_keyid pubkey counter encdata revealed_mackeys
-   - Make a new OTR Data Message, with the given pieces (note that the
-     data part is already encrypted).  MAC it with the given mackey.
+ - otr_remac mackey sender_instance receiver_instance flags snd_keyid rcv_keyid pubkey counter encdata revealed_mackeys
+   - Make a new OTR protocol version 3 Data Message, with the given
+     pieces (note that the data part is already encrypted).  MAC it 
+     with the given mackey.
 
 .SH SEE ALSO
 .BR "Off-the-Record Messaging" ,
diff --git a/toolkit/parse.c b/toolkit/parse.c
index 5f357fc..ecefafb 100644
--- a/toolkit/parse.c
+++ b/toolkit/parse.c
@@ -1,6 +1,7 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *                           Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -39,7 +40,7 @@ void dump_mpi(FILE *stream, const char *title, gcry_mpi_t val)
 {
     size_t plen;
     unsigned char *d;
-    
+
     gcry_mpi_print(GCRYMPI_FMT_USG, NULL, 0, &plen, val);
     d = malloc(plen);
     gcry_mpi_print(GCRYMPI_FMT_USG, d, plen, NULL, val);
@@ -64,7 +65,7 @@ static unsigned char *decode(const char *msg, size_t *lenp)
 {
     const char *header, *footer;
     unsigned char *raw;
-	
+
     /* Find the header */
     header = strstr(msg, "?OTR:");
     if (!header) return NULL;
@@ -200,8 +201,18 @@ CommitMsg parse_commit(const char *msg)
     cmsg->raw = raw;
 
     require_len(3);
-    if (memcmp(bufp, "\x00\x02\x02", 3)) goto inv;
-    bufp += 3; lenp -= 3;
+
+    cmsg->version = bufp[1];
+
+    if (!memcmp(bufp, "\x00\x03\x02", 3)) {
+	bufp += 3; lenp -= 3;
+	read_int(cmsg->sender_instance);
+	read_int(cmsg->receiver_instance);
+    } else if (!memcmp(bufp, "\x00\x02\x02", 3)) {
+	bufp += 3; lenp -= 3;
+	cmsg->sender_instance = 0;
+	cmsg->receiver_instance = 0;
+    } else goto inv;
 
     read_int(cmsg->enckeylen);
     cmsg->enckey = malloc(cmsg->enckeylen);
@@ -249,8 +260,18 @@ KeyMsg parse_key(const char *msg)
     kmsg->raw = raw;
 
     require_len(3);
-    if (memcmp(bufp, "\x00\x02\x0a", 3)) goto inv;
-    bufp += 3; lenp -= 3;
+
+    kmsg->version = bufp[1];
+
+    if (!memcmp(bufp, "\x00\x03\x0a", 3)) {
+	bufp += 3; lenp -= 3;
+	read_int(kmsg->sender_instance);
+	read_int(kmsg->receiver_instance);
+    } else if (!memcmp(bufp, "\x00\x02\x0a", 3)) {
+	bufp += 3; lenp -= 3;
+	kmsg->sender_instance = 0;
+	kmsg->receiver_instance = 0;
+    } else goto inv;
 
     read_mpi(kmsg->y);
 
@@ -290,8 +311,18 @@ RevealSigMsg parse_revealsig(const char *msg)
     rmsg->raw = raw;
 
     require_len(3);
-    if (memcmp(bufp, "\x00\x02\x11", 3)) goto inv;
-    bufp += 3; lenp -= 3;
+
+    rmsg->version = bufp[1];
+
+    if (!memcmp(bufp, "\x00\x03\x11", 3)) {
+	bufp += 3; lenp -= 3;
+	read_int(rmsg->sender_instance);
+	read_int(rmsg->receiver_instance);
+    } else if (!memcmp(bufp, "\x00\x02\x11", 3)) {
+	bufp += 3; lenp -= 3;
+	rmsg->sender_instance = 0;
+	rmsg->receiver_instance = 0;
+    } else goto inv;
 
     read_int(rmsg->keylen);
     rmsg->key = malloc(rmsg->keylen);
@@ -341,8 +372,18 @@ SignatureMsg parse_signature(const char *msg)
     smsg->raw = raw;
 
     require_len(3);
-    if (memcmp(bufp, "\x00\x02\x12", 3)) goto inv;
-    bufp += 3; lenp -= 3;
+
+    smsg->version = bufp[1];
+
+    if (!memcmp(bufp, "\x00\x03\x12", 3)) {
+	bufp += 3; lenp -= 3;
+	read_int(smsg->sender_instance);
+	read_int(smsg->receiver_instance);
+    } else if (!memcmp(bufp, "\x00\x02\x12", 3)) {
+	bufp += 3; lenp -= 3;
+	smsg->sender_instance = 0;
+	smsg->receiver_instance = 0;
+    } else goto inv;
 
     read_int(smsg->encsiglen);
     smsg->encsig = malloc(smsg->encsiglen);
@@ -389,18 +430,28 @@ DataMsg parse_datamsg(const char *msg)
     datam->macstart = bufp;
 
     require_len(3);
-    if (memcmp(bufp, "\x00\x01\x03", 3) && memcmp(bufp, "\x00\x02\x03", 3))
-	goto inv;
+    if (memcmp(bufp, "\x00\x01\x03", 3) && memcmp(bufp, "\x00\x03\x03", 3) &&
+	memcmp(bufp, "\x00\x02\x03", 3)) goto inv;
+
     version = bufp[1];
+
+    datam->sender_instance = 0;
+    datam->receiver_instance = 0;
+    datam->version = version;
+    datam->flags = -1;
     bufp += 3; lenp -= 3;
 
-    if (version == 2) {
+    if (version == 3) {
+	read_int(datam->sender_instance);
+	read_int(datam->receiver_instance);
+    }
+
+    if (version == 2 || version == 3) {
 	require_len(1);
 	datam->flags = bufp[0];
 	bufp += 1; lenp -= 1;
-    } else {
-	datam->flags = -1;
     }
+
     read_int(datam->sender_keyid);
     read_int(datam->rcpt_keyid);
     read_mpi(datam->y);
@@ -413,9 +464,10 @@ DataMsg parse_datamsg(const char *msg)
     read_raw(datam->mac, 20);
     read_int(datam->mackeyslen);
     datam->mackeys = malloc(datam->mackeyslen);
+
     if (!datam->mackeys && datam->mackeyslen > 0) goto inv;
-    read_raw(datam->mackeys, datam->mackeyslen);
 
+    read_raw(datam->mackeys, datam->mackeyslen);
     if (lenp != 0) goto inv;
 
     return datam;
@@ -435,11 +487,12 @@ char *remac_datamsg(DataMsg datamsg, unsigned char mackey[20])
     size_t base64len;
     char *outmsg;
     unsigned char *raw, *bufp;
-    unsigned char version = (datamsg->flags >= 0 ? 2 : 1);
-    
+    unsigned char version = datamsg->version;
+
     /* Calculate the size of the message that will result */
     gcry_mpi_print(GCRYMPI_FMT_USG, NULL, 0, &ylen, datamsg->y);
-    rawlen = 3 + (version == 2 ? 1 : 0) + 4 + 4 + 4 + ylen + 8 + 4 +
+    rawlen = 3 + (version == 3 ? 8 : 0) + (version == 2 ||
+	version == 3 ? 1 : 0) + 4 + 4 + 4 + ylen + 8 + 4 +
 	datamsg->encmsglen + 20 + 4 + datamsg->mackeyslen;
 
     /* Construct the new raw message (note that some of the pieces may
@@ -457,16 +510,22 @@ char *remac_datamsg(DataMsg datamsg, unsigned char mackey[20])
     datamsg->raw = raw;
     datamsg->rawlen = rawlen;
 
-    if (version == 1) {
-	memmove(bufp, "\x00\x01\x03", 3);
-    } else {
-	memmove(bufp, "\x00\x02\x03", 3);
-    }
+
+    memmove(bufp, "\x00", 1);
+    memmove(bufp+1, &version, 1);
+    memmove(bufp+2, "\x03", 1);
     bufp += 3; lenp -= 3;
-    if (version == 2) {
+
+    if (version == 3) {
+	write_int(datamsg->sender_instance);
+	write_int(datamsg->receiver_instance);
+    }
+
+    if (version == 2 || version == 3) {
 	bufp[0] = datamsg->flags;
 	bufp += 1; lenp -= 1;
     }
+
     write_int(datamsg->sender_keyid);
     write_int(datamsg->rcpt_keyid);
     write_mpi(datamsg->y, ylen);
@@ -482,7 +541,7 @@ char *remac_datamsg(DataMsg datamsg, unsigned char mackey[20])
     write_raw(datamsg->mac, 20);
     write_int(datamsg->mackeyslen);
     write_raw(datamsg->mackeys, datamsg->mackeyslen);
-    
+
     if (lenp != 0) {
 	fprintf(stderr, "Error creating OTR Data Message.\n");
 	exit(1);
@@ -500,15 +559,20 @@ char *remac_datamsg(DataMsg datamsg, unsigned char mackey[20])
 
 /* Assemble a new Data Message from its pieces.  Return a
  * newly-allocated string containing the base64 representation. */
-char *assemble_datamsg(unsigned char mackey[20], int flags,
-	unsigned int sender_keyid, unsigned int rcpt_keyid, gcry_mpi_t y,
+char *assemble_datamsg(unsigned char mackey[20],
+	unsigned char version, unsigned int sender_instance,
+	unsigned int receiver_instance, int flags, unsigned int sender_keyid,
+	unsigned int rcpt_keyid, gcry_mpi_t y,
 	unsigned char ctr[8], unsigned char *encmsg, size_t encmsglen,
 	unsigned char *mackeys, size_t mackeyslen)
 {
     DataMsg datam = calloc(1, sizeof(struct s_DataMsg));
     char *newmsg = NULL;
     if (!datam) goto inv;
+    datam->version = version;
     datam->flags = flags;
+    datam->sender_instance = sender_instance;
+    datam->receiver_instance = receiver_instance;
     datam->sender_keyid = sender_keyid;
     datam->rcpt_keyid = rcpt_keyid;
     datam->y = gcry_mpi_copy(y);
diff --git a/toolkit/parse.h b/toolkit/parse.h
index f98cb6a..d1f8750 100644
--- a/toolkit/parse.h
+++ b/toolkit/parse.h
@@ -1,6 +1,7 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *                           Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -37,6 +38,9 @@ typedef struct s_DataMsg {
     unsigned char *raw;         /* The base64-decoded data; must be free()d */
     size_t rawlen;
     int flags;
+    unsigned char version;
+    unsigned int sender_instance;
+    unsigned int receiver_instance;
     unsigned int sender_keyid;
     unsigned int rcpt_keyid;
     gcry_mpi_t y;
@@ -52,6 +56,9 @@ typedef struct s_DataMsg {
 
 typedef struct s_CommitMsg {
     unsigned char *raw;         /* The base64-decoded data; must be free()d */
+    unsigned char version;
+    unsigned int sender_instance;
+    unsigned int receiver_instance;
     unsigned char *enckey;
     size_t enckeylen;
     unsigned char *hashkey;
@@ -60,11 +67,17 @@ typedef struct s_CommitMsg {
 
 typedef struct s_KeyMsg {
     unsigned char *raw;         /* The base64-decoded data; must be free()d */
+    unsigned char version;
+    unsigned int sender_instance;
+    unsigned int receiver_instance;
     gcry_mpi_t y;
 } * KeyMsg;
 
 typedef struct s_RevealSigMsg {
     unsigned char *raw;         /* The base64-decoded data; must be free()d */
+    unsigned char version;
+    unsigned int sender_instance;
+    unsigned int receiver_instance;
     unsigned char *key;
     size_t keylen;
     unsigned char *encsig;
@@ -74,6 +87,9 @@ typedef struct s_RevealSigMsg {
 
 typedef struct s_SignatureMsg {
     unsigned char *raw;         /* The base64-decoded data; must be free()d */
+    unsigned char version;
+    unsigned int sender_instance;
+    unsigned int receiver_instance;
     unsigned char *encsig;
     size_t encsiglen;
     unsigned char mac[20];
@@ -131,8 +147,10 @@ char *remac_datamsg(DataMsg datamsg, unsigned char mackey[20]);
 
 /* Assemble a new Data Message from its pieces.  Return a
  * newly-allocated string containing the base64 representation. */
-char *assemble_datamsg(unsigned char mackey[20], int flags,
-	unsigned int sender_keyid, unsigned int rcpt_keyid, gcry_mpi_t y,
+char *assemble_datamsg(unsigned char mackey[20],
+	unsigned char version, unsigned int sender_instance,
+	unsigned int receiver_instance, int flags, unsigned int sender_keyid,
+	unsigned int rcpt_keyid, gcry_mpi_t y,
 	unsigned char ctr[8], unsigned char *encmsg, size_t encmsglen,
 	unsigned char *mackeys, size_t mackeyslen);
 
diff --git a/toolkit/readotr.c b/toolkit/readotr.c
index 04e9fca..7f0f270 100644
--- a/toolkit/readotr.c
+++ b/toolkit/readotr.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
diff --git a/toolkit/readotr.h b/toolkit/readotr.h
index 75a7e2f..86f1073 100644
--- a/toolkit/readotr.h
+++ b/toolkit/readotr.h
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
diff --git a/toolkit/sesskeys.c b/toolkit/sesskeys.c
index d823ebc..e7e06e7 100644
--- a/toolkit/sesskeys.c
+++ b/toolkit/sesskeys.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
diff --git a/toolkit/sesskeys.h b/toolkit/sesskeys.h
index 7a98ac8..617c05a 100644
--- a/toolkit/sesskeys.h
+++ b/toolkit/sesskeys.h
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
diff --git a/toolkit/sha1hmac.c b/toolkit/sha1hmac.c
index 272a4fb..d58caff 100644
--- a/toolkit/sha1hmac.c
+++ b/toolkit/sha1hmac.c
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify
diff --git a/toolkit/sha1hmac.h b/toolkit/sha1hmac.h
index ad5159a..bb53829 100644
--- a/toolkit/sha1hmac.h
+++ b/toolkit/sha1hmac.h
@@ -1,6 +1,6 @@
 /*
  *  Off-the-Record Messaging Toolkit
- *  Copyright (C) 2004-2008  Ian Goldberg, Chris Alexander, Nikita Borisov
+ *  Copyright (C) 2004-2012  Ian Goldberg, Chris Alexander, Nikita Borisov
  *                           <otr at cypherpunks.ca>
  *
  *  This program is free software; you can redistribute it and/or modify

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/libotr.git



More information about the Pkg-privacy-commits mailing list