/* 
 * Demo reassembler just prints out information about each packet seen
 * This version does NOT use callbacks in the main loop
 * (though there's a callback hidden in nextPacket())
 *
 * YOU WILL NEED jnetpcap.jar IN YOUR CLASSPATH TO COMPILE!
 */

import java.util.Date;
import java.net.*;		// for InetAddress, etc
import java.text.*;		// for DateFormat stuff


import org.jnetpcap.Pcap;
import org.jnetpcap.packet.PcapPacket;
import org.jnetpcap.packet.Payload;
import org.jnetpcap.packet.PcapPacketHandler;

import org.jnetpcap.protocol.lan.*;		// defines Ethernet
import org.jnetpcap.protocol.network.*;		// defines Ip4
import org.jnetpcap.protocol.tcpip.*;		// defines Tcp


class reassemblerDemo {

    /*
     * tcpdump1: two TCP streams, from ports 53617 and 53618, to 5432
     */
    //public static final int SNAPLEN = 1600;  // not used for offline reads?

    public static void main(String[] args) {
	String filename = null;
	if (args.length < 1) {
		System.out.println("usage: reassembler <filename>");
		return;
	}
	filename = args[0];

	// master Pcap object
	Pcap pcap = fileInit(filename);

	int packetCount = 0;
	int tcpCount = 0;
	int synCount = 0;
	DateFormat df = new SimpleDateFormat("H-m-s.SSS");
	int THEPORT = 53617;
	int ISN = 0;

	while (true) {
		boolean isSyn = false, isFin = false, hasData = false;
		PcapPacket p = nextPacket(pcap);
		if (p==null) break;
		packetCount++;
		// The following create the header objects for the current packet
		Ethernet eth = new Ethernet();	// create Ethernet header
		Ip4 ip = new Ip4();		// create IP header object
		Tcp tcp = new Tcp();		// create TCP header object
		Payload payload = new Payload(); // create data-payload object
		/* */
		Date date = new Date(p.getCaptureHeader().timestampInMillis());
		int len = p.getCaptureHeader().wirelen();
		/* 
		System.out.println("Received packet at " +  
		    	new Date(p.getCaptureHeader().timestampInMillis())
			+ " len=" + p.getCaptureHeader().wirelen()  // Original length 
		);
		/* */
		if (p.hasHeader(eth) && p.hasHeader(ip) && p.hasHeader(tcp)) {
		    tcpCount++;
		    System.out.print("" + (packetCount -1) + ": ");	// pld: new
		    System.out.print("TCP packet: ");
		    InetAddress sourceIP = inetAddress(ip.source());
		    InetAddress destIP   = inetAddress(ip.destination());
		    int srcport = tcp.source();
		    int dstport = tcp.destination();
		    PSocket src = new PSocket(sourceIP, srcport);
		    PSocket dest =new PSocket(destIP,   dstport);
		    System.out.print( src + " ==> " + dest);
		    if (tcp.flags_SYN()) {
			synCount++;
			if (tcp.flags_ACK()) System.out.print(" SYN/ACK, ");
			else System.out.print(" first SYN, ");
			ISN = (int) tcp.seq();
			System.out.print("ISN=" + ISN);
			isSyn = true;
		    }
		    if (tcp.flags_FIN()) {
			System.out.print(" FIN");
			isFin = true;
		    }
		    if (tcp.flags_RST()) {
			System.out.print(" RST");	// should handle better?
			isFin = true;
		    }
		    
		    if (p.hasHeader(payload) && tcp.getPayloadLength() != 0 ) {
			byte[] data = payload.getByteArray(0, payload.size());
			//byte[] data = payload.data();
			//System.out.println("seq=" + (tcp.seq()-ISN));
			String datastr = new String(data);
			if (datastr.length() !=0) {
			    System.out.print(" data: " + datastr);
			}
			hasData = true;
		    }
		    if (!isSyn && !isFin && !hasData)
			System.out.print(" ACK only");
		    System.out.println();
		} else {
			System.out.println("... not a TCP packet");
		}
	}

	System.out.println("packet count = " + packetCount);
	System.out.println("TCP count = " + tcpCount);
	System.out.println("TCP SYN count = " + synCount);


    }

/**
 * fileInit returns a Pcap object from a saved tcpdump file stream
 */
    public static Pcap fileInit(String filename) {
 	Pcap pcap;
	StringBuilder errbuf = new StringBuilder();
	pcap = Pcap.openOffline(filename, errbuf);
	if (pcap == null) {
		System.out.println("open failed! " + errbuf);
		System.exit(0);
	}
	return pcap;
    }

    private static PcapPacket thePacket;	// used in nextPacket()
    private static PcapPacket prevPacket;
	
    private static PcapPacket nextPacket(Pcap pcap) {
		
	PcapPacketHandler<String> smallHandler = new PcapPacketHandler<String>() {
	    public void nextPacket(PcapPacket packet, String user) {
		prevPacket = thePacket;
		thePacket = packet;
	    }
	};

	int count;
	//count = pcap.loop(1, smallHandler, null);
	count = pcap.dispatch(1, smallHandler, null);
	//if (prevPacket == thePacket) return null;
	if (count == 0) return null;
	return thePacket;
    }

/**
 * inetAddress(byte[] buf): converts an array of four bytes to an InetAddress
 */
    private static InetAddress inetAddress(byte[] buf) {
	InetAddress addr = null;
	try {
		addr = InetAddress.getByAddress(buf);
	} catch (UnknownHostException uhe) {}
	return addr;
    }

    // for printing socket info -- pld
    static public class PSocket {
	InetAddress addr;
	int port;
	public PSocket(InetAddress a, int p) {
		addr = a; port = p;
	}
	public int getPort() {return port;}
	public InetAddress getAddr() {return addr;}
	public String toString() {
		String theString = "<";
		theString = theString + addr.getHostAddress();
		theString = theString + ",";
		theString = theString + port;
		theString = theString + ">";
		return theString;
	}
	// .equals method for comparing PSocket objects
	public boolean equals(Object o) {
		if (! (o instanceof PSocket)) return false;
		PSocket p = (PSocket) o;
		if (getPort() == p.getPort() && getAddr().equals(p.getAddr())) {
			return true;
		} else {
			return false;
		}
	}
	// when you implement .equals, you must also implement this:
	// "^" means XOR; the 37 is there to make it asymmetric
	public int hashCode() {
		return addr.hashCode() ^ (37*port);
	}
    }

    /*
     * note that PSocketPair could serve as the "key" type as you look up
     * a given socketpair to get its connection data.
     */

    static public class PSocketPair {
	PSocket src, dst;
	public PSocketPair(PSocket a, PSocket b) {
		src = a; dst = b;
	}
	public PSocket getSrc() {return src;}
	public PSocket getDst() {return dst;}
	/* */
	public String toString() {
		String theString = "";
		theString = theString + src;
		theString = theString + " ==> ";
		theString = theString + dst;
		return theString;
	}
	/* */
	public boolean equals(Object o) {
		if (! (o instanceof PSocketPair)) return false;
		PSocketPair p = (PSocketPair) o;
		if (src.equals(p.getSrc()) && dst.equals(p.getDst())) {
			return true;
		} else {
			return false;
		}
	}
	// always supply hashCode() with .equals(). 
	// "^" means XOR; the 37 is there to make it asymmetric
	public int hashCode() {
		return src.hashCode() ^ (37* dst.hashCode());
	}
    }


}
