package ssh;

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

/**
 * This class reads an ssh binary packet protocol stream, and
 * produces BinaryPacketIn 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: BinaryPacketInputStream.java,v 1.5 2000/05/26 06:17:30 jonh Exp $
 */
public class BinaryPacketInputStream {
	// TODO: dataIn should be 'private'; it's public for debugging
	public DataInputStream dataIn;	// underlying stream
	Cipher cipher;					// cipher in place for incoming data;
									// null for no encipherment

	public BinaryPacketInputStream(InputStream i) {
		dataIn	= (i instanceof DataInputStream)
					? ((DataInputStream) i)
					: new DataInputStream(i);
		cipher	= null;
	}

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

	public void close()
		throws IOException {
		dataIn.close();
	}

	public BinaryPacketIn readPacket()
		throws IOException {

		// get packet length
		int length = dataIn.readInt();
		assert(length>4 && length<=262144);
			// per page 4 of draft-ylonen-ssh-protocol-00.txt

		// length doesn't count padding, which we know will be there.
		// "The amount of padding is (8 - (length % 8)) bytes"
		// So lengthIncludingPad is the remaining number of bytes to
		// read to complete this packet.
		// padLength = 8 - (len%8);
		int padLength = 8 - (length&7);
		// len = (len & ~7) | (len & 7)
		// len = (len & ~7) + (len & 7)
		// len = (len & ~7) + len%8
		// len+pad = len&~7 + (len%8) + 8 - (len%8)
		// len+pad = len&~7+8
		int lengthIncludingPad = (length&~7)+8;

		// Get the rest of the packet
		byte[] body = new byte[lengthIncludingPad];
		dataIn.readFully(body, 0, lengthIncludingPad);

		// body is encrypted once session key is established
		if (cipher!=null) {
			byte[] decrypted = new byte[body.length];
			cipher.decipher(decrypted, body, body.length);
			System.arraycopy(decrypted, 0, body, 0, body.length);
			for (int i=0; i<decrypted.length; i++) {
				decrypted[i] = 0;
			}
		}

		// compute CRC on packet body -- everything but length field
		CRC32 crc = new CRC32();
		crc.update(body, 0, body.length - 4);	// don't CRC the CRC field
		int packet_crc = Endian.BigGetInt(body, body.length-4);
		if (crc.getValue() != packet_crc) {
			throw new IOException("Packet CRC ("+packet_crc
				+") != computed CRC ("+crc.getValue()+").\n"
				+"CRC was computed over "+(body.length-4)+" bytes."
				);
		}

		// TODO: when we support compression, decompress type+data field
		// here.

		// pass remaining parts to a BinaryPacketIn object
		ByteArrayInputStream bais =
			new ByteArrayInputStream(body, padLength, length);
		return new BinaryPacketIn(bais, length, body);
	}

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