src/net/java/otr4j/io/SerializationUtils.java
changeset 895 b2e1b45382a4
parent 810 0ff0059f2ec3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/SerializationUtils.java	Fri Apr 15 03:01:09 2011 +0200
@@ -0,0 +1,341 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.security.PublicKey;
+import java.util.List;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+import org.bouncycastle2.util.encoders.Base64;
+
+import net.java.otr4j.io.messages.AbstractEncodedMessage;
+import net.java.otr4j.io.messages.AbstractMessage;
+import net.java.otr4j.io.messages.DHCommitMessage;
+import net.java.otr4j.io.messages.DHKeyMessage;
+import net.java.otr4j.io.messages.DataMessage;
+import net.java.otr4j.io.messages.ErrorMessage;
+import net.java.otr4j.io.messages.MysteriousT;
+import net.java.otr4j.io.messages.PlainTextMessage;
+import net.java.otr4j.io.messages.QueryMessage;
+import net.java.otr4j.io.messages.RevealSignatureMessage;
+import net.java.otr4j.io.messages.SignatureM;
+import net.java.otr4j.io.messages.SignatureMessage;
+import net.java.otr4j.io.messages.SignatureX;
+
+/**
+ * 
+ * @author George Politis
+ */
+public class SerializationUtils {
+	// Mysterious X IO.
+	public static SignatureX toMysteriousX(byte[] b) throws IOException {
+		ByteArrayInputStream in = new ByteArrayInputStream(b);
+		OtrInputStream ois = new OtrInputStream(in);
+		SignatureX x = ois.readMysteriousX();
+		ois.close();
+		return x;
+	}
+
+	public static byte[] toByteArray(SignatureX x) throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		OtrOutputStream oos = new OtrOutputStream(out);
+		oos.writeMysteriousX(x);
+		byte[] b = out.toByteArray();
+		oos.close();
+		return b;
+	}
+
+	// Mysterious M IO.
+	public static byte[] toByteArray(SignatureM m) throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		OtrOutputStream oos = new OtrOutputStream(out);
+		oos.writeMysteriousX(m);
+		byte[] b = out.toByteArray();
+		oos.close();
+		return b;
+	}
+
+	// Mysterious T IO.
+	public static byte[] toByteArray(MysteriousT t) throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		OtrOutputStream oos = new OtrOutputStream(out);
+		oos.writeMysteriousT(t);
+		byte[] b = out.toByteArray();
+		out.close();
+		return b;
+	}
+
+	// Basic IO.
+	public static byte[] writeData(byte[] b) throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		OtrOutputStream oos = new OtrOutputStream(out);
+		oos.writeData(b);
+		byte[] otrb = out.toByteArray();
+		out.close();
+		return otrb;
+	}
+
+	// BigInteger IO.
+	public static byte[] writeMpi(BigInteger bigInt) throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		OtrOutputStream oos = new OtrOutputStream(out);
+		oos.writeBigInt(bigInt);
+		byte[] b = out.toByteArray();
+		oos.close();
+		return b;
+	}
+
+	public static BigInteger readMpi(byte[] b) throws IOException {
+		ByteArrayInputStream in = new ByteArrayInputStream(b);
+		OtrInputStream ois = new OtrInputStream(in);
+		BigInteger bigint = ois.readBigInt();
+		ois.close();
+		return bigint;
+	}
+
+	// Public Key IO.
+	public static byte[] writePublicKey(PublicKey pubKey) throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		OtrOutputStream oos = new OtrOutputStream(out);
+		oos.writePublicKey(pubKey);
+		byte[] b = out.toByteArray();
+		oos.close();
+		return b;
+	}
+
+	// Message IO.
+	public static String toString(AbstractMessage m) throws IOException {
+		StringWriter writer = new StringWriter();
+		writer.write(SerializationConstants.HEAD);
+
+		switch (m.messageType) {
+		case AbstractMessage.MESSAGE_ERROR:
+			ErrorMessage error = (ErrorMessage) m;
+			writer.write(SerializationConstants.HEAD_ERROR);
+			writer.write(error.error);
+			break;
+		case AbstractMessage.MESSAGE_PLAINTEXT:
+			PlainTextMessage plaintxt = (PlainTextMessage) m;
+			writer.write(plaintxt.cleanText);
+			if (plaintxt.versions != null && plaintxt.versions.size() > 0) {
+				writer.write(" \\t  \\t\\t\\t\\t \\t \\t \\t  ");
+				for (int version : plaintxt.versions) {
+					if (version == 1)
+						writer.write("  \\t\\t  \\t ");
+
+					if (version == 2)
+						writer.write(" \\t \\t  \\t ");
+				}
+			}
+			break;
+		case AbstractMessage.MESSAGE_QUERY:
+			QueryMessage query = (QueryMessage) m;
+			if (query.versions.size() == 1 && query.versions.get(0) == 1) {
+				writer.write(SerializationConstants.HEAD_QUERY_Q);
+			} else {
+				writer.write(SerializationConstants.HEAD_QUERY_V);
+				for (int version : query.versions)
+					writer.write(String.valueOf(version));
+
+				writer.write(SerializationConstants.HEAD_QUERY_Q);
+			}
+			break;
+		case AbstractEncodedMessage.MESSAGE_DHKEY:
+		case AbstractEncodedMessage.MESSAGE_REVEALSIG:
+		case AbstractEncodedMessage.MESSAGE_SIGNATURE:
+		case AbstractEncodedMessage.MESSAGE_DH_COMMIT:
+		case AbstractEncodedMessage.MESSAGE_DATA:
+			ByteArrayOutputStream o = new ByteArrayOutputStream();
+			OtrOutputStream s = new OtrOutputStream(o);
+
+			switch (m.messageType) {
+			case AbstractEncodedMessage.MESSAGE_DHKEY:
+				DHKeyMessage dhkey = (DHKeyMessage) m;
+				s.writeShort(dhkey.protocolVersion);
+				s.writeByte(dhkey.messageType);
+				s.writeDHPublicKey(dhkey.dhPublicKey);
+				break;
+			case AbstractEncodedMessage.MESSAGE_REVEALSIG:
+				RevealSignatureMessage revealsig = (RevealSignatureMessage) m;
+				s.writeShort(revealsig.protocolVersion);
+				s.writeByte(revealsig.messageType);
+				s.writeData(revealsig.revealedKey);
+				s.writeData(revealsig.xEncrypted);
+				s.writeMac(revealsig.xEncryptedMAC);
+				break;
+			case AbstractEncodedMessage.MESSAGE_SIGNATURE:
+				SignatureMessage sig = (SignatureMessage) m;
+				s.writeShort(sig.protocolVersion);
+				s.writeByte(sig.messageType);
+				s.writeData(sig.xEncrypted);
+				s.writeMac(sig.xEncryptedMAC);
+				break;
+			case AbstractEncodedMessage.MESSAGE_DH_COMMIT:
+				DHCommitMessage dhcommit = (DHCommitMessage) m;
+				s.writeShort(dhcommit.protocolVersion);
+				s.writeByte(dhcommit.messageType);
+				s.writeData(dhcommit.dhPublicKeyEncrypted);
+				s.writeData(dhcommit.dhPublicKeyHash);
+				break;
+			case AbstractEncodedMessage.MESSAGE_DATA:
+				DataMessage data = (DataMessage) m;
+				s.writeShort(data.protocolVersion);
+				s.writeByte(data.messageType);
+				s.writeByte(data.flags);
+				s.writeInt(data.senderKeyID);
+				s.writeInt(data.recipientKeyID);
+				s.writeDHPublicKey(data.nextDH);
+				s.writeCtr(data.ctr);
+				s.writeData(data.encryptedMessage);
+				s.writeMac(data.mac);
+				s.writeData(data.oldMACKeys);
+				break;
+			}
+
+			writer.write(SerializationConstants.HEAD_ENCODED);
+			writer.write(new String(Base64.encode(o.toByteArray())));
+			writer.write(".");
+			break;
+		default:
+			throw new IOException("Illegal message type.");
+		}
+
+		return writer.toString();
+	}
+
+	static final Pattern patternWhitespace = Pattern
+			.compile("( \\t  \\t\\t\\t\\t \\t \\t \\t  )(  \\t\\t  \\t )?( \\t \\t  \\t )?");
+
+	public static AbstractMessage toMessage(String s) throws IOException {
+		if (s == null || s.length() <= 1)
+			return null;
+
+		if (s.indexOf(SerializationConstants.HEAD) != 0
+				|| s.length() <= SerializationConstants.HEAD.length()) {
+			// Try to detect whitespace tag.
+			final Matcher matcher = patternWhitespace.matcher(s);
+
+			boolean v1 = false;
+			boolean v2 = false;
+			while (matcher.find()) {
+				if (!v1 && matcher.start(2) > -1)
+					v1 = true;
+
+				if (!v2 && matcher.start(3) > -1)
+					v2 = true;
+
+				if (v1 && v2)
+					break;
+			}
+
+			String cleanText = matcher.replaceAll("");
+			List<Integer> versions;
+			if (v1 && v2) {
+				versions = new Vector<Integer>(2);
+				versions.add(0, 1);
+				versions.add(0, 2);
+			} else if (v1) {
+				versions = new Vector<Integer>(1);
+				versions.add(0, 1);
+			} else if (v2) {
+				versions = new Vector<Integer>(1);
+				versions.add(2);
+			} else
+				versions = null;
+
+			return new PlainTextMessage(versions, cleanText);
+		} else {
+			char contentType = s.charAt(SerializationConstants.HEAD.length());
+			String content = s
+					.substring(SerializationConstants.HEAD.length() + 1);
+			switch (contentType) {
+			case SerializationConstants.HEAD_ENCODED:
+				ByteArrayInputStream bin = new ByteArrayInputStream(Base64
+						.decode(content.getBytes()));
+				OtrInputStream otr = new OtrInputStream(bin);
+				// We have an encoded message.
+				int protocolVersion = otr.readShort();
+				int messageType = otr.readByte();
+				switch (messageType) {
+				case AbstractEncodedMessage.MESSAGE_DATA:
+					int flags = otr.readByte();
+					int senderKeyID = otr.readInt();
+					int recipientKeyID = otr.readInt();
+					DHPublicKey nextDH = otr.readDHPublicKey();
+					byte[] ctr = otr.readCtr();
+					byte[] encryptedMessage = otr.readData();
+					byte[] mac = otr.readMac();
+					byte[] oldMacKeys = otr.readMac();
+					return new DataMessage(protocolVersion, flags, senderKeyID,
+							recipientKeyID, nextDH, ctr, encryptedMessage, mac,
+							oldMacKeys);
+				case AbstractEncodedMessage.MESSAGE_DH_COMMIT:
+					byte[] dhPublicKeyEncrypted = otr.readData();
+					byte[] dhPublicKeyHash = otr.readData();
+					return new DHCommitMessage(protocolVersion,
+							dhPublicKeyHash, dhPublicKeyEncrypted);
+				case AbstractEncodedMessage.MESSAGE_DHKEY:
+					DHPublicKey dhPublicKey = otr.readDHPublicKey();
+					return new DHKeyMessage(protocolVersion, dhPublicKey);
+				case AbstractEncodedMessage.MESSAGE_REVEALSIG: {
+					byte[] revealedKey = otr.readData();
+					byte[] xEncrypted = otr.readData();
+					byte[] xEncryptedMac = otr.readMac();
+					return new RevealSignatureMessage(protocolVersion,
+							xEncrypted, xEncryptedMac, revealedKey);
+				}
+				case AbstractEncodedMessage.MESSAGE_SIGNATURE: {
+					byte[] xEncryted = otr.readData();
+					byte[] xEncryptedMac = otr.readMac();
+					return new SignatureMessage(protocolVersion, xEncryted,
+							xEncryptedMac);
+				}
+				default:
+					throw new IOException("Illegal message type.");
+				}
+			case SerializationConstants.HEAD_ERROR:
+				return new ErrorMessage(AbstractMessage.MESSAGE_ERROR, content);
+			case SerializationConstants.HEAD_QUERY_V:
+			case SerializationConstants.HEAD_QUERY_Q:
+				List<Integer> versions = new Vector<Integer>();
+				String versionString = null;
+				if (SerializationConstants.HEAD_QUERY_Q == contentType) {
+					versions.add(1);
+					if (content.charAt(0) == 'v') {
+						versionString = content.substring(1, content
+								.indexOf('?'));
+					}
+				} else if (SerializationConstants.HEAD_QUERY_V == contentType) {
+					versionString = content.substring(0, content.indexOf('?'));
+				}
+
+				if (versionString != null) {
+					StringReader sr = new StringReader(versionString);
+					int c;
+					while ((c = sr.read()) != -1)
+						if (!versions.contains(c))
+							versions.add(Integer.parseInt(String
+									.valueOf((char) c)));
+				}
+				QueryMessage query = new QueryMessage(versions);
+				return query;
+			default:
+				throw new IOException("Uknown message type.");
+			}
+		}
+	}
+}