/* -*- mode: c; c-file-style: "openbsd" -*- */
/*
 * Copyright (c) 2009 Vincent Bernat <bernat@luffy.cx>
 * Copyright (c) 2014 Michael Chapman
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifndef _FRAME_H
#define _FRAME_H

static union {
	uint8_t f_uint8;
	uint16_t f_uint16;
	uint32_t f_uint32;
} types;

/* This set of macro are used to build packets. The current position in buffer
 * is `pos'. The length of the remaining space in buffer is `length'. `type'
 * should be a member of `types'.
 *
 * This was stolen from ladvd which was adapted from Net::CDP. The original
 * author of those macros, Michael Chapman, has relicensed those macros under
 * the ISC license. */

#define POKE(value, type, func)                                                      \
  ((length >= sizeof(type)) &&                                                       \
      (type = func(value), memcpy(pos, &type, sizeof(type)), length -= sizeof(type), \
	  pos += sizeof(type), 1))
#define POKE_UINT8(value) POKE(value, types.f_uint8, )
#define POKE_UINT16(value) POKE(value, types.f_uint16, htons)
#define POKE_UINT32(value) POKE(value, types.f_uint32, htonl)
#define POKE_BYTES(value, bytes) \
  ((length >= (bytes)) &&        \
      (memcpy(pos, value, bytes), length -= (bytes), pos += (bytes), 1))
#define POKE_SAVE(where) (where = pos, 1)
#define POKE_RESTORE(where)      \
  do {                           \
    if ((where) > pos)           \
      length -= ((where)-pos);   \
    else                         \
      length += (pos - (where)); \
    pos = (where);               \
  } while (0)

/* This set of macro are used to parse packets. The same variable as for POKE_*
 * are used. There is no check on boundaries. */

#define PEEK(type, func)                                                          \
  (memcpy(&type, pos, sizeof(type)), length -= sizeof(type), pos += sizeof(type), \
      func(type))
#define PEEK_UINT8 PEEK(types.f_uint8, )
#define PEEK_UINT16 PEEK(types.f_uint16, ntohs)
#define PEEK_UINT32 PEEK(types.f_uint32, ntohl)
#define PEEK_BYTES(value, bytes) \
  do {                           \
    memcpy(value, pos, bytes);   \
    length -= (bytes);           \
    pos += (bytes);              \
  } while (0)
#define PEEK_DISCARD(bytes) \
  do {                      \
    length -= (bytes);      \
    pos += (bytes);         \
  } while (0)
#define PEEK_DISCARD_UINT8 PEEK_DISCARD(1)
#define PEEK_DISCARD_UINT16 PEEK_DISCARD(2)
#define PEEK_DISCARD_UINT32 PEEK_DISCARD(4)
#define PEEK_CMP(value, bytes) \
  (length -= (bytes), pos += (bytes), memcmp(pos - bytes, value, bytes))
#define PEEK_SAVE POKE_SAVE
#define PEEK_RESTORE POKE_RESTORE

/* LLDP specific. We need a `tlv' pointer. */
#define POKE_START_LLDP_TLV(type) (tlv = pos, POKE_UINT16(type << 9))
#define POKE_END_LLDP_TLV                                  \
  (memcpy(&types.f_uint16, tlv, sizeof(uint16_t)),         \
      types.f_uint16 |= htons((pos - (tlv + 2)) & 0x01ff), \
      memcpy(tlv, &types.f_uint16, sizeof(uint16_t)), 1)

/* Same for CDP */
#define POKE_START_CDP_TLV(type) ((void)POKE_UINT16(type), tlv = pos, POKE_UINT16(0))
#define POKE_END_CDP_TLV                  \
  (types.f_uint16 = htons(pos - tlv + 2), \
      memcpy(tlv, &types.f_uint16, sizeof(uint16_t)), 1)

/* Same for EDP */
#define POKE_START_EDP_TLV(type) \
  ((void)POKE_UINT8(EDP_TLV_MARKER), (void)POKE_UINT8(type), tlv = pos, POKE_UINT16(0))
#define POKE_END_EDP_TLV POKE_END_CDP_TLV

#endif /* _FRAME_H */
