/**************************************************************************** * net/sixlowpan/sixlowpan_framelist.c * * SPDX-License-Identifier: BSD-3-Clause * * Copyright (C) 2017-2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Parts of this file derive from Contiki: * * Copyright (c) 2008, Swedish Institute of Computer Science. * All rights reserved. * Authors: Adam Dunkels * Nicolas Tsiftes * Niclas Finne * Mathilde Durvy * Julien Abeille * Joakim Eriksson * Joel Hoglund * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include "utils/utils.h" #include "sixlowpan/sixlowpan_internal.h" #ifdef CONFIG_NET_6LOWPAN /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Configuration ************************************************************/ /* A single IOB must be big enough to hold a full frame. This we have to * check at run time. A IOB must also be big enough to hold the maximum MAC * header (25 bytes?) plus the FCS and have some amount of space left for * the payload. */ #define MAX_MACHDR 25 /* REVISIT: This is IEEE 802.15.4 specific */ #if CONFIG_IOB_BUFSIZE < (SIXLOWPAN_MAC_FCSSIZE + MAX_MACHDR) # error CONFIG_IOB_BUFSIZE too small to hold a IEEE802.14.5 frame #endif /* There must be at least enough IOBs to hold the full MTU. Probably still * won't work unless there are a few more. */ #if CONFIG_NET_6LOWPAN_PKTSIZE > (CONFIG_IOB_BUFSIZE * CONFIG_IOB_NBUFFERS) # error Not enough IOBs to hold one full 6LoWPAN packet #endif /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: sixlowpan_compress_ipv6hdr * * Description: * IPv6 dispatch "compression" function. Packets "Compression" when only * IPv6 dispatch is used * * There is no compression in this case, all fields are sent * inline. We just add the IPv6 dispatch byte before the packet. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | IPv6 Dsp | IPv6 header and payload ... * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * Input Parameters: * ipv6hdr - Pointer to the IPv6 header to "compress" * fptr - Pointer to the beginning of the frame under construction * * Returned Value: * None * ****************************************************************************/ static int sixlowpan_compress_ipv6hdr(FAR const struct ipv6_hdr_s *ipv6hdr, FAR uint8_t *fptr) { /* Indicate the IPv6 dispatch and length */ fptr[g_frame_hdrlen] = SIXLOWPAN_DISPATCH_IPV6; g_frame_hdrlen += SIXLOWPAN_IPV6_HDR_LEN; /* Copy the IPv6 header and adjust pointers */ memcpy(&fptr[g_frame_hdrlen], ipv6hdr, IPv6_HDRLEN); g_frame_hdrlen += IPv6_HDRLEN; g_uncomp_hdrlen += IPv6_HDRLEN; return COMPRESS_HDR_INLINE; } /**************************************************************************** * Name: sixlowpan_protosize * * Description: * Get the size of any uncompressed protocol header that follows the * IPv6 header. * ****************************************************************************/ static uint16_t sixlowpan_protosize(FAR const struct ipv6_hdr_s *ipv6hdr, FAR uint8_t *fptr) { uint16_t protosize; /* Copy the following protocol header, */ switch (ipv6hdr->proto) { #ifdef CONFIG_NET_TCP case IP_PROTO_TCP: { FAR struct tcp_hdr_s *tcp = &((FAR struct ipv6tcp_hdr_s *)ipv6hdr)->tcp; /* The TCP header length is encoded in the top 4 bits of the * tcpoffset field (in units of 32-bit words). */ protosize = ((uint16_t)tcp->tcpoffset >> 4) << 2; } break; #endif #ifdef CONFIG_NET_UDP case IP_PROTO_UDP: protosize = UDP_HDRLEN; break; #endif #ifdef CONFIG_NET_ICMPv6 case IP_PROTO_ICMP6: protosize = ICMPv6_HDRLEN; break; #endif default: nwarn("WARNING: Unrecognized proto: %u\n", ipv6hdr->proto); protosize = 0; break; } return protosize; } /**************************************************************************** * Name: sixlowpan_ieee802154_metadata * * Description: * Create the meta data that describes the IEEE 802.15.4 MAC header. * * Input Parameters: * radio - The radio network driver instance * destmac - The IEEE802.15.4 MAC address of the destination * meta - Location to return the final metadata. * * Returned Value: * OK is returned on success; Otherwise a negated errno value is returned. * ****************************************************************************/ #ifdef CONFIG_WIRELESS_IEEE802154 static int sixlowpan_ieee802154_metadata(FAR struct radio_driver_s *radio, FAR const struct netdev_varaddr_s *destmac, FAR union sixlowpan_metadata_u *meta) { struct ieee802_txmetadata_s pktmeta; int ret; /* Reset frame meta data */ memset(&pktmeta, 0, sizeof(struct ieee802_txmetadata_s)); pktmeta.xmits = CONFIG_NET_6LOWPAN_MAX_MACTRANSMITS; /* Set stream mode for all TCP packets, except FIN packets. */ #if 0 /* Currently the frame type is always data */ if (ipv6->proto == IP_PROTO_TCP) { FAR const struct tcp_hdr_s *tcp = &((FAR const struct ipv6tcp_hdr_s *)ipv6)->tcp; if ((tcp->flags & TCP_FIN) == 0 && (tcp->flags & TCP_CTL) != TCP_ACK) { pktmeta.type = FRAME_ATTR_TYPE_STREAM; } else if ((tcp->flags & TCP_FIN) == TCP_FIN) { pktmeta.type = FRAME_ATTR_TYPE_STREAM_END; } } #endif /* Set the source and destination address. The source MAC address * is a fixed size, determined by a configuration setting. The * destination MAC address many be either short or extended. */ #ifdef CONFIG_NET_6LOWPAN_EXTENDEDADDR pktmeta.sextended = TRUE; sixlowpan_eaddrcopy(pktmeta.source.nm_addr, &radio->r_dev.d_mac.radio.nv_addr); #else sixlowpan_saddrcopy(pktmeta.source.nm_addr, &radio->r_dev.d_mac.radio.nv_addr); #endif /* Copy the destination node address into the meta data */ if (destmac->nv_addrlen == NET_6LOWPAN_EADDRSIZE) { pktmeta.dextended = TRUE; sixlowpan_eaddrcopy(pktmeta.dest.nm_addr, destmac->nv_addr); } else { DEBUGASSERT(destmac->nv_addrlen == NET_6LOWPAN_SADDRSIZE); sixlowpan_saddrcopy(pktmeta.dest.nm_addr, destmac->nv_addr); } /* Get the destination PAN ID. * * REVISIT: For now I am assuming that the source and destination * PAN IDs are the same. */ sixlowpan_src_panid(radio, pktmeta.dpanid); /* Based on the collected attributes and addresses, construct the MAC meta * data structure that we need to interface with the IEEE802.15.4 MAC (we * will update the MSDU payload size when the IOB has been setup). */ ret = sixlowpan_meta_data(radio, &pktmeta, &meta->ieee802154); if (ret < 0) { nerr("ERROR: sixlowpan_meta_data() failed: %d\n", ret); } return ret; } #endif /**************************************************************************** * Name: sixlowpan_pktradio_metadata * * Description: * Create the meta data that describes the MAC header for a generic radio. * * Input Parameters: * radio - The radio network driver instance * destmac - The radio-specific MAC address of the destination * meta - Location to return the final metadata. * * Returned Value: * OK is returned on success; Otherwise a negated errno value is returned. * ****************************************************************************/ #ifdef CONFIG_WIRELESS_PKTRADIO static int sixlowpan_pktradio_metadata(FAR struct radio_driver_s *radio, FAR const struct netdev_varaddr_s *destmac, FAR union sixlowpan_metadata_u *meta) { FAR struct pktradio_metadata_s *pktmeta = &meta->pktradio; /* Reset the meta data */ memset(pktmeta, 0, sizeof(struct pktradio_metadata_s)); /* Set the source address */ pktmeta->pm_src.pa_addrlen = radio->r_dev.d_mac.radio.nv_addrlen; memcpy(pktmeta->pm_src.pa_addr, radio->r_dev.d_mac.radio.nv_addr, radio->r_dev.d_mac.radio.nv_addrlen); /* Set the destination address. * REVISIT: Do we need to check for multicast or broadcast addresses * here? */ pktmeta->pm_dest.pa_addrlen = destmac->nv_addrlen; memcpy(pktmeta->pm_dest.pa_addr, destmac->nv_addr, destmac->nv_addrlen); return OK; } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: sixlowpan_queue_frames * * Description: * Process an outgoing UDP, TCP, or ICMPv6 packet. This function is * called from send event handler when a TX poll is received. It * formats the list of frames to be sent by the IEEE802.15.4 MAC driver. * * The payload data is in the caller 'buf' and is of length 'buflen'. * Compressed headers will be added and if necessary the packet is * fragmented. The resulting packet/fragments are submitted to the MAC * where they are queue for transfer. * * Input Parameters: * radio - The radio network driver instance * ipv6 - IPv6 header followed by TCP, UDP, or ICMPv6 header. * buf - Beginning of the packet packet to send (with IPv6 + protocol * headers) * buflen - Length of data to send (includes IPv6 and protocol headers) * destmac - The IEEE802.15.4 MAC address of the destination * * Returned Value: * Ok is returned on success; Otherwise a negated errno value is returned. * This function is expected to fail if the driver is not an IEEE802.15.4 * MAC network driver. In that case, the UDP/TCP will fall back to normal * IPv4/IPv6 formatting. * * Assumptions: * Called with the network locked. * ****************************************************************************/ int sixlowpan_queue_frames(FAR struct radio_driver_s *radio, FAR const struct ipv6_hdr_s *ipv6, FAR const void *buf, size_t buflen, FAR const struct netdev_varaddr_s *destmac) { union sixlowpan_metadata_u meta; FAR struct iob_s *iob; FAR uint8_t *fptr; int framer_hdrlen; struct netdev_varaddr_s bcastmac; uint16_t framelen; uint16_t pktlen; uint16_t paysize; uint16_t outlen = 0; uint8_t protosize; int ret; ninfo("buflen=%lu\n", (unsigned long)buflen); /* Initialize global data. Locking the network guarantees that we have * exclusive use of the global values for intermediate calculations. */ g_uncomp_hdrlen = 0; g_frame_hdrlen = 0; protosize = 0; /* The destination address will be tagged to each outbound packet. If the * argument destmac is NULL, we are sending a broadcast packet. */ if (destmac == NULL) { memset(&bcastmac, 0, sizeof(struct netdev_varaddr_s)); destmac = &bcastmac; } /* Pre-allocate the IOB to hold frame or the first fragment, waiting if * necessary. */ iob = net_ioballoc(false); DEBUGASSERT(iob != NULL); fptr = iob->io_data; ninfo("Sending packet length %zd\n", buflen); /* Get the metadata that describes the MAC header on the packet */ #ifdef CONFIG_WIRELESS_IEEE802154 #ifdef CONFIG_WIRELESS_PKTRADIO if (radio->r_dev.d_lltype == NET_LL_IEEE802154) #endif { ret = sixlowpan_ieee802154_metadata(radio, destmac, &meta); } #endif #ifdef CONFIG_WIRELESS_PKTRADIO #ifdef CONFIG_WIRELESS_IEEE802154 else #endif { ret = sixlowpan_pktradio_metadata(radio, destmac, &meta); } #endif /* Pre-calculate frame header length. */ framer_hdrlen = sixlowpan_frame_hdrlen(radio, &meta); if (framer_hdrlen < 0) { /* Failed to determine the size of the header failed. */ nerr("ERROR: sixlowpan_frame_hdrlen() failed: %d\n", framer_hdrlen); return framer_hdrlen; } /* This sill be the initial offset into io_data. Valid data begins at * this offset and must be reflected in io_offset. */ g_frame_hdrlen = framer_hdrlen; iob->io_offset = framer_hdrlen; #ifndef CONFIG_NET_6LOWPAN_COMPRESSION_IPv6 if (buflen >= CONFIG_NET_6LOWPAN_COMPRESSION_THRESHOLD) { /* Try to compress the headers */ #if defined(CONFIG_NET_6LOWPAN_COMPRESSION_HC1) ret = sixlowpan_compresshdr_hc1(radio, ipv6, destmac, fptr); #elif defined(CONFIG_NET_6LOWPAN_COMPRESSION_HC06) ret = sixlowpan_compresshdr_hc06(radio, ipv6, destmac, fptr); #else # error No compression specified #endif } else #endif /* !CONFIG_NET_6LOWPAN_COMPRESSION_IPv6 */ { /* Small.. use IPv6 dispatch (no compression) */ ret = sixlowpan_compress_ipv6hdr(ipv6, fptr); } /* Get the size of any uncompressed protocol headers */ if (ret == COMPRESS_HDR_INLINE) { protosize = sixlowpan_protosize(ipv6, fptr); } ninfo("Header of length=%u protosize=%u\n", g_frame_hdrlen, protosize); /* Get the maximum packet size supported by this radio. */ ret = sixlowpan_radio_framelen(radio); if (ret < 0) { nerr("ERROR: sixlowpan_radio_framelen() failed: %d\n", ret); return ret; } /* Limit to the maximum size supported by the IOBs */ if (ret > CONFIG_IOB_BUFSIZE) { ret = CONFIG_IOB_BUFSIZE; } /* Reserve space at the end for any FCS that the hardware may include * in the payload. */ ret -= SIXLOWPAN_MAC_FCSSIZE; if (ret < MAX_MACHDR || ret > UINT16_MAX) { nerr("ERROR: Invalid frame size: %d\n", ret); return ret; } framelen = (uint16_t)ret; /* Check if we need to fragment the packet into several frames. * We may need to reserve space at the end of the frame for a 2-byte FCS */ if (buflen > (framelen - g_frame_hdrlen - protosize)) { /* qhead will hold the generated frame list; frames will be * added at qtail. */ FAR struct sixlowpan_reassbuf_s *reass; FAR struct iob_s *qhead; FAR struct iob_s *qtail; FAR uint8_t *frame1; FAR uint8_t *fragptr; uint16_t frag1_hdrlen; /* Recover the reassembly buffer from the driver d_buf. */ reass = (FAR struct sixlowpan_reassbuf_s *)radio->r_dev.d_buf; DEBUGASSERT(reass != NULL); /* The outbound IPv6 packet is too large to fit into a single 15.4 * packet, so we fragment it into multiple packets and send them. * The first fragment contains frag1 dispatch, then * IPv6/HC1/HC06/HC_UDP dispatchs/headers. * The following fragments contain only the fragn dispatch. */ ninfo("Sending fragmented packet length %zd\n", buflen); /* Create 1st Fragment */ /* Move HC1/HC06/IPv6 header to make space for the FRAG1 header at the * beginning of the frame. */ fragptr = fptr + framer_hdrlen; memmove(fragptr + SIXLOWPAN_FRAG1_HDR_LEN, fragptr, g_frame_hdrlen - framer_hdrlen); /* Setup up the fragment header. * * The fragment header contains three fields: Datagram size, datagram * tag and datagram offset: * * 1. Datagram size describes the total (un-fragmented) payload. * 2. Datagram tag identifies the set of fragments and is used to * match fragments of the same payload. * 3. Datagram offset identifies the fragment's offset within the * unfragmented payload. * * The fragment header length is 4 bytes for the first header and 5 * bytes for all subsequent headers. */ pktlen = buflen + g_uncomp_hdrlen + protosize; PUTHOST16(fragptr, SIXLOWPAN_FRAG_DISPATCH_SIZE, ((SIXLOWPAN_DISPATCH_FRAG1 << 8) | pktlen)); PUTHOST16(fragptr, SIXLOWPAN_FRAG_TAG, reass->rb_dgramtag); g_frame_hdrlen += SIXLOWPAN_FRAG1_HDR_LEN; /* Copy any uncompressed protocol headers that must appear only in th * first fragment. */ if (protosize > 0) { FAR uint8_t *src = (FAR uint8_t *)ipv6 + IPv6_HDRLEN; memcpy(fptr + g_frame_hdrlen, src, protosize); } /* Copy payload and enqueue. NOTE that the size is a multiple of eight * bytes. */ paysize = (framelen - g_frame_hdrlen) & ~7; memcpy(fptr + g_frame_hdrlen + protosize, buf, paysize - protosize); /* Set outlen to what we already sent from the IP payload */ iob->io_len = paysize + g_frame_hdrlen; outlen = paysize; ninfo("First fragment: length %d, tag %d\n", paysize, reass->rb_dgramtag); sixlowpan_dumpbuffer("Outgoing frame", (FAR const uint8_t *)iob->io_data, iob->io_len); /* Add the first frame to the IOB queue */ ninfo("Queuing frame io_len=%u io_offset=%u\n", iob->io_len, iob->io_offset); qhead = iob; qtail = iob; /* Keep track of the total amount of data queue */ iob->io_pktlen = iob->io_len; /* Create following fragments */ frame1 = iob->io_data; frag1_hdrlen = g_frame_hdrlen; while (outlen < (buflen + protosize)) { uint16_t fragn_hdrlen; /* Allocate an IOB to hold the next fragment, waiting if * necessary. */ iob = net_ioballoc(false); DEBUGASSERT(iob != NULL); /* Initialize the IOB */ iob->io_offset = framer_hdrlen; fptr = iob->io_data; /* Copy the HC1/HC06/IPv6 header the frame header from first * frame, into the correct location after the FRAGN header * of subsequent frames. */ fragptr = fptr + framer_hdrlen; memcpy(fragptr + SIXLOWPAN_FRAGN_HDR_LEN, frame1 + framer_hdrlen + SIXLOWPAN_FRAG1_HDR_LEN, frag1_hdrlen - framer_hdrlen); fragn_hdrlen = frag1_hdrlen - SIXLOWPAN_FRAG1_HDR_LEN; /* Setup up the FRAGN header after the frame header. */ PUTHOST16(fragptr, SIXLOWPAN_FRAG_DISPATCH_SIZE, ((SIXLOWPAN_DISPATCH_FRAGN << 8) | pktlen)); PUTHOST16(fragptr, SIXLOWPAN_FRAG_TAG, reass->rb_dgramtag); fragptr[SIXLOWPAN_FRAG_OFFSET] = outlen >> 3; fragn_hdrlen += SIXLOWPAN_FRAGN_HDR_LEN; /* Copy payload and enqueue. * * Check for the last fragment. */ paysize = (framelen - fragn_hdrlen) & SIXLOWPAN_DISPATCH_FRAG_MASK; if (paysize > buflen - outlen + protosize) { /* Last fragment, truncate to the correct length */ paysize = buflen - outlen + protosize; } memcpy(fptr + fragn_hdrlen, buf + outlen - protosize, paysize); /* Set outlen to what we already sent from the IP payload */ iob->io_len = paysize + fragn_hdrlen; outlen += paysize; ninfo("Fragment offset=%d, paysize=%d, rb_dgramtag=%d\n", outlen >> 3, paysize, reass->rb_dgramtag); sixlowpan_dumpbuffer("Outgoing frame", (FAR const uint8_t *)iob->io_data, iob->io_len); /* Add the next frame to the tail of the IOB queue */ ninfo("Queuing frame io_len=%u io_offset=%u\n", iob->io_len, iob->io_offset); qtail->io_flink = iob; qtail = iob; /* Keep track of the total amount of data queue */ qhead->io_pktlen += iob->io_len; } /* Submit all of the fragments to the MAC. We send all frames back- * to-back like this to minimize any possible condition where some * frame which is not a fragment from this sequence from intervening. */ for (iob = qhead; iob != NULL; iob = qhead) { /* Remove the IOB containing the frame from the list */ qhead = iob->io_flink; iob->io_flink = NULL; /* And submit the frame to the MAC */ ninfo("Submitting frame\n"); ret = sixlowpan_frame_submit(radio, &meta, iob); if (ret < 0) { nerr("ERROR: sixlowpan_frame_submit() failed: %d\n", ret); } } /* Update the datagram TAG value */ reass->rb_dgramtag++; } else { /* The packet does not need to be fragmented just copy the "payload" * and send in one frame. */ /* Copy any uncompressed protocol headers that must appear only in th * first fragment. */ if (protosize > 0) { FAR uint8_t *src = (FAR uint8_t *)ipv6 + IPv6_HDRLEN; memcpy(fptr + g_frame_hdrlen, src, protosize); } /* Copy the payload into the frame. */ memcpy(fptr + g_frame_hdrlen + protosize, buf, buflen); iob->io_len = buflen + g_frame_hdrlen + protosize; iob->io_pktlen = iob->io_len; ninfo("Non-fragmented: length %d\n", iob->io_len); sixlowpan_dumpbuffer("Outgoing frame", (FAR const uint8_t *)iob->io_data, iob->io_len); /* And submit the frame to the MAC */ ninfo("Submitting frame length=%u io_offset=%u\n", iob->io_len, iob->io_offset); ret = sixlowpan_frame_submit(radio, &meta, iob); if (ret < 0) { nerr("ERROR: sixlowpan_frame_submit() failed: %d\n", ret); } } return OK; } #endif /* CONFIG_NET_6LOWPAN */