blob: 150d879d3d9c0b74fbf14dba621722d45fd02fdb [file] [log] [blame]
/** @file
* @brief 6lopan related functions
*/
/*
* Copyright (c) 2016 Intel Corporation
* Copyright (c) 2019 Alexander Wachter
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_6lo, CONFIG_NET_6LO_LOG_LEVEL);
#include <errno.h>
#include <zephyr/net/net_core.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_stats.h>
#include <zephyr/net/udp.h>
#include "net_private.h"
#include "6lo.h"
#include "6lo_private.h"
#if defined(CONFIG_NET_6LO_CONTEXT)
struct net_6lo_context {
struct in6_addr prefix;
struct net_if *iface;
uint16_t lifetime;
uint8_t is_used : 1;
uint8_t compress : 1;
uint8_t cid : 4;
uint8_t unused : 2;
};
static inline uint8_t get_6co_compress(struct net_icmpv6_nd_opt_6co *opt)
{
return (opt->flag & 0x10) >> 4;
}
static inline uint8_t get_6co_cid(struct net_icmpv6_nd_opt_6co *opt)
{
return opt->flag & 0x0F;
}
static struct net_6lo_context ctx_6co[CONFIG_NET_MAX_6LO_CONTEXTS];
#endif
static const uint8_t udp_nhc_inline_size_table[] = {4, 3, 3, 1};
static const uint8_t tf_inline_size_table[] = {4, 3, 1, 0};
/* The first bit of the index is SAC | SAC=0 | SAC=1 |*/
static const uint8_t sa_inline_size_table[] = {16, 8, 2, 0, 0, 8, 2, 0};
/* The first bit is M, the second DAC
* | M=0 DAC=0 | M=0 DAC=1 | M=1 DAC=0 | M=1 DAC=1 (DAM always 00)
*/
static const uint8_t da_inline_size_table[] = {
16, 8, 2, 0, 0, 8, 2, 0, 16, 6, 4, 1, 6
};
static int get_udp_nhc_inlined_size(uint8_t nhc)
{
int size = 0;
if ((nhc & 0xF8) != NET_6LO_NHC_UDP_BARE) {
NET_DBG("UDP NHC dispatch doesn't match");
return 0;
}
if (!(nhc & NET_6LO_NHC_UDP_CHECKSUM)) {
size += 2U;
}
size += udp_nhc_inline_size_table[(nhc & NET_6LO_NHC_UDP_PORT_MASK)];
NET_DBG("Size of inlined UDP HDR data: %d", size);
return size;
}
static int get_ihpc_inlined_size(uint16_t iphc)
{
int size = 0;
if (((iphc >> 8) & NET_6LO_DISPATCH_IPHC_MASK) !=
NET_6LO_DISPATCH_IPHC) {
NET_DBG("IPHC dispatch doesn't match");
return -1;
}
size += tf_inline_size_table[(iphc & NET_6LO_IPHC_TF_MASK) >>
NET_6LO_IPHC_TF_POS];
if (!(iphc & NET_6LO_IPHC_NH_MASK)) {
size += 1U;
}
if (!(iphc & NET_6LO_IPHC_HLIM_MASK)) {
size += 1U;
}
if (iphc & NET_6LO_IPHC_CID_MASK) {
size += 1U;
}
size += sa_inline_size_table[(iphc & NET_6LO_IPHC_SA_MASK) >>
NET_6LO_IPHC_SAM_POS];
size += da_inline_size_table[(iphc & NET_6LO_IPHC_DA_MASK) >>
NET_6LO_IPHC_DAM_POS];
NET_DBG("Size of inlined IP HDR data: %d", size);
return size;
}
/* TODO: Unicast-Prefix based IPv6 Multicast(dst) address compression
* Mesh header compression
*/
static inline bool net_6lo_ll_prefix_padded_with_zeros(struct in6_addr *addr)
{
return (net_ipv6_is_ll_addr(addr) &&
(UNALIGNED_GET(&addr->s6_addr16[1]) == 0x00) &&
(UNALIGNED_GET(&addr->s6_addr32[1]) == 0x00));
}
static inline bool net_6lo_addr_16_bit_compressible(struct in6_addr *addr)
{
return ((UNALIGNED_GET(&addr->s6_addr32[2]) == htonl(0xFF)) &&
(UNALIGNED_GET(&addr->s6_addr16[6]) == htons(0xFE00)));
}
static inline bool net_6lo_maddr_8_bit_compressible(struct in6_addr *addr)
{
return ((addr->s6_addr[1] == 0x02) &&
(UNALIGNED_GET(&addr->s6_addr16[1]) == 0x00) &&
(UNALIGNED_GET(&addr->s6_addr32[1]) == 0x00) &&
(UNALIGNED_GET(&addr->s6_addr32[2]) == 0x00) &&
(addr->s6_addr[14] == 0x00));
}
static inline bool net_6lo_maddr_32_bit_compressible(struct in6_addr *addr)
{
return ((UNALIGNED_GET(&addr->s6_addr32[1]) == 0x00) &&
(UNALIGNED_GET(&addr->s6_addr32[2]) == 0x00) &&
(addr->s6_addr[12] == 0x00));
}
static inline bool net_6lo_maddr_48_bit_compressible(struct in6_addr *addr)
{
return ((UNALIGNED_GET(&addr->s6_addr32[1]) == 0x00) &&
(UNALIGNED_GET(&addr->s6_addr16[4]) == 0x00) &&
(addr->s6_addr[10] == 0x00));
}
#if defined(CONFIG_NET_6LO_CONTEXT)
/* RFC 6775, 4.2, 5.4.2, 5.4.3 and 7.2*/
static inline void set_6lo_context(struct net_if *iface, uint8_t index,
struct net_icmpv6_nd_opt_6co *context)
{
ctx_6co[index].is_used = true;
ctx_6co[index].iface = iface;
/*TODO: Start timer */
ctx_6co[index].lifetime = context->lifetime;
ctx_6co[index].compress = get_6co_compress(context);
ctx_6co[index].cid = get_6co_cid(context);
net_ipv6_addr_copy_raw((uint8_t *)&ctx_6co[index].prefix, context->prefix);
}
void net_6lo_set_context(struct net_if *iface,
struct net_icmpv6_nd_opt_6co *context)
{
int unused = -1;
uint8_t i;
/* If the context information already exists, update or remove
* as per data.
*/
for (i = 0U; i < CONFIG_NET_MAX_6LO_CONTEXTS; i++) {
if (!ctx_6co[i].is_used) {
unused = i;
continue;
}
if (ctx_6co[i].iface == iface &&
ctx_6co[i].cid == get_6co_cid(context)) {
/* Remove if lifetime is zero */
if (!context->lifetime) {
ctx_6co[i].is_used = false;
return;
}
/* Update the context */
set_6lo_context(iface, i, context);
return;
}
}
/* Cache the context information. */
if (unused != -1) {
set_6lo_context(iface, unused, context);
return;
}
NET_DBG("Either no free slots in the table or exceeds limit");
}
/* Get the context by matching cid */
static inline struct net_6lo_context *
get_6lo_context_by_cid(struct net_if *iface, uint8_t cid)
{
uint8_t i;
for (i = 0U; i < CONFIG_NET_MAX_6LO_CONTEXTS; i++) {
if (!ctx_6co[i].is_used) {
continue;
}
if (ctx_6co[i].iface == iface && ctx_6co[i].cid == cid) {
return &ctx_6co[i];
}
}
return NULL;
}
/* Get the context by addr */
static inline struct net_6lo_context *
get_6lo_context_by_addr(struct net_if *iface, struct in6_addr *addr)
{
uint8_t i;
for (i = 0U; i < CONFIG_NET_MAX_6LO_CONTEXTS; i++) {
if (!ctx_6co[i].is_used) {
continue;
}
if (ctx_6co[i].iface == iface &&
!memcmp(ctx_6co[i].prefix.s6_addr, addr->s6_addr, 8)) {
return &ctx_6co[i];
}
}
return NULL;
}
#endif
/* Helper routine to compress Traffic class and Flow label */
/* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |Version| Traffic Class | Flow Label |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* version: 4 bits, Traffic Class: 8 bits, Flow label: 20 bits
* The Traffic Class field in the IPv6 header comprises 6 bits of
* Diffserv extension [RFC2474] and 2 bits of Explicit Congestion
* Notification (ECN) [RFC3168]
*/
/* IPHC (compressed) format of traffic class is ECN, DSCP but original
* IPv6 traffic class format is DSCP, ECN.
* DSCP(6), ECN(2).
*/
static uint8_t *compress_tfl(struct net_ipv6_hdr *ipv6, uint8_t *inline_ptr,
uint16_t *iphc)
{
uint8_t tcl;
tcl = ((ipv6->vtc & 0x0F) << 4) | ((ipv6->tcflow & 0xF0) >> 4);
tcl = (tcl << 6) | (tcl >> 2); /* ECN(2), DSCP(6) */
if (((ipv6->tcflow & 0x0F) == 0U) && (ipv6->flow == 0U)) {
if (((ipv6->vtc & 0x0F) == 0U) && ((ipv6->tcflow & 0xF0) == 0U)) {
NET_DBG("Traffic class and Flow label elided");
/* Traffic class and Flow label elided */
*iphc |= NET_6LO_IPHC_TF_11;
} else {
NET_DBG("Flow label elided");
/* Flow label elided */
*iphc |= NET_6LO_IPHC_TF_10;
inline_ptr -= sizeof(tcl);
*inline_ptr = tcl;
}
} else {
if (((ipv6->vtc & 0x0F) == 0U) && (ipv6->tcflow & 0x30)) {
NET_DBG("ECN + 2-bit Pad + Flow Label, DSCP is elided");
/* ECN + 2-bit Pad + Flow Label, DSCP is elided.*/
*iphc |= NET_6LO_IPHC_TF_01;
inline_ptr -= sizeof(ipv6->flow);
memmove(inline_ptr, &ipv6->flow, sizeof(ipv6->flow));
inline_ptr -= sizeof(uint8_t);
*inline_ptr = (tcl & 0xC0) | (ipv6->tcflow & 0x0F);
} else {
NET_DBG("ECN + DSCP + 4-bit Pad + Flow Label");
/* ECN + DSCP + 4-bit Pad + Flow Label */
*iphc |= NET_6LO_IPHC_TF_00;
inline_ptr -= sizeof(ipv6->flow);
memmove(inline_ptr, &ipv6->flow, sizeof(ipv6->flow));
inline_ptr -= sizeof(uint8_t);
*inline_ptr = ipv6->tcflow & 0x0F;
inline_ptr -= sizeof(tcl);
*inline_ptr = tcl;
}
}
return inline_ptr;
}
/* Helper to compress Hop limit */
static uint8_t *compress_hoplimit(struct net_ipv6_hdr *ipv6, uint8_t *inline_ptr,
uint16_t *iphc)
{
/* Hop Limit */
switch (ipv6->hop_limit) {
case 1:
NET_DBG("HLIM compressed (1)");
*iphc |= NET_6LO_IPHC_HLIM1;
break;
case 64:
NET_DBG("HLIM compressed (64)");
*iphc |= NET_6LO_IPHC_HLIM64;
break;
case 255:
NET_DBG("HLIM compressed (255)");
*iphc |= NET_6LO_IPHC_HLIM255;
break;
default:
inline_ptr -= sizeof(ipv6->hop_limit);
*inline_ptr = ipv6->hop_limit;
break;
}
return inline_ptr;
}
/* Helper to compress Next header */
static uint8_t *compress_nh(struct net_ipv6_hdr *ipv6, uint8_t *inline_ptr,
uint16_t *iphc)
{
/* Next header */
if (ipv6->nexthdr == IPPROTO_UDP) {
*iphc |= NET_6LO_IPHC_NH_1;
} else {
inline_ptr -= sizeof(ipv6->nexthdr);
*inline_ptr = ipv6->nexthdr;
}
return inline_ptr;
}
/* Helpers to compress Source Address */
static uint8_t *compress_sa(struct net_ipv6_hdr *ipv6, struct net_pkt *pkt,
uint8_t *inline_ptr, uint16_t *iphc)
{
NET_ASSERT(net_pkt_lladdr_src(pkt)->addr);
/* Address is fully elided */
if (net_ipv6_addr_based_on_ll((struct in6_addr *)ipv6->src,
net_pkt_lladdr_src(pkt))) {
NET_DBG("SAM_11 src address is fully elided");
*iphc |= NET_6LO_IPHC_SAM_11;
return inline_ptr;
}
/* Following 64 bits are 0000:00ff:fe00:XXXX */
if (net_6lo_addr_16_bit_compressible((struct in6_addr *)ipv6->src)) {
NET_DBG("SAM_10 src addr 16 bit compressible");
*iphc |= NET_6LO_IPHC_SAM_10;
inline_ptr -= sizeof(uint16_t);
memmove(inline_ptr, &ipv6->src[14], sizeof(uint16_t));
return inline_ptr;
}
NET_DBG("SAM_01 src 64 bits are inlined");
/* Remaining 64 bits are in-line */
*iphc |= NET_6LO_IPHC_SAM_01;
inline_ptr -= 8U;
memmove(inline_ptr, &ipv6->src[8], 8U);
return inline_ptr;
}
static uint8_t *set_sa_inline(struct net_ipv6_hdr *ipv6, uint8_t *inline_ptr,
uint16_t *iphc)
{
*iphc |= NET_6LO_IPHC_SAM_00;
inline_ptr -= 16U;
memmove(inline_ptr, &ipv6->src[0], 16U);
return inline_ptr;
}
#if defined(CONFIG_NET_6LO_CONTEXT)
static uint8_t *compress_sa_ctx(struct net_ipv6_hdr *ipv6, uint8_t *inline_ptr,
struct net_pkt *pkt, uint16_t *iphc,
struct net_6lo_context *src)
{
NET_ASSERT(net_pkt_lladdr_src(pkt)->addr);
NET_DBG("SAC_1 src address context based");
*iphc |= NET_6LO_IPHC_SAC_1;
if (net_ipv6_addr_based_on_ll((struct in6_addr *)ipv6->src,
net_pkt_lladdr_src(pkt))) {
NET_DBG("SAM_11 src address is fully elided");
/* Address is fully elided */
*iphc |= NET_6LO_IPHC_SAM_11;
return inline_ptr;
}
/* Following 64 bits are 0000:00ff:fe00:XXXX */
if (net_6lo_addr_16_bit_compressible((struct in6_addr *)ipv6->src)) {
NET_DBG("SAM_10 src addr 16 bit compressible");
*iphc |= NET_6LO_IPHC_SAM_10;
inline_ptr -= sizeof(uint16_t);
memmove(inline_ptr, &ipv6->src[14], sizeof(uint16_t));
return inline_ptr;
}
NET_DBG("SAM_01 src remaining 64 bits are inlined");
/* Remaining 64 bits are in-line */
*iphc |= NET_6LO_IPHC_SAM_01;
inline_ptr -= 8U;
memmove(inline_ptr, &ipv6->src[8], 8U);
return inline_ptr;
}
#endif
/* Helpers to compress Destination Address */
static uint8_t *compress_da_mcast(struct net_ipv6_hdr *ipv6, uint8_t *inline_ptr,
uint16_t *iphc)
{
*iphc |= NET_6LO_IPHC_M_1;
NET_DBG("M_1 dst is mcast");
if (net_6lo_maddr_8_bit_compressible((struct in6_addr *)ipv6->dst)) {
NET_DBG("DAM_11 dst maddr 8 bit compressible");
/* last byte */
*iphc |= NET_6LO_IPHC_DAM_11;
inline_ptr -= sizeof(uint8_t);
memmove(inline_ptr, &ipv6->dst[15], sizeof(uint8_t));
return inline_ptr;
}
if (net_6lo_maddr_32_bit_compressible((struct in6_addr *)ipv6->dst)) {
NET_DBG("DAM_10 4 bytes: 2nd byte + last three bytes");
/* 4 bytes: 2nd byte + last three bytes */
*iphc |= NET_6LO_IPHC_DAM_10;
inline_ptr -= 3U;
memmove(inline_ptr, &ipv6->dst[13], 3U);
inline_ptr -= sizeof(uint8_t);
memmove(inline_ptr, &ipv6->dst[1], sizeof(uint8_t));
return inline_ptr;
}
if (net_6lo_maddr_48_bit_compressible((struct in6_addr *)ipv6->dst)) {
NET_DBG("DAM_01 6 bytes: 2nd byte + last five bytes");
/* 6 bytes: 2nd byte + last five bytes */
*iphc |= NET_6LO_IPHC_DAM_01;
inline_ptr -= 5U;
memmove(inline_ptr, &ipv6->dst[11], 5U);
inline_ptr -= sizeof(uint8_t);
memmove(inline_ptr, &ipv6->dst[1], sizeof(uint8_t));
return inline_ptr;
}
NET_DBG("DAM_00 dst complete addr inlined");
/* complete address NET_6LO_IPHC_DAM_00 */
inline_ptr -= 16U;
memmove(inline_ptr, &ipv6->dst[0], 16U);
return inline_ptr;
}
static uint8_t *compress_da(struct net_ipv6_hdr *ipv6, struct net_pkt *pkt,
uint8_t *inline_ptr, uint16_t *iphc)
{
NET_ASSERT(net_pkt_lladdr_dst(pkt)->addr);
/* Address is fully elided */
if (net_ipv6_addr_based_on_ll((struct in6_addr *)ipv6->dst,
net_pkt_lladdr_dst(pkt))) {
NET_DBG("DAM_11 dst addr fully elided");
*iphc |= NET_6LO_IPHC_DAM_11;
return inline_ptr;
}
/* Following 64 bits are 0000:00ff:fe00:XXXX */
if (net_6lo_addr_16_bit_compressible((struct in6_addr *)ipv6->dst)) {
NET_DBG("DAM_10 dst addr 16 bit compressible");
*iphc |= NET_6LO_IPHC_DAM_10;
inline_ptr -= sizeof(uint16_t);
memmove(inline_ptr, &ipv6->dst[14], sizeof(uint16_t));
return inline_ptr;
}
NET_DBG("DAM_01 remaining 64 bits are inlined");
/* Remaining 64 bits are in-line */
*iphc |= NET_6LO_IPHC_DAM_01;
inline_ptr -= 8U;
memmove(inline_ptr, &ipv6->dst[8], 8U);
return inline_ptr;
}
static uint8_t *set_da_inline(struct net_ipv6_hdr *ipv6, uint8_t *inline_ptr,
uint16_t *iphc)
{
*iphc |= NET_6LO_IPHC_DAM_00;
inline_ptr -= 16U;
memmove(inline_ptr, &ipv6->dst[0], 16U);
return inline_ptr;
}
#if defined(CONFIG_NET_6LO_CONTEXT)
static uint8_t *compress_da_ctx(struct net_ipv6_hdr *ipv6, uint8_t *inline_ptr,
struct net_pkt *pkt, uint16_t *iphc,
struct net_6lo_context *dst)
{
*iphc |= NET_6LO_IPHC_DAC_1;
if (net_ipv6_addr_based_on_ll((struct in6_addr *)ipv6->dst,
net_pkt_lladdr_dst(pkt))) {
NET_DBG("DAM_11 dst addr fully elided");
*iphc |= NET_6LO_IPHC_DAM_11;
return inline_ptr;
}
/* Following 64 bits are 0000:00ff:fe00:XXXX */
if (net_6lo_addr_16_bit_compressible((struct in6_addr *)ipv6->dst)) {
NET_DBG("DAM_10 dst addr 16 bit compressible");
*iphc |= NET_6LO_IPHC_DAM_10;
inline_ptr -= sizeof(uint16_t);
memmove(inline_ptr, &ipv6->dst[14], sizeof(uint16_t));
return inline_ptr;
}
NET_DBG("DAM_01 remaining 64 bits are inlined");
/* Remaining 64 bits are in-line */
*iphc |= NET_6LO_IPHC_DAM_01;
inline_ptr -= 8U;
memmove(inline_ptr, &ipv6->dst[8], 8U);
return inline_ptr;
}
#endif
/* Helper to compress Next header UDP */
static inline uint8_t *compress_nh_udp(struct net_udp_hdr *udp, uint8_t *inline_ptr,
bool compress_checksum)
{
uint8_t nhc = NET_6LO_NHC_UDP_BARE;
uint8_t *inline_ptr_udp = inline_ptr;
uint8_t tmp;
/* 4.3.3 UDP LOWPAN_NHC Format
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 1 | 1 | 1 | 1 | 0 | C | P |
* +---+---+---+---+---+---+---+---+
*/
/* Port compression
* 00: All 16 bits for src and dst are inlined.
* 01: All 16 bits for src port inlined. First 8 bits of dst port is
* 0xf0 and elided. The remaining 8 bits of dst port inlined.
* 10: First 8 bits of src port 0xf0 and elided. The remaining 8 bits
* of src port inlined. All 16 bits of dst port inlined.
* 11: First 12 bits of both src and dst are 0xf0b and elided. The
* remaining 4 bits for each are inlined.
*/
if (compress_checksum) {
nhc |= NET_6LO_NHC_UDP_CHECKSUM;
} else {
inline_ptr_udp -= sizeof(udp->chksum);
memmove(inline_ptr_udp, &udp->chksum, sizeof(udp->chksum));
}
if ((((htons(udp->src_port) >> 4) & 0xFFF) ==
NET_6LO_NHC_UDP_4_BIT_PORT) &&
(((htons(udp->dst_port) >> 4) & 0xFFF) ==
NET_6LO_NHC_UDP_4_BIT_PORT)) {
NET_DBG("UDP ports src and dst 4 bits inlined");
/** src: first 16 bits elided, next 4 bits inlined
* dst: first 16 bits elided, next 4 bits inlined
*/
nhc |= NET_6LO_NHC_UDP_PORT_11;
tmp = (uint8_t)(htons(udp->src_port));
tmp = tmp << 4;
tmp |= (((uint8_t)(htons(udp->dst_port))) & 0x0F);
inline_ptr_udp -= sizeof(tmp);
*inline_ptr_udp = tmp;
} else if (((htons(udp->dst_port) >> 8) & 0xFF) ==
NET_6LO_NHC_UDP_8_BIT_PORT) {
NET_DBG("UDP ports src full, dst 8 bits inlined");
/* dst: first 8 bits elided, next 8 bits inlined
* src: fully carried inline
*/
nhc |= NET_6LO_NHC_UDP_PORT_01;
inline_ptr_udp -= sizeof(uint8_t);
*inline_ptr_udp = (uint8_t)(htons(udp->dst_port));
inline_ptr_udp -= sizeof(udp->src_port);
memmove(inline_ptr_udp, &udp->src_port, sizeof(udp->src_port));
} else if (((htons(udp->src_port) >> 8) & 0xFF) ==
NET_6LO_NHC_UDP_8_BIT_PORT) {
NET_DBG("UDP ports src 8bits, dst full inlined");
/* src: first 8 bits elided, next 8 bits inlined
* dst: fully carried inline
*/
nhc |= NET_6LO_NHC_UDP_PORT_10;
inline_ptr_udp -= sizeof(udp->dst_port);
memmove(inline_ptr_udp, &udp->dst_port, sizeof(udp->dst_port));
inline_ptr_udp -= sizeof(uint8_t);
*inline_ptr_udp = (uint8_t)(htons(udp->src_port));
} else {
NET_DBG("Can not compress ports, ports are inlined");
/* can not compress ports, ports are inlined */
inline_ptr_udp -= sizeof(udp->dst_port) + sizeof(udp->src_port);
memmove(inline_ptr_udp, &udp->src_port,
sizeof(udp->dst_port) + sizeof(udp->src_port));
}
inline_ptr_udp -= sizeof(nhc);
*inline_ptr_udp = nhc;
return inline_ptr_udp;
}
#if defined(CONFIG_NET_6LO_CONTEXT)
static struct net_6lo_context *get_src_addr_ctx(struct net_pkt *pkt,
struct net_ipv6_hdr *ipv6)
{
/* If compress flag is unset means use only in uncompression. */
struct net_6lo_context *src;
src = get_6lo_context_by_addr(net_pkt_iface(pkt),
(struct in6_addr *)ipv6->src);
if (!src || !src->compress) {
return NULL;
}
return src;
}
static struct net_6lo_context *get_dst_addr_ctx(struct net_pkt *pkt,
struct net_ipv6_hdr *ipv6)
{
/* If compress flag is unset means use only in uncompression. */
struct net_6lo_context *dst;
dst = get_6lo_context_by_addr(net_pkt_iface(pkt),
(struct in6_addr *)ipv6->dst);
if (!dst || !dst->compress) {
return NULL;
}
return dst;
}
#endif /* CONFIG_NET_6LO_CONTEXT */
/* RFC 6282 LOWPAN IPHC Encoding format (3.1)
* Base Format
* 0 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* | 0 | 1 | 1 | TF |NH | HLIM |CID|SAC| SAM | M |DAC| DAM |
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
static inline int compress_IPHC_header(struct net_pkt *pkt)
{
#if defined(CONFIG_NET_6LO_CONTEXT)
struct net_6lo_context *src_ctx = NULL;
struct net_6lo_context *dst_ctx = NULL;
#endif
uint8_t compressed = 0;
uint16_t iphc = (NET_6LO_DISPATCH_IPHC << 8);
struct net_ipv6_hdr *ipv6 = NET_IPV6_HDR(pkt);
struct net_udp_hdr *udp;
uint8_t *inline_pos;
if (pkt->frags->len < NET_IPV6H_LEN) {
NET_ERR("Invalid length %d, min %d",
pkt->frags->len, NET_IPV6H_LEN);
return -EINVAL;
}
if (ipv6->nexthdr == IPPROTO_UDP &&
pkt->frags->len < NET_IPV6UDPH_LEN) {
NET_ERR("Invalid length %d, min %d",
pkt->frags->len, NET_IPV6UDPH_LEN);
return -EINVAL;
}
inline_pos = pkt->buffer->data + NET_IPV6H_LEN;
if (ipv6->nexthdr == IPPROTO_UDP) {
udp = (struct net_udp_hdr *)inline_pos;
inline_pos += NET_UDPH_LEN;
inline_pos = compress_nh_udp(udp, inline_pos, false);
}
if (net_6lo_ll_prefix_padded_with_zeros((struct in6_addr *)ipv6->dst)) {
inline_pos = compress_da(ipv6, pkt, inline_pos, &iphc);
goto da_end;
}
if (net_ipv6_is_addr_mcast((struct in6_addr *)ipv6->dst)) {
inline_pos = compress_da_mcast(ipv6, inline_pos, &iphc);
goto da_end;
}
#if defined(CONFIG_NET_6LO_CONTEXT)
dst_ctx = get_dst_addr_ctx(pkt, ipv6);
if (dst_ctx) {
iphc |= NET_6LO_IPHC_CID_1;
inline_pos = compress_da_ctx(ipv6, inline_pos, pkt, &iphc,
dst_ctx);
goto da_end;
}
#endif
inline_pos = set_da_inline(ipv6, inline_pos, &iphc);
da_end:
if (net_6lo_ll_prefix_padded_with_zeros((struct in6_addr *)ipv6->src)) {
inline_pos = compress_sa(ipv6, pkt, inline_pos, &iphc);
goto sa_end;
}
if (net_ipv6_is_addr_unspecified((struct in6_addr *)ipv6->src)) {
NET_DBG("SAM_00, SAC_1 unspecified src address");
/* Unspecified IPv6 src address */
iphc |= NET_6LO_IPHC_SAC_1;
iphc |= NET_6LO_IPHC_SAM_00;
goto sa_end;
}
#if defined(CONFIG_NET_6LO_CONTEXT)
src_ctx = get_src_addr_ctx(pkt, ipv6);
if (src_ctx) {
inline_pos = compress_sa_ctx(ipv6, inline_pos, pkt, &iphc,
src_ctx);
iphc |= NET_6LO_IPHC_CID_1;
goto sa_end;
}
#endif
inline_pos = set_sa_inline(ipv6, inline_pos, &iphc);
sa_end:
inline_pos = compress_hoplimit(ipv6, inline_pos, &iphc);
inline_pos = compress_nh(ipv6, inline_pos, &iphc);
inline_pos = compress_tfl(ipv6, inline_pos, &iphc);
#if defined(CONFIG_NET_6LO_CONTEXT)
if (iphc & NET_6LO_IPHC_CID_1) {
inline_pos -= sizeof(uint8_t);
*inline_pos = 0;
if (src_ctx) {
*inline_pos = src_ctx->cid << 4;
}
if (dst_ctx) {
*inline_pos |= dst_ctx->cid & 0x0F;
}
}
#endif
inline_pos -= sizeof(iphc);
iphc = htons(iphc);
memmove(inline_pos, &iphc, sizeof(iphc));
compressed = inline_pos - pkt->buffer->data;
net_buf_pull(pkt->buffer, compressed);
return compressed;
}
/* Helper to uncompress Traffic class and Flow label */
static inline uint8_t *uncompress_tfl(uint16_t iphc, uint8_t *cursor,
struct net_ipv6_hdr *ipv6)
{
uint8_t tcl;
/* Uncompress tcl and flow label */
switch (iphc & NET_6LO_IPHC_TF_11) {
case NET_6LO_IPHC_TF_00:
NET_DBG("ECN + DSCP + 4-bit Pad + Flow Label");
tcl = *cursor;
cursor++;
tcl = (tcl >> 6) | (tcl << 2);
ipv6->vtc |= ((tcl & 0xF0) >> 4);
ipv6->tcflow = ((tcl & 0x0F) << 4) | (*cursor & 0x0F);
cursor++;
memmove(&ipv6->flow, cursor, sizeof(ipv6->flow));
cursor += sizeof(ipv6->flow);
break;
case NET_6LO_IPHC_TF_01:
NET_DBG("ECN + 2-bit Pad + Flow Label, DSCP is elided");
tcl = ((*cursor & 0xF0) >> 6);
ipv6->tcflow = ((tcl & 0x0F) << 4) | (*cursor & 0x0F);
cursor++;
memmove(&ipv6->flow, cursor, sizeof(ipv6->flow));
cursor += sizeof(ipv6->flow);
break;
case NET_6LO_IPHC_TF_10:
NET_DBG("Flow label elided");
tcl = *cursor;
cursor++;
tcl = (tcl >> 6) | (tcl << 2);
ipv6->vtc |= ((tcl & 0xF0) >> 4);
ipv6->tcflow = (tcl & 0x0F) << 4;
ipv6->flow = 0U;
break;
case NET_6LO_IPHC_TF_11:
NET_DBG("Tcl and Flow label elided");
ipv6->tcflow = 0U;
ipv6->flow = 0U;
break;
}
return cursor;
}
/* Helper to uncompress Hoplimit */
static inline uint8_t *uncompress_hoplimit(uint16_t iphc, uint8_t *cursor,
struct net_ipv6_hdr *ipv6)
{
switch (iphc & NET_6LO_IPHC_HLIM_MASK) {
case NET_6LO_IPHC_HLIM:
ipv6->hop_limit = *cursor;
cursor++;
break;
case NET_6LO_IPHC_HLIM1:
ipv6->hop_limit = 1U;
break;
case NET_6LO_IPHC_HLIM64:
ipv6->hop_limit = 64U;
break;
case NET_6LO_IPHC_HLIM255:
ipv6->hop_limit = 255U;
break;
}
return cursor;
}
/* Helper to uncompress Source Address */
static inline uint8_t *uncompress_sa(uint16_t iphc, uint8_t *cursor,
struct net_ipv6_hdr *ipv6,
struct net_pkt *pkt)
{
struct in6_addr src_ip;
NET_DBG("SAC_0");
net_ipv6_addr_copy_raw((uint8_t *)&src_ip, ipv6->src);
switch (iphc & NET_6LO_IPHC_SAM_MASK) {
case NET_6LO_IPHC_SAM_00:
NET_DBG("SAM_00 full src addr inlined");
memmove(src_ip.s6_addr, cursor, sizeof(src_ip.s6_addr));
cursor += sizeof(src_ip.s6_addr);
break;
case NET_6LO_IPHC_SAM_01:
NET_DBG("SAM_01 last 64 bits are inlined");
memmove(&src_ip.s6_addr[8], cursor, 8);
cursor += 8U;
src_ip.s6_addr32[0] = 0x00;
src_ip.s6_addr32[1] = 0x00;
src_ip.s6_addr[0] = 0xFE;
src_ip.s6_addr[1] = 0x80;
break;
case NET_6LO_IPHC_SAM_10:
NET_DBG("SAM_10 src addr 16 bit compressed");
memmove(&src_ip.s6_addr[14], cursor, 2);
cursor += 2U;
src_ip.s6_addr16[6] = 0x00;
src_ip.s6_addr32[0] = 0x00;
src_ip.s6_addr32[1] = 0x00;
src_ip.s6_addr32[2] = 0x00;
src_ip.s6_addr[0] = 0xFE;
src_ip.s6_addr[1] = 0x80;
src_ip.s6_addr[11] = 0xFF;
src_ip.s6_addr[12] = 0xFE;
break;
case NET_6LO_IPHC_SAM_11:
NET_DBG("SAM_11 generate src addr from ll");
net_ipv6_addr_create_iid(&src_ip, net_pkt_lladdr_src(pkt));
break;
}
net_ipv6_addr_copy_raw(ipv6->src, (uint8_t *)&src_ip);
return cursor;
}
#if defined(CONFIG_NET_6LO_CONTEXT)
static inline uint8_t *uncompress_sa_ctx(uint16_t iphc, uint8_t *cursor,
struct net_ipv6_hdr *ipv6,
struct net_6lo_context *ctx,
struct net_pkt *pkt)
{
struct in6_addr src_ip;
net_ipv6_addr_copy_raw((uint8_t *)&src_ip, ipv6->src);
switch (iphc & NET_6LO_IPHC_SAM_MASK) {
case NET_6LO_IPHC_SAM_01:
NET_DBG("SAM_01 last 64 bits are inlined");
/* First 8 bytes are from context */
memmove(&src_ip.s6_addr[0], &ctx->prefix.s6_addr[0], 8);
/* And the rest are carried in-line*/
memmove(&src_ip.s6_addr[8], cursor, 8);
cursor += 8U;
break;
case NET_6LO_IPHC_SAM_10:
NET_DBG("SAM_10 src addr 16 bit compressed");
/* 16 bit carried in-line */
memmove(&src_ip.s6_addr[14], cursor, 2);
cursor += 2U;
/* First 8 bytes are from context */
memmove(&src_ip.s6_addr[0], &ctx->prefix.s6_addr[0], 8);
src_ip.s6_addr32[2] = 0x00;
src_ip.s6_addr16[6] = 0x00;
src_ip.s6_addr[11] = 0xFF;
src_ip.s6_addr[12] = 0xFE;
break;
case NET_6LO_IPHC_SAM_11:
NET_DBG("SAM_11 generate src addr from ll");
/* RFC 6282, 3.1.1. If SAC = 1 and SAM = 11
* Derive addr using context information and
* the encapsulating header.
* (e.g., 802.15.4 or IPv6 source address).
*/
net_ipv6_addr_create_iid(&src_ip, net_pkt_lladdr_src(pkt));
/* net_ipv6_addr_create_iid will copy first 8 bytes
* as link local prefix.
* Overwrite first 8 bytes from context prefix here.
*/
memmove(&src_ip.s6_addr[0], &ctx->prefix.s6_addr[0], 8);
break;
}
net_ipv6_addr_copy_raw(ipv6->src, (uint8_t *)&src_ip);
return cursor;
}
#endif
/* Helpers to uncompress Destination Address */
static inline uint8_t *uncompress_da_mcast(uint16_t iphc, uint8_t *cursor,
struct net_ipv6_hdr *ipv6)
{
struct in6_addr dst_ip;
NET_DBG("Dst is multicast");
net_ipv6_addr_copy_raw((uint8_t *)&dst_ip, ipv6->dst);
if (iphc & NET_6LO_IPHC_DAC_1) {
NET_WARN("Unsupported DAM options");
return 0;
}
/* If M=1 and DAC=0:
* 00: 128 bits, The full address is carried in-line.
* 01: 48 bits, The address takes the form ffXX::00XX:XXXX:XXXX.
* 10: 32 bits, The address takes the form ffXX::00XX:XXXX.
* 11: 8 bits, The address takes the form ff02::00XX.
*/
switch (iphc & NET_6LO_IPHC_DAM_MASK) {
case NET_6LO_IPHC_DAM_00:
NET_DBG("DAM_00 full dst addr inlined");
memmove(&dst_ip.s6_addr[0], cursor,
sizeof(dst_ip.s6_addr));
cursor += sizeof(dst_ip.s6_addr);
break;
case NET_6LO_IPHC_DAM_01:
NET_DBG("DAM_01 2nd byte and last five bytes");
dst_ip.s6_addr[1] = *cursor;
cursor++;
memmove(&dst_ip.s6_addr[11], cursor, 5);
cursor += 5U;
dst_ip.s6_addr[0] = 0xFF;
dst_ip.s6_addr16[1] = 0x00;
dst_ip.s6_addr32[1] = 0x00;
dst_ip.s6_addr[10] = 0x00;
dst_ip.s6_addr16[4] = 0x00;
break;
case NET_6LO_IPHC_DAM_10:
NET_DBG("DAM_10 2nd byte and last three bytes");
dst_ip.s6_addr[1] = *cursor;
cursor++;
memmove(&dst_ip.s6_addr[13], cursor, 3);
cursor += 3U;
dst_ip.s6_addr[0] = 0xFF;
dst_ip.s6_addr16[1] = 0x00;
dst_ip.s6_addr32[1] = 0x00;
dst_ip.s6_addr32[2] = 0x00;
dst_ip.s6_addr[12] = 0x00;
break;
case NET_6LO_IPHC_DAM_11:
NET_DBG("DAM_11 8 bit compressed");
dst_ip.s6_addr[15] = *cursor;
cursor++;
dst_ip.s6_addr[14] = 0x00;
dst_ip.s6_addr32[0] = 0x00;
dst_ip.s6_addr32[1] = 0x00;
dst_ip.s6_addr32[2] = 0x00;
dst_ip.s6_addr16[6] = 0x00;
dst_ip.s6_addr[0] = 0xFF;
dst_ip.s6_addr[1] = 0x02;
break;
}
net_ipv6_addr_copy_raw(ipv6->dst, (uint8_t *)&dst_ip);
return cursor;
}
/* Helper to uncompress Destination Address */
static inline uint8_t *uncompress_da(uint16_t iphc, uint8_t *cursor,
struct net_ipv6_hdr *ipv6,
struct net_pkt *pkt)
{
struct in6_addr dst_ip;
NET_DBG("DAC_0");
net_ipv6_addr_copy_raw((uint8_t *)&dst_ip, ipv6->dst);
switch (iphc & NET_6LO_IPHC_DAM_MASK) {
case NET_6LO_IPHC_DAM_00:
NET_DBG("DAM_00 full dst addr inlined");
memmove(&dst_ip.s6_addr[0], cursor,
sizeof(dst_ip.s6_addr));
cursor += sizeof(dst_ip.s6_addr);
break;
case NET_6LO_IPHC_DAM_01:
NET_DBG("DAM_01 last 64 bits are inlined");
memmove(&dst_ip.s6_addr[8], cursor, 8);
cursor += 8U;
dst_ip.s6_addr32[0] = 0x00;
dst_ip.s6_addr32[1] = 0x00;
dst_ip.s6_addr[0] = 0xFE;
dst_ip.s6_addr[1] = 0x80;
break;
case NET_6LO_IPHC_DAM_10:
NET_DBG("DAM_10 dst addr 16 bit compressed");
memmove(&dst_ip.s6_addr[14], cursor, 2);
cursor += 2U;
dst_ip.s6_addr32[0] = 0x00;
dst_ip.s6_addr32[1] = 0x00;
dst_ip.s6_addr32[2] = 0x00;
dst_ip.s6_addr16[6] = 0x00;
dst_ip.s6_addr[0] = 0xFE;
dst_ip.s6_addr[1] = 0x80;
dst_ip.s6_addr[11] = 0xFF;
dst_ip.s6_addr[12] = 0xFE;
break;
case NET_6LO_IPHC_DAM_11:
NET_DBG("DAM_11 generate dst addr from ll");
net_ipv6_addr_create_iid(&dst_ip, net_pkt_lladdr_dst(pkt));
break;
}
net_ipv6_addr_copy_raw(ipv6->dst, (uint8_t *)&dst_ip);
return cursor;
}
#if defined(CONFIG_NET_6LO_CONTEXT)
static inline uint8_t *uncompress_da_ctx(uint16_t iphc, uint8_t *cursor,
struct net_ipv6_hdr *ipv6,
struct net_6lo_context *ctx,
struct net_pkt *pkt)
{
struct in6_addr dst_ip;
NET_DBG("DAC_1");
net_ipv6_addr_copy_raw((uint8_t *)&dst_ip, ipv6->dst);
switch (iphc & NET_6LO_IPHC_DAM_MASK) {
case NET_6LO_IPHC_DAM_01:
NET_DBG("DAM_01 last 64 bits are inlined");
/* Last 8 bytes carried in-line */
memmove(&dst_ip.s6_addr[8], cursor, 8);
cursor += 8U;
/* First 8 bytes are from context */
memmove(&dst_ip.s6_addr[0], &ctx->prefix.s6_addr[0], 8);
break;
case NET_6LO_IPHC_DAM_10:
NET_DBG("DAM_10 src addr 16 bit compressed");
/* 16 bit carried in-line */
memmove(&dst_ip.s6_addr[14], cursor, 2);
cursor += 2U;
/* First 8 bytes are from context */
memmove(&dst_ip.s6_addr[0], &ctx->prefix.s6_addr[0], 8);
dst_ip.s6_addr32[2] = 0x00;
dst_ip.s6_addr16[6] = 0x00;
dst_ip.s6_addr[11] = 0xFF;
dst_ip.s6_addr[12] = 0xFE;
break;
case NET_6LO_IPHC_DAM_11:
NET_DBG("DAM_11 generate src addr from ll");
/* RFC 6282, 3.1.1. If SAC = 1 and SAM = 11
* Derive addr using context information and
* the encapsulating header.
* (e.g., 802.15.4 or IPv6 source address).
*/
net_ipv6_addr_create_iid(&dst_ip, net_pkt_lladdr_dst(pkt));
/* net_ipv6_addr_create_iid will copy first 8 bytes
* as link local prefix.
* Overwrite first 8 bytes from context prefix here.
*/
memmove(&dst_ip.s6_addr[0], &ctx->prefix.s6_addr[0], 8);
break;
}
net_ipv6_addr_copy_raw(ipv6->dst, (uint8_t *)&dst_ip);
return cursor;
}
#endif
/* Helper to uncompress NH UDP */
static uint8_t *uncompress_nh_udp(uint8_t nhc, uint8_t *cursor,
struct net_udp_hdr *udp)
{
/* Port uncompression
* 00: All 16 bits for src and dst are inlined
* 01: src, 16 bits are lined, dst(0xf0) 8 bits are inlined
* 10: dst, 16 bits are lined, src(0xf0) 8 bits are inlined
* 11: src, dst (0xf0b) 4 bits are inlined
*/
/* UDP header uncompression */
switch (nhc & NET_6LO_NHC_UDP_PORT_11) {
case NET_6LO_NHC_UDP_PORT_00:
NET_DBG("src and dst ports are inlined");
memmove(&udp->src_port, cursor, sizeof(udp->src_port));
cursor += sizeof(udp->src_port);
memmove(&udp->dst_port, cursor, sizeof(udp->dst_port));
cursor += sizeof(udp->dst_port);
break;
case NET_6LO_NHC_UDP_PORT_01:
NET_DBG("src full, dst 8 bits inlined");
memmove(&udp->src_port, cursor, sizeof(udp->src_port));
cursor += sizeof(udp->src_port);
udp->dst_port = htons(((uint16_t)NET_6LO_NHC_UDP_8_BIT_PORT << 8) |
*cursor);
cursor++;
break;
case NET_6LO_NHC_UDP_PORT_10:
NET_DBG("src 8 bits, dst full inlined");
udp->src_port = htons(((uint16_t)NET_6LO_NHC_UDP_8_BIT_PORT << 8) |
*cursor);
cursor++;
memmove(&udp->dst_port, cursor, sizeof(udp->dst_port));
cursor += sizeof(udp->dst_port);
break;
case NET_6LO_NHC_UDP_PORT_11:
NET_DBG("src and dst 4 bits inlined");
udp->src_port = htons((NET_6LO_NHC_UDP_4_BIT_PORT << 4) |
(*cursor >> 4));
udp->dst_port = htons((NET_6LO_NHC_UDP_4_BIT_PORT << 4) |
(*cursor & 0x0F));
cursor++;
break;
}
if (!(nhc & NET_6LO_NHC_UDP_CHECKSUM)) {
memmove(&udp->chksum, cursor, sizeof(udp->chksum));
cursor += sizeof(udp->chksum);
}
return cursor;
}
#if defined(CONFIG_NET_6LO_CONTEXT)
/* Helper function to uncompress src and dst contexts */
static inline void uncompress_cid(struct net_pkt *pkt, uint8_t cid,
struct net_6lo_context **src,
struct net_6lo_context **dst)
{
uint8_t cid_tmp;
/* Extract source and destination Context Index,
* Either src or dest address is context based or both.
*/
cid_tmp = (cid >> 4) & 0x0F;
*src = get_6lo_context_by_cid(net_pkt_iface(pkt), cid_tmp);
if (!(*src)) {
NET_DBG("Unknown src cid %d", cid_tmp);
}
cid_tmp = cid & 0x0F;
*dst = get_6lo_context_by_cid(net_pkt_iface(pkt), cid_tmp);
if (!(*dst)) {
NET_DBG("Unknown dst cid %d", cid_tmp);
}
}
#endif
static bool uncompress_IPHC_header(struct net_pkt *pkt)
{
struct net_udp_hdr *udp = NULL;
struct net_buf *frag = NULL;
uint8_t nhc = 0;
int nhc_inline_size = 0;
struct net_ipv6_hdr *ipv6;
uint16_t len;
uint16_t iphc;
int inline_size, compressed_hdr_size;
size_t diff;
uint8_t *cursor;
#if defined(CONFIG_NET_6LO_CONTEXT)
struct net_6lo_context *src = NULL;
struct net_6lo_context *dst = NULL;
#endif
iphc = ntohs(UNALIGNED_GET((uint16_t *)pkt->buffer->data));
inline_size = get_ihpc_inlined_size(iphc);
if (inline_size < 0) {
return false;
}
compressed_hdr_size = sizeof(iphc) + inline_size;
diff = sizeof(struct net_ipv6_hdr) - compressed_hdr_size;
if (iphc & NET_6LO_IPHC_NH_MASK) {
nhc = *(pkt->buffer->data + sizeof(iphc) + inline_size);
if ((nhc & 0xF8) != NET_6LO_NHC_UDP_BARE) {
NET_ERR("Unsupported next header");
return false;
}
nhc_inline_size = get_udp_nhc_inlined_size(nhc);
compressed_hdr_size += sizeof(uint8_t) + nhc_inline_size;
diff += sizeof(struct net_udp_hdr) - sizeof(uint8_t) -
nhc_inline_size;
}
if (pkt->buffer->len < compressed_hdr_size) {
NET_ERR("Scattered compressed header?");
return false;
}
if (net_buf_tailroom(pkt->buffer) >= diff) {
NET_DBG("Enough tailroom. Uncompress inplace");
frag = pkt->buffer;
net_buf_add(frag, diff);
cursor = frag->data + diff;
memmove(cursor, frag->data, frag->len - diff);
} else {
NET_DBG("Not enough tailroom. Get new fragment");
cursor = pkt->buffer->data;
frag = net_pkt_get_frag(pkt, NET_6LO_RX_PKT_TIMEOUT);
if (!frag) {
NET_ERR("Can't get frag for uncompression");
return false;
}
net_buf_pull(pkt->buffer, compressed_hdr_size);
net_buf_add(frag, nhc ? NET_IPV6UDPH_LEN : NET_IPV6H_LEN);
}
ipv6 = (struct net_ipv6_hdr *)(frag->data);
cursor += sizeof(iphc);
if (iphc & NET_6LO_IPHC_CID_1) {
#if defined(CONFIG_NET_6LO_CONTEXT)
uncompress_cid(pkt, *cursor, &src, &dst);
cursor++;
#else
NET_ERR("Context based uncompression not enabled");
return false;
#endif
}
/* Version is always 6 */
ipv6->vtc = 0x60;
net_pkt_set_ip_hdr_len(pkt, NET_IPV6H_LEN);
/* Uncompress Traffic class and Flow label */
cursor = uncompress_tfl(iphc, cursor, ipv6);
if (!(iphc & NET_6LO_IPHC_NH_MASK)) {
ipv6->nexthdr = *cursor;
cursor++;
}
/* Uncompress Hoplimit */
cursor = uncompress_hoplimit(iphc, cursor, ipv6);
/* Uncompress Source Address */
if (iphc & NET_6LO_IPHC_SAC_1) {
NET_DBG("SAC_1");
if ((iphc & NET_6LO_IPHC_SAM_MASK) == NET_6LO_IPHC_SAM_00) {
NET_DBG("SAM_00 unspecified address");
memset(&ipv6->src[0], 0,
sizeof(ipv6->src));
} else if (IS_ENABLED(CONFIG_NET_6LO_CONTEXT)) {
#if defined(CONFIG_NET_6LO_CONTEXT)
if (!src) {
NET_ERR("Src context doesn't exists");
goto fail;
}
cursor = uncompress_sa_ctx(iphc, cursor, ipv6, src, pkt);
#endif
} else {
NET_ERR("Context based uncompression not enabled");
goto fail;
}
} else {
cursor = uncompress_sa(iphc, cursor, ipv6, pkt);
}
/* Uncompress Destination Address */
if (iphc & NET_6LO_IPHC_M_1) {
if (iphc & NET_6LO_IPHC_DAC_1) {
/* TODO: DAM00 Unicast-Prefix-based IPv6 Multicast
* Addresses. DAM_01, DAM_10 and DAM_11 are reserved.
*/
NET_ERR("DAC_1 and M_1 is not supported");
goto fail;
} else {
cursor = uncompress_da_mcast(iphc, cursor, ipv6);
}
} else {
if (iphc & NET_6LO_IPHC_DAC_1) {
#if defined(CONFIG_NET_6LO_CONTEXT)
if (!dst) {
NET_ERR("Dst context doesn't exists");
goto fail;
}
cursor = uncompress_da_ctx(iphc, cursor, ipv6, dst, pkt);
#else
NET_ERR("Context based uncompression not enabled");
goto fail;
#endif
} else {
cursor = uncompress_da(iphc, cursor, ipv6, pkt);
}
}
if (iphc & NET_6LO_IPHC_NH_MASK) {
ipv6->nexthdr = IPPROTO_UDP;
udp = (struct net_udp_hdr *)(frag->data + NET_IPV6H_LEN);
/* skip nhc */
cursor++;
cursor = uncompress_nh_udp(nhc, cursor, udp);
}
if (frag != pkt->buffer) {
/* Insert the fragment (this one holds uncompressed headers) */
net_pkt_frag_insert(pkt, frag);
}
/* Set IPv6 header and UDP (if next header is) length */
len = net_pkt_get_len(pkt) - NET_IPV6H_LEN;
ipv6->len = htons(len);
if (ipv6->nexthdr == IPPROTO_UDP && udp) {
udp->len = htons(len);
if (nhc & NET_6LO_NHC_UDP_CHECKSUM) {
udp->chksum = net_calc_chksum_udp(pkt);
}
}
net_pkt_cursor_init(pkt);
return true;
fail:
if (frag != pkt->buffer) {
net_pkt_frag_unref(frag);
}
return false;
}
/* Adds IPv6 dispatch as first byte and adjust fragments */
static inline int compress_ipv6_header(struct net_pkt *pkt)
{
struct net_buf *buffer = pkt->buffer;
if (net_buf_tailroom(buffer) >= 1U) {
memmove(buffer->data + 1U, buffer->data, buffer->len);
net_buf_add(buffer, 1U);
buffer->data[0] = NET_6LO_DISPATCH_IPV6;
return 0;
}
buffer = net_pkt_get_frag(pkt, K_FOREVER);
if (!buffer) {
return -ENOBUFS;
}
buffer->data[0] = NET_6LO_DISPATCH_IPV6;
net_buf_add(buffer, 1);
net_pkt_frag_insert(pkt, buffer);
/* Compact the fragments, so that gaps will be filled */
net_pkt_compact(pkt);
return 0;
}
static inline bool uncompress_ipv6_header(struct net_pkt *pkt)
{
/* Pull off IPv6 dispatch header and adjust data and length */
net_buf_pull(pkt->buffer, 1U);
net_pkt_cursor_init(pkt);
return true;
}
int net_6lo_compress(struct net_pkt *pkt, bool iphc)
{
if (iphc) {
return compress_IPHC_header(pkt);
} else {
return compress_ipv6_header(pkt);
}
}
bool net_6lo_uncompress(struct net_pkt *pkt)
{
NET_ASSERT(pkt && pkt->frags);
if ((pkt->frags->data[0] & NET_6LO_DISPATCH_IPHC_MASK) ==
NET_6LO_DISPATCH_IPHC) {
/* Uncompress IPHC header */
return uncompress_IPHC_header(pkt);
} else if (pkt->frags->data[0] == NET_6LO_DISPATCH_IPV6) {
/* Uncompress IPv6 header, it has only IPv6 dispatch in the
* beginning */
return uncompress_ipv6_header(pkt);
}
NET_DBG("pkt %p is not compressed", pkt);
return true;
}
int net_6lo_uncompress_hdr_diff(struct net_pkt *pkt)
{
int inline_size, compressed_hdr_size, nhc_inline_size, diff;
uint16_t iphc;
uint8_t nhc;
if (pkt->frags->data[0] == NET_6LO_DISPATCH_IPV6) {
return -1;
}
if ((pkt->frags->data[0] & NET_6LO_DISPATCH_IPHC_MASK) !=
NET_6LO_DISPATCH_IPHC) {
return 0;
}
iphc = ntohs(UNALIGNED_GET((uint16_t *)pkt->buffer->data));
inline_size = get_ihpc_inlined_size(iphc);
if (inline_size < 0) {
return INT_MAX;
}
compressed_hdr_size = sizeof(iphc) + inline_size;
diff = sizeof(struct net_ipv6_hdr) - compressed_hdr_size;
if (iphc & NET_6LO_IPHC_NH_MASK) {
nhc = *(pkt->buffer->data + sizeof(iphc) + inline_size);
if ((nhc & 0xF8) != NET_6LO_NHC_UDP_BARE) {
NET_ERR("Unsupported next header");
return INT_MAX;
}
nhc_inline_size = get_udp_nhc_inlined_size(nhc);
diff += sizeof(struct net_udp_hdr) - sizeof(uint8_t) -
nhc_inline_size;
}
return diff;
}