//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <NetMessage.java Sun 2005/03/13 11:08:45 barr rimbase.rimonbarr.com>
//

// Copyright (C) 2004 by Cornell University
// All rights reserved.
// Refer to LICENSE for terms and conditions of use.

package jist.swans.net;

import java.util.Arrays;

import jist.swans.Constants;
import jist.swans.misc.Message;
import jist.swans.misc.MessageBytes;
import jist.swans.misc.Pickle;
import jist.swans.misc.Util;
import jist.swans.route.RouteAodv;
import jist.swans.trans.TransTcp;
import jist.swans.trans.TransUdp;

/**
 * Network packet.
 *
 * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
 * @version $Id: NetMessage.java,v 1.17 2005/03/13 16:11:55 barr Exp $
 * @since SWANS1.0
 */
public abstract class NetMessage implements Message, Cloneable
{

  //////////////////////////////////////////////////
  // IPv4 packet: (size = 20 + 4 * options + body)
  //   version                size: 4 bits
  //   header length          size: 4 bits
  //   type of service (tos)  size: 1
  //     - priority             size: 3 bits
  //     - delay bit            size: 1 bit
  //     - throughput bit       size: 1 bit
  //     - reliability bit      size: 1 bit
  //     - reserved             size: 2 bits
  //   total length           size: 2 (measured in 64 bit chunks)
  //   id                     size: 2
  //   control flags          size: 3 bits
  //     - reserved (=0)        size: 1 bit
  //     - unfragmentable       size: 1 bit
  //     - more fragments       size: 1 bit
  //   fragment offset        size: 13 bits
  //   ttl                    size: 1
  //   protocol               size: 1
  //   header chksum          size: 2
  //   src                    size: 4
  //   dst                    size: 4
  //   options:               size: 4 * number
  //   packet payload:        size: variable
  //

  /**
   * IPv4 network packet.
   */
  public static class Ip extends NetMessage
  {

    /** Fixed IP packet size. */
    public static final int BASE_SIZE = 20;

    /** Next identification number to use. */
    private static short nextId = 0;

    //////////////////////////////////////////////////
    // message contents
    //

    /** immutable bit. */
    private boolean frozen;
    /** ip packet source address. */
    private NetAddress src;
    /** ip packet destination address. */
    private NetAddress dst;
    /** ip packet payload. */
    private Message payload;
    /** ip packet priority level. */
    private byte priority;
    /** ip packet protocol, such as TCP, UDP, etc. */
    private short protocol;
    /** ip packet time-to-live. */
    private byte ttl;
    /** ip packet identification. */
    private short id;
    /** ip packet fragment offset. */
    private short fragOffset;

    // options
    /** source route. */
    private IpOptionSourceRoute srcRoute;

    /**
     * Create new IPv4 packet.
     *
     * @param payload packet payload
     * @param src packet source address
     * @param dst packet destination address
     * @param protocol packet protocol
     * @param priority packet priority
     * @param ttl packet time-to-live
     * @param id packet identification
     * @param fragOffset packet fragmentation offset
     */
    public Ip(Message payload, NetAddress src, NetAddress dst,
        short protocol, byte priority, byte ttl, short id, short fragOffset)
    {
      if(payload==null) throw new NullPointerException();
      this.frozen = false;
      this.payload = payload;
      this.src = src;
      this.dst = dst;
      this.protocol = protocol;
      this.priority = priority;
      this.ttl = ttl;
      this.id = id;
      this.fragOffset = fragOffset;
    }

    /**
     * Create new IPv4 packet with default id.
     *
     * @param payload packet payload
     * @param src packet source address
     * @param dst packet destination address
     * @param protocol packet protocol
     * @param priority packet priority
     * @param ttl packet time-to-live
     */
    public Ip(Message payload, NetAddress src, NetAddress dst,
        short protocol, byte priority, byte ttl)
    {
      this(payload, src, dst, protocol, priority, ttl, nextId++, (short)0);
    }

    public Ip(byte[] msg, int offset) throws IllegalFormatException {
      setBytes(msg, offset);
    }


    /**
     * Render packet immutable.
     *
     * @return immutable packet, possibly intern()ed
     */
    public Ip freeze()
    {
      // todo: could perform an intern/hashCons here
      this.frozen = true;
      return this;
    }

    /**
     * Whether packet is immutable.
     *
     * @return whether packet is immutable
     */
    public boolean isFrozen()
    {
      return frozen;
    }

    /**
     * Make copy of packet, usually in order to modify it.
     *
     * @return mutable copy of packet.
     */
    public Ip copy()
    {
      NetMessage.Ip ip2 = new Ip(payload, src, dst, protocol, priority, ttl, id, fragOffset);
      ip2.srcRoute = this.srcRoute;
      return ip2;
    }

    //////////////////////////////////////////////////
    // accessors
    //

    /**
     * Return packet source.
     *
     * @return packet source
     */
    public NetAddress getSrc()
    {
      return src;
    }

    /**
     * Return packet destination.
     *
     * @return packet destination
     */
    public NetAddress getDst()
    {
      return dst;
    }

    /**
     * Return packet payload.
     *
     * @return packet payload
     */
    public Message getPayload()
    {
      return payload;
    }

    /**
     * Return packet priority.
     *
     * @return packet priority
     */
    public byte getPriority()
    {
      return priority;
    }

    /**
     * Return packet protocol.
     *
     * @return packet protocol
     */
    public short getProtocol()
    {
      return protocol;
    }

    /**
     * Return packet identification.
     *
     * @return packet identification
     */
    public short getId()
    {
      return id;
    }

    /**
     * Return packet fragmentation offset.
     *
     * @return packet fragmentation offset
     */
    public short getFragOffset()
    {
      return fragOffset;
    }

    //////////////////////////////////////////////////
    // TTL
    //

    /**
     * Return packet time-to-live.
     *
     * @return time-to-live
     */
    public byte getTTL()
    {
      return ttl;
    }

    /**
     * Create indentical packet with decremented TTL.
     */
    public void decTTL()
    {
      if(frozen) throw new IllegalStateException();
      ttl--;
    }

    public void setTTL(byte ttl)
    {
      if(frozen) throw new IllegalStateException();
      this.ttl = ttl;
    }

    //////////////////////////////////////////////////
    // source route
    //

    /**
     * Returns whether packet contains source route.
     *
     * @return whether packet contains source route
     */
    public boolean hasSourceRoute()
    {
      return srcRoute!=null;
    }

    /**
     * Return source route. (do not modify)
     *
     * @return source route (do not modify)
     */
    public NetAddress[] getSourceRoute()
    {
      return srcRoute.getRoute();
    }

    /**
     * Return source route pointer.
     *
     * @return source route pointer
     */
    public int getSourceRoutePointer()
    {
      return srcRoute.getPtr();
    }

    /**
     * Set source route.
     *
     * @param srcRoute source route
     */
    public void setSourceRoute(IpOptionSourceRoute srcRoute)
    {
      if(frozen) throw new IllegalStateException();
      this.srcRoute = srcRoute;
    }

    /** {@inheritDoc} */
    public String toString()
    {
      return "ip(src="+src+" dst="+dst+" size="+getSize()+" prot="+protocol+
        " id=" + id + " ttl="+ttl+" route="+srcRoute+" data="+payload+")";
    }

    //////////////////////////////////////////////////
    // message interface
    //

    /** {@inheritDoc} */
    public int getSize()
    {
      int size = payload.getSize();
      if(size==Constants.ZERO_WIRE_SIZE)
      {
        return Constants.ZERO_WIRE_SIZE;
      }
      // todo: options
      return BASE_SIZE + size;
    }

    /** {@inheritDoc} */
    public void getBytes(byte[] msg, int offset)
    {
      int headerLen = BASE_SIZE;

      /* 0     version == 4, header length        */
      int byte0 = (4<<4) + (headerLen>>2);
      Pickle.ubyteToArray(byte0, msg, offset+0);
      /* 1     type of service       */
      msg[offset+1] = 0;
      /* 2-3   total length        */
      Pickle.ushortToArray(getSize(), msg, offset+2);  // TODO htons??
      /* 4-5   identification        */
      Pickle.ushortToArray(id, msg, offset+4);
      /* 6-7   fragment offset field       */
      Pickle.ushortToArray(fragOffset, msg, offset+6);
      /* 8     time to live        */
      msg[offset+8] = ttl;
      /* 9     protocol        */
      Pickle.ubyteToArray(protocol, msg, offset+9);
      /* 10-11 checksum        */
      //Pickle.ushortToArray(0, msg, offset+10);
      msg[offset+10] = 0;
      msg[offset+11] = 0;
      /* 12-15 source address        */
      Pickle.NetAddressToArray(src, msg, offset+12);
      /* 16-19 destination address       */
      Pickle.NetAddressToArray(dst, msg, offset+16);

      /* 10-11 checksum        */
      int checksum = checksum(msg, offset, headerLen);
      Pickle.ushortToArray(checksum, msg, offset+10);

      // TODO implement source route options
      if (null != srcRoute)
        throw new RuntimeException("IP options not implemented yet");

      if (null != payload)
        payload.getBytes(msg, offset + headerLen);
    }

    public void setBytes(byte[] msg, int offset) throws IllegalFormatException {
      frozen = false;

      /* 0     version == 4, header length        */
      int headerLen = (msg[offset+0] & 0x0F)<<2;
      if (headerLen != BASE_SIZE)
        throw new IllegalFormatException("IP options not implemented yet");
      /* 1     type of service       */
      //msg[offset+1] = 0;
      /* 2-3   total length        */
      /*int len = */Pickle.arrayToUShort(msg, offset+2);
      /* 4-5   identification        */
      id = (short)Pickle.arrayToUShort(msg, offset+4);
      /* 6-7   fragment offset field       */
      fragOffset = (short)Pickle.arrayToUShort(msg, offset+6);
      /* 8     time to live        */
      ttl = msg[offset+8];
      /* 9     protocol        */
      protocol = (short)Pickle.arrayToUByte(msg, offset+9);
      /* 12-15 source address        */
      src = new NetAddress(msg, offset+12);
      /* 16-19 destination address       */
      dst = new NetAddress(msg, offset+16);

      // TODO implement source route options
      srcRoute = null;

      switch (protocol) {
      case Constants.NET_PROTOCOL_TCP:
        payload = new TransTcp.TcpMessage(msg, offset+headerLen);
        break;
      case Constants.NET_PROTOCOL_UDP:
        payload = new TransUdp.UdpMessage(msg, offset+headerLen);
        break;
      case Constants.NET_PROTOCOL_AODV:
        payload = RouteAodv.fromBytes(msg, offset + headerLen);
        break;
      default:
        payload = new MessageBytes(msg, offset+headerLen, msg.length-offset-headerLen);
        break;
      }

//      if (Main.ASSERT) {
//        byte[] t1 = new byte[getSize()];
//        byte[] t2 = new byte[getSize()];
//        this.getBytes(t2, 0);
//        System.arraycopy(msg, offset, t1, 0, getSize());
//        Util.assertion(Arrays.equals(t1, t2));
  //
//        /* 10-11 checksum        */
//        //int chksum = Pickle.arrayToUShort(msg, offset+10);
//        int chksum = checksum(msg, offset, headerLen);
//        Util.assertion(0 == chksum);
//      }
    }

//    /**
//     * IP checksum algorithm.
//     * see RFC1071
//     * the checksum is the one's complement of the sum of 16-bit values summed up by one's complement arithmetic
//     *
//     * @param format a format object containing the buffer to checksum
//     *
//     * @return checksum value
//     *
//     */
//   public int checksum(byte[] msg, int offset, int length) {
//      // if the length is odd, we must read a byte at the end, not a short
//      boolean flag = (length % 2) != 0;
//      if (flag)
//        length--;
//
//      int sum = 0;
//      int i;
//      for (i = 0; i < length; i += 2) {
//        sum += Pickle.arrayToUShort(msg, offset + i);
//      }
//
//      // read the last byte if length is odd
//      if (flag) {
//        sum += msg[offset + i];
//      }
//      sum %= 0xffff;
//
//      // return the one's complement of the sum
//      return ((~sum) & 0x0000ffff);
//    }

   public int checksum(byte[] msg, int offset, int length) {
     // click port
     int nleft = length;
     int sum = 0;
     int i = 0;

     /*
       * Our algorithm is simple, using a 32 bit accumulator (sum), we add
       * sequential 16 bit words to it, and at the end, fold back all the
       * carry bits from the top 16 bits into the lower 16 bits.
       */
     while (nleft > 1)  {
       sum +=  Pickle.arrayToUShort(msg, offset + i);
       i += 2;
       nleft -= 2;
     }

     /* mop up an odd byte, if necessary */
     if (nleft == 1) {
       sum +=  Pickle.arrayToUByte(msg, offset + i);
     }

     /* add back carry outs from top 16 bits to low 16 bits */
     sum = (sum & 0xffff) + (sum >> 16);
     sum += (sum >> 16);
     /* guaranteed now that the lower 16 bits of sum are correct */

     // return the one's complement of the sum
     return ((~sum) & 0x0000ffff);
   }

  public int hashCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + ((dst == null) ? 0 : dst.hashCode());
    result = PRIME * result + fragOffset;
    result = PRIME * result + id;
    result = PRIME * result + ((payload == null) ? 0 : payload.hashCode());
    result = PRIME * result + protocol;
    result = PRIME * result + ((src == null) ? 0 : src.hashCode());
    result = PRIME * result + ((srcRoute == null) ? 0 : srcRoute.hashCode());
    return result;
  }

  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    final Ip other = (Ip) obj;
    if (dst == null) {
      if (other.dst != null)
        return false;
    } else if (!dst.equals(other.dst))
      return false;
    if (fragOffset != other.fragOffset)
      return false;
    if (id != other.id)
      return false;
    if (payload == null) {
      if (other.payload != null)
        return false;
    } else if (!payload.equals(other.payload))
      return false;
    if (priority != other.priority)
      return false;
    if (protocol != other.protocol)
      return false;
    if (src == null) {
      if (other.src != null)
        return false;
    } else if (!src.equals(other.src))
      return false;
    if (srcRoute == null) {
      if (other.srcRoute != null)
        return false;
    } else if (!srcRoute.equals(other.srcRoute))
      return false;
    if (ttl != other.ttl)
      return false;
    return true;
  }

  } // class: Ip


  /**
   * A generic IP packet option.
   */
  public abstract static class IpOption implements Message
  {
    /**
     * Return option type field.
     *
     * @return option type field
     */
    public abstract byte getType();

    /**
     * Return option length (in bytes/octets).
     *
     * @return option length (in bytes/octets)
     */
    public abstract int getSize();

  } // class: IpOption


  /**
   * An IP packet source route option.
   */
  public static class IpOptionSourceRoute extends IpOption
  {
    private static int hashCode(Object[] array) {
      final int PRIME = 31;
      if (array == null)
        return 0;
      int result = 1;
      for (int index = 0; index < array.length; index++) {
        result = PRIME * result + (array[index] == null ? 0 : array[index].hashCode());
      }
      return result;
    }

    /** option type constant: source route. */
    public static final byte TYPE = (byte)137;

    /** source route. */
    private final NetAddress[] route;
    /** source route pointer: index into route. */
    private final int ptr;

    /**
     * Create new source route option.
     *
     * @param route source route
     */
    public IpOptionSourceRoute(NetAddress[] route)
    {
      this(route, (byte)0);
    }

    /**
     * Create new source route option.
     *
     * @param route source route
     * @param ptr source route pointer
     */
    public IpOptionSourceRoute(NetAddress[] route, int ptr)
    {
      this.route = route;
      this.ptr = ptr;
    }

    /**
     * Return source route.
     *
     * @return source route (do not modify)
     */
    public NetAddress[] getRoute()
    {
      return route;
    }

    /**
     * Return source route pointer: index into route.
     *
     * @return source route pointer: index into route
     */
    public int getPtr()
    {
      return ptr;
    }

    /** {@inheritDoc} */
    public byte getType()
    {
      return TYPE;
    }

    /** {@inheritDoc} */
    public int getSize()
    {
      return (byte)(route.length*4 + 3);
    }

    /** {@inheritDoc} */
    public void getBytes(byte[] msg, int offset)
    {
      throw new RuntimeException("not implemented");
    }

    /** {@inheritDoc} */
    public String toString()
    {
      return ptr+":["+Util.stringJoin(route, ",")+"]";
    }

    public int hashCode() {
      final int PRIME = 31;
      int result = 1;
      result = PRIME * result + ptr;
      result = PRIME * result + IpOptionSourceRoute.hashCode(route);
      return result;
    }

    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      final IpOptionSourceRoute other = (IpOptionSourceRoute) obj;
      if (ptr != other.ptr)
        return false;
      if (!Arrays.equals(route, other.route))
        return false;
      return true;
    }

  } // class: IpOptionSourceRoute


} // class: NetMessage

/*
todo:
#define IP_MAXPACKET    65535       // maximum packet size
#define MAXTTL      255     // maximum time to live (seconds)
#define IPDEFTTL    64      // default ttl, from RFC 1340
#define IP_MSS      576     // default maximum segment size
*/

