// Copyright (c) 1999
// The Regents of The University of Michigan
// All rights reserved

// Permission is granted to use, copy and redistribute this software
// for noncommercial education and research purposes, so long as no
// fee is charged, and so long as the copyright notice above, this
// grant of permission, and the disclaimer below appear in all copies
// made; and so long as the name of The University of Michigan is not
// used in any advertising or publicity pertaining to the use or
// distribution of this software without specific, written prior
// authorization.  Permission to modify or otherwise create derivative
// works of this software is not granted.
//
// This software is provided as is, without representation as to its
// fitness for any purpose, and without warranty of any kind, either
// express or implied, including without limitation the implied
// warranties of merchantability and fitness for a particular purpose.
// The Regents of The University of Michigan shall not be liable for
// any damages, including special, indirect, incidental, or consequential
// damages, with respect to any claim arising out of or in connection
// with the use of the software, even if it has been or is hereafter
// advised of the possibility of such damages.

// Contact: info@citi.umich.edu


/*
   A very small tcp stack and web server for Cyberflex Access
 */

import javacard.framework.*;
import javacardx.framework.*;

public class ip7816 extends Applet
{
    static final byte IP_CLA = (byte)0xfe;
    static final byte IP_INS = (byte)0xfe;
    static final byte IP_P1  = (byte)0x00;
    static final byte IP_P2  = (byte)0x21;

    static final byte FL_ACK = 0x10;
    static final byte FL_PSH = 0x8;
    static final byte FL_RST = 0x4;
    static final byte FL_SYN = 0x2;
    static final byte FL_FIN = 0x1;

    static final byte ST_LISTEN = 0;
    static final byte ST_ESTAB = 2;
    static final byte ST_FW1 = 3;
    static final byte ST_FW2 = 4;

    static final short CD = ISO.OFFSET_CDATA;
    static final short MTU = 248;	// Fits in a 256 byte apdu
    static final short WINDOW = 2680;

    // "TCB"
    // In a full tcp implementation we would keep track of this per connection.
    // This implementation only handles one connection at a time.
    // As a result, very little of this state is actually used after
    // the reply packet has been sent.

    short id, tcb_port;	// ip id, tcp port
    byte tcb_st;	// state

    private ip7816() {
	register();
    }

    public static void install(APDU apdu) {
	new ip7816();
    }

    public boolean select() {
	id = 1;
	tcb_st = ST_LISTEN;
	return true;
    }

    public static void main(String args[]) {
	ISOException.throwIt((short) 0x7264);
    }

    private short ip_input(APDU apdu, byte pkt[]) {
	short i, len, port, rcvnxt0, rcvnxt1, sndnxt, offset, datlen, ck, d0, d1;
	byte fl;

	len = apdu.setIncomingAndReceive();
	if (len < 40)
	    return ISO.SW_WRONG_LENGTH;

	// Packet may have been truncated by ip7816d; find real len
	len = Util.getShort(pkt, (short)(CD+2));

	// If it's not http, just drop it
	if (Util.getShort(pkt, (short)(CD+22)) != 80)
	    return ISO.SW_NO_ERROR;

	// Get source port
	port = Util.getShort(pkt, (short)(CD+20));

	// Get the sender's sequence and ack
	// XXX Note this is 16-bit; we don't handle overflow
	rcvnxt0 = Util.getShort(pkt, (short)(CD+24));
	rcvnxt1 = Util.getShort(pkt, (short)(CD+26));
	sndnxt = Util.getShort(pkt, (short)(CD+30));

	// Find the payload
	offset = (short) (((pkt[CD+32] >> 2) & 0x3c) + 20);
	datlen = (short) (len - offset);

	len = 40;
	fl = FL_ACK;

	apdu.waitExtension();

	// Figure out what kind of packet this is, and respond
	if ((pkt[CD+33] & FL_SYN) != 0) {

	    // SYN
	    sndnxt = 0;
	    rcvnxt1++;
	    fl |= FL_SYN;
	    tcb_st = ST_ESTAB;

	} else if (datlen > 0) {

	    // incoming data
	    rcvnxt1 += datlen;

	    // Get the url (two chars after "GET /")
	    if (pkt[CD+offset+5] == 0x20)
		// Turn "/" into "in"
		d0 = 0x696e;
	    else
		d0 = Util.getShort(pkt, (short)(CD+offset+5));

	    // select the file and get its size
	    // if file not found, try "nf" then "in"
	    if (CyberflexFile.selectFile(d0) != ST.SUCCESS
		&& CyberflexFile.selectFile((short)0x6e66) != ST.SUCCESS)
		CyberflexFile.selectFile((short)0x696e);
	    len += (short) (CyberflexFile.getFileSize() - 16);
	    fl |= FL_PSH;
	    tcb_port = port;

	} else if ((pkt[CD+33] & FL_FIN) != 0) {

	    // FIN
	    rcvnxt1++;
	    // Don't bother with FIN-WAIT-2, TIME-WAIT, or CLOSED; they just cause trouble
	    tcb_st = ST_LISTEN;

	} else if ((pkt[CD+33] & FL_ACK) != 0) {

	    // ack with no data
	    if (tcb_port == port && sndnxt > 1) {
		// calculate no of bytes left to send
		i = (short) (CyberflexFile.getFileSize() - 16 - (sndnxt - 1));
		if (i == 0) {
		    // EOF; send FIN
		    fl |= FL_FIN;
		    tcb_st = ST_FW1;
		} else if (i > 0) {
		    // not EOF; send next segment
		    len += i;
		    fl |= FL_PSH;
		} else {
		    // ack of FIN; no reply
		    return ISO.SW_NO_ERROR;
		}
	    } else
		return ISO.SW_NO_ERROR;		// No reply packet

	} else
	    return ISO.SW_NO_ERROR;		// drop it

	// Send reply packet
	if (len > MTU)
	    len = MTU;

	// Read next segment of data into buffer
	if (len > 40)
	    CyberflexOS.readBinaryFile(pkt, (short)40, (short)(sndnxt - 1), (short)(len - 40));

	// swap source and destination IP addresses
	Util.arrayCopy(pkt, (short)(CD+16), pkt, (short)12, (short)4);
	Util.arrayCopy(pkt, (short)(CD+12), pkt, (short)16, (short)4);

	// clear rest of packet headers
	for (i = 0; i < 12; i++)
	    pkt[i] = 0;
	for (i = 20; i < 40; i++)
	    pkt[i] = 0;

	// Fill in IP header
	pkt[0] = 0x45;		// version, header len
	Util.setShort(pkt, (short)2, len);
	Util.setShort(pkt, (short)4, id);
	pkt[8] = 60;		// ttl
	pkt[9] = 6;		// protocol (tcp)

	apdu.waitExtension();

	// Calculate IP header checksum
	ck = d0 = d1 = 0;
	for (i = 0; i < 20; i += 2) {
	    d0 += (short) (pkt[i] & 0xff);
	    d1 += (short) (pkt[i+1] & 0xff);
	}
	// This works because IP header is too short to overflow high byte
	ck = (short) ~(((d0 >> 8) & 0xff) + (d0 << 8) + d1);
	Util.setShort(pkt, (short)10, ck);

	apdu.waitExtension();

	// Fill in TCP header
	pkt[21] = 80; // Source port
	Util.setShort(pkt, (short)22, port);
	Util.setShort(pkt, (short)26, sndnxt);
	Util.setShort(pkt, (short)28, rcvnxt0);
	Util.setShort(pkt, (short)30, rcvnxt1);
	pkt[32] = 0x50;		// data offset = 20 (no options)
	pkt[33] = fl;		// flags
	Util.setShort(pkt, (short)34, WINDOW);

	// Calculate TCP checksum
	ck = d0 = d1 = 0;
	pkt[len] = 0;
	for (i = 12; i < len; i += 2) {
	    d0 += (short) (pkt[i] & 0xff);
	    d1 += (short) (pkt[i+1] & 0xff);
	}
	d1 += 6 + len - 20;
	ck = (short) ((d0 & 0xff) + ((d1 >> 8) & 0xff));
	ck = (short) ~(((d0 >> 8) & 0xff) + (d1 & 0xff) + ((ck >> 8) & 0xff) + (ck << 8));
	Util.setShort(pkt, (short)36, ck);

	// Send return packet
	apdu.setOutgoingAndSend((short)0, len);

	return ISO.SW_BYTES_REMAINING_00;
    }

    public void process(APDU apdu) {
	short status = ISO.SW_NO_ERROR;
	byte b[] = apdu.getBuffer();

	// Check cla & ins
	if (b[ISO.OFFSET_CLA] != IP_CLA) {
	    if (b[ISO.OFFSET_INS] == ISO.INS_SELECT) {
		if (b[ISO.OFFSET_P1] != 4)
		    status = ISO.SW_WRONG_P1P2;
	    } else if (b[ISO.OFFSET_INS] != 0xc0)
		status = ISO.SW_CLA_NOT_SUPPORTED;
	}
	if (b[ISO.OFFSET_INS] != IP_INS)
	    status = ISO.SW_INS_NOT_SUPPORTED;

	if (status == ISO.SW_NO_ERROR) {
	    // Incoming IP packet
	    status = ip_input(apdu, b);
	}

	if (status != ISO.SW_BYTES_REMAINING_00)
	    ISOException.throwIt(status);
    }
}
