457 lines
15 KiB
C
457 lines
15 KiB
C
/*********************************************************************
|
|
PicoTCP. Copyright (c) 2012-2017 Altran Intelligent Systems. Some rights
|
|
reserved. See LICENSE and COPYING for usage.
|
|
|
|
Authors: Jelle De Vleeschouwer
|
|
*********************************************************************/
|
|
|
|
#include "pico_stack.h"
|
|
#include "pico_frame.h"
|
|
#include "pico_802154.h"
|
|
#include "pico_6lowpan.h"
|
|
#include "pico_protocol.h"
|
|
#include "pico_addressing.h"
|
|
#include "pico_6lowpan_ll.h"
|
|
|
|
#ifdef PICO_SUPPORT_802154
|
|
|
|
/*******************************************************************************
|
|
* Macros
|
|
******************************************************************************/
|
|
|
|
#define PICO_802154_VALID(am) ((am) == 2 || (am) == 3 ? 1 : 0)
|
|
|
|
/*******************************************************************************
|
|
* Constants
|
|
******************************************************************************/
|
|
|
|
/* Frame type definitions */
|
|
#define FCF_TYPE_BEACON (short_be(0x0000u))
|
|
#define FCF_TYPE_DATA (short_be(0x0001u))
|
|
#define FCF_TYPE_ACK (short_be(0x0002u))
|
|
#define FCF_TYPE_CMD (short_be(0x0003u))
|
|
|
|
/* Frame version definitions */
|
|
#define FCF_VER_2003 (short_be(0x0000u))
|
|
#define FCF_VER_2006 (short_be(0x1000u))
|
|
#define FCF_SEC (short_be(0x0008u))
|
|
#define FCF_NO_SEC (short_be(0x0000u))
|
|
#define FCF_PENDING (short_be(0x0010u))
|
|
#define FCF_NO_PENDING (short_be(0x0000u))
|
|
#define FCF_ACK_REQ (short_be(0x0020u))
|
|
#define FCF_NO_ACK_REQ (short_be(0x0000u))
|
|
#define FCF_INTRA_PAN (short_be(0x0040u))
|
|
#define FCF_INTER_PAN (short_be(0x0000u))
|
|
|
|
/* Commonly used addresses */
|
|
#define ADDR_802154_BCAST (short_be(0xFFFFu))
|
|
#define ADDR_802154_UNSPEC (short_be(0xFFFEu))
|
|
|
|
#ifndef PICO_6LOWPAN_NOMAC
|
|
|
|
/*******************************************************************************
|
|
* ENDIANNESS
|
|
******************************************************************************/
|
|
|
|
/* Swaps the two 8-bit values, the pointer A and B point at */
|
|
static void pico_swap(uint8_t *a, uint8_t *b)
|
|
{
|
|
*a = *a ^ *b;
|
|
*b = *a ^ *b;
|
|
*a = *a ^ *b;
|
|
}
|
|
|
|
/* Converts an IEEE802.15.4 address, which is little endian by standard, to
|
|
* IETF-endianness, which is big endian. */
|
|
static void
|
|
addr_802154_to_ietf(struct pico_802154 *addr)
|
|
{
|
|
int32_t i = 0;
|
|
int32_t end = SIZE_6LOWPAN(addr->mode) - 1;
|
|
for (i = 0; i < (int32_t)((uint8_t)SIZE_6LOWPAN(addr->mode) >> 1); i++) {
|
|
pico_swap(&addr->addr.data[i], &addr->addr.data[end - i]);
|
|
}
|
|
}
|
|
|
|
/* Converts an IEE802.15.4 address in IETF format, which is used to form the IID
|
|
* of the host's IPv6 addresses, back to IEEE-endianess, which is little
|
|
* endian. */
|
|
static void
|
|
addr_802154_to_ieee(struct pico_802154 *addr)
|
|
{
|
|
addr_802154_to_ietf(addr);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* FRAME
|
|
******************************************************************************/
|
|
|
|
/* Retrieves the addressing mode of the destination address from the MHR's frame
|
|
* control field. */
|
|
static uint8_t
|
|
dst_am(struct pico_802154_hdr *hdr)
|
|
{
|
|
return (uint8_t)((hdr->fcf >> 10) & 0x3);
|
|
}
|
|
|
|
/* Retrieves the addressing mode of the source address from the MHR's frame
|
|
* control field */
|
|
static uint8_t
|
|
src_am(struct pico_802154_hdr *hdr)
|
|
{
|
|
return (uint8_t)((hdr->fcf >> 14) & 0x3);
|
|
}
|
|
|
|
/* Determines the size of an IEEE802.15.4-header, based on the addressing
|
|
* modes */
|
|
static uint8_t
|
|
frame_802154_hdr_len(struct pico_802154_hdr *hdr)
|
|
{
|
|
return (uint8_t)(SIZE_802154_MHR_MIN + SIZE_6LOWPAN(src_am(hdr)) + SIZE_6LOWPAN(dst_am(hdr)));
|
|
}
|
|
|
|
/* Gets the source address out of a mapped IEEE802.15.4-frame, converts it
|
|
* to host endianess */
|
|
static struct pico_802154
|
|
frame_802154_src(struct pico_802154_hdr *hdr)
|
|
{
|
|
struct pico_802154 src = { .addr.data = { 0 }, .mode = src_am(hdr) };
|
|
uint8_t *addresses = (uint8_t *)hdr + sizeof(struct pico_802154_hdr);
|
|
uint16_t len = SIZE_6LOWPAN(src.mode);
|
|
memcpy(src.addr.data, addresses + SIZE_6LOWPAN(dst_am(hdr)), len);
|
|
addr_802154_to_ietf(&src);
|
|
return src;
|
|
}
|
|
|
|
/* Gets the destination address out of a mapped IEEE802.15.4-frame, converts
|
|
* it to host endianess */
|
|
static struct pico_802154
|
|
frame_802154_dst(struct pico_802154_hdr *hdr)
|
|
{
|
|
struct pico_802154 dst = { .addr.data = { 0 }, .mode = dst_am(hdr) };
|
|
uint8_t *addresses = (uint8_t *)hdr + sizeof(struct pico_802154_hdr);
|
|
uint16_t len = SIZE_6LOWPAN(dst.mode);
|
|
memcpy(dst.addr.data, addresses, len);
|
|
addr_802154_to_ietf(&dst);
|
|
return dst;
|
|
}
|
|
|
|
/* Maps a 802.15.4 frame structure onto a flat buffer, fills in the entire
|
|
* header and set the payload pointer right after the MHR. */
|
|
static void
|
|
frame_802154_format(uint8_t *buf, uint8_t seq, uint16_t intra_pan, uint16_t ack,
|
|
uint16_t sec, struct pico_6lowpan_short pan, struct pico_802154 src,
|
|
struct pico_802154 dst)
|
|
{
|
|
uint8_t *addresses = (uint8_t *)(buf + sizeof(struct pico_802154_hdr));
|
|
struct pico_802154_hdr *hdr = (struct pico_802154_hdr *)buf;
|
|
uint16_t sam = 0, dam = 0;
|
|
|
|
hdr->fcf = 0; /* Clear out control field */
|
|
intra_pan = (uint16_t)(intra_pan & FCF_INTRA_PAN);
|
|
ack = (uint16_t)(ack & FCF_ACK_REQ);
|
|
sec = (uint16_t)(sec & FCF_SEC);
|
|
dam = short_be((uint16_t)(dst.mode << 10));
|
|
sam = short_be((uint16_t)(src.mode << 14));
|
|
|
|
/* Fill in frame control field */
|
|
hdr->fcf |= (uint16_t)(FCF_TYPE_DATA | sec );
|
|
hdr->fcf |= (uint16_t)(FCF_NO_PENDING | ack);
|
|
hdr->fcf |= (uint16_t)(intra_pan | dam | FCF_VER_2003);
|
|
hdr->fcf |= (uint16_t)(sam);
|
|
hdr->fcf = short_be(hdr->fcf); // Convert to IEEE endianness
|
|
|
|
hdr->seq = seq; // Sequence number
|
|
|
|
/* Convert addresses to IEEE-endianness */
|
|
pan.addr = short_be(pan.addr);
|
|
addr_802154_to_ieee(&src);
|
|
addr_802154_to_ieee(&dst);
|
|
|
|
/* Fill in the addresses */
|
|
memcpy(&hdr->pan_id, &pan.addr, SIZE_6LOWPAN_SHORT);
|
|
memcpy(addresses, dst.addr.data, SIZE_6LOWPAN(dst.mode));
|
|
memcpy(addresses + SIZE_6LOWPAN(dst.mode), src.addr.data,SIZE_6LOWPAN(src.mode));
|
|
}
|
|
|
|
#endif /* PICO_6LOWPAN_NOMAC */
|
|
|
|
/* Removes the IEEE802.15.4 MAC header before the frame */
|
|
static int32_t
|
|
pico_802154_process_in(struct pico_frame *f)
|
|
{
|
|
#ifndef PICO_6LOWPAN_NOMAC
|
|
struct pico_802154_hdr *hdr = (struct pico_802154_hdr *)f->net_hdr;
|
|
uint16_t fcf = short_be(hdr->fcf);
|
|
uint8_t hlen = 0;
|
|
f->src.pan = frame_802154_src(hdr);
|
|
f->dst.pan = frame_802154_dst(hdr);
|
|
|
|
/* I claim the datalink header */
|
|
f->datalink_hdr = f->net_hdr;
|
|
|
|
if (fcf & FCF_SEC) {
|
|
f->flags |= PICO_FRAME_FLAG_LL_SEC;
|
|
}
|
|
|
|
hlen = frame_802154_hdr_len(hdr);
|
|
|
|
/* XXX: Generic procedure to move forward in incoming processing function
|
|
* is updating the net_hdr-pointer */
|
|
f->net_hdr = f->datalink_hdr + (int32_t)hlen;
|
|
|
|
return (int32_t)hlen;
|
|
#else
|
|
IGNORE_PARAMETER(f);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/* Prepends the IEEE802.15.4 MAC header before the frame */
|
|
static int32_t
|
|
pico_802154_process_out(struct pico_frame *f)
|
|
{
|
|
#ifndef PICO_6LOWPAN_NOMAC
|
|
int32_t len = (int32_t)(SIZE_802154_MHR_MIN + SIZE_6LOWPAN(f->dst.pan.mode) + SIZE_6LOWPAN(f->src.pan.mode));
|
|
uint8_t sec = (uint8_t)((f->flags & PICO_FRAME_FLAG_LL_SEC) ? (FCF_SEC) : (FCF_NO_SEC));
|
|
struct pico_6lowpan_info *info = (struct pico_6lowpan_info *)f->dev->eth;
|
|
uint16_t headroom = (uint16_t)(f->net_hdr - f->buffer);
|
|
static uint8_t seq = 0;
|
|
uint32_t grow = 0;
|
|
int32_t ret = 0;
|
|
|
|
if (headroom < (uint16_t)len) { /* Check if there's enough headroom to prepend 802.15.4 header */
|
|
grow = (uint32_t)(len - headroom);
|
|
ret = pico_frame_grow_head(f, (uint32_t)(f->buffer_len + grow));
|
|
if (ret) {
|
|
pico_frame_discard(f);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* XXX: General procedure to seek backward in an outgoing processing function
|
|
* is to update the datalink_hdr */
|
|
f->datalink_hdr = f->datalink_hdr - len;
|
|
|
|
/* Format the IEEE802.15.4 header */
|
|
frame_802154_format(f->datalink_hdr, seq++, FCF_INTRA_PAN, FCF_NO_ACK_REQ, sec, info->pan_id, f->src.pan, f->dst.pan);
|
|
return len;
|
|
#else
|
|
IGNORE_PARAMETER(f);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/* Get the EUI-64 of the device in a structured form */
|
|
static struct pico_802154
|
|
addr_802154_ext_dev(struct pico_6lowpan_info *info)
|
|
{
|
|
struct pico_802154 addr;
|
|
memcpy(addr.addr.data, info->addr_ext.addr, SIZE_6LOWPAN_EXT);
|
|
addr.mode = AM_6LOWPAN_EXT;
|
|
return addr;
|
|
}
|
|
|
|
/* Get the short address of the device in a structured form */
|
|
static struct pico_802154
|
|
addr_802154_short_dev(struct pico_6lowpan_info *info)
|
|
{
|
|
struct pico_802154 addr;
|
|
memcpy(addr.addr.data, (uint8_t *)&(info->addr_short.addr), SIZE_6LOWPAN_SHORT);
|
|
addr.mode = AM_6LOWPAN_SHORT;
|
|
return addr;
|
|
}
|
|
|
|
/* Based on the source IPv6-address, this function derives the link layer source
|
|
* address */
|
|
static struct pico_802154
|
|
addr_802154_ll_src(struct pico_frame *f)
|
|
{
|
|
struct pico_ip6 src = ((struct pico_ipv6_hdr *)f->net_hdr)->src;
|
|
if (IID_16(&src.addr[8])) {
|
|
/* IPv6 source is derived from the device's short address, use that
|
|
* short address so decompressor can derive the IPv6 source from
|
|
* the encapsulating header */
|
|
return addr_802154_short_dev((struct pico_6lowpan_info *)f->dev->eth);
|
|
} else {
|
|
/* IPv6 source is derived from the device's extended address, use
|
|
* the device's extended address so */
|
|
return addr_802154_ext_dev((struct pico_6lowpan_info *)f->dev->eth);
|
|
}
|
|
}
|
|
|
|
/* Based on the destination IPv6-address, this function derives the link layer
|
|
* destination address */
|
|
static struct pico_802154
|
|
addr_802154_ll_dst(struct pico_frame *f)
|
|
{
|
|
struct pico_ip6 dst = ((struct pico_ipv6_hdr *)f->net_hdr)->dst;
|
|
struct pico_802154 addr = { .addr.data = { 0 }, .mode = 0 };
|
|
addr.mode = AM_6LOWPAN_NONE;
|
|
|
|
/* If the address is multicast use 802.15.4 BCAST address 0xFFFF */
|
|
if (pico_ipv6_is_multicast(dst.addr)) {
|
|
addr.addr._short.addr = short_be(ADDR_802154_BCAST);
|
|
addr.mode = AM_6LOWPAN_SHORT;
|
|
}
|
|
/* If the address is link local derive the link layer address from the IID */
|
|
else { // if (pico_ipv6_is_linklocal(dst.addr)) {
|
|
if (IID_16(&dst.addr[8])) {
|
|
addr.addr.data[0] = dst.addr[14];
|
|
addr.addr.data[1] = dst.addr[15];
|
|
addr.mode = AM_6LOWPAN_SHORT;
|
|
} else {
|
|
memcpy(addr.addr.data, &dst.addr[8], SIZE_6LOWPAN_EXT);
|
|
addr.addr.data[0] = (uint8_t)(addr.addr.data[0] ^ 0x02);
|
|
addr.mode = AM_6LOWPAN_EXT;
|
|
}
|
|
}
|
|
/*
|
|
else {
|
|
struct pico_802154 *n = (struct pico_802154 *)pico_ipv6_get_neighbor(f);
|
|
if (n) {
|
|
memcpy(addr.addr.data, n->addr.data, SIZE_6LOWPAN(n->mode));
|
|
addr.mode = n->mode;
|
|
} else {
|
|
pico_ipv6_nd_postpone(f);
|
|
}
|
|
}
|
|
*/
|
|
return addr;
|
|
}
|
|
|
|
/* Estimates the size the MAC header would be based on the source and destination
|
|
* link layer address */
|
|
static int32_t
|
|
pico_802154_estimator(struct pico_frame *f)
|
|
{
|
|
return (int32_t)(SIZE_802154_MHR_MIN + SIZE_6LOWPAN(f->src.pan.mode) + SIZE_6LOWPAN(f->dst.pan.mode) + f->dev->overhead);
|
|
}
|
|
|
|
/* Retrieve address from temporarily flat buffer */
|
|
static int32_t
|
|
addr_802154_from_buf(union pico_ll_addr *addr, uint8_t *buf)
|
|
{
|
|
uint8_t len = (uint8_t)*buf++;
|
|
|
|
if (len > 8) // OOB check
|
|
return -1;
|
|
|
|
memcpy(addr->pan.addr.data, buf, len);
|
|
if (SIZE_6LOWPAN_EXT == len)
|
|
addr->pan.mode = AM_6LOWPAN_EXT;
|
|
else if (SIZE_6LOWPAN_SHORT == len)
|
|
addr->pan.mode = AM_6LOWPAN_SHORT;
|
|
else
|
|
addr->pan.mode = AM_6LOWPAN_NONE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* If 'dest' is not set, this function will get the link layer address for a
|
|
* certain source IPv6 address, if 'dest' is set it will get it for the a
|
|
* destination address */
|
|
static int32_t
|
|
addr_802154_from_net(union pico_ll_addr *addr, struct pico_frame *f, int32_t dest)
|
|
{
|
|
if (dest) {
|
|
addr->pan = addr_802154_ll_dst(f);
|
|
} else {
|
|
addr->pan = addr_802154_ll_src(f);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Determines the length of an IEEE802.15.4 address */
|
|
static int32_t
|
|
addr_802154_len(union pico_ll_addr *addr)
|
|
{
|
|
return SIZE_6LOWPAN(addr->pan.mode);
|
|
}
|
|
|
|
/* Compares 2 IEE802.15.4 addresses */
|
|
static int32_t
|
|
addr_802154_cmp(union pico_ll_addr *a, union pico_ll_addr *b)
|
|
{
|
|
if (a->pan.mode != b->pan.mode) {
|
|
return (int32_t)((int32_t)a->pan.mode - (int32_t)b->pan.mode);
|
|
} else {
|
|
return memcmp(a->pan.addr.data, b->pan.addr.data, SIZE_6LOWPAN(b->pan.mode));
|
|
}
|
|
}
|
|
|
|
/* Derive an IPv6 IID from an IEEE802.15.4 address */
|
|
static int32_t
|
|
addr_802154_iid(uint8_t iid[8], union pico_ll_addr *addr)
|
|
{
|
|
uint8_t buf[8] = {0,0,0,0xff,0xfe,0,0,0};
|
|
struct pico_802154 pan = addr->pan;
|
|
|
|
if (AM_6LOWPAN_SHORT == pan.mode) {
|
|
buf[6] = (uint8_t)(pan.addr._short.addr);
|
|
buf[7] = (uint8_t)(pan.addr._short.addr >> 8);
|
|
} else if (AM_6LOWPAN_EXT == pan.mode) {
|
|
memcpy(buf, pan.addr.data, SIZE_6LOWPAN_EXT);
|
|
buf[0] ^= (uint8_t)0x02;
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
memcpy(iid, buf, 8);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Allocates a pico_frame but makes sure the network-buffer starts on an 4-byte aligned address,
|
|
* this is required by upper layer of the stack. IEEE802.15.4's header isn't necessarily 4/8-byte
|
|
* aligned since the minimum size of an IEEE802.15.4 header is '5'. The datalink header therefore
|
|
* might not (and most probably isn't) aligned on an aligned address. The datalink header will of
|
|
* the size passed in 'headroom'
|
|
*
|
|
* @param size Size of the actual frame provided for network-layer and above
|
|
* @param headroom Size of the headroom for datalink-buffer
|
|
* @param overhead Size of the overhead to keep for the device driver
|
|
*
|
|
* @return struct pico_frame *, returns the allocated frame upon success, 'NULL' otherwise.
|
|
*/
|
|
static struct pico_frame *
|
|
pico_frame_alloc_with_headroom(uint16_t size, uint16_t headroom, uint16_t overhead)
|
|
{
|
|
int network_offset = (((headroom + overhead) >> 2) + 1) << 2; // Sufficient headroom for alignment
|
|
struct pico_frame *f = pico_frame_alloc((uint32_t)(size + network_offset));
|
|
|
|
if (!f)
|
|
return NULL;
|
|
|
|
f->net_hdr = f->buffer + network_offset;
|
|
f->datalink_hdr = f->net_hdr - headroom;
|
|
return f;
|
|
}
|
|
|
|
/* Allocates a frame with the maximum MAC header size + device's overhead-parameter since this is
|
|
* the lowest level of the frame allocation chain */
|
|
static struct pico_frame *
|
|
pico_802154_frame_alloc(struct pico_device *dev, uint16_t size)
|
|
{
|
|
struct pico_frame *f = pico_frame_alloc_with_headroom(size, SIZE_802154_MHR_MAX, (uint16_t)dev->overhead);
|
|
if (!f)
|
|
return NULL;
|
|
|
|
f->dev = dev;
|
|
return f;
|
|
}
|
|
|
|
const struct pico_6lowpan_ll_protocol pico_6lowpan_ll_802154 = {
|
|
.process_in = pico_802154_process_in,
|
|
.process_out = pico_802154_process_out,
|
|
.estimate = pico_802154_estimator,
|
|
.addr_from_buf = addr_802154_from_buf,
|
|
.addr_from_net = addr_802154_from_net,
|
|
.addr_len = addr_802154_len,
|
|
.addr_cmp = addr_802154_cmp,
|
|
.addr_iid = addr_802154_iid,
|
|
.alloc = pico_802154_frame_alloc,
|
|
};
|
|
|
|
#endif /* PICO_SUPPORT_802154 */
|