/*
 * "Copyright (c) 2008 The Regents of the University  of California.
 * All rights reserved."
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without written agreement is
 * hereby granted, provided that the above copyright notice, the following
 * two paragraphs and the author appear in all copies of this software.
 *
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
 * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
 *
 */
#include "6lowpan.h"
#include "ip.h"
#include "lib6lowpan.h"


#ifndef __6LOWPAN_16BIT_ADDRESS
#error "Only 16-bit short addresses are supported"
#endif

/*
 *  Library implementation of packing of 6lowpan packets.  
 *
 *  This should allow uniform code treatment between pc and mote code;
 *  the goal is to write ANSI C here...  This means no nx_ types,
 *  unfortunately.
 *
 *  Accessing fields programtically is probably a little less
 *  efficient, but that can be improved.  By precomputing the packet
 *  headers present, we can make the overhead not too bad.  The #1
 *  goal of this library is portability and readability.
 *
 *  The broadcast and mesh headers may or may not be useful, and are
 *  off by default to reduce code size.  Removing them reduces the
 *  library size by about 600 bytes.
 */


/*
 * Return the length (in bytes) of the buffer required to pack lowmsg
 * into a buffer.
 */
inline uint8_t *getLowpanPayload(packed_lowmsg_t *lowmsg) {
  uint8_t len = 0;
#if LIB6LOWPAN_FULL
  if (lowmsg->headers & LOWMSG_MESH_HDR)
    len += LOWMSG_MESH_LEN;
  if (lowmsg->headers & LOWMSG_BCAST_HDR)
    len += LOWMSG_BCAST_LEN;
#endif
  if (lowmsg->headers & LOWMSG_FRAG1_HDR)
    len += LOWMSG_FRAG1_LEN;
  if (lowmsg->headers & LOWMSG_FRAGN_HDR)
    len += LOWMSG_FRAGN_LEN;
  return lowmsg->data + len;
}

/*
 * Return a bitmap indicating which lowpan headers are
 *  present in the message pointed to by lowmsg.
 *
 */
inline uint16_t getHeaderBitmap(packed_lowmsg_t *lowmsg) {
  uint16_t headers = 0;
  uint8_t *buf = lowmsg->data;
  uint16_t len = lowmsg->len;
  if (buf == NULL) return headers;

  if (len > 0 && ((*buf) >> 6) == LOWPAN_NALP_PATTERN) {
    return LOWMSG_NALP;
  }
  
#if LIB6LOWPAN_FULL
  if (len > 0 && ((*buf) >> 6) == LOWPAN_MESH_PATTERN) {
    if (!(*buf & LOWPAN_MESH_V_MASK) ||
        !(*buf & LOWPAN_MESH_F_MASK)) {
      // we will not parse a packet with 64-bit addressing.
      return LOWMSG_NALP;
    }
    headers |= LOWMSG_MESH_HDR;
    buf += LOWMSG_MESH_LEN;
    len -= LOWMSG_MESH_LEN;
  }
  if (len > 0 && (*buf) == LOWPAN_BCAST_PATTERN) {
    headers |= LOWMSG_BCAST_HDR;
    buf += LOWMSG_BCAST_LEN;
    len -= LOWMSG_BCAST_LEN;
  }
#endif 

  if (len > 0 && ((*buf) >> 3) == LOWPAN_FRAG1_PATTERN) {
    headers |= LOWMSG_FRAG1_HDR;
    buf += LOWMSG_FRAG1_LEN;
    len -= LOWMSG_FRAG1_LEN;
  }
  if (len > 0 && ((*buf) >> 3) == LOWPAN_FRAGN_PATTERN) {
    headers |= LOWMSG_FRAGN_HDR;
    buf += LOWMSG_FRAGN_LEN;
    len -= LOWMSG_FRAGN_LEN;
  }
  return headers;
}

/*
 * Fill in dispatch values
 */
inline uint8_t setupHeaders(packed_lowmsg_t *packed, uint16_t headers) {
  uint8_t *buf = packed->data;
  uint16_t len = packed->len;
  if (packed == NULL) return 1;
  if (buf == NULL) return 1;
  packed->headers = 0;
#if LIB6LOWPAN_FULL
  if (headers & LOWMSG_MESH_HDR)  {
    if (len < LOWMSG_MESH_LEN) return 1;
    packed->headers |= LOWMSG_MESH_HDR;
    *buf = LOWPAN_MESH_PATTERN << 6 | LOWPAN_MESH_V_MASK | LOWPAN_MESH_F_MASK;
    buf += LOWMSG_MESH_LEN;
    len -= LOWMSG_MESH_LEN;
  }
  if (headers & LOWMSG_BCAST_HDR) {
    if (len < LOWMSG_BCAST_LEN) return 1;
    packed->headers |= LOWMSG_BCAST_HDR;
    *buf = LOWPAN_BCAST_PATTERN;
    buf += LOWMSG_BCAST_LEN;
    len -= LOWMSG_BCAST_LEN;
  }
#endif
  if (headers & LOWMSG_FRAG1_HDR) {
    if (len < LOWMSG_FRAG1_HDR) return 1;
    packed->headers |= LOWMSG_FRAG1_HDR;
    *buf = LOWPAN_FRAG1_PATTERN << 3;
    buf += LOWMSG_FRAG1_LEN;
    len -= LOWMSG_FRAG1_LEN;
  }
  if (headers & LOWMSG_FRAGN_HDR) {
    if (len < LOWMSG_FRAGN_HDR) return 1;
    packed->headers |= LOWMSG_FRAGN_HDR;
    *buf = LOWPAN_FRAGN_PATTERN << 3;
  }
  return 0;
  
}

/*
 * Test if various headers are present are enabled
 */
#ifdef LIB6LOWPAN_FULL
inline uint8_t hasMeshHeader(packed_lowmsg_t *msg) {
  return (msg->headers & LOWMSG_MESH_HDR);
}
inline uint8_t hasBcastHeader(packed_lowmsg_t *msg) {
  return (msg->headers & LOWMSG_BCAST_HDR);
}
#endif
inline uint8_t hasFrag1Header(packed_lowmsg_t *msg) {
  return (msg->headers & LOWMSG_FRAG1_HDR);
}
inline uint8_t hasFragNHeader(packed_lowmsg_t *msg) {
  return (msg->headers & LOWMSG_FRAGN_HDR);
}
#ifdef LIB6LOWPAN_FULL
/*
 * Mesh header fields
 *
 *  return FAIL if the message doesn't have a mesh header
 */
inline uint8_t getMeshHopsLeft(packed_lowmsg_t *msg, uint8_t *hops) {
  uint8_t *buf = msg->data;
  if (!hasMeshHeader(msg) || msg->data == NULL || hops == NULL) return 1;
  *hops = (*buf) & LOWPAN_MESH_HOPS_MASK;
  return 0;
}
inline uint8_t getMeshOriginAddr(packed_lowmsg_t *msg, ieee154_saddr_t *origin) {
  uint8_t *buf = msg->data;
  if (!hasMeshHeader(msg) || msg->data == NULL || origin == NULL) return 1;
  // skip 64-bit addresses
  if (!(*buf & LOWPAN_MESH_V_MASK)) return 1;
  buf += 1;
  *origin = ntoh16(*((uint16_t *)buf));
  return 0;
}
inline uint8_t getMeshFinalAddr(packed_lowmsg_t *msg, ieee154_saddr_t *final) {
  uint8_t *buf = msg->data;
  if (!hasMeshHeader(msg) || msg->data == NULL || final == NULL) return 1;
  // skip 64-bit addresses
  if (!(*buf & LOWPAN_MESH_F_MASK)) return 1;
  buf += 3;
  *final = ntoh16(*((uint16_t *)buf));
  return 0;
}


inline uint8_t setMeshHopsLeft(packed_lowmsg_t *msg, uint8_t hops) {
  uint8_t *buf = msg->data;
  if (!hasMeshHeader(msg) || msg->data == NULL) return 1;
  
  *buf = 0xb0;
  *buf |= hops & LOWPAN_MESH_HOPS_MASK;
  return 0;
}
inline uint8_t setMeshOriginAddr(packed_lowmsg_t *msg, ieee154_saddr_t origin) {
  uint8_t *buf = msg->data;
  if (!hasMeshHeader(msg) || msg->data == NULL) return 1;
  // skip 64-bit addresses
  if (!(*buf & LOWPAN_MESH_V_MASK)) return 1;
  buf += 1;
  *((uint16_t *)buf) = hton16(origin);
  return 0;
}
inline uint8_t setMeshFinalAddr(packed_lowmsg_t *msg, ieee154_saddr_t final) {
  uint8_t *buf = msg->data;
  if (!hasMeshHeader(msg) || msg->data == NULL) return 1;
  // skip 64-bit addresses
  if (!(*buf & LOWPAN_MESH_F_MASK)) return 1;
  buf += 3;
  *((uint16_t *)buf) = hton16(final);
  return 0;
}
 
/*
 * Broadcast header fields
 */
inline uint8_t getBcastSeqno(packed_lowmsg_t *msg, uint8_t *seqno) {
  uint8_t *buf = msg->data;
  if (buf == NULL || seqno == NULL || !hasBcastHeader(msg)) return 1;
  if (hasMeshHeader(msg)) buf += LOWMSG_MESH_LEN;
  if (*buf != LOWPAN_BCAST_PATTERN) return 2;
  buf += 1;
  *seqno = *buf;
  return 0;
}

inline uint8_t setBcastSeqno(packed_lowmsg_t *msg, uint8_t seqno) {
  uint8_t *buf = msg->data;
  if (buf == NULL || !hasBcastHeader(msg)) return 1;
  if (hasMeshHeader(msg)) buf += LOWMSG_MESH_LEN;
  if (*buf != LOWPAN_BCAST_PATTERN) return 2;
  buf += 1;
  *buf = seqno;
  return 0;
}
#endif

/*
 * Fragmentation header fields
 */
inline uint8_t getFragDgramSize(packed_lowmsg_t *msg, uint16_t *size) {
  uint8_t *buf = msg->data;
  uint8_t s[2];
  if (buf == NULL || size == NULL) return 1;
#ifdef LIB6LOWPAN_FULL
  if (hasMeshHeader(msg)) buf += LOWMSG_MESH_LEN;
  if (hasBcastHeader(msg)) buf += LOWMSG_BCAST_LEN;
#endif
  if ((*buf >> 3) != LOWPAN_FRAG1_PATTERN &&
      (*buf >> 3) != LOWPAN_FRAGN_PATTERN) return 1;

  s[0] = *buf & 0x7;
  buf++;
  s[1] = *buf;
  *size = ntoh16 ( *(uint16_t *)s);
  return 0;
}
inline uint8_t getFragDgramTag(packed_lowmsg_t *msg, uint16_t *tag) {
  uint8_t *buf = msg->data;
  if (buf == NULL || tag == NULL) return 1;
#ifdef LIB6LOWPAN_FULL
  if (hasMeshHeader(msg)) buf += LOWMSG_MESH_LEN;
  if (hasBcastHeader(msg)) buf += LOWMSG_BCAST_LEN;
#endif
  if ((*buf >> 3) != LOWPAN_FRAG1_PATTERN &&
      (*buf >> 3) != LOWPAN_FRAGN_PATTERN) return 1;
  buf += 2;
  //*tag = (*buf << 8) | *(buf + 1);  ;
  *tag = ntoh16( *(uint16_t *)buf);
  return 0;
}
inline uint8_t getFragDgramOffset(packed_lowmsg_t *msg, uint8_t *size) {
  uint8_t *buf = msg->data;
  if (buf == NULL || size == NULL) return 1;
#ifdef LIB6LOWPAN_FULL
  if (hasMeshHeader(msg)) buf += LOWMSG_MESH_LEN;
  if (hasBcastHeader(msg)) buf += LOWMSG_BCAST_LEN;
#endif
  if ((*buf >> 3) != LOWPAN_FRAGN_PATTERN) return 1;
  buf += 4;
  *size = *buf;
  return 0;

}


inline uint8_t setFragDgramSize(packed_lowmsg_t *msg, uint16_t size) {
  uint8_t *buf = msg->data;
  if (buf == NULL) return 1;
#ifdef LIB6LOWPAN_FULL
  if (hasMeshHeader(msg)) buf += LOWMSG_MESH_LEN;
  if (hasBcastHeader(msg)) buf += LOWMSG_BCAST_LEN;
#endif
  if ((*buf >> 3) != LOWPAN_FRAG1_PATTERN &&
      (*buf >> 3) != LOWPAN_FRAGN_PATTERN) return 1;
  // zero out the dgram size first.
  *buf &= 0xf8;
  *(buf + 1) = 0;
  *((uint16_t *)buf) |= hton16(size & 0x7ff);
  return 0;
}

inline uint8_t setFragDgramTag(packed_lowmsg_t *msg, uint16_t tag) {
  uint8_t *buf = msg->data;
  if (buf == NULL) return 1;
#ifdef LIB6LOWPAN_FULL
  if (hasMeshHeader(msg)) buf += LOWMSG_MESH_LEN;
  if (hasBcastHeader(msg)) buf += LOWMSG_BCAST_LEN;
#endif
  
  if ((*buf >> 3) != LOWPAN_FRAG1_PATTERN &&
      (*buf >> 3) != LOWPAN_FRAGN_PATTERN) return 1;
  buf += 2;
  *(uint16_t *)buf = ntoh16(tag);
  return 0;
}
inline uint8_t setFragDgramOffset(packed_lowmsg_t *msg, uint8_t size) {
  uint8_t *buf = msg->data;
  if (buf == NULL) return 1;
#ifdef LIB6LOWPAN_FULL
  if (hasMeshHeader(msg)) buf += LOWMSG_MESH_LEN;
  if (hasBcastHeader(msg)) buf += LOWMSG_BCAST_LEN;
#endif

  if ((*buf >> 3) != LOWPAN_FRAGN_PATTERN) return 1;
  buf += 4;
  *buf = size;
  return 0;
}
