package ssh;

import java.io.*;
import java.net.*;
import java.util.*;
import Tools.*;

/**
 * This class writes an ssh binary packet protocol stream,
 * consuming BinaryPacketOut objects representing each packet.<p>
 *
 * Source: draft-ylonen-ssh-protocol-00.txt, pages 3-4.
 * 
 * @license This code is Copyright 1999 Jon Howell. It is available for
 * use under the GNU Public License, available at:
 * http://www.gnu.org/copyleft/gpl.html
 * 
 * @author Jon Howell <jonh@cs.dartmouth.edu>
 * @rcs $Id: BinaryPacketOutputStream.java,v 1.3 2000/05/26 06:17:31 jonh Exp $
 */
public class BinaryPacketOutputStream {
	private DataOutputStream dataOut;
	Cipher cipher;

	public BinaryPacketOutputStream(OutputStream o) {
		dataOut	= (o instanceof DataOutputStream)
					? ((DataOutputStream) o)
					: new DataOutputStream(o);
		cipher	= null;
	}

	public void setCipher(Cipher cipher) {
		this.cipher = cipher;
	}

	public BinaryPacketOut newPacket()
		throws IOException {
		return new BinaryPacketOut(new ByteArrayOutputStream());
	}

	public void writePacket(BinaryPacketOut op)
		throws IOException {

		// TODO: compress here

		int typeDataLen = op.size();
		int len = typeDataLen+4;				// length field as it will
												// appear in packet
		int padlen = 8-(len%8);

		// create a buffer for the data that gets enciphered
		byte[] padTypeDataCRC = new byte[padlen+len];
		// copy in type and data fields from BinaryPacketOut object
		System.arraycopy(op.toByteArray(), 0,
				padTypeDataCRC, padlen, typeDataLen);
		// TODO: write padlen random bytes to the beginning of buffer
		// write CRC to end of buffer
		CRC32 crc = new CRC32();
		crc.update(padTypeDataCRC, 0, padlen+typeDataLen);
		Endian.BigPutInt(padTypeDataCRC, padlen+typeDataLen,
			(int) crc.getValue());

		// encipher pad, type, data, and CRC fields
		byte[] enciphered = new byte[padTypeDataCRC.length];
		if (cipher!=null) {
			assert(padTypeDataCRC.length%8==0);
			cipher.encipher(enciphered,
					padTypeDataCRC, padTypeDataCRC.length);
		} else {
			// no encipherment
			System.arraycopy(padTypeDataCRC, 0,
				enciphered, 0, padTypeDataCRC.length);
		}

		dataOut.writeInt(len);
		dataOut.write(enciphered);
		dataOut.flush();

		// destroy sensitive data
		for (int i=0; i<padTypeDataCRC.length; i++) {
			padTypeDataCRC[i] = 0;
		}
		// notice we can't destroy the bytes that were in the buffers
		// of the BinaryPacketOut's ByteArrayOutputStream internal buffers
	}

	private void assert(boolean condition)
		throws IOException {
		if (!condition) {
			throw new IOException("assertion failed");
		}
	}
}
