//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <TransUdp.java Sun 2005/03/13 11:03:15 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.trans;

import jist.swans.mac.MacAddress;
import jist.swans.net.NetAddress;
import jist.swans.net.NetInterface;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.misc.MessageBytes;
import jist.swans.misc.Pickle;
import jist.swans.misc.Util;
import jist.swans.Constants;

import jist.runtime.JistAPI;
import jist.runtime.Main;

import org.apache.log4j.*;

import java.util.HashMap;

/**
 * UDP implementation. Does not perform fragmentation.
 *
 * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
 * @version $Id: TransUdp.java,v 1.30 2005/03/13 16:11:56 barr Exp $
 * @since SWANS1.0
 */
public class TransUdp extends AbstractTrans implements TransInterface.TransUdpInterface
{
  /**
   * UDP logger.
   */
  public static final Logger log = Logger.getLogger(TransUdp.class.getName());


  /**
   * UDP packet structure.
   * <code>
   *   UDP packet: (size = 8 + payload)
   *     src port         size: 2
   *     dst port         size: 2
   *     payload length   size: 2
   *     checksum         size: 2
   *     payload          size: variable
   * </code>
   */
  public static class UdpMessage extends TransMessage
  {
    //////////////////////////////////////////////////
    // constants
    //

    /**
     * UDP packet header size.
     */
    public static final int HEADER_SIZE = 8;

    //////////////////////////////////////////////////
    // locals
    //

    /**
     * packet source address.
     */
    private int srcPort;

    /**
     * packet destination address.
     */
    private int dstPort;

    /**
     * packet payload.
     */
    private Message payload;

    /**
     * packet checksum (if given)
     */
    private int checksum;

    //////////////////////////////////////////////////
    // initialization
    //

    /**
     * Create an new UDP packet.
     *
     * @param srcPort message source port
     * @param dstPort message destination port
     * @param payload message payload
     */
    public UdpMessage(int srcPort, int dstPort, Message payload)
    {
      this.srcPort = srcPort;
      this.dstPort = dstPort;
      this.payload = payload;
      this.checksum = 0;
    }

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

    public UdpMessage(byte[] data, int offset) {
      // 0-1: source port (unsigned short)
      srcPort = Pickle.arrayToUShort(data, offset + 0);
      // 2-3: destination port (unsigned short)
      dstPort = Pickle.arrayToUShort(data, offset + 2);
      // 4-5: UDP length
      int len = Pickle.arrayToUShort(data, offset + 4);
      // 6-7: checksum
      checksum = Pickle.arrayToUShort(data, offset + 6);

      // check len
      if (Main.ASSERT)
        Util.assertion(len == data.length - offset);

      // payload
      int payloadLen = len - HEADER_SIZE;
      byte[] temp = new byte [payloadLen];
      System.arraycopy(data, offset + HEADER_SIZE, temp, 0, payloadLen);
      payload = new MessageBytes(temp);
    }

    /**
     * Return packet source port.
     *
     * @return packet source port
     */
    public int getSrcPort()
    {
      return srcPort;
    }

    /**
     * Return packet destination port.
     *
     * @return packet destination port
     */
    public int getDstPort()
    {
      return dstPort;
    }

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

    /** {@inheritDoc} */
    public String toString()
    {
      return "udp(srcPort="+srcPort+" dstPort="+dstPort+" data="+payload+")";
    }

    /** {@inheritDoc} */
    public int getSize()
    {
      return HEADER_SIZE + payload.getSize();
    }

    /** {@inheritDoc} */
    public void getBytes(byte[] msg, int offset)
    {
      // source port (unsigned short)
      Pickle.ushortToArray(srcPort, msg, offset);
      // destination port (unsigned short)
      Pickle.ushortToArray(dstPort, msg, offset+2);
      // payload length (unsigned short)
      Pickle.ushortToArray(payload.getSize()+HEADER_SIZE, msg, offset+4);
      // checksum (unsigned short)
      Pickle.ushortToArray(checksum, msg, offset+6);
      // payload
      payload.getBytes(msg, offset+8);
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = 1;
      result = PRIME * result + checksum;
      result = PRIME * result + dstPort;
      result = PRIME * result + ((payload == null) ? 0 : payload.hashCode());
      result = PRIME * result + srcPort;
      return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      final UdpMessage other = (UdpMessage) obj;
      if (checksum != other.checksum)
        return false;
      if (dstPort != other.dstPort)
        return false;
      if (payload == null) {
        if (other.payload != null)
          return false;
      } else if (!payload.equals(other.payload))
        return false;
      if (srcPort != other.srcPort)
        return false;
      return true;
    }

  } // class: UdpMessage


  //////////////////////////////////////////////////
  // locals
  //

  /** network layer entity. */
  private NetInterface netEntity;
  /** self-referencing proxy entity. */
  private TransInterface.TransUdpInterface self;

  /** socket handlers. */
  private HashMap handlers; // Integer(port) -> SocketHandler

  private NetAddress localAddr;

  //////////////////////////////////////////////////
  // initialize
  //

  /**
   * Initialize UDP transport implementation.
   */
  public TransUdp()
  {
    self = (TransInterface.TransUdpInterface)JistAPI.proxy(
        this, TransInterface.TransUdpInterface.class);
    handlers = new HashMap();
  }

  //////////////////////////////////////////////////
  // hookup entities
  //

  /**
   * Return self-referencing proxy entity.
   *
   * @return self-referencing proxy entity
   */
  public TransInterface.TransUdpInterface getProxy()
  {
    return self;
  }

  public TransInterface getTransProxy() {
    return self;
  }

  /**
   * Set network layer entity.
   *
   * @param netEntity network layer entity
   */
  public void setNetEntity(NetInterface netEntity)
  {
    if(!JistAPI.isEntity(netEntity)) throw new IllegalArgumentException("expected entity");
    this.netEntity = netEntity;
  }

  /** {@inheritDoc} */
  public void addSocketHandler(int port, SocketHandler handler)
  {
    if(log.isInfoEnabled())
    {
      log.info("add socket handler: "+port);
    }
    handlers.put(new Integer(port), handler);
  }

  /** {@inheritDoc} */
  public void delSocketHandler(int port)
  {
    if(log.isInfoEnabled())
    {
      log.info("delete socket handler: "+port);
    }
    handlers.remove(new Integer(port));
  }

  //////////////////////////////////////////////////
  // send/receive
  //

  /** {@inheritDoc} */
  public void send(Message msg, MessageAnno anno, NetAddress dst, int dstPort,
      int srcPort, byte priority)
  {
    UdpMessage udp = new UdpMessage(srcPort, dstPort, msg);
    if (null != anno)
      anno.put(MessageAnno.ANNO_TRANS_TXTIME, JistAPI.getTime());

    if (this.segmentSentEvent.isActive()) {
      if (null == localAddr)
        localAddr = netEntity.getAddress();
      this.segmentSentEvent.handle(msg, anno, localAddr, srcPort, dst, dstPort);
    }

    JistAPI.sleep(Constants.TRANS_DELAY);
    if(log.isInfoEnabled())
    {
      log.info(getNode().getNodeId() + " send: t="+JistAPI.getTime()+" dst="+dst+":"+dstPort+" srcPort="+srcPort+" data="+msg);
    }
    netEntity.send(udp, dst, Constants.NET_PROTOCOL_UDP,
        priority, Constants.TTL_DEFAULT, anno);
  }

  /** {@inheritDoc} */
  public void receive(Message msg, NetAddress src, MacAddress lastHop,
      byte macId, NetAddress dst, byte priority, byte ttl, MessageAnno anno)
  {
    UdpMessage udp = (UdpMessage)msg;
    int dstPort = udp.getDstPort();
    SocketHandler handler =
      (SocketHandler)handlers.get(new Integer(dstPort));
    if(handler==null)
      return;

    JistAPI.sleep(Constants.TRANS_DELAY);

    msg = udp.getPayload();
    int srcPort = udp.getSrcPort();
    if(log.isInfoEnabled())
      log.info(getNode().getNodeId() + " receive: t="+JistAPI.getTime()+" src="+src+":"+srcPort+" dstPort="+dstPort+" from="+lastHop+" data="+msg);

    if (this.segmentArrivedEvent.isActive())
      this.segmentArrivedEvent.handle(msg, anno, src, srcPort, dst, dstPort);

    handler.receive(msg, anno, src, srcPort);
  }

} // class: TransUdp

