public abstract class AbstractEncodedMessage extends AbstractMessage {

	// Encoded Message Types
	public static final int MESSAGE_DH_COMMIT = 0x02;
	public static final int MESSAGE_DATA = 0x03;
	public static final int MESSAGE_DHKEY = 0x0a;
	public static final int MESSAGE_REVEALSIG = 0x11;
	public static final int MESSAGE_SIGNATURE = 0x12;

	public int protocolVersion;

	public int senderInstanceTag;

	public int receiverInstanceTag;

	public AbstractEncodedMessage(int messageType, int protocolVersion) {
		super(messageType);
		this.protocolVersion = protocolVersion;
	}

	public AbstractEncodedMessage(int messageType, int protocolVersion,
			int senderInstanceTag)
	{
		this(messageType, protocolVersion, senderInstanceTag, 0);
	}

	public AbstractEncodedMessage(int messageType, int protocolVersion,
			int senderInstanceTag, int recipientInstanceTag)
	{
		super(messageType);
		this.protocolVersion = protocolVersion;
		this.senderInstanceTag = senderInstanceTag;
		this.receiverInstanceTag = recipientInstanceTag;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + protocolVersion;
		result = prime * result + senderInstanceTag;
		result = prime * result + receiverInstanceTag;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		AbstractEncodedMessage other = (AbstractEncodedMessage) obj;
		if (protocolVersion != other.protocolVersion)
			return false;
		if (senderInstanceTag != other.senderInstanceTag)
			return false;
		if (receiverInstanceTag != other.receiverInstanceTag)
			return false;
		return true;
	}
} public abstract class AbstractMessage {

	// Unencoded
	public static final int MESSAGE_ERROR = 0xff;
	public static final int MESSAGE_QUERY = 0x100;
	public static final int MESSAGE_PLAINTEXT = 0x102;

	public int messageType;

	public AbstractMessage(int messageType) {
		this.messageType = messageType;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + messageType;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		AbstractMessage other = (AbstractMessage) obj;
		if (messageType != other.messageType)
			return false;
		return true;
	}
} public class DHCommitMessage extends AbstractEncodedMessage {

	public byte[] dhPublicKeyEncrypted;
	public byte[] dhPublicKeyHash;

	public DHCommitMessage(int protocolVersion, byte[] dhPublicKeyHash,
			byte[] dhPublicKeyEncrypted)
	{
		super(MESSAGE_DH_COMMIT, protocolVersion);
		this.dhPublicKeyEncrypted = dhPublicKeyEncrypted;
		this.dhPublicKeyHash = dhPublicKeyHash;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + Arrays.hashCode(dhPublicKeyEncrypted);
		result = prime * result + Arrays.hashCode(dhPublicKeyHash);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		DHCommitMessage other = (DHCommitMessage) obj;
		if (!Arrays.equals(dhPublicKeyEncrypted, other.dhPublicKeyEncrypted))
			return false;
		if (!Arrays.equals(dhPublicKeyHash, other.dhPublicKeyHash))
			return false;
		return true;
	}
} public class DHKeyMessage extends AbstractEncodedMessage {

	public DHPublicKey dhPublicKey;

	public DHKeyMessage(int protocolVersion, DHPublicKey dhPublicKey) {
		super(MESSAGE_DHKEY, protocolVersion);
		this.dhPublicKey = dhPublicKey;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		// TODO: Needs work.
		result = prime * result
				+ ((dhPublicKey == null) ? 0 : dhPublicKey.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		DHKeyMessage other = (DHKeyMessage) obj;
		if (dhPublicKey == null) {
			if (other.dhPublicKey != null)
				return false;
		} else if (dhPublicKey.getY().compareTo(other.dhPublicKey.getY()) != 0)
			return false;
		return true;
	}
} public class DataMessage extends AbstractEncodedMessage {

	public byte[] mac;
	public byte[] oldMACKeys;

	public int flags;
	public int senderKeyID;
	public int recipientKeyID;
	public DHPublicKey nextDH;
	public byte[] ctr;
	public byte[] encryptedMessage;

	public DataMessage(int protocolVersion, int flags, int senderKeyID,
			int recipientKeyID, DHPublicKey nextDH, byte[] ctr,
			byte[] encryptedMessage, byte[] mac, byte[] oldMacKeys)
	{
		super(MESSAGE_DATA, protocolVersion);

		this.flags = flags;
		this.senderKeyID = senderKeyID;
		this.recipientKeyID = recipientKeyID;
		this.nextDH = nextDH;
		this.ctr = ctr;
		this.encryptedMessage = encryptedMessage;
		this.mac = mac;
		this.oldMACKeys = oldMacKeys;
	}

	public DataMessage(MysteriousT t, byte[] mac, byte[] oldMacKeys) {
		this(t.protocolVersion, t.flags, t.senderKeyID, t.recipientKeyID,
				t.nextDH, t.ctr, t.encryptedMessage, mac, oldMacKeys);
	}

	public MysteriousT getT() {
		return new MysteriousT(protocolVersion, senderInstanceTag,
				receiverInstanceTag, flags, senderKeyID, recipientKeyID, nextDH,
				ctr, encryptedMessage);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + Arrays.hashCode(ctr);
		result = prime * result + Arrays.hashCode(encryptedMessage);
		result = prime * result + flags;
		result = prime * result + Arrays.hashCode(mac);
		// TODO: Needs work.
		result = prime * result + ((nextDH == null) ? 0 : nextDH.hashCode());
		result = prime * result + Arrays.hashCode(oldMACKeys);
		result = prime * result + recipientKeyID;
		result = prime * result + senderKeyID;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		DataMessage other = (DataMessage) obj;
		if (!Arrays.equals(ctr, other.ctr))
			return false;
		if (!Arrays.equals(encryptedMessage, other.encryptedMessage))
			return false;
		if (flags != other.flags)
			return false;
		if (!Arrays.equals(mac, other.mac))
			return false;
		if (nextDH == null) {
			if (other.nextDH != null)
				return false;
		} else if (!nextDH.equals(other.nextDH))
			return false;
		if (!Arrays.equals(oldMACKeys, other.oldMACKeys))
			return false;
		if (recipientKeyID != other.recipientKeyID)
			return false;
		if (senderKeyID != other.senderKeyID)
			return false;
		return true;
	}
} public class ErrorMessage extends AbstractMessage {

	public String error;

	public ErrorMessage(int messageType, String error) {
		super(messageType);
		this.error = error;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + ((error == null) ? 0 : error.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		ErrorMessage other = (ErrorMessage) obj;
		if (error == null) {
			if (other.error != null)
				return false;
		} else if (!error.equals(other.error))
			return false;
		return true;
	}
} public class PlainTextMessage extends QueryMessage {

	public String cleanText;

	public PlainTextMessage(List<Integer> versions, String cleanText) {
		super(MESSAGE_PLAINTEXT, versions);
		this.cleanText = cleanText;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result
				+ ((cleanText == null) ? 0 : cleanText.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		PlainTextMessage other = (PlainTextMessage) obj;
		if (cleanText == null) {
			if (other.cleanText != null)
				return false;
		} else if (!cleanText.equals(other.cleanText))
			return false;
		return true;
	}
} public class QueryMessage extends AbstractMessage {

	public List<Integer> versions;

	protected QueryMessage(int messageType, List<Integer> versions) {
		super(messageType);
		this.versions = (versions == null) ? versions
				: Collections.unmodifiableList(
						new ArrayList<Integer>(versions));
	}

	public QueryMessage(List<Integer> versions) {
		this(MESSAGE_QUERY, versions);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result
				+ ((versions == null) ? 0 : versions.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		QueryMessage other = (QueryMessage) obj;
		if (versions == null) {
			if (other.versions != null)
				return false;
		} else if (!versions.equals(other.versions))
			return false;
		return true;
	}
} public class RevealSignatureMessage extends SignatureMessage {

	public byte[] revealedKey;

	public RevealSignatureMessage(int protocolVersion, byte[] xEncrypted,
			byte[] xEncryptedMAC, byte[] revealedKey)
	{
		super(MESSAGE_REVEALSIG, protocolVersion, xEncrypted, xEncryptedMAC);

		this.revealedKey = revealedKey;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + Arrays.hashCode(revealedKey);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		RevealSignatureMessage other = (RevealSignatureMessage) obj;
		if (!Arrays.equals(revealedKey, other.revealedKey))
			return false;
		return true;
	}
} public class SignatureM {

	public DHPublicKey localPubKey;
	public DHPublicKey remotePubKey;
	public PublicKey localLongTermPubKey;
	public int keyPairID;

	public SignatureM(DHPublicKey localPubKey, DHPublicKey remotePublicKey,
			PublicKey localLongTermPublicKey, int keyPairID)
	{
		this.localPubKey = localPubKey;
		this.remotePubKey = remotePublicKey;
		this.localLongTermPubKey = localLongTermPublicKey;
		this.keyPairID = keyPairID;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + keyPairID;
		// TODO: Needs work.
		result = prime
				* result
				+ ((localLongTermPubKey == null) ? 0 : localLongTermPubKey
						.hashCode());
		result = prime * result
				+ ((localPubKey == null) ? 0 : localPubKey.hashCode());
		result = prime * result
				+ ((remotePubKey == null) ? 0 : remotePubKey.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		// TODO: Needs work.
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		SignatureM other = (SignatureM) obj;
		if (keyPairID != other.keyPairID)
			return false;
		if (localLongTermPubKey == null) {
			if (other.localLongTermPubKey != null)
				return false;
		} else if (!localLongTermPubKey.equals(other.localLongTermPubKey))
			return false;
		if (localPubKey == null) {
			if (other.localPubKey != null)
				return false;
		} else if (!localPubKey.equals(other.localPubKey))
			return false;
		if (remotePubKey == null) {
			if (other.remotePubKey != null)
				return false;
		} else if (!remotePubKey.equals(other.remotePubKey))
			return false;
		return true;
	}
} public class SignatureMessage extends AbstractEncodedMessage {

	public byte[] xEncrypted;
	public byte[] xEncryptedMAC;

	protected SignatureMessage(int messageType, int protocolVersion,
			byte[] xEncrypted, byte[] xEncryptedMAC)
	{
		super(messageType, protocolVersion);
		this.xEncrypted = xEncrypted;
		this.xEncryptedMAC = xEncryptedMAC;
	}

	public SignatureMessage(int protocolVersion, byte[] xEncrypted,
			byte[] xEncryptedMAC)
	{
		this(MESSAGE_SIGNATURE, protocolVersion, xEncrypted, xEncryptedMAC);
	}

	public byte[] decrypt(byte[] key) throws OtrException {
		return new OtrCryptoEngineImpl().aesDecrypt(key, null, xEncrypted);
	}

	public boolean verify(byte[] key) throws OtrException {
		// Hash the key.
		byte[] xbEncrypted;
		try {
			xbEncrypted = SerializationUtils.writeData(xEncrypted);
		} catch (IOException e) {
			throw new OtrException(e);
		}

		byte[] trialXEncryptedMAC = new OtrCryptoEngineImpl().sha256Hmac160(
				xbEncrypted, key);
		// Verify signature.
		return Arrays.equals(this.xEncryptedMAC, trialXEncryptedMAC);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + Arrays.hashCode(xEncrypted);
		result = prime * result + Arrays.hashCode(xEncryptedMAC);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		SignatureMessage other = (SignatureMessage) obj;
		if (!Arrays.equals(xEncrypted, other.xEncrypted))
			return false;
		if (!Arrays.equals(xEncryptedMAC, other.xEncryptedMAC))
			return false;
		return true;
	}
} interface OTRv {
	int ONE = 1;

	int TWO = 2;

	int THREE = 3;
}
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.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.interfaces.DHPublicKey;
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;
import net.java.otr4j.session.Session.OTRv;

/*
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.java.otr4j.io;




/**
 * @author George Politis
 */
public class SerializationUtils {

	private static final Pattern PATTERN_WHITESPACE = Pattern
			.compile("( \\t  \\t\\t\\t\\t \\t \\t \\t  )( \\t \\t  \\t )?(  \\t\\t  \\t )?(  \\t\\t  \\t\\t)?");
	private static final char[] HEX_ENCODER = {'0', '1', '2', '3', '4', '5',
			'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
	private static final String HEX_DECODER = "0123456789ABCDEF";

	/** Hide the ctor, because this is an utility class. */
	private 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();
		oos.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();
		oos.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();
		if (m.messageType != AbstractMessage.MESSAGE_PLAINTEXT)
			writer.write(SerializationConstants.HEAD);

		switch (m.messageType) {
			case AbstractMessage.MESSAGE_ERROR:
				ErrorMessage error = (ErrorMessage) m;
				writer.write(SerializationConstants.HEAD_ERROR);
				writer.write(SerializationConstants.ERROR_PREFIX);
				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 == OTRv.ONE)
							writer.write(" \t \t  \t ");

						if (version == OTRv.TWO)
							writer.write("  \t\t  \t ");

						if (version == OTRv.THREE)
							writer.write("  \t\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);
						if (dhkey.protocolVersion == OTRv.THREE) {
							s.writeInt(dhkey.senderInstanceTag);
							s.writeInt(dhkey.receiverInstanceTag);
						}
						s.writeDHPublicKey(dhkey.dhPublicKey);
						break;
					case AbstractEncodedMessage.MESSAGE_REVEALSIG:

						RevealSignatureMessage revealsig = (RevealSignatureMessage) m;
