diff --git a/core/net/mac/nordc.c b/core/net/mac/nordc.c new file mode 100644 index 000000000..bb41335ff --- /dev/null +++ b/core/net/mac/nordc.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +/** + * \file + * This RDC layer does nothing. It is meant for use with MAC + * layers that do not use a RDC at all, such as TSCH. + * \author + * Simon Duquennoy + * + */ + +#include "net/mac/rdc.h" +#include "net/netstack.h" + +/*---------------------------------------------------------------------------*/ +static void +send_packet(mac_callback_t sent, void *ptr) +{ +} +/*---------------------------------------------------------------------------*/ +static void +send_list(mac_callback_t sent, void *ptr, struct rdc_buf_list *buf_list) +{ +} +/*---------------------------------------------------------------------------*/ +static void +packet_input(void) +{ +} +/*---------------------------------------------------------------------------*/ +static int +on(void) +{ + return 0; +} +/*---------------------------------------------------------------------------*/ +static int +off(int keep_radio_on) +{ + return 0; +} +/*---------------------------------------------------------------------------*/ +static unsigned short +channel_check_interval(void) +{ + return 0; +} +/*---------------------------------------------------------------------------*/ +static void +init(void) +{ +} +/*---------------------------------------------------------------------------*/ +const struct rdc_driver nordc_driver = { + "nordc", + init, + send_packet, + send_list, + packet_input, + on, + off, + channel_check_interval, +}; +/*---------------------------------------------------------------------------*/ diff --git a/core/net/mac/tsch/Makefile.tsch b/core/net/mac/tsch/Makefile.tsch new file mode 100644 index 000000000..59487923c --- /dev/null +++ b/core/net/mac/tsch/Makefile.tsch @@ -0,0 +1 @@ +CONTIKI_SOURCEFILES += tsch.c tsch-slot-operation.c tsch-queue.c tsch-packet.c tsch-schedule.c tsch-log.c tsch-rpl.c diff --git a/core/net/mac/tsch/README.md b/core/net/mac/tsch/README.md new file mode 100644 index 000000000..8ffd639b2 --- /dev/null +++ b/core/net/mac/tsch/README.md @@ -0,0 +1,201 @@ +# IEEE 802.15.4e TSCH (TimeSlotted Channel Hopping) + +## Overview + +TSCH is a MAC layer of the [IEEE 802.15.4e-2012 amendment][ieee802.15.4e-2012], +currently being integrated as part of the new IEEE 802.15.4-2015. +[6TiSCH][ietf-6tisch-wg] is an IETF Working Group focused on IPv6 over TSCH. +This is a Contiki implementation of TSCH and the 6TiSCH so-called "minimal configuration", +which defines how to run a basic RPL+TSCH network. + +It was developped by: +* Simon Duquennoy, SICS, simonduq@sics.se, github user: [simonduq](https://github.com/simonduq) +* Beshr Al Nahas, SICS (now Chalmers University), beshr@chalmers.se, github user: [beshrns](https://github.com/beshrns) + +You can find an extensive evaluation of this implementation in our paper [*Orchestra: Robust Mesh Networks Through Autonomously Scheduled TSCH*](http://www.simonduquennoy.net/papers/duquennoy15orchestra.pdf), ACM SenSys'15. + +## Features + +This implementation includes: + * Standard IEEE 802.15.4e-2012 frame version 2 + * Standard TSCH joining procedure with Enhanced Beacons with the following Information Elements: + * TSCH synchronization (join priority and ASN) + * TSCH slotframe and link (basic schedule) + * TSCH timeslot (timeslot timing template) + * TSCH channel hopping sequence (hopping sequence template) + * Standard TSCH link selection and slot operation (10ms slots by default) + * Standard TSCH synchronization, including with ACK/NACK time correction Information Element + * Standard TSCH queues and CSMA-CA mechanism + * Standard TSCH security + * Standard 6TiSCH TSCH-RPL interaction (6TiSCH Minimal Configuration and Minimal Schedule) + * A scheduling API to add/remove slotframes and links + * A system for logging from TSCH timeslot operation interrupt, with postponed printout + * Orchestra: an autonomous scheduler for TSCH+RPL networks + +It has been tested on the following platforms: + * NXP JN516x (`jn516x`, tested on hardware) + * Tmote Sky (`sky`, tested on hardware and in cooja) + * Zolertia Z1 (`z1`, tested in cooja only) + +This implementation was present at the ETSI Plugtest +event in Prague in July 2015, and did successfully inter-operate with all +four implementations it was tested against. + +We have designed this implementation with IPv6 and RPL in mind, but the code is fully independent +from upper layers (with the exception of the optional `tsch-rpl.[ch]`), and has been +also tested with Rime (currently only with 64-bit link-later addresses). + +## Code structure + +The IEEE 802.15.4e-2012 frame format is implemented in: +* `core/net/mac/frame802154.[ch]`: handling of frame version 2 +* `core/net/mac/frame802154-ie.[ch]`: handling of Information Elements + +TSCH is implemented in: +* `core/net/mac/tsch/tsch.[ch]`: TSCH management (association, keep-alive), processes handling pending +outgoing and incoming packets, and interface with Contiki's upper layers as a MAC driver. TSCH does not +require a RDC (nordc is recommended). +* `tsch-slot-operation.[ch]`: TSCH low-level slot operation, fully interrupt-driven. Node wake up at every active +slot (according to the slotframes and links installed), transmit or receive frames and ACKs. Received packets are +stored in a ringbuf for latter upper-layer processing. Outgoing packets that are dequeued (because acknowledged +or dropped) are stored in another ringbuf for upper-layer processing. +* `tsch-asn.h`: TSCH macros for Absolute Slot Number (ASN) handling. +* `tsch-packet.[ch]`: TSCH Enhanced ACK (EACK) and enhanced Beacon (EB) creation and parsing. +* `tsch-queue.[ch]`: TSCH per-neighbor queue, neighbor state, and CSMA-CA. +* `tsch-schedule.[ch]`: TSCH slotframe and link handling, and API for slotframe and link installation/removal. +* `tsch-security.[ch]`: TSCH security, i.e. securing frames and ACKs from interrupt with ASN as part of the Nonce. +Implements the 6TiSCH minimal configuration K1-K2 keys pair. +* `tsch-rpl.[ch]`: used for TSCH+RPL networks, to align TSCH and RPL states (preferred parent -> time source, +rank -> join priority) as defined in the 6TiSCH minimal configuration. +* `tsch-log.[ch]`: logging system for TSCH, including delayed messages for logging from slot operation interrupt. + +Orchestra is implemented in: +* `apps/orchestra`: see `apps/orchestra/README.md` for more information. + +## Using TSCH + +A simple TSCH+RPL example is included under `examples/ipv6/rpl-tsch`. +To use TSCH, first make sure your platform supports it. +Currently, `jn516x`, `sky` and `z1` are the supported platforms. +To add your own, we refer the reader to the next section. + +To add TSCH to your application, first include the TSCH module from your makefile with: + +`MODULES += core/net/mac/tsch` + +Then, enable TSCH from your project conf with the following: + +``` +/* Netstack layers */ +#undef NETSTACK_CONF_MAC +#define NETSTACK_CONF_MAC tschmac_driver +#undef NETSTACK_CONF_RDC +#define NETSTACK_CONF_RDC nordc_driver +#undef NETSTACK_CONF_FRAMER +#define NETSTACK_CONF_FRAMER framer_802154 + +/* IEEE802.15.4 frame version */ +#undef FRAME802154_CONF_VERSION +#define FRAME802154_CONF_VERSION FRAME802154_IEEE802154E_2012 +``` + +If you are running with RPL, it is recommended to enable the `tsch-rpl` module with: + +``` +/* TSCH and RPL callbacks */ +#define RPL_CALLBACK_PARENT_SWITCH tsch_rpl_callback_parent_switch +#define RPL_CALLBACK_NEW_DIO_INTERVAL tsch_rpl_callback_new_dio_interval +#define TSCH_CALLBACK_JOINING_NETWORK tsch_rpl_callback_joining_network +#define TSCH_CALLBACK_LEAVING_NETWORK tsch_rpl_callback_leaving_network +``` + +On CC2420-based platforms, enable SFD timestamps with: + +``` +/* Disable DCO calibration (uses timerB) */ +#undef DCOSYNCH_CONF_ENABLED +#define DCOSYNCH_CONF_ENABLED 0 + +/* Enable SFD timestamps (uses timerB) */ +#undef CC2420_CONF_SFD_TIMESTAMPS +#define CC2420_CONF_SFD_TIMESTAMPS 1 +``` + +To configure TSCH, see the macros in `.h` files under `core/net/mac/tsch/` and redefine your own in your `project-conf.h`. + +## Using TSCH with Security + +To include TSCH standard-compliant security, set the following: +``` +/* Enable security */ +#undef LLSEC802154_CONF_SECURITY_LEVEL +#define LLSEC802154_CONF_SECURITY_LEVEL 1 +/* TSCH uses explicit keys to identify k1 and k2 */ +#undef LLSEC802154_CONF_USES_EXPLICIT_KEYS +#define LLSEC802154_CONF_USES_EXPLICIT_KEYS 1 +/* TSCH uses the ASN rather than frame counter to construct the Nonce */ +#undef LLSEC802154_CONF_USES_FRAME_COUNTER +#define LLSEC802154_CONF_USES_FRAME_COUNTER 0 +``` + +The keys can be configured in `net/mac/tsch/tsch-security.h`. +Nodes handle security level and keys dynamically, i.e. as specified by the incoming frame header rather that compile-time defined. + +By default, when including security, the PAN coordinator will transmit secured EBs. +Use `tsch_set_pan_secured` to explicitly ask the coordinator to secure EBs or not. + +When associating, nodes with security included can join both secured or non-secured networks. +Set `TSCH_CONF_JOIN_SECURED_ONLY` to force joining secured networks only. +Likewise, set `TSCH_JOIN_MY_PANID_ONLY` to force joining networks with a specific PANID only. + +## TSCH Scheduling + +By default (see `TSCH_SCHEDULE_WITH_6TISCH_MINIMAL`), our implementation runs a 6TiSCH minimal schedule, which emulates an always-on link on top of TSCH. +The schedule consists in a single shared slot for all transmissions and receptions, in a slotframe of length `TSCH_SCHEDULE_DEFAULT_LENGTH`. + +As an alternative, we provide Orchestra (under `apps/orchestra`), an autonomous scheduling solution for TSCH where nodes maintain their own schedule locally, solely based on their local RPL state. +Orchestra can be simply enabled and should work out-of-the-box with its default settings as long as RPL is also enabled. +See `apps/orchestra/README.md` for more information. + +Finally, one can also implement his own scheduler, centralized or distributed, based on the scheduling API provides in `core/net/mac/tsch/tsch-schedule.h`. + +## Porting TSCH to a new platform + +Porting TSCH to a new platform requires a few new features in the radio driver, a number of timing-related configuration paramters. +The easiest is probably to start from one of the existing port: `jn516x`, `sky`, `z1`. + +### Radio features required for TSCH + +The main new feature required for TSCH is the so-called *poll mode*, a new Rx mode for Contiki radio drivers. +In poll mode, radio interrupts are disabled, and the radio driver never calls upper layers. +Instead, TSCH will poll the driver for incoming packets, from interrupt, exactly when it expects one. + +TSCH will check when initializing (in `tsch_init`) that the radio driver supports all required features, namely: +* get and set Rx mode (`RADIO_PARAM_RX_MODE`) as follows: + * enable address filtering with `RADIO_RX_MODE_ADDRESS_FILTER` + * disable auto-ack with `RADIO_RX_MODE_AUTOACK` + * enable poll mode with `RADIO_RX_MODE_POLL_MODE` +* get and set Tx mode (`RADIO_PARAM_TX_MODE`) as follows: + * disable CCA-before-sending with `RADIO_TX_MODE_SEND_ON_CCA` +* set radio channel with `RADIO_PARAM_CHANNEL` +* get last packet timestamp with `RADIO_PARAM_LAST_PACKET_TIMESTAMP` +* optionally: get last packet RSSI with `RADIO_PARAM_LAST_RSSI` +* optionally: get last packet LQI with `RADIO_PARAM_LAST_LQI` + +### Timing macros required for TSCH + +The following macros must be provided: +* `US_TO_RTIMERTICKS(US)`: converts micro-seconds to rtimer ticks +* `RTIMERTICKS_TO_US(T)`: converts rtimer ticks to micro-seconds +* `RADIO_DELAY_BEFORE_TX`: the delay between radio Tx request and SFD sent, in rtimer ticks +* `RADIO_DELAY_BEFORE_RX`: the delay between radio Rx request and start listening, in rtimer ticks +* optionally, `TSCH_CONF_DEFAULT_TIMESLOT_LENGTH`: the default TSCH timeslot length, useful i.e. for platforms +too slow for the default 10ms timeslots. + +## Additional documentation + +1. [IEEE 802.15.4e-2012 ammendment][ieee802.15.4e-2012] +2. [IETF 6TiSCH Working Group][ietf-6tisch-wg] + +[ieee802.15.4e-2012]: http://standards.ieee.org/getieee802/download/802.15.4e-2012.pdf +[ietf-6tisch-wg]: https://datatracker.ietf.org/wg/6tisch diff --git a/core/net/mac/tsch/tsch-log.c b/core/net/mac/tsch/tsch-log.c new file mode 100644 index 000000000..bfb033e9d --- /dev/null +++ b/core/net/mac/tsch/tsch-log.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +/** + * \file + * Log functions for TSCH, meant for logging from interrupt + * during a timeslot operation. Saves ASN, slot and link information + * and adds the log to a ringbuf for later printout. + * \author + * Simon Duquennoy + * + */ + +#include "contiki.h" +#include +#include "net/mac/tsch/tsch.h" +#include "net/mac/tsch/tsch-queue.h" +#include "net/mac/tsch/tsch-private.h" +#include "net/mac/tsch/tsch-log.h" +#include "net/mac/tsch/tsch-packet.h" +#include "net/mac/tsch/tsch-schedule.h" +#include "net/mac/tsch/tsch-slot-operation.h" +#include "lib/ringbufindex.h" + +#if TSCH_LOG_LEVEL >= 1 +#define DEBUG DEBUG_PRINT +#else /* TSCH_LOG_LEVEL */ +#define DEBUG DEBUG_NONE +#endif /* TSCH_LOG_LEVEL */ +#include "net/ip/uip-debug.h" + +#if TSCH_LOG_LEVEL >= 2 /* Skip this file for log levels 0 or 1 */ + +PROCESS_NAME(tsch_pending_events_process); + +/* Check if TSCH_LOG_QUEUE_LEN is a power of two */ +#if (TSCH_LOG_QUEUE_LEN & (TSCH_LOG_QUEUE_LEN - 1)) != 0 +#error TSCH_LOG_QUEUE_LEN must be power of two +#endif +static struct ringbufindex log_ringbuf; +static struct tsch_log_t log_array[TSCH_LOG_QUEUE_LEN]; +static int log_dropped = 0; + +/*---------------------------------------------------------------------------*/ +/* Process pending log messages */ +void +tsch_log_process_pending(void) +{ + static int last_log_dropped = 0; + int16_t log_index; + /* Loop on accessing (without removing) a pending input packet */ + if(log_dropped != last_log_dropped) { + printf("TSCH:! logs dropped %u\n", log_dropped); + last_log_dropped = log_dropped; + } + while((log_index = ringbufindex_peek_get(&log_ringbuf)) != -1) { + struct tsch_log_t *log = &log_array[log_index]; + struct tsch_slotframe *sf = tsch_schedule_get_slotframe_by_handle(log->link->slotframe_handle); + printf("TSCH: {asn-%x.%lx link-%u-%u-%u-%u ch-%u} ", + log->asn.ms1b, log->asn.ls4b, + log->link->slotframe_handle, sf ? sf->size.val : 0, log->link->timeslot, log->link->channel_offset, + tsch_calculate_channel(&log->asn, log->link->channel_offset)); + switch(log->type) { + case tsch_log_tx: + printf("%s-%u-%u %u tx %d, st %d-%d", + log->tx.dest == 0 ? "bc" : "uc", log->tx.is_data, log->tx.sec_level, + log->tx.datalen, + log->tx.dest, + log->tx.mac_tx_status, log->tx.num_tx); + if(log->tx.drift_used) { + printf(", dr %d", log->tx.drift); + } + printf("\n"); + break; + case tsch_log_rx: + printf("%s-%u-%u %u rx %d", + log->rx.is_unicast == 0 ? "bc" : "uc", log->rx.is_data, log->tx.sec_level, + log->rx.datalen, + log->rx.src); + if(log->rx.drift_used) { + printf(", dr %d", log->rx.drift); + } + printf(", edr %d\n", (int)log->rx.estimated_drift); + break; + case tsch_log_message: + printf("%s\n", log->message); + break; + } + /* Remove input from ringbuf */ + ringbufindex_get(&log_ringbuf); + } +} +/*---------------------------------------------------------------------------*/ +/* Prepare addition of a new log. + * Returns pointer to log structure if success, NULL otherwise */ +struct tsch_log_t * +tsch_log_prepare_add(void) +{ + int log_index = ringbufindex_peek_put(&log_ringbuf); + if(log_index != -1) { + struct tsch_log_t *log = &log_array[log_index]; + log->asn = current_asn; + log->link = current_link; + return log; + } else { + log_dropped++; + return NULL; + } +} +/*---------------------------------------------------------------------------*/ +/* Actually add the previously prepared log */ +void +tsch_log_commit(void) +{ + ringbufindex_put(&log_ringbuf); + process_poll(&tsch_pending_events_process); +} +/*---------------------------------------------------------------------------*/ +/* Initialize log module */ +void +tsch_log_init(void) +{ + ringbufindex_init(&log_ringbuf, TSCH_LOG_QUEUE_LEN); +} + +#endif /* TSCH_LOG_LEVEL */ diff --git a/core/net/mac/tsch/tsch-log.h b/core/net/mac/tsch/tsch-log.h new file mode 100644 index 000000000..9b8032577 --- /dev/null +++ b/core/net/mac/tsch/tsch-log.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +#ifndef __TSCH_LOG_H__ +#define __TSCH_LOG_H__ + +/********** Includes **********/ + +#include "contiki.h" +#include "sys/rtimer.h" +#include "net/mac/tsch/tsch-private.h" + +/******** Configuration *******/ + +/* The length of the log queue, i.e. maximum number postponed log messages */ +#ifdef TSCH_LOG_CONF_QUEUE_LEN +#define TSCH_LOG_QUEUE_LEN TSCH_LOG_CONF_QUEUE_LEN +#else /* TSCH_LOG_CONF_QUEUE_LEN */ +#define TSCH_LOG_QUEUE_LEN 8 +#endif /* TSCH_LOG_CONF_QUEUE_LEN */ + +/* Returns an integer ID from a link-layer address */ +#ifdef TSCH_LOG_CONF_ID_FROM_LINKADDR +#define TSCH_LOG_ID_FROM_LINKADDR(addr) TSCH_LOG_CONF_ID_FROM_LINKADDR(addr) +#else /* TSCH_LOG_ID_FROM_LINKADDR */ +#define TSCH_LOG_ID_FROM_LINKADDR(addr) ((addr) ? (addr)->u8[LINKADDR_SIZE - 1] : 0) +#endif /* TSCH_LOG_ID_FROM_LINKADDR */ + +/* TSCH log levels: + * 0: no log + * 1: basic PRINTF enabled + * 2: basic PRINTF enabled and tsch-log module enabled */ +#ifdef TSCH_LOG_CONF_LEVEL +#define TSCH_LOG_LEVEL TSCH_LOG_CONF_LEVEL +#else /* TSCH_LOG_CONF_LEVEL */ +#define TSCH_LOG_LEVEL 2 +#endif /* TSCH_LOG_CONF_LEVEL */ + +#if TSCH_LOG_LEVEL < 2 /* For log level 0 or 1, the logging functions do nothing */ + +#define tsch_log_init() +#define tsch_log_process_pending() +#define TSCH_LOG_ADD(log_type, init_code) + +#else /* TSCH_LOG_LEVEL */ + +/************ Types ***********/ + +/* Structure for a log. Union of different types of logs */ +struct tsch_log_t { + enum { tsch_log_tx, + tsch_log_rx, + tsch_log_message + } type; + struct asn_t asn; + struct tsch_link *link; + union { + char message[48]; + struct { + int mac_tx_status; + int dest; + int drift; + uint8_t num_tx; + uint8_t datalen; + uint8_t is_data; + uint8_t sec_level; + uint8_t drift_used; + } tx; + struct { + int src; + int drift; + int estimated_drift; + uint8_t datalen; + uint8_t is_unicast; + uint8_t is_data; + uint8_t sec_level; + uint8_t drift_used; + } rx; + }; +}; + +/********** Functions *********/ + +/* Prepare addition of a new log. + * Returns pointer to log structure if success, NULL otherwise */ +struct tsch_log_t *tsch_log_prepare_add(void); +/* Actually add the previously prepared log */ +void tsch_log_commit(void); +/* Initialize log module */ +void tsch_log_init(void); +/* Process pending log messages */ +void tsch_log_process_pending(void); + +/************ Macros **********/ + +/* Use this macro to add a log to the queue (will be printed out + * later, after leaving interrupt context) */ +#define TSCH_LOG_ADD(log_type, init_code) do { \ + struct tsch_log_t *log = tsch_log_prepare_add(); \ + if(log != NULL) { \ + log->type = (log_type); \ + init_code; \ + tsch_log_commit(); \ + } \ +} while(0); + +#endif /* TSCH_LOG_LEVEL */ + +#endif /* __TSCH_LOG_H__ */ diff --git a/core/net/mac/tsch/tsch-packet.c b/core/net/mac/tsch/tsch-packet.c new file mode 100644 index 000000000..dbfad6082 --- /dev/null +++ b/core/net/mac/tsch/tsch-packet.c @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +/** + * \file + * TSCH packet format management + * \author + * Simon Duquennoy + * Beshr Al Nahas + */ + +#include "contiki.h" +#include "net/packetbuf.h" +#include "net/mac/tsch/tsch.h" +#include "net/mac/tsch/tsch-packet.h" +#include "net/mac/tsch/tsch-private.h" +#include "net/mac/tsch/tsch-schedule.h" +#include "net/mac/tsch/tsch-security.h" +#include "net/mac/tsch/tsch-log.h" +#include "net/mac/frame802154.h" +#include "net/mac/framer-802154.h" +#include "net/netstack.h" +#include "net/llsec/anti-replay.h" +#include "lib/ccm-star.h" +#include "lib/aes-128.h" +#include +#include + +#if TSCH_LOG_LEVEL >= 1 +#define DEBUG DEBUG_PRINT +#else /* TSCH_LOG_LEVEL */ +#define DEBUG DEBUG_NONE +#endif /* TSCH_LOG_LEVEL */ +#include "net/ip/uip-debug.h" + +/*---------------------------------------------------------------------------*/ +/* Construct enhanced ACK packet and return ACK length */ +int +tsch_packet_create_eack(uint8_t *buf, int buf_size, + linkaddr_t *dest_addr, uint8_t seqno, int16_t drift, int nack) +{ + int ret; + uint8_t curr_len = 0; + frame802154_t p; + struct ieee802154_ies ies; + + memset(&p, 0, sizeof(p)); + p.fcf.frame_type = FRAME802154_ACKFRAME; + p.fcf.frame_version = FRAME802154_IEEE802154E_2012; + p.fcf.ie_list_present = 1; + /* Compression unset. According to IEEE802.15.4e-2012: + * - if no address is present: elide PAN ID + * - if at least one address is present: include exactly one PAN ID (dest by default) */ + p.fcf.panid_compression = 0; + p.dest_pid = IEEE802154_PANID; + p.seq = seqno; +#if TSCH_PACKET_EACK_WITH_DEST_ADDR + if(dest_addr != NULL) { + p.fcf.dest_addr_mode = FRAME802154_LONGADDRMODE; + linkaddr_copy((linkaddr_t *)&p.dest_addr, dest_addr); + } +#endif +#if TSCH_PACKET_EACK_WITH_SRC_ADDR + p.fcf.src_addr_mode = FRAME802154_LONGADDRMODE; + p.src_pid = IEEE802154_PANID; + linkaddr_copy((linkaddr_t *)&p.src_addr, &linkaddr_node_addr); +#endif +#if TSCH_SECURITY_ENABLED + if(tsch_is_pan_secured) { + p.fcf.security_enabled = 1; + p.aux_hdr.security_control.security_level = TSCH_SECURITY_KEY_SEC_LEVEL_ACK; + p.aux_hdr.security_control.key_id_mode = FRAME802154_1_BYTE_KEY_ID_MODE; + p.aux_hdr.security_control.frame_counter_suppression = 1; + p.aux_hdr.security_control.frame_counter_size = 1; + p.aux_hdr.key_index = TSCH_SECURITY_KEY_INDEX_ACK; + } +#endif /* TSCH_SECURITY_ENABLED */ + + if((curr_len = frame802154_create(&p, buf)) == 0) { + return 0; + } + + /* Append IE timesync */ + memset(&ies, 0, sizeof(ies)); + ies.ie_time_correction = drift; + ies.ie_is_nack = nack; + + if((ret = frame80215e_create_ie_header_ack_nack_time_correction(buf+curr_len, buf_size-curr_len, &ies)) == -1) { + return -1; + } + curr_len += ret; + + return curr_len; +} +/*---------------------------------------------------------------------------*/ +/* Parse enhanced ACK packet, extract drift and nack */ +int +tsch_packet_parse_eack(const uint8_t *buf, int buf_size, + uint8_t seqno, frame802154_t *frame, struct ieee802154_ies *ies, uint8_t *hdr_len) +{ + uint8_t curr_len = 0; + int ret; + linkaddr_t dest; + + if(frame == NULL || buf_size < 0) { + return 0; + } + /* Parse 802.15.4-2006 frame, i.e. all fields before Information Elements */ + if((ret = frame802154_parse((uint8_t *)buf, buf_size, frame)) < 3) { + return 0; + } + if(hdr_len != NULL) { + *hdr_len = ret; + } + curr_len += ret; + + /* Check seqno */ + if(seqno != frame->seq) { + return 0; + } + + /* Check destination PAN ID */ + if(frame802154_check_dest_panid(frame) == 0) { + return 0; + } + + /* Check destination address (if any) */ + if(frame802154_extract_linkaddr(frame, NULL, &dest) == 0 || + (!linkaddr_cmp(&dest, &linkaddr_node_addr) + && !linkaddr_cmp(&dest, &linkaddr_null))) { + return 0; + } + + if(ies != NULL) { + memset(ies, 0, sizeof(struct ieee802154_ies)); + } + + if(frame->fcf.ie_list_present) { + int mic_len = 0; +#if TSCH_SECURITY_ENABLED + /* Check if there is space for the security MIC (if any) */ + mic_len = tsch_security_mic_len(frame); + if(buf_size < curr_len + mic_len) { + return 0; + } +#endif /* TSCH_SECURITY_ENABLED */ + /* Parse information elements. We need to substract the MIC length, as the exact payload len is needed while parsing */ + if((ret = frame802154e_parse_information_elements(buf + curr_len, buf_size - curr_len - mic_len, ies)) == -1) { + return 0; + } + curr_len += ret; + } + + if(hdr_len != NULL) { + *hdr_len += ies->ie_payload_ie_offset; + } + + return curr_len; +} +/*---------------------------------------------------------------------------*/ +/* Create an EB packet */ +int +tsch_packet_create_eb(uint8_t *buf, int buf_size, uint8_t seqno, + uint8_t *hdr_len, uint8_t *tsch_sync_ie_offset) +{ + int ret = 0; + uint8_t curr_len = 0; + uint8_t mlme_ie_offset; + + frame802154_t p; + struct ieee802154_ies ies; + + if(buf_size < TSCH_PACKET_MAX_LEN) { + return 0; + } + + /* Create 802.15.4 header */ + memset(&p, 0, sizeof(p)); + p.fcf.frame_type = FRAME802154_BEACONFRAME; + p.fcf.ie_list_present = 1; + p.fcf.frame_version = FRAME802154_IEEE802154E_2012; + p.fcf.src_addr_mode = FRAME802154_LONGADDRMODE; + p.fcf.dest_addr_mode = FRAME802154_SHORTADDRMODE; + p.seq = seqno; + p.fcf.sequence_number_suppression = FRAME802154_SUPPR_SEQNO; + /* It is important not to compress PAN ID, as this would result in not including either + * source nor destination PAN ID, leaving potential joining devices unaware of the PAN ID. */ + p.fcf.panid_compression = 0; + + p.src_pid = frame802154_get_pan_id(); + p.dest_pid = frame802154_get_pan_id(); + linkaddr_copy((linkaddr_t *)&p.src_addr, &linkaddr_node_addr); + p.dest_addr[0] = 0xff; + p.dest_addr[1] = 0xff; + +#if TSCH_SECURITY_ENABLED + if(tsch_is_pan_secured) { + p.fcf.security_enabled = packetbuf_attr(PACKETBUF_ATTR_SECURITY_LEVEL) > 0; + p.aux_hdr.security_control.security_level = packetbuf_attr(PACKETBUF_ATTR_SECURITY_LEVEL); + p.aux_hdr.security_control.key_id_mode = packetbuf_attr(PACKETBUF_ATTR_KEY_ID_MODE); + p.aux_hdr.security_control.frame_counter_suppression = 1; + p.aux_hdr.security_control.frame_counter_size = 1; + p.aux_hdr.key_index = packetbuf_attr(PACKETBUF_ATTR_KEY_INDEX); + } +#endif /* TSCH_SECURITY_ENABLED */ + + if((curr_len = frame802154_create(&p, buf)) == 0) { + return 0; + } + + /* Prepare Information Elements for inclusion in the EB */ + memset(&ies, 0, sizeof(ies)); + + /* Add TSCH timeslot timing IE. */ +#if TSCH_PACKET_EB_WITH_TIMESLOT_TIMING + { + int i; + ies.ie_tsch_timeslot_id = 1; + for(i = 0; i < tsch_ts_elements_count; i++) { + ies.ie_tsch_timeslot[i] = RTIMERTICKS_TO_US(tsch_timing[i]); + } + } +#endif /* TSCH_PACKET_EB_WITH_TIMESLOT_TIMING */ + + /* Add TSCH hopping sequence IE */ +#if TSCH_PACKET_EB_WITH_HOPPING_SEQUENCE + if(tsch_hopping_sequence_length.val <= sizeof(ies.ie_hopping_sequence_list)) { + ies.ie_channel_hopping_sequence_id = 1; + ies.ie_hopping_sequence_len = tsch_hopping_sequence_length.val; + memcpy(ies.ie_hopping_sequence_list, tsch_hopping_sequence, ies.ie_hopping_sequence_len); + } +#endif /* TSCH_PACKET_EB_WITH_HOPPING_SEQUENCE */ + + /* Add Slotframe and Link IE */ +#if TSCH_PACKET_EB_WITH_SLOTFRAME_AND_LINK + { + /* Send slotframe 0 with link at timeslot 0 */ + struct tsch_slotframe *sf0 = tsch_schedule_get_slotframe_by_handle(0); + struct tsch_link *link0 = tsch_schedule_get_link_by_timeslot(sf0, 0); + if(sf0 && link0) { + ies.ie_tsch_slotframe_and_link.num_slotframes = 1; + ies.ie_tsch_slotframe_and_link.slotframe_handle = sf0->handle; + ies.ie_tsch_slotframe_and_link.slotframe_size = sf0->size.val; + ies.ie_tsch_slotframe_and_link.num_links = 1; + ies.ie_tsch_slotframe_and_link.links[0].timeslot = link0->timeslot; + ies.ie_tsch_slotframe_and_link.links[0].channel_offset = link0->channel_offset; + ies.ie_tsch_slotframe_and_link.links[0].link_options = link0->link_options; + } + } +#endif /* TSCH_PACKET_EB_WITH_SLOTFRAME_AND_LINK */ + + /* First add header-IE termination IE to stipulate that next come payload IEs */ + if((ret = frame80215e_create_ie_header_list_termination_1(buf + curr_len, buf_size - curr_len, &ies)) == -1) { + return -1; + } + curr_len += ret; + + /* We start payload IEs, save offset */ + if(hdr_len != NULL) { + *hdr_len = curr_len; + } + + /* Save offset of the MLME IE descriptor, we need to know the total length + * before writing it */ + mlme_ie_offset = curr_len; + curr_len += 2; /* Space needed for MLME descriptor */ + + /* Save the offset of the TSCH Synchronization IE, needed to update ASN and join priority before sending */ + if(tsch_sync_ie_offset != NULL) { + *tsch_sync_ie_offset = curr_len; + } + if((ret = frame80215e_create_ie_tsch_synchronization(buf + curr_len, buf_size - curr_len, &ies)) == -1) { + return -1; + } + curr_len += ret; + + if((ret = frame80215e_create_ie_tsch_timeslot(buf + curr_len, buf_size - curr_len, &ies)) == -1) { + return -1; + } + curr_len += ret; + + if((ret = frame80215e_create_ie_tsch_channel_hopping_sequence(buf + curr_len, buf_size - curr_len, &ies)) == -1) { + return -1; + } + curr_len += ret; + + if((ret = frame80215e_create_ie_tsch_slotframe_and_link(buf + curr_len, buf_size - curr_len, &ies)) == -1) { + return -1; + } + curr_len += ret; + + ies.ie_mlme_len = curr_len - mlme_ie_offset - 2; + if((ret = frame80215e_create_ie_mlme(buf + mlme_ie_offset, buf_size - mlme_ie_offset, &ies)) == -1) { + return -1; + } + + /* Payload IE list termination: optional */ + /* + if((ret = frame80215e_create_ie_payload_list_termination(buf + curr_len, buf_size - curr_len, &ies)) == -1) { + return -1; + } + curr_len += ret; + */ + + return curr_len; +} +/*---------------------------------------------------------------------------*/ +/* Update ASN in EB packet */ +int +tsch_packet_update_eb(uint8_t *buf, int buf_size, uint8_t tsch_sync_ie_offset) +{ + struct ieee802154_ies ies; + ies.ie_asn = current_asn; + ies.ie_join_priority = tsch_join_priority; + frame80215e_create_ie_tsch_synchronization(buf+tsch_sync_ie_offset, buf_size-tsch_sync_ie_offset, &ies); + return 1; +} +/*---------------------------------------------------------------------------*/ +/* Parse a IEEE 802.15.4e TSCH Enhanced Beacon (EB) */ +int +tsch_packet_parse_eb(const uint8_t *buf, int buf_size, + frame802154_t *frame, struct ieee802154_ies *ies, uint8_t *hdr_len, int frame_without_mic) +{ + uint8_t curr_len = 0; + int ret; + + if(frame == NULL || buf_size < 0) { + return 0; + } + + /* Parse 802.15.4-2006 frame, i.e. all fields before Information Elements */ + if((ret = frame802154_parse((uint8_t *)buf, buf_size, frame)) == 0) { + PRINTF("TSCH:! parse_eb: failed to parse frame\n"); + return 0; + } + + if(frame->fcf.frame_version < FRAME802154_IEEE802154E_2012 + || frame->fcf.frame_type != FRAME802154_BEACONFRAME) { + PRINTF("TSCH:! parse_eb: frame is not a valid TSCH beacon. Frame version %u, type %u, FCF %02x %02x\n", + frame->fcf.frame_version, frame->fcf.frame_type, buf[0], buf[1]); + PRINTF("TSCH:! parse_eb: frame was from 0x%x/", frame->src_pid); + PRINTLLADDR((const uip_lladdr_t *)&frame->src_addr); + PRINTF(" to 0x%x/", frame->dest_pid); + PRINTLLADDR((const uip_lladdr_t *)&frame->dest_addr); + PRINTF("\n"); + return 0; + } + + if(hdr_len != NULL) { + *hdr_len = ret; + } + curr_len += ret; + + if(ies != NULL) { + memset(ies, 0, sizeof(struct ieee802154_ies)); + ies->ie_join_priority = 0xff; /* Use max value in case the Beacon does not include a join priority */ + } + if(frame->fcf.ie_list_present) { + /* Calculate space needed for the security MIC, if any, before attempting to parse IEs */ + int mic_len = 0; +#if TSCH_SECURITY_ENABLED + if(!frame_without_mic) { + mic_len = tsch_security_mic_len(frame); + if(buf_size < curr_len + mic_len) { + return 0; + } + } +#endif /* TSCH_SECURITY_ENABLED */ + + /* Parse information elements. We need to substract the MIC length, as the exact payload len is needed while parsing */ + if((ret = frame802154e_parse_information_elements(buf + curr_len, buf_size - curr_len - mic_len, ies)) == -1) { + PRINTF("TSCH:! parse_eb: failed to parse IEs\n"); + return 0; + } + curr_len += ret; + } + + if(hdr_len != NULL) { + *hdr_len += ies->ie_payload_ie_offset; + } + + return curr_len; +} +/*---------------------------------------------------------------------------*/ diff --git a/core/net/mac/tsch/tsch-packet.h b/core/net/mac/tsch/tsch-packet.h new file mode 100644 index 000000000..24f533a88 --- /dev/null +++ b/core/net/mac/tsch/tsch-packet.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +#ifndef __TSCH_PACKET_H__ +#define __TSCH_PACKET_H__ + +/********** Includes **********/ + +#include "contiki.h" +#include "net/mac/tsch/tsch-private.h" +#include "net/mac/frame802154.h" +#include "net/mac/frame802154e-ie.h" + +/******** Configuration *******/ + +/* TSCH EB: include timeslot timing Information Element? */ +#ifdef TSCH_PACKET_CONF_EB_WITH_TIMESLOT_TIMING +#define TSCH_PACKET_EB_WITH_TIMESLOT_TIMING TSCH_PACKET_CONF_EB_WITH_TIMESLOT_TIMING +#else +#define TSCH_PACKET_EB_WITH_TIMESLOT_TIMING 0 +#endif + +/* TSCH EB: include hopping sequence Information Element? */ +#ifdef TSCH_PACKET_CONF_EB_WITH_HOPPING_SEQUENCE +#define TSCH_PACKET_EB_WITH_HOPPING_SEQUENCE TSCH_PACKET_CONF_EB_WITH_HOPPING_SEQUENCE +#else +#define TSCH_PACKET_EB_WITH_HOPPING_SEQUENCE 0 +#endif + +/* TSCH EB: include slotframe and link Information Element? */ +#ifdef TSCH_PACKET_CONF_EB_WITH_SLOTFRAME_AND_LINK +#define TSCH_PACKET_EB_WITH_SLOTFRAME_AND_LINK TSCH_PACKET_CONF_EB_WITH_SLOTFRAME_AND_LINK +#else +#define TSCH_PACKET_EB_WITH_SLOTFRAME_AND_LINK 0 +#endif + +/* Include source address in ACK? */ +#ifdef TSCH_PACKET_CONF_EACK_WITH_SRC_ADDR +#define TSCH_PACKET_EACK_WITH_SRC_ADDR TSCH_PACKET_CONF_EACK_WITH_SRC_ADDR +#else +#define TSCH_PACKET_EACK_WITH_SRC_ADDR 0 +#endif + +/* Include destination address in ACK? */ +#ifdef TSCH_PACKET_CONF_EACK_WITH_DEST_ADDR +#define TSCH_PACKET_EACK_WITH_DEST_ADDR TSCH_PACKET_CONF_EACK_WITH_DEST_ADDR +#else +#define TSCH_PACKET_EACK_WITH_DEST_ADDR 1 /* Include destination address +by default, useful in case of duplicate seqno */ +#endif + +/********** Constants *********/ + +/* Max TSCH packet lenght */ +#define TSCH_PACKET_MAX_LEN 127 + +/********** Functions *********/ + +/* Construct enhanced ACK packet and return ACK length */ +int tsch_packet_create_eack(uint8_t *buf, int buf_size, + linkaddr_t *dest_addr, uint8_t seqno, int16_t drift, int nack); +/* Parse enhanced ACK packet, extract drift and nack */ +int tsch_packet_parse_eack(const uint8_t *buf, int buf_size, + uint8_t seqno, frame802154_t *frame, struct ieee802154_ies *ies, uint8_t *hdr_len); +/* Create an EB packet */ +int tsch_packet_create_eb(uint8_t *buf, int buf_size, + uint8_t seqno, uint8_t *hdr_len, uint8_t *tsch_sync_ie_ptr); +/* Update ASN in EB packet */ +int tsch_packet_update_eb(uint8_t *buf, int buf_size, uint8_t tsch_sync_ie_offset); +/* Parse EB and extract ASN and join priority */ +int tsch_packet_parse_eb(const uint8_t *buf, int buf_size, + frame802154_t *frame, struct ieee802154_ies *ies, + uint8_t *hdrlen, int frame_without_mic); + +#endif /* __TSCH_PACKET_H__ */ diff --git a/core/net/mac/tsch/tsch-queue.c b/core/net/mac/tsch/tsch-queue.c new file mode 100644 index 000000000..affae7fb0 --- /dev/null +++ b/core/net/mac/tsch/tsch-queue.c @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +/** + * \file + * Per-neighbor packet queues for TSCH MAC. + * The list of neighbors uses the TSCH lock, but per-neighbor packet array are lock-free. + * Read-only operation on neighbor and packets are allowed from interrupts and outside of them. + * *Other operations are allowed outside of interrupt only.* + * \author + * Simon Duquennoy + * Beshr Al Nahas + * Domenico De Guglielmo + */ + +#include "contiki.h" +#include "lib/list.h" +#include "lib/memb.h" +#include "net/queuebuf.h" +#include "net/mac/rdc.h" +#include "net/mac/tsch/tsch.h" +#include "net/mac/tsch/tsch-private.h" +#include "net/mac/tsch/tsch-queue.h" +#include "net/mac/tsch/tsch-schedule.h" +#include "net/mac/tsch/tsch-slot-operation.h" +#include "net/mac/tsch/tsch-log.h" +#include + +#if TSCH_LOG_LEVEL >= 1 +#define DEBUG DEBUG_PRINT +#else /* TSCH_LOG_LEVEL */ +#define DEBUG DEBUG_NONE +#endif /* TSCH_LOG_LEVEL */ +#include "net/ip/uip-debug.h" + +/* Check if TSCH_QUEUE_NUM_PER_NEIGHBOR is power of two */ +#if (TSCH_QUEUE_NUM_PER_NEIGHBOR & (TSCH_QUEUE_NUM_PER_NEIGHBOR - 1)) != 0 +#error TSCH_QUEUE_NUM_PER_NEIGHBOR must be power of two +#endif + +/* We have as many packets are there are queuebuf in the system */ +MEMB(packet_memb, struct tsch_packet, QUEUEBUF_NUM); +MEMB(neighbor_memb, struct tsch_neighbor, TSCH_QUEUE_MAX_NEIGHBOR_QUEUES); +LIST(neighbor_list); + +/* Broadcast and EB virtual neighbors */ +struct tsch_neighbor *n_broadcast; +struct tsch_neighbor *n_eb; + +/*---------------------------------------------------------------------------*/ +/** + * A pseudo-random generator with better properties than msp430-libc's default + **/ + +static uint32_t tsch_random_seed; + +static void +tsch_random_init(uint32_t x) +{ + tsch_random_seed = x; +} +static uint8_t +tsch_random_byte(uint8_t window) +{ + tsch_random_seed = tsch_random_seed * 1103515245 + 12345; + return (tsch_random_seed / 65536) & window; +} +/*---------------------------------------------------------------------------*/ +/* Add a TSCH neighbor */ +struct tsch_neighbor * +tsch_queue_add_nbr(const linkaddr_t *addr) +{ + struct tsch_neighbor *n = NULL; + /* If we have an entry for this neighbor already, we simply update it */ + n = tsch_queue_get_nbr(addr); + if(n == NULL) { + if(tsch_get_lock()) { + /* Allocate a neighbor */ + n = memb_alloc(&neighbor_memb); + if(n != NULL) { + /* Initialize neighbor entry */ + memset(n, 0, sizeof(struct tsch_neighbor)); + ringbufindex_init(&n->tx_ringbuf, TSCH_QUEUE_NUM_PER_NEIGHBOR); + linkaddr_copy(&n->addr, addr); + n->is_broadcast = linkaddr_cmp(addr, &tsch_eb_address) + || linkaddr_cmp(addr, &tsch_broadcast_address); + tsch_queue_backoff_reset(n); + /* Add neighbor to the list */ + list_add(neighbor_list, n); + } + tsch_release_lock(); + } + } + return n; +} +/*---------------------------------------------------------------------------*/ +/* Get a TSCH neighbor */ +struct tsch_neighbor * +tsch_queue_get_nbr(const linkaddr_t *addr) +{ + if(!tsch_is_locked()) { + struct tsch_neighbor *n = list_head(neighbor_list); + while(n != NULL) { + if(linkaddr_cmp(&n->addr, addr)) { + return n; + } + n = list_item_next(n); + } + } + return NULL; +} +/*---------------------------------------------------------------------------*/ +/* Get a TSCH time source (we currently assume there is only one) */ +struct tsch_neighbor * +tsch_queue_get_time_source(void) +{ + if(!tsch_is_locked()) { + struct tsch_neighbor *curr_nbr = list_head(neighbor_list); + while(curr_nbr != NULL) { + if(curr_nbr->is_time_source) { + return curr_nbr; + } + curr_nbr = list_item_next(curr_nbr); + } + } + return NULL; +} +/*---------------------------------------------------------------------------*/ +/* Update TSCH time source */ +int +tsch_queue_update_time_source(const linkaddr_t *new_addr) +{ + if(!tsch_is_locked()) { + if(!tsch_is_coordinator) { + struct tsch_neighbor *old_time_src = tsch_queue_get_time_source(); + struct tsch_neighbor *new_time_src = new_addr ? tsch_queue_add_nbr(new_addr) : NULL; + + if(new_time_src != old_time_src) { + PRINTF("TSCH: update time source: %u -> %u\n", + TSCH_LOG_ID_FROM_LINKADDR(old_time_src ? &old_time_src->addr : NULL), + TSCH_LOG_ID_FROM_LINKADDR(new_time_src ? &new_time_src->addr : NULL)); + + /* Update time source */ + if(new_time_src != NULL) { + new_time_src->is_time_source = 1; + } + + if(old_time_src != NULL) { + old_time_src->is_time_source = 0; + } + +#ifdef TSCH_CALLBACK_NEW_TIME_SOURCE + TSCH_CALLBACK_NEW_TIME_SOURCE(old_time_src, new_time_src); +#endif + + return 1; + } + } + } + return 0; +} +/*---------------------------------------------------------------------------*/ +/* Flush a neighbor queue */ +static void +tsch_queue_flush_nbr_queue(struct tsch_neighbor *n) +{ + while(!tsch_queue_is_empty(n)) { + struct tsch_packet *p = tsch_queue_remove_packet_from_queue(n); + if(p != NULL) { + /* Set return status for packet_sent callback */ + p->ret = MAC_TX_ERR; + PRINTF("TSCH-queue:! flushing packet\n"); + /* Call packet_sent callback */ + mac_call_sent_callback(p->sent, p->ptr, p->ret, p->transmissions); + /* Free packet queuebuf */ + tsch_queue_free_packet(p); + } + } +} +/*---------------------------------------------------------------------------*/ +/* Remove TSCH neighbor queue */ +static void +tsch_queue_remove_nbr(struct tsch_neighbor *n) +{ + if(n != NULL) { + if(tsch_get_lock()) { + + /* Remove neighbor from list */ + list_remove(neighbor_list, n); + + tsch_release_lock(); + + /* Flush queue */ + tsch_queue_flush_nbr_queue(n); + + /* Free neighbor */ + memb_free(&neighbor_memb, n); + } + } +} +/*---------------------------------------------------------------------------*/ +/* Add packet to neighbor queue. Use same lockfree implementation as ringbuf.c (put is atomic) */ +struct tsch_packet * +tsch_queue_add_packet(const linkaddr_t *addr, mac_callback_t sent, void *ptr) +{ + struct tsch_neighbor *n = NULL; + int16_t put_index = -1; + struct tsch_packet *p = NULL; + if(!tsch_is_locked()) { + n = tsch_queue_add_nbr(addr); + if(n != NULL) { + put_index = ringbufindex_peek_put(&n->tx_ringbuf); + if(put_index != -1) { + p = memb_alloc(&packet_memb); + if(p != NULL) { + /* Enqueue packet */ +#ifdef TSCH_CALLBACK_PACKET_READY + TSCH_CALLBACK_PACKET_READY(); +#endif + p->qb = queuebuf_new_from_packetbuf(); + if(p->qb != NULL) { + p->sent = sent; + p->ptr = ptr; + p->ret = MAC_TX_DEFERRED; + p->transmissions = 0; + /* Add to ringbuf (actual add committed through atomic operation) */ + n->tx_array[put_index] = p; + ringbufindex_put(&n->tx_ringbuf); + return p; + } else { + memb_free(&packet_memb, p); + } + } + } + } + } + PRINTF("TSCH-queue:! add packet failed: %u %p %d %p %p\n", tsch_is_locked(), n, put_index, p, p ? p->qb : NULL); + return 0; +} +/*---------------------------------------------------------------------------*/ +/* Returns the number of packets currently in the queue */ +int +tsch_queue_packet_count(const linkaddr_t *addr) +{ + struct tsch_neighbor *n = NULL; + if(!tsch_is_locked()) { + n = tsch_queue_add_nbr(addr); + if(n != NULL) { + return ringbufindex_elements(&n->tx_ringbuf); + } + } + return -1; +} +/*---------------------------------------------------------------------------*/ +/* Remove first packet from a neighbor queue */ +struct tsch_packet * +tsch_queue_remove_packet_from_queue(struct tsch_neighbor *n) +{ + if(!tsch_is_locked()) { + if(n != NULL) { + /* Get and remove packet from ringbuf (remove committed through an atomic operation */ + int16_t get_index = ringbufindex_get(&n->tx_ringbuf); + if(get_index != -1) { + return n->tx_array[get_index]; + } else { + return NULL; + } + } + } + return NULL; +} +/*---------------------------------------------------------------------------*/ +/* Free a packet */ +void +tsch_queue_free_packet(struct tsch_packet *p) +{ + if(p != NULL) { + queuebuf_free(p->qb); + memb_free(&packet_memb, p); + } +} +/*---------------------------------------------------------------------------*/ +/* Flush all neighbor queues */ +void +tsch_queue_flush_all(void) +{ + /* Deallocate unneeded neighbors */ + if(!tsch_is_locked()) { + struct tsch_neighbor *n = list_head(neighbor_list); + while(n != NULL) { + struct tsch_neighbor *next_n = list_item_next(n); + tsch_queue_flush_nbr_queue(n); + n = next_n; + } + } +} +/*---------------------------------------------------------------------------*/ +/* Deallocate neighbors with empty queue */ +void +tsch_queue_free_unused_neighbors(void) +{ + /* Deallocate unneeded neighbors */ + if(!tsch_is_locked()) { + struct tsch_neighbor *n = list_head(neighbor_list); + while(n != NULL) { + struct tsch_neighbor *next_n = list_item_next(n); + /* Queue is empty, no tx link to this neighbor: deallocate. + * Always keep time source and virtual broadcast neighbors. */ + if(!n->is_broadcast && !n->is_time_source && !n->tx_links_count + && tsch_queue_is_empty(n)) { + tsch_queue_remove_nbr(n); + } + n = next_n; + } + } +} +/*---------------------------------------------------------------------------*/ +/* Is the neighbor queue empty? */ +int +tsch_queue_is_empty(const struct tsch_neighbor *n) +{ + return !tsch_is_locked() && n != NULL && ringbufindex_empty(&n->tx_ringbuf); +} +/*---------------------------------------------------------------------------*/ +/* Returns the first packet from a neighbor queue */ +struct tsch_packet * +tsch_queue_get_packet_for_nbr(const struct tsch_neighbor *n, struct tsch_link *link) +{ + if(!tsch_is_locked()) { + int is_shared_link = link != NULL && link->link_options & LINK_OPTION_SHARED; + if(n != NULL) { + int16_t get_index = ringbufindex_peek_get(&n->tx_ringbuf); + if(get_index != -1 && + !(is_shared_link && !tsch_queue_backoff_expired(n))) { /* If this is a shared link, + make sure the backoff has expired */ +#if TSCH_WITH_LINK_SELECTOR + int packet_attr_slotframe = queuebuf_attr(n->tx_array[get_index]->qb, PACKETBUF_ATTR_TSCH_SLOTFRAME); + int packet_attr_timeslot = queuebuf_attr(n->tx_array[get_index]->qb, PACKETBUF_ATTR_TSCH_TIMESLOT); + if(packet_attr_slotframe != 0xffff && packet_attr_slotframe != link->slotframe_handle) { + return NULL; + } + if(packet_attr_timeslot != 0xffff && packet_attr_timeslot != link->timeslot) { + return NULL; + } +#endif + return n->tx_array[get_index]; + } + } + } + return NULL; +} +/*---------------------------------------------------------------------------*/ +/* Returns the head packet from a neighbor queue (from neighbor address) */ +struct tsch_packet * +tsch_queue_get_packet_for_dest_addr(const linkaddr_t *addr, struct tsch_link *link) +{ + if(!tsch_is_locked()) { + return tsch_queue_get_packet_for_nbr(tsch_queue_get_nbr(addr), link); + } + return NULL; +} +/* Returns the head packet of any neighbor queue with zero backoff counter. + * Writes pointer to the neighbor in *n */ +struct tsch_packet * +tsch_queue_get_unicast_packet_for_any(struct tsch_neighbor **n, struct tsch_link *link) +{ + if(!tsch_is_locked()) { + struct tsch_neighbor *curr_nbr = list_head(neighbor_list); + struct tsch_packet *p = NULL; + while(curr_nbr != NULL) { + if(!curr_nbr->is_broadcast && curr_nbr->tx_links_count == 0) { + /* Only look up for non-broadcast neighbors we do not have a tx link to */ + p = tsch_queue_get_packet_for_nbr(curr_nbr, link); + if(p != NULL) { + if(n != NULL) { + *n = curr_nbr; + } + return p; + } + } + curr_nbr = list_item_next(curr_nbr); + } + } + return NULL; +} +/*---------------------------------------------------------------------------*/ +/* May the neighbor transmit over a shared link? */ +int +tsch_queue_backoff_expired(const struct tsch_neighbor *n) +{ + return n->backoff_window == 0; +} +/*---------------------------------------------------------------------------*/ +/* Reset neighbor backoff */ +void +tsch_queue_backoff_reset(struct tsch_neighbor *n) +{ + n->backoff_window = 0; + n->backoff_exponent = TSCH_MAC_MIN_BE; +} +/*---------------------------------------------------------------------------*/ +/* Increment backoff exponent, pick a new window */ +void +tsch_queue_backoff_inc(struct tsch_neighbor *n) +{ + /* Increment exponent */ + n->backoff_exponent = MIN(n->backoff_exponent + 1, TSCH_MAC_MAX_BE); + /* Pick a window (number of shared slots to skip) */ + n->backoff_window = tsch_random_byte((1 << n->backoff_exponent) - 1); + /* Add one to the window as we will decrement it at the end of the current slot + * through tsch_queue_update_all_backoff_windows */ + n->backoff_window++; +} +/*---------------------------------------------------------------------------*/ +/* Decrement backoff window for all queues directed at dest_addr */ +void +tsch_queue_update_all_backoff_windows(const linkaddr_t *dest_addr) +{ + if(!tsch_is_locked()) { + int is_broadcast = linkaddr_cmp(dest_addr, &tsch_broadcast_address); + struct tsch_neighbor *n = list_head(neighbor_list); + while(n != NULL) { + if(n->backoff_window != 0 /* Is the queue in backoff state? */ + && ((n->tx_links_count == 0 && is_broadcast) + || (n->tx_links_count > 0 && linkaddr_cmp(dest_addr, &n->addr)))) { + n->backoff_window--; + } + n = list_item_next(n); + } + } +} +/*---------------------------------------------------------------------------*/ +/* Initialize TSCH queue module */ +void +tsch_queue_init(void) +{ + list_init(neighbor_list); + tsch_random_init(*((uint32_t *)&linkaddr_node_addr) + + *((uint32_t *)&linkaddr_node_addr + 1)); + memb_init(&neighbor_memb); + memb_init(&packet_memb); + /* Add virtual EB and the broadcast neighbors */ + n_eb = tsch_queue_add_nbr(&tsch_eb_address); + n_broadcast = tsch_queue_add_nbr(&tsch_broadcast_address); +} +/*---------------------------------------------------------------------------*/ diff --git a/core/net/mac/tsch/tsch-queue.h b/core/net/mac/tsch/tsch-queue.h new file mode 100644 index 000000000..212129fb5 --- /dev/null +++ b/core/net/mac/tsch/tsch-queue.h @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +#ifndef __TSCH_QUEUE_H__ +#define __TSCH_QUEUE_H__ + +/********** Includes **********/ + +#include "contiki.h" +#include "lib/ringbufindex.h" +#include "net/linkaddr.h" +#include "net/mac/tsch/tsch-schedule.h" + +/******** Configuration *******/ + +/* The maximum number of outgoing packets towards each neighbor + * Must be power of two to enable atomic ringbuf operations. + * Note: the total number of outgoing packets in the system (for + * all neighbors) is defined via QUEUEBUF_CONF_NUM */ +#ifdef TSCH_QUEUE_CONF_NUM_PER_NEIGHBOR +#define TSCH_QUEUE_NUM_PER_NEIGHBOR TSCH_QUEUE_CONF_NUM_PER_NEIGHBOR +#else +#define TSCH_QUEUE_NUM_PER_NEIGHBOR 8 +#endif + +/* The number of neighbor queues. There two queues allocated for + * one EBs, one for broadcasts. Other queues are real neighbors */ +#ifdef TSCH_QUEUE_CONF_MAX_NEIGHBOR_QUEUES +#define TSCH_QUEUE_MAX_NEIGHBOR_QUEUES TSCH_QUEUE_CONF_MAX_NEIGHBOR_QUEUES +#else +#define TSCH_QUEUE_MAX_NEIGHBOR_QUEUES 8 +#endif + +/* TSCH CSMA-CA parameters, see IEEE 802.15.4e-2012 */ +/* Min backoff exponent */ +#ifdef TSCH_CONF_MAC_MIN_BE +#define TSCH_MAC_MIN_BE TSCH_CONF_MAC_MIN_BE +#else +#define TSCH_MAC_MIN_BE 1 +#endif +/* Max backoff exponent */ +#ifdef TSCH_CONF_MAC_MAX_BE +#define TSCH_MAC_MAX_BE TSCH_CONF_MAC_MAX_BE +#else +#define TSCH_MAC_MAX_BE 7 +#endif +/* Max number of re-transmissions */ +#ifdef TSCH_CONF_MAC_MAX_FRAME_RETRIES +#define TSCH_MAC_MAX_FRAME_RETRIES TSCH_CONF_MAC_MAX_FRAME_RETRIES +#else +#define TSCH_MAC_MAX_FRAME_RETRIES 8 +#endif + +/*********** Callbacks *********/ + +/* Called by TSCH when switching time source */ +#ifdef TSCH_CALLBACK_NEW_TIME_SOURCE +struct tsch_neighbor; +void TSCH_CALLBACK_NEW_TIME_SOURCE(const struct tsch_neighbor *old, const struct tsch_neighbor *new); +#endif + +/* Called by TSCH every time a packet is ready to be added to the send queue */ +#ifdef TSCH_CALLBACK_PACKET_READY +void TSCH_CALLBACK_PACKET_READY(void); +#endif + +/************ Types ***********/ + +/* TSCH packet information */ +struct tsch_packet { + struct queuebuf *qb; /* pointer to the queuebuf to be sent */ + mac_callback_t sent; /* callback for this packet */ + void *ptr; /* MAC callback parameter */ + uint8_t transmissions; /* #transmissions performed for this packet */ + uint8_t ret; /* status -- MAC return code */ + uint8_t header_len; /* length of header and header IEs (needed for link-layer security) */ + uint8_t tsch_sync_ie_offset; /* Offset within the frame used for quick update of EB ASN and join priority */ +}; + +/* TSCH neighbor information */ +struct tsch_neighbor { + /* Neighbors are stored as a list: "next" must be the first field */ + struct tsch_neighbor *next; + linkaddr_t addr; /* MAC address of the neighbor */ + uint8_t is_broadcast; /* is this neighbor a virtual neighbor used for broadcast (of data packets or EBs) */ + uint8_t is_time_source; /* is this neighbor a time source? */ + uint8_t backoff_exponent; /* CSMA backoff exponent */ + uint8_t backoff_window; /* CSMA backoff window (number of slots to skip) */ + uint8_t last_backoff_window; /* Last CSMA backoff window */ + uint8_t tx_links_count; /* How many links do we have to this neighbor? */ + uint8_t dedicated_tx_links_count; /* How many dedicated links do we have to this neighbor? */ + /* Array for the ringbuf. Contains pointers to packets. + * Its size must be a power of two to allow for atomic put */ + struct tsch_packet *tx_array[TSCH_QUEUE_NUM_PER_NEIGHBOR]; + /* Circular buffer of pointers to packet. */ + struct ringbufindex tx_ringbuf; +}; + +/***** External Variables *****/ + +/* Broadcast and EB virtual neighbors */ +extern struct tsch_neighbor *n_broadcast; +extern struct tsch_neighbor *n_eb; + +/********** Functions *********/ + +/* Add a TSCH neighbor */ +struct tsch_neighbor *tsch_queue_add_nbr(const linkaddr_t *addr); +/* Get a TSCH neighbor */ +struct tsch_neighbor *tsch_queue_get_nbr(const linkaddr_t *addr); +/* Get a TSCH time source (we currently assume there is only one) */ +struct tsch_neighbor *tsch_queue_get_time_source(void); +/* Update TSCH time source */ +int tsch_queue_update_time_source(const linkaddr_t *new_addr); +/* Add packet to neighbor queue. Use same lockfree implementation as ringbuf.c (put is atomic) */ +struct tsch_packet *tsch_queue_add_packet(const linkaddr_t *addr, mac_callback_t sent, void *ptr); +/* Returns the number of packets currently a given neighbor queue */ +int tsch_queue_packet_count(const linkaddr_t *addr); +/* Remove first packet from a neighbor queue. The packet is stored in a separate + * dequeued packet list, for later processing. Return the packet. */ +struct tsch_packet *tsch_queue_remove_packet_from_queue(struct tsch_neighbor *n); +/* Free a packet */ +void tsch_queue_free_packet(struct tsch_packet *p); +/* Flush all neighbor queues */ +void tsch_queue_flush_all(void); +/* Deallocate neighbors with empty queue */ +void tsch_queue_free_unused_neighbors(void); +/* Is the neighbor queue empty? */ +int tsch_queue_is_empty(const struct tsch_neighbor *n); +/* Returns the first packet from a neighbor queue */ +struct tsch_packet *tsch_queue_get_packet_for_nbr(const struct tsch_neighbor *n, struct tsch_link *link); +/* Returns the head packet from a neighbor queue (from neighbor address) */ +struct tsch_packet *tsch_queue_get_packet_for_dest_addr(const linkaddr_t *addr, struct tsch_link *link); +/* Returns the head packet of any neighbor queue with zero backoff counter. + * Writes pointer to the neighbor in *n */ +struct tsch_packet *tsch_queue_get_unicast_packet_for_any(struct tsch_neighbor **n, struct tsch_link *link); +/* May the neighbor transmit over a share link? */ +int tsch_queue_backoff_expired(const struct tsch_neighbor *n); +/* Reset neighbor backoff */ +void tsch_queue_backoff_reset(struct tsch_neighbor *n); +/* Increment backoff exponent, pick a new window */ +void tsch_queue_backoff_inc(struct tsch_neighbor *n); +/* Decrement backoff window for all queues directed at dest_addr */ +void tsch_queue_update_all_backoff_windows(const linkaddr_t *dest_addr); +/* Initialize TSCH queue module */ +void tsch_queue_init(void); + +#endif /* __TSCH_QUEUE_H__ */ diff --git a/core/net/mac/tsch/tsch-rpl.c b/core/net/mac/tsch/tsch-rpl.c new file mode 100644 index 000000000..772c886b3 --- /dev/null +++ b/core/net/mac/tsch/tsch-rpl.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + */ + +/** + * \file + * Interaction between TSCH and RPL + * + * \author Simon Duquennoy + */ + +#if UIP_CONF_IPV6_RPL + +#include "contiki.h" +#include "net/rpl/rpl.h" +#include "net/rpl/rpl-private.h" +#include "net/mac/tsch/tsch.h" +#include "net/mac/tsch/tsch-private.h" +#include "net/mac/tsch/tsch-schedule.h" +#include "net/mac/tsch/tsch-log.h" +#include "tsch-rpl.h" + +#if TSCH_LOG_LEVEL >= 1 +#define DEBUG DEBUG_PRINT +#else /* TSCH_LOG_LEVEL */ +#define DEBUG DEBUG_NONE +#endif /* TSCH_LOG_LEVEL */ +#include "net/ip/uip-debug.h" + +/*---------------------------------------------------------------------------*/ +/* To use, set #define TSCH_CALLBACK_JOINING_NETWORK tsch_rpl_callback_joining_network */ +void +tsch_rpl_callback_joining_network(void) +{ +} +/*---------------------------------------------------------------------------*/ +/* Upon leaving a TSCH network, perform a local repair + * (cleanup neighbor state, reset Trickle timer etc) + * To use, set #define TSCH_CALLBACK_LEAVING_NETWORK tsch_rpl_callback_leaving_network */ +void +tsch_rpl_callback_leaving_network(void) +{ + rpl_dag_t *dag = rpl_get_any_dag(); + if(dag != NULL) { + rpl_local_repair(dag->instance); + } +} +/*---------------------------------------------------------------------------*/ +/* Set TSCH EB period based on current RPL DIO period. + * To use, set #define RPL_CALLBACK_PARENT_SWITCH tsch_rpl_callback_new_dio_interval */ +void +tsch_rpl_callback_new_dio_interval(uint8_t dio_interval) +{ + /* Transmit EBs only if we have a valid rank as per 6TiSCH minimal */ + rpl_dag_t *dag = rpl_get_any_dag(); + if(dag != NULL && dag->rank != INFINITE_RANK) { + /* If we are root set TSCH as coordinator */ + if(dag->rank == ROOT_RANK(dag->instance)) { + tsch_set_coordinator(1); + } + /* Set EB period */ + tsch_set_eb_period(TSCH_EB_PERIOD); + /* Set join priority based on RPL rank */ + tsch_set_join_priority(DAG_RANK(dag->rank, dag->instance) - 1); + } else { + tsch_set_eb_period(0); + } +} +/*---------------------------------------------------------------------------*/ +/* Set TSCH time source based on current RPL preferred parent. + * To use, set #define RPL_CALLBACK_PARENT_SWITCH tsch_rpl_callback_parent_switch */ +void +tsch_rpl_callback_parent_switch(rpl_parent_t *old, rpl_parent_t *new) +{ + if(tsch_is_associated == 1) { + tsch_queue_update_time_source( + (const linkaddr_t *)uip_ds6_nbr_lladdr_from_ipaddr( + rpl_get_parent_ipaddr(new))); + } +} +#endif /* UIP_CONF_IPV6_RPL */ diff --git a/core/net/mac/tsch/tsch-rpl.h b/core/net/mac/tsch/tsch-rpl.h new file mode 100644 index 000000000..43d7b0d2a --- /dev/null +++ b/core/net/mac/tsch/tsch-rpl.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + */ + + +#ifndef __TSCH_RPL_H__ +#define __TSCH_RPL_H__ + +/********** Includes **********/ + +#include "net/rpl/rpl.h" +#include "net/mac/tsch/tsch-queue.h" + +/********** Functions *********/ + +/* To use, set #define TSCH_CALLBACK_JOINING_NETWORK tsch_rpl_callback_joining_network */ +void tsch_rpl_callback_joining_network(void); +/* Upon leaving a TSCH network, perform a local repair + * (cleanup neighbor state, reset Trickle timer etc) + * To use, set #define TSCH_CALLBACK_LEAVING_NETWORK tsch_rpl_callback_leaving_network */ +void tsch_rpl_callback_leaving_network(void); +/* Set TSCH EB period based on current RPL DIO period. + * To use, set #define RPL_CALLBACK_PARENT_SWITCH tsch_rpl_callback_new_dio_interval */ +void tsch_rpl_callback_new_dio_interval(uint8_t dio_interval); +/* Set TSCH time source based on current RPL preferred parent. + * To use, set #define RPL_CALLBACK_PARENT_SWITCH tsch_rpl_callback_parent_switch */ +void tsch_rpl_callback_parent_switch(rpl_parent_t *old, rpl_parent_t *new); + +#endif /* __TSCH_RPL_H__ */ diff --git a/core/net/mac/tsch/tsch-schedule.c b/core/net/mac/tsch/tsch-schedule.c new file mode 100644 index 000000000..8b15e0faf --- /dev/null +++ b/core/net/mac/tsch/tsch-schedule.c @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +/** + * \file + * IEEE 802.15.4 TSCH MAC schedule manager. + * \author + * Simon Duquennoy + * Beshr Al Nahas + */ + +#include "contiki.h" +#include "dev/leds.h" +#include "lib/memb.h" +#include "net/nbr-table.h" +#include "net/packetbuf.h" +#include "net/queuebuf.h" +#include "net/mac/tsch/tsch.h" +#include "net/mac/tsch/tsch-queue.h" +#include "net/mac/tsch/tsch-private.h" +#include "net/mac/tsch/tsch-packet.h" +#include "net/mac/tsch/tsch-schedule.h" +#include "net/mac/tsch/tsch-log.h" +#include "net/mac/frame802154.h" +#include "sys/process.h" +#include "sys/rtimer.h" +#include + +#if TSCH_LOG_LEVEL >= 1 +#define DEBUG DEBUG_PRINT +#else /* TSCH_LOG_LEVEL */ +#define DEBUG DEBUG_NONE +#endif /* TSCH_LOG_LEVEL */ +#include "net/ip/uip-debug.h" + +/* Pre-allocated space for links */ +MEMB(link_memb, struct tsch_link, TSCH_SCHEDULE_MAX_LINKS); +/* Pre-allocated space for slotframes */ +MEMB(slotframe_memb, struct tsch_slotframe, TSCH_SCHEDULE_MAX_SLOTFRAMES); +/* List of slotframes (each slotframe holds its own list of links) */ +LIST(slotframe_list); + +/* Adds and returns a slotframe (NULL if failure) */ +struct tsch_slotframe * +tsch_schedule_add_slotframe(uint16_t handle, uint16_t size) +{ + if(size == 0) { + return NULL; + } + + if(tsch_schedule_get_slotframe_by_handle(handle)) { + /* A slotframe with this handle already exists */ + return NULL; + } + + if(tsch_get_lock()) { + struct tsch_slotframe *sf = memb_alloc(&slotframe_memb); + if(sf != NULL) { + /* Initialize the slotframe */ + sf->handle = handle; + ASN_DIVISOR_INIT(sf->size, size); + LIST_STRUCT_INIT(sf, links_list); + /* Add the slotframe to the global list */ + list_add(slotframe_list, sf); + } + PRINTF("TSCH-schedule: add_slotframe %u %u\n", + handle, size); + tsch_release_lock(); + return sf; + } + return NULL; +} +/*---------------------------------------------------------------------------*/ +/* Removes all slotframes, resulting in an empty schedule */ +int +tsch_schedule_remove_all_slotframes(void) +{ + struct tsch_slotframe *sf; + while((sf = list_head(slotframe_list))) { + if(tsch_schedule_remove_slotframe(sf) == 0) { + return 0; + } + } + return 1; +} +/*---------------------------------------------------------------------------*/ +/* Removes a slotframe Return 1 if success, 0 if failure */ +int +tsch_schedule_remove_slotframe(struct tsch_slotframe *slotframe) +{ + if(slotframe != NULL) { + /* Remove all links belonging to this slotframe */ + struct tsch_link *l; + while((l = list_head(slotframe->links_list))) { + tsch_schedule_remove_link(slotframe, l); + } + + /* Now that the slotframe has no links, remove it. */ + if(tsch_get_lock()) { + PRINTF("TSCH-schedule: remove slotframe %u %u\n", slotframe->handle, slotframe->size.val); + memb_free(&slotframe_memb, slotframe); + list_remove(slotframe_list, slotframe); + tsch_release_lock(); + return 1; + } + } + return 0; +} +/*---------------------------------------------------------------------------*/ +/* Looks for a slotframe from a handle */ +struct tsch_slotframe * +tsch_schedule_get_slotframe_by_handle(uint16_t handle) +{ + if(!tsch_is_locked()) { + struct tsch_slotframe *sf = list_head(slotframe_list); + while(sf != NULL) { + if(sf->handle == handle) { + return sf; + } + sf = list_item_next(sf); + } + } + return NULL; +} +/*---------------------------------------------------------------------------*/ +/* Looks for a link from a handle */ +struct tsch_link * +tsch_schedule_get_link_by_handle(uint16_t handle) +{ + if(!tsch_is_locked()) { + struct tsch_slotframe *sf = list_head(slotframe_list); + while(sf != NULL) { + struct tsch_link *l = list_head(sf->links_list); + /* Loop over all items. Assume there is max one link per timeslot */ + while(l != NULL) { + if(l->handle == handle) { + return l; + } + l = list_item_next(l); + } + sf = list_item_next(sf); + } + } + return NULL; +} +/*---------------------------------------------------------------------------*/ +/* Adds a link to a slotframe, return a pointer to it (NULL if failure) */ +struct tsch_link * +tsch_schedule_add_link(struct tsch_slotframe *slotframe, + uint8_t link_options, enum link_type link_type, const linkaddr_t *address, + uint16_t timeslot, uint16_t channel_offset) +{ + struct tsch_link *l = NULL; + if(slotframe != NULL) { + /* We currently support only one link per timeslot in a given slotframe. */ + /* Start with removing the link currently installed at this timeslot (needed + * to keep neighbor state in sync with link options etc.) */ + tsch_schedule_remove_link_by_timeslot(slotframe, timeslot); + if(!tsch_get_lock()) { + PRINTF("TSCH-schedule:! add_link memb_alloc couldn't take lock\n"); + } else { + l = memb_alloc(&link_memb); + if(l == NULL) { + PRINTF("TSCH-schedule:! add_link memb_alloc failed\n"); + } else { + static int current_link_handle = 0; + struct tsch_neighbor *n; + /* Add the link to the slotframe */ + list_add(slotframe->links_list, l); + /* Initialize link */ + l->handle = current_link_handle++; + l->link_options = link_options; + l->link_type = link_type; + l->slotframe_handle = slotframe->handle; + l->timeslot = timeslot; + l->channel_offset = channel_offset; + l->data = NULL; + if(address == NULL) { + address = &linkaddr_null; + } + linkaddr_copy(&l->addr, address); + + PRINTF("TSCH-schedule: add_link %u %u %u %u %u %u\n", + slotframe->handle, link_options, link_type, timeslot, channel_offset, TSCH_LOG_ID_FROM_LINKADDR(address)); + + /* Release the lock before we update the neighbor (will take the lock) */ + tsch_release_lock(); + + if(l->link_options & LINK_OPTION_TX) { + n = tsch_queue_add_nbr(&l->addr); + /* We have a tx link to this neighbor, update counters */ + if(n != NULL) { + n->tx_links_count++; + if(!(l->link_options & LINK_OPTION_SHARED)) { + n->dedicated_tx_links_count++; + } + } + } + } + } + } + return l; +} +/*---------------------------------------------------------------------------*/ +/* Removes a link from slotframe. Return 1 if success, 0 if failure */ +int +tsch_schedule_remove_link(struct tsch_slotframe *slotframe, struct tsch_link *l) +{ + if(slotframe != NULL && l != NULL && l->slotframe_handle == slotframe->handle) { + if(tsch_get_lock()) { + uint8_t link_options; + linkaddr_t addr; + + /* Save link option and addr in local variables as we need them + * after freeing the link */ + link_options = l->link_options; + linkaddr_copy(&addr, &l->addr); + + /* The link to be removed is scheduled as next, set it to NULL + * to abort the next link operation */ + if(l == current_link) { + current_link = NULL; + } + PRINTF("TSCH-schedule: remove_link %u %u %u %u %u\n", + slotframe->handle, l->link_options, l->timeslot, l->channel_offset, + TSCH_LOG_ID_FROM_LINKADDR(&l->addr)); + + list_remove(slotframe->links_list, l); + memb_free(&link_memb, l); + + /* Release the lock before we update the neighbor (will take the lock) */ + tsch_release_lock(); + + /* This was a tx link to this neighbor, update counters */ + if(link_options & LINK_OPTION_TX) { + struct tsch_neighbor *n = tsch_queue_add_nbr(&addr); + if(n != NULL) { + n->tx_links_count--; + if(!(link_options & LINK_OPTION_SHARED)) { + n->dedicated_tx_links_count--; + } + } + } + + return 1; + } else { + PRINTF("TSCH-schedule:! remove_link memb_alloc couldn't take lock\n"); + } + } + return 0; +} +/*---------------------------------------------------------------------------*/ +/* Removes a link from slotframe and timeslot. Return a 1 if success, 0 if failure */ +int +tsch_schedule_remove_link_by_timeslot(struct tsch_slotframe *slotframe, uint16_t timeslot) +{ + return slotframe != NULL && + tsch_schedule_remove_link(slotframe, tsch_schedule_get_link_by_timeslot(slotframe, timeslot)); +} +/*---------------------------------------------------------------------------*/ +/* Looks within a slotframe for a link with a given timeslot */ +struct tsch_link * +tsch_schedule_get_link_by_timeslot(struct tsch_slotframe *slotframe, uint16_t timeslot) +{ + if(!tsch_is_locked()) { + if(slotframe != NULL) { + struct tsch_link *l = list_head(slotframe->links_list); + /* Loop over all items. Assume there is max one link per timeslot */ + while(l != NULL) { + if(l->timeslot == timeslot) { + return l; + } + l = list_item_next(l); + } + return l; + } + } + return NULL; +} +/*---------------------------------------------------------------------------*/ +/* Returns the next active link after a given ASN, and a backup link (for the same ASN, with Rx flag) */ +struct tsch_link * +tsch_schedule_get_next_active_link(struct asn_t *asn, uint16_t *time_offset, + struct tsch_link **backup_link) +{ + uint16_t time_to_curr_best = 0; + struct tsch_link *curr_best = NULL; + struct tsch_link *curr_backup = NULL; /* Keep a back link in case the current link + turns out useless when the time comes. For instance, for a Tx-only link, if there is + no outgoing packet in queue. In that case, run the backup link instead. The backup link + must have Rx flag set. */ + if(!tsch_is_locked()) { + struct tsch_slotframe *sf = list_head(slotframe_list); + /* For each slotframe, look for the earliest occurring link */ + while(sf != NULL) { + /* Get timeslot from ASN, given the slotframe length */ + uint16_t timeslot = ASN_MOD(*asn, sf->size); + struct tsch_link *l = list_head(sf->links_list); + while(l != NULL) { + uint16_t time_to_timeslot = + l->timeslot > timeslot ? + l->timeslot - timeslot : + sf->size.val + l->timeslot - timeslot; + if(curr_best == NULL || time_to_timeslot < time_to_curr_best) { + time_to_curr_best = time_to_timeslot; + curr_best = l; + curr_backup = NULL; + } else if(time_to_timeslot == time_to_curr_best) { + struct tsch_link *new_best = NULL; + /* Two links are overlapping, we need to select one of them. + * By standard: prioritize Tx links first, second by lowest handle */ + if((curr_best->link_options & LINK_OPTION_TX) == (l->link_options & LINK_OPTION_TX)) { + /* Both or neither links have Tx, select the one with lowest handle */ + if(l->slotframe_handle < curr_best->slotframe_handle) { + new_best = l; + } + } else { + /* Select the link that has the Tx option */ + if(l->link_options & LINK_OPTION_TX) { + new_best = l; + } + } + + /* Maintain backup_link */ + if(curr_backup == NULL) { + /* Check if 'l' best can be used as backup */ + if(new_best != l && (l->link_options & LINK_OPTION_RX)) { /* Does 'l' have Rx flag? */ + curr_backup = l; + } + /* Check if curr_best can be used as backup */ + if(new_best != curr_best && (curr_best->link_options & LINK_OPTION_RX)) { /* Does curr_best have Rx flag? */ + curr_backup = curr_best; + } + } + + /* Maintain curr_best */ + if(new_best != NULL) { + curr_best = new_best; + } + } + + l = list_item_next(l); + } + sf = list_item_next(sf); + } + if(time_offset != NULL) { + *time_offset = time_to_curr_best; + } + } + if(backup_link != NULL) { + *backup_link = curr_backup; + } + return curr_best; +} +/*---------------------------------------------------------------------------*/ +/* Module initialization, call only once at startup. Returns 1 is success, 0 if failure. */ +int +tsch_schedule_init(void) +{ + if(tsch_get_lock()) { + memb_init(&link_memb); + memb_init(&slotframe_memb); + list_init(slotframe_list); + tsch_release_lock(); + return 1; + } else { + return 0; + } +} +/*---------------------------------------------------------------------------*/ +/* Create a 6TiSCH minimal schedule */ +void +tsch_schedule_create_minimal(void) +{ + struct tsch_slotframe *sf_min; + /* First, empty current schedule */ + tsch_schedule_remove_all_slotframes(); + /* Build 6TiSCH minimal schedule. + * We pick a slotframe length of TSCH_SCHEDULE_DEFAULT_LENGTH */ + sf_min = tsch_schedule_add_slotframe(0, TSCH_SCHEDULE_DEFAULT_LENGTH); + /* Add a single Tx|Rx|Shared slot using broadcast address (i.e. usable for unicast and broadcast). + * We set the link type to advertising, which is not compliant with 6TiSCH minimal schedule + * but is required according to 802.15.4e if also used for EB transmission. + * Timeslot: 0, channel offset: 0. */ + tsch_schedule_add_link(sf_min, + LINK_OPTION_RX | LINK_OPTION_TX | LINK_OPTION_SHARED | LINK_OPTION_TIME_KEEPING, + LINK_TYPE_ADVERTISING, &tsch_broadcast_address, + 0, 0); +} +/*---------------------------------------------------------------------------*/ +/* Prints out the current schedule (all slotframes and links) */ +void +tsch_schedule_print(void) +{ + if(!tsch_is_locked()) { + struct tsch_slotframe *sf = list_head(slotframe_list); + + printf("Schedule: slotframe list\n"); + + while(sf != NULL) { + struct tsch_link *l = list_head(sf->links_list); + + printf("[Slotframe] Handle %u, size %u\n", sf->handle, sf->size.val); + printf("List of links:\n"); + + while(l != NULL) { + printf("[Link] Options %02x, type %u, timeslot %u, channel offset %u, address %u\n", + l->link_options, l->link_type, l->timeslot, l->channel_offset, l->addr.u8[7]); + l = list_item_next(l); + } + + sf = list_item_next(sf); + } + + printf("Schedule: end of slotframe list\n"); + } +} +/*---------------------------------------------------------------------------*/ diff --git a/core/net/mac/tsch/tsch-schedule.h b/core/net/mac/tsch/tsch-schedule.h new file mode 100644 index 000000000..7b8af2801 --- /dev/null +++ b/core/net/mac/tsch/tsch-schedule.h @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +#ifndef __TSCH_SCHEDULE_H__ +#define __TSCH_SCHEDULE_H__ + +/********** Includes **********/ + +#include "contiki.h" +#include "lib/list.h" +#include "net/mac/tsch/tsch-private.h" +#include "net/mac/tsch/tsch-queue.h" +#include "net/mac/tsch/tsch-slot-operation.h" +#include "net/linkaddr.h" + +/******** Configuration *******/ + +/* Initializes TSCH with a 6TiSCH minimal schedule */ +#ifdef TSCH_SCHEDULE_CONF_WITH_6TISCH_MINIMAL +#define TSCH_SCHEDULE_WITH_6TISCH_MINIMAL TSCH_SCHEDULE_CONF_WITH_6TISCH_MINIMAL +#else +#define TSCH_SCHEDULE_WITH_6TISCH_MINIMAL 1 +#endif + +/* 6TiSCH Minimal schedule slotframe length */ +#ifdef TSCH_SCHEDULE_CONF_DEFAULT_LENGTH +#define TSCH_SCHEDULE_DEFAULT_LENGTH TSCH_SCHEDULE_CONF_DEFAULT_LENGTH +#else +#define TSCH_SCHEDULE_DEFAULT_LENGTH 7 +#endif + +/* Max number of TSCH slotframes */ +#ifdef TSCH_SCHEDULE_CONF_MAX_SLOTFRAMES +#define TSCH_SCHEDULE_MAX_SLOTFRAMES TSCH_SCHEDULE_CONF_MAX_SLOTFRAMES +#else +#define TSCH_SCHEDULE_MAX_SLOTFRAMES 4 +#endif + +/* Max number of links */ +#ifdef TSCH_SCHEDULE_CONF_MAX_LINKS +#define TSCH_SCHEDULE_MAX_LINKS TSCH_SCHEDULE_CONF_MAX_LINKS +#else +#define TSCH_SCHEDULE_MAX_LINKS 32 +#endif + +/********** Constants *********/ + +/* Link options */ +#define LINK_OPTION_TX 1 +#define LINK_OPTION_RX 2 +#define LINK_OPTION_SHARED 4 +#define LINK_OPTION_TIME_KEEPING 8 + +/************ Types ***********/ + +/* 802.15.4e link types. + * LINK_TYPE_ADVERTISING_ONLY is an extra one: for EB-only links. */ +enum link_type { LINK_TYPE_NORMAL, LINK_TYPE_ADVERTISING, LINK_TYPE_ADVERTISING_ONLY }; + +struct tsch_link { + /* Links are stored as a list: "next" must be the first field */ + struct tsch_link *next; + /* Unique identifier */ + uint16_t handle; + /* MAC address of neighbor */ + linkaddr_t addr; + /* Slotframe identifier */ + uint16_t slotframe_handle; + /* Identifier of Slotframe to which this link belongs + * Unused. */ + /* uint8_t handle; */ + /* Timeslot for this link */ + uint16_t timeslot; + /* Channel offset for this link */ + uint16_t channel_offset; + /* A bit string that defines + * b0 = Transmit, b1 = Receive, b2 = Shared, b3 = Timekeeping, b4 = reserved */ + uint8_t link_options; + /* Type of link. NORMAL = 0. ADVERTISING = 1, and indicates + the link may be used to send an Enhanced beacon. */ + enum link_type link_type; + /* Any other data for upper layers */ + void *data; +}; + +struct tsch_slotframe { + /* Slotframes are stored as a list: "next" must be the first field */ + struct tsch_slotframe *next; + /* Unique identifier */ + uint16_t handle; + /* Number of timeslots in the slotframe. + * Stored as struct asn_divisor_t because we often need ASN%size */ + struct asn_divisor_t size; + /* List of links belonging to this slotframe */ + LIST_STRUCT(links_list); +}; + +/********** Functions *********/ + +/* Module initialization, call only once at startup. Returns 1 is success, 0 if failure. */ +int tsch_schedule_init(void); +/* Create a 6TiSCH minimal schedule */ +void tsch_schedule_create_minimal(void); +/* Prints out the current schedule (all slotframes and links) */ +void tsch_schedule_print(void); + +/* Adds and returns a slotframe (NULL if failure) */ +struct tsch_slotframe *tsch_schedule_add_slotframe(uint16_t handle, uint16_t size); +/* Looks for a slotframe from a handle */ +struct tsch_slotframe *tsch_schedule_get_slotframe_by_handle(uint16_t handle); +/* Removes a slotframe Return 1 if success, 0 if failure */ +int tsch_schedule_remove_slotframe(struct tsch_slotframe *slotframe); +/* Removes all slotframes, resulting in an empty schedule */ +int tsch_schedule_remove_all_slotframes(void); + +/* Returns next slotframe */ +struct tsch_slotframe *tsch_schedule_slotframes_next(struct tsch_slotframe *sf); +/* Adds a link to a slotframe, return a pointer to it (NULL if failure) */ +struct tsch_link *tsch_schedule_add_link(struct tsch_slotframe *slotframe, + uint8_t link_options, enum link_type link_type, const linkaddr_t *address, + uint16_t timeslot, uint16_t channel_offset); +/* Looks for a link from a handle */ +struct tsch_link *tsch_schedule_get_link_by_handle(uint16_t handle); +/* Looks within a slotframe for a link with a given timeslot */ +struct tsch_link *tsch_schedule_get_link_by_timeslot(struct tsch_slotframe *slotframe, uint16_t timeslot); +/* Removes a link. Return 1 if success, 0 if failure */ +int tsch_schedule_remove_link(struct tsch_slotframe *slotframe, struct tsch_link *l); +/* Removes a link from slotframe and timeslot. Return a 1 if success, 0 if failure */ +int tsch_schedule_remove_link_by_timeslot(struct tsch_slotframe *slotframe, uint16_t timeslot); + +/* Returns the next active link after a given ASN, and a backup link (for the same ASN, with Rx flag) */ +struct tsch_link * tsch_schedule_get_next_active_link(struct asn_t *asn, uint16_t *time_offset, + struct tsch_link **backup_link); + +#endif /* __TSCH_SCHEDULE_H__ */ diff --git a/core/net/mac/tsch/tsch-security.c b/core/net/mac/tsch/tsch-security.c new file mode 100644 index 000000000..936a55194 --- /dev/null +++ b/core/net/mac/tsch/tsch-security.c @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +/** + * \file + * TSCH security + * \author + * Simon Duquennoy + */ + +#include "contiki.h" +#include "net/mac/tsch/tsch.h" +#include "net/mac/tsch/tsch-packet.h" +#include "net/mac/tsch/tsch-private.h" +#include "net/mac/tsch/tsch-schedule.h" +#include "net/mac/tsch/tsch-security.h" +#include "net/mac/tsch/tsch-log.h" +#include "net/mac/frame802154.h" +#include "net/mac/framer-802154.h" +#include "net/netstack.h" +#include "net/packetbuf.h" +#include "lib/ccm-star.h" +#include "lib/aes-128.h" +#include +#include + +#if TSCH_LOG_LEVEL >= 1 +#define DEBUG DEBUG_PRINT +#else /* TSCH_LOG_LEVEL */ +#define DEBUG DEBUG_NONE +#endif /* TSCH_LOG_LEVEL */ +#include "net/ip/uip-debug.h" + +/* The two keys K1 and K2 from 6TiSCH minimal configuration + * K1: well-known, used for EBs + * K2: secret, used for data and ACK + * */ +static aes_key keys[] = { + TSCH_SECURITY_K1, + TSCH_SECURITY_K2 +}; +#define N_KEYS (sizeof(keys) / sizeof(aes_key)) + +/*---------------------------------------------------------------------------*/ +static void +aead(const uint8_t *nonce, + uint8_t *m, uint8_t m_len, + const uint8_t *a, uint8_t a_len, + uint8_t *result, uint8_t mic_len, + int forward) +{ + if(!forward) { + /* decrypt */ + CCM_STAR.ctr(m, m_len, nonce); + } + CCM_STAR.mic( + (const uint8_t *)m, m_len, + nonce, + a, a_len, + result, + mic_len); + + if(forward) { + /* encrypt */ + CCM_STAR.ctr(m, m_len, nonce); + } +} +/*---------------------------------------------------------------------------*/ +static void +tsch_security_init_nonce(uint8_t *nonce, + const linkaddr_t *sender, struct asn_t *asn) +{ + memcpy(nonce, sender, 8); + nonce[8] = asn->ms1b; + nonce[9] = (asn->ls4b >> 24) & 0xff; + nonce[10] = (asn->ls4b >> 16) & 0xff; + nonce[11] = (asn->ls4b >> 8) & 0xff; + nonce[12] = (asn->ls4b) & 0xff; +} +/*---------------------------------------------------------------------------*/ +static int +tsch_security_check_level(const frame802154_t *frame) +{ + uint8_t required_security_level; + uint8_t required_key_index; + + /* Sanity check */ + if(frame == NULL) { + return 0; + } + + /* Non-secured frame, ok iff we are not in a secured PAN + * (i.e. scanning or associated to a non-secured PAN) */ + if(frame->fcf.security_enabled == 0) { + return !(tsch_is_associated == 1 && tsch_is_pan_secured == 1); + } + + /* The frame is secured, that we are not in an unsecured PAN */ + if(tsch_is_associated == 1 && tsch_is_pan_secured == 0) { + return 0; + } + + /* The frame is secured, check its security level */ + switch(frame->fcf.frame_type) { + case FRAME802154_BEACONFRAME: + required_security_level = TSCH_SECURITY_KEY_SEC_LEVEL_EB; + required_key_index = TSCH_SECURITY_KEY_INDEX_EB; + break; + case FRAME802154_ACKFRAME: + required_security_level = TSCH_SECURITY_KEY_SEC_LEVEL_ACK; + required_key_index = TSCH_SECURITY_KEY_INDEX_ACK; + break; + default: + required_security_level = TSCH_SECURITY_KEY_SEC_LEVEL_OTHER; + required_key_index = TSCH_SECURITY_KEY_INDEX_OTHER; + break; + } + return frame->aux_hdr.security_control.security_level == required_security_level + && frame->aux_hdr.key_index == required_key_index; +} +/*---------------------------------------------------------------------------*/ +int +tsch_security_mic_len(const frame802154_t *frame) +{ + if(frame != NULL && frame->fcf.security_enabled) { + return 2 << (frame->aux_hdr.security_control.security_level & 0x03); + } else { + return 0; + } +} +/*---------------------------------------------------------------------------*/ +int +tsch_security_secure_frame(uint8_t *hdr, uint8_t *outbuf, + int hdrlen, int datalen, struct asn_t *asn) +{ + frame802154_t frame; + uint8_t key_index = 0; + uint8_t security_level = 0; + uint8_t with_encryption; + uint8_t mic_len; + uint8_t nonce[16]; + + uint8_t a_len; + uint8_t m_len; + + if(hdr == NULL || outbuf == NULL || hdrlen < 0 || datalen < 0) { + return 0; + } + + /* Parse the frame header to extract security settings */ + if(frame802154_parse(hdr, hdrlen + datalen, &frame) < 3) { + return 0; + } + + if(!frame.fcf.security_enabled) { + /* Security is not enabled for this frame, we're done */ + return 1; + } + + /* Read security key index */ + key_index = frame.aux_hdr.key_index; + security_level = frame.aux_hdr.security_control.security_level; + with_encryption = (security_level & 0x4) ? 1 : 0; + mic_len = tsch_security_mic_len(&frame); + + if(key_index == 0 || key_index > N_KEYS) { + return 0; + } + + tsch_security_init_nonce(nonce, &linkaddr_node_addr, asn); + + if(with_encryption) { + a_len = hdrlen; + m_len = datalen; + } else { + a_len = hdrlen + datalen; + m_len = 0; + } + + /* Copy source data to output */ + if(hdr != outbuf) { + memcpy(outbuf, hdr, a_len + m_len); + } + + CCM_STAR.set_key(keys[key_index - 1]); + + aead(nonce, + outbuf + a_len, m_len, + outbuf, a_len, + outbuf + hdrlen + datalen, mic_len, 1 + ); + + return mic_len; +} +/*---------------------------------------------------------------------------*/ +int +tsch_security_parse_frame(const uint8_t *hdr, int hdrlen, int datalen, + const frame802154_t *frame, const linkaddr_t *sender, struct asn_t *asn) +{ + uint8_t generated_mic[16]; + uint8_t key_index = 0; + uint8_t security_level = 0; + uint8_t with_encryption; + uint8_t mic_len; + uint8_t nonce[16]; + uint8_t a_len; + uint8_t m_len; + + if(frame == NULL || hdr == NULL || hdrlen < 0 || datalen < 0) { + return 0; + } + + if(!tsch_security_check_level(frame)) { + /* Wrong security level */ + return 0; + } + + /* No security: nothing more to check */ + if(!frame->fcf.security_enabled) { + return 1; + } + + key_index = frame->aux_hdr.key_index; + security_level = frame->aux_hdr.security_control.security_level; + with_encryption = (security_level & 0x4) ? 1 : 0; + mic_len = tsch_security_mic_len(frame); + + /* Check if key_index is in supported range */ + if(key_index == 0 || key_index > N_KEYS) { + return 0; + } + + tsch_security_init_nonce(nonce, sender, asn); + + if(with_encryption) { + a_len = hdrlen; + m_len = datalen; + } else { + a_len = hdrlen + datalen; + m_len = 0; + } + + CCM_STAR.set_key(keys[key_index - 1]); + + aead(nonce, + (uint8_t *)hdr + a_len, m_len, + (uint8_t *)hdr, a_len, + generated_mic, mic_len, 0 + ); + + if(mic_len > 0 && memcmp(generated_mic, hdr + hdrlen + datalen, mic_len) != 0) { + return 0; + } else { + return 1; + } +} diff --git a/core/net/mac/tsch/tsch-security.h b/core/net/mac/tsch/tsch-security.h new file mode 100644 index 000000000..694afa78e --- /dev/null +++ b/core/net/mac/tsch/tsch-security.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2014, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +#ifndef __TSCH_SECURITY_H__ +#define __TSCH_SECURITY_H__ + +/********** Includes **********/ + +#include "contiki.h" +#include "net/mac/tsch/tsch-asn.h" +#include "net/mac/tsch/tsch-private.h" +#include "net/mac/frame802154.h" +#include "net/mac/frame802154e-ie.h" + +/******** Configuration *******/ + +/* To enable TSCH security: + * - set LLSEC802154_CONF_SECURITY_LEVEL + * - set LLSEC802154_CONF_USES_EXPLICIT_KEYS + * - unset LLSEC802154_CONF_USES_FRAME_COUNTER + * */ +#define TSCH_SECURITY_ENABLED (LLSEC802154_CONF_SECURITY_LEVEL != 0) +#if TSCH_SECURITY_ENABLED && !LLSEC802154_CONF_USES_EXPLICIT_KEYS +#error TSCH_SECURITY_ENABLED set but LLSEC802154_CONF_USES_EXPLICIT_KEYS unset +#endif /* TSCH_SECURITY_ENABLED */ +#if TSCH_SECURITY_ENABLED && LLSEC802154_CONF_USES_FRAME_COUNTER +#error TSCH_SECURITY_ENABLED set but LLSEC802154_CONF_USES_FRAME_COUNTER set +#endif /* TSCH_SECURITY_ENABLED */ + +/* K1, defined in 6TiSCH minimal, is well-known (offers no security) and used for EBs only */ +#ifdef TSCH_SECURITY_CONF_K1 +#define TSCH_SECURITY_K1 TSCH_SECURITY_CONF_K1 +#else /* TSCH_SECURITY_CONF_K1 */ +#define TSCH_SECURITY_K1 { 0x36, 0x54, 0x69, 0x53, 0x43, 0x48, 0x20, 0x6D, 0x69, 0x6E, 0x69, 0x6D, 0x61, 0x6C, 0x31, 0x35 } +#endif /* TSCH_SECURITY_CONF_K1 */ + +/* K2, defined in 6TiSCH minimal, is used for all but EB traffic */ +#ifdef TSCH_SECURITY_CONF_K2 +#define TSCH_SECURITY_K2 TSCH_SECURITY_CONF_K2 +#else /* TSCH_SECURITY_CONF_K2 */ +#define TSCH_SECURITY_K2 { 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xca, 0xfe, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xca, 0xfe } +#endif /* TSCH_SECURITY_CONF_K2 */ + +/* Key used for EBs */ +#ifdef TSCH_SECURITY_CONF_KEY_INDEX_EB +#define TSCH_SECURITY_KEY_INDEX_EB TSCH_SECURITY_CONF_KEY_INDEX_EB +#else +#define TSCH_SECURITY_KEY_INDEX_EB 1 /* Use K1 as per 6TiSCH minimal */ +#endif + +/* Security level for EBs */ +#ifdef TSCH_SECURITY_CONF_SEC_LEVEL_EB +#define TSCH_SECURITY_KEY_SEC_LEVEL_EB TSCH_SECURITY_CONF_SEC_LEVEL_EB +#else +#define TSCH_SECURITY_KEY_SEC_LEVEL_EB 1 /* MIC-32, as per 6TiSCH minimal */ +#endif + +/* Key used for ACK */ +#ifdef TSCH_SECURITY_CONF_KEY_INDEX_ACK +#define TSCH_SECURITY_KEY_INDEX_ACK TSCH_SECURITY_CONF_KEY_INDEX_ACK +#else +#define TSCH_SECURITY_KEY_INDEX_ACK 2 /* Use K2 as per 6TiSCH minimal */ +#endif + +/* Security level for ACKs */ +#ifdef TSCH_SECURITY_CONF_SEC_LEVEL_ACK +#define TSCH_SECURITY_KEY_SEC_LEVEL_ACK TSCH_SECURITY_CONF_SEC_LEVEL_ACK +#else +#define TSCH_SECURITY_KEY_SEC_LEVEL_ACK 5 /* Encryption + MIC-32, as per 6TiSCH minimal */ +#endif + +/* Key used for Other (Data, Cmd) */ +#ifdef TSCH_SECURITY_CONF_KEY_INDEX_OTHER +#define TSCH_SECURITY_KEY_INDEX_OTHER TSCH_SECURITY_CONF_KEY_INDEX_OTHER +#else +#define TSCH_SECURITY_KEY_INDEX_OTHER 2 /* Use K2 as per 6TiSCH minimal */ +#endif + +/* Security level for Other (Data, Cmd) */ +#ifdef TSCH_SECURITY_CONF_SEC_LEVEL_OTHER +#define TSCH_SECURITY_KEY_SEC_LEVEL_OTHER TSCH_SECURITY_CONF_SEC_LEVEL_OTHER +#else +#define TSCH_SECURITY_KEY_SEC_LEVEL_OTHER 5 /* Encryption + MIC-32, as per 6TiSCH minimal */ +#endif + +/************ Types ***********/ + +typedef uint8_t aes_key[16]; + +/********** Functions *********/ + +int tsch_security_mic_len(const frame802154_t *frame); +int tsch_security_secure_frame(uint8_t *hdr, uint8_t *outbuf, + int hdrlen, int datalen, struct asn_t *asn); +int tsch_security_parse_frame(const uint8_t *hdr, int hdrlen, int datalen, + const frame802154_t *frame, const linkaddr_t *sender, struct asn_t *asn); + +#endif /* __TSCH_SECURITY_H__ */ diff --git a/core/net/mac/tsch/tsch-slot-operation.c b/core/net/mac/tsch/tsch-slot-operation.c new file mode 100644 index 000000000..be6c96422 --- /dev/null +++ b/core/net/mac/tsch/tsch-slot-operation.c @@ -0,0 +1,968 @@ +/* + * Copyright (c) 2015, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +/** + * \file + * TSCH slot operation implementation, running from interrupt. + * \author + * Simon Duquennoy + * Beshr Al Nahas + * + */ + +#include "contiki.h" +#include "dev/radio.h" +#include "net/netstack.h" +#include "net/packetbuf.h" +#include "net/queuebuf.h" +#include "net/mac/framer-802154.h" +#include "net/mac/tsch/tsch.h" +#include "net/mac/tsch/tsch-slot-operation.h" +#include "net/mac/tsch/tsch-queue.h" +#include "net/mac/tsch/tsch-private.h" +#include "net/mac/tsch/tsch-log.h" +#include "net/mac/tsch/tsch-packet.h" +#include "net/mac/tsch/tsch-security.h" + +#if TSCH_LOG_LEVEL >= 1 +#define DEBUG DEBUG_PRINT +#else /* TSCH_LOG_LEVEL */ +#define DEBUG DEBUG_NONE +#endif /* TSCH_LOG_LEVEL */ +#include "net/ip/uip-debug.h" + +/* TSCH debug macros, i.e. to set LEDs or GPIOs on various TSCH + * timeslot events */ +#ifndef TSCH_DEBUG_INIT +#define TSCH_DEBUG_INIT() +#endif +#ifndef TSCH_DEBUG_INTERRUPT +#define TSCH_DEBUG_INTERRUPT() +#endif +#ifndef TSCH_DEBUG_RX_EVENT +#define TSCH_DEBUG_RX_EVENT() +#endif +#ifndef TSCH_DEBUG_TX_EVENT +#define TSCH_DEBUG_TX_EVENT() +#endif +#ifndef TSCH_DEBUG_SLOT_START +#define TSCH_DEBUG_SLOT_START() +#endif +#ifndef TSCH_DEBUG_SLOT_END +#define TSCH_DEBUG_SLOT_END() +#endif + +/* Check if TSCH_MAX_INCOMING_PACKETS is power of two */ +#if (TSCH_MAX_INCOMING_PACKETS & (TSCH_MAX_INCOMING_PACKETS - 1)) != 0 +#error TSCH_MAX_INCOMING_PACKETS must be power of two +#endif + +/* Check if TSCH_DEQUEUED_ARRAY_SIZE is power of two and greater or equal to QUEUEBUF_NUM */ +#if TSCH_DEQUEUED_ARRAY_SIZE < QUEUEBUF_NUM +#error TSCH_DEQUEUED_ARRAY_SIZE must be greater or equal to QUEUEBUF_NUM +#endif +#if (TSCH_DEQUEUED_ARRAY_SIZE & (TSCH_DEQUEUED_ARRAY_SIZE - 1)) != 0 +#error TSCH_QUEUE_NUM_PER_NEIGHBOR must be power of two +#endif + +/* Truncate received drift correction information to maximum half + * of the guard time (one fourth of TSCH_DEFAULT_TS_RX_WAIT) */ +#define SYNC_IE_BOUND ((int32_t)US_TO_RTIMERTICKS(TSCH_DEFAULT_TS_RX_WAIT / 4)) + +/* By default: check that rtimer runs at >=32kHz and use a guard time of 10us */ +#if RTIMER_SECOND < (32 * 1024) +#error "TSCH: RTIMER_SECOND < (32 * 1024)" +#endif +#if RTIMER_SECOND >= 200000 +#define RTIMER_GUARD (RTIMER_SECOND / 100000) +#else +#define RTIMER_GUARD 2u +#endif + +/* A ringbuf storing outgoing packets after they were dequeued. + * Will be processed layer by tsch_tx_process_pending */ +struct ringbufindex dequeued_ringbuf; +struct tsch_packet *dequeued_array[TSCH_DEQUEUED_ARRAY_SIZE]; +/* A ringbuf storing incoming packets. + * Will be processed layer by tsch_rx_process_pending */ +struct ringbufindex input_ringbuf; +struct input_packet input_array[TSCH_MAX_INCOMING_PACKETS]; + +/* Last time we received Sync-IE (ACK or data packet from a time source) */ +static struct asn_t last_sync_asn; + +/* A global lock for manipulating data structures safely from outside of interrupt */ +static volatile int tsch_locked = 0; +/* As long as this is set, skip all slot operation */ +static volatile int tsch_lock_requested = 0; + +/* Last estimated drift in RTIMER ticks + * (Sky: 1 tick ~= 30.52 uSec) */ +static int32_t drift_correction = 0; +static struct tsch_neighbor *drift_neighbor = NULL; + +/* Used from tsch_slot_operation and sub-protothreads */ +static rtimer_clock_t volatile current_slot_start; + +/* Are we currently inside a slot? */ +static volatile int tsch_in_slot_operation = 0; + +/* Info about the link, packet and neighbor of + * the current (or next) slot */ +struct tsch_link *current_link = NULL; +/* A backup link with Rx flag, overlapping with current_link. + * If the current link is Tx-only and the Tx queue + * is empty while executing the link, fallback to the backup link. */ +static struct tsch_link *backup_link = NULL; +static struct tsch_packet *current_packet = NULL; +static struct tsch_neighbor *current_neighbor = NULL; + +/* Protothread for association */ +PT_THREAD(tsch_scan(struct pt *pt)); +/* Protothread for slot operation, called from rtimer interrupt + * and scheduled from tsch_schedule_slot_operation */ +static PT_THREAD(tsch_slot_operation(struct rtimer *t, void *ptr)); +static struct pt slot_operation_pt; +/* Sub-protothreads of tsch_slot_operation */ +static PT_THREAD(tsch_tx_slot(struct pt *pt, struct rtimer *t)); +static PT_THREAD(tsch_rx_slot(struct pt *pt, struct rtimer *t)); + +/*---------------------------------------------------------------------------*/ +/* TSCH locking system. TSCH is locked during slot operations */ + +/* Is TSCH locked? */ +int +tsch_is_locked(void) +{ + return tsch_locked; +} + +/* Lock TSCH (no slot operation) */ +int +tsch_get_lock(void) +{ + if(!tsch_locked) { + rtimer_clock_t busy_wait_time; + int busy_wait = 0; /* Flag used for logging purposes */ + /* Make sure no new slot operation will start */ + tsch_lock_requested = 1; + /* Wait for the end of current slot operation. */ + if(tsch_in_slot_operation) { + busy_wait = 1; + busy_wait_time = RTIMER_NOW(); + while(tsch_in_slot_operation); + busy_wait_time = RTIMER_NOW() - busy_wait_time; + } + if(!tsch_locked) { + /* Take the lock if it is free */ + tsch_locked = 1; + tsch_lock_requested = 0; + if(busy_wait) { + /* Issue a log whenever we had to busy wait until getting the lock */ + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "!get lock delay %u", busy_wait_time); + ); + } + return 1; + } + } + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "!failed to lock"); + ); + return 0; +} + +/* Release TSCH lock */ +void +tsch_release_lock(void) +{ + tsch_locked = 0; +} + +/*---------------------------------------------------------------------------*/ +/* Channel hopping utility functions */ + +/* Return channel from ASN and channel offset */ +uint8_t +tsch_calculate_channel(struct asn_t *asn, uint8_t channel_offset) +{ + uint16_t index_of_0 = ASN_MOD(*asn, tsch_hopping_sequence_length); + uint16_t index_of_offset = (index_of_0 + channel_offset) % tsch_hopping_sequence_length.val; + return tsch_hopping_sequence[index_of_offset]; +} + +/*---------------------------------------------------------------------------*/ +/* Timing utility functions */ + +/* Checks if the current time has passed a ref time + offset. Assumes + * a single overflow and ref time prior to now. */ +static uint8_t +check_timer_miss(rtimer_clock_t ref_time, rtimer_clock_t offset, rtimer_clock_t now) +{ + rtimer_clock_t target = ref_time + offset; + int now_has_overflowed = now < ref_time; + int target_has_overflowed = target < ref_time; + + if(now_has_overflowed == target_has_overflowed) { + /* Both or none have overflowed, just compare now to the target */ + return target <= now; + } else { + /* Either now or target of overflowed. + * If it is now, then it has passed the target. + * If it is target, then we haven't reached it yet. + * */ + return now_has_overflowed; + } +} +/*---------------------------------------------------------------------------*/ +/* Schedule a wakeup at a specified offset from a reference time. + * Provides basic protection against missed deadlines and timer overflows + * A non-zero return value signals to tsch_slot_operation a missed deadline. + * If conditional: schedule only if the deadline is not missed, else busy wait. + * If not conditional: schedule regardless of deadline miss. */ +static uint8_t +tsch_schedule_slot_operation(struct rtimer *tm, rtimer_clock_t ref_time, rtimer_clock_t offset, int conditional, const char *str) +{ + rtimer_clock_t now = RTIMER_NOW(); + int r; + int missed = check_timer_miss(ref_time, offset - RTIMER_GUARD, now); + + if(missed) { + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "!dl-miss-%d %s %d %d", + conditional, str, + (int)(now-ref_time), (int)offset); + ); + + if(conditional) { + BUSYWAIT_UNTIL_ABS(0, ref_time, offset); + return 0; + } + } + ref_time += offset; + r = rtimer_set(tm, ref_time, 1, (void (*)(struct rtimer *, void *))tsch_slot_operation, NULL); + if(r != RTIMER_OK) { + return 0; + } + return 1; +} +/*---------------------------------------------------------------------------*/ +/* Schedule slot operation conditionally, and YIELD if success only */ +#define TSCH_SCHEDULE_AND_YIELD(pt, tm, ref_time, offset, str) \ + do { \ + if(tsch_schedule_slot_operation(tm, ref_time, offset, 1, str)) { \ + PT_YIELD(pt); \ + } \ + } while(0); + +/*---------------------------------------------------------------------------*/ +/* Get EB, broadcast or unicast packet to be sent, and target neighbor. */ +static struct tsch_packet * +get_packet_and_neighbor_for_link(struct tsch_link *link, struct tsch_neighbor **target_neighbor) +{ + struct tsch_packet *p = NULL; + struct tsch_neighbor *n = NULL; + + /* Is this a Tx link? */ + if(link->link_options & LINK_OPTION_TX) { + /* is it for advertisement of EB? */ + if(link->link_type == LINK_TYPE_ADVERTISING || link->link_type == LINK_TYPE_ADVERTISING_ONLY) { + /* fetch EB packets */ + n = n_eb; + p = tsch_queue_get_packet_for_nbr(n, link); + } + if(link->link_type != LINK_TYPE_ADVERTISING_ONLY) { + /* NORMAL link or no EB to send, pick a data packet */ + if(p == NULL) { + /* Get neighbor queue associated to the link and get packet from it */ + n = tsch_queue_get_nbr(&link->addr); + p = tsch_queue_get_packet_for_nbr(n, link); + /* if it is a broadcast slot and there were no broadcast packets, pick any unicast packet */ + if(p == NULL && n == n_broadcast) { + p = tsch_queue_get_unicast_packet_for_any(&n, link); + } + } + } + } + /* return nbr (by reference) */ + if(target_neighbor != NULL) { + *target_neighbor = n; + } + + return p; +} +/*---------------------------------------------------------------------------*/ +/* Post TX: Update neighbor state after a transmission */ +static int +update_neighbor_state(struct tsch_neighbor *n, struct tsch_packet *p, + struct tsch_link *link, uint8_t mac_tx_status) +{ + int in_queue = 1; + int is_shared_link = link->link_options & LINK_OPTION_SHARED; + int is_unicast = !n->is_broadcast; + + if(mac_tx_status == MAC_TX_OK) { + /* Successful transmission */ + tsch_queue_remove_packet_from_queue(n); + in_queue = 0; + + /* Update CSMA state in the unicast case */ + if(is_unicast) { + if(is_shared_link || tsch_queue_is_empty(n)) { + /* If this is a shared link, reset backoff on success. + * Otherwise, do so only is the queue is empty */ + tsch_queue_backoff_reset(n); + } + } + } else { + /* Failed transmission */ + if(p->transmissions >= TSCH_MAC_MAX_FRAME_RETRIES + 1) { + /* Drop packet */ + tsch_queue_remove_packet_from_queue(n); + in_queue = 0; + } + /* Update CSMA state in the unicast case */ + if(is_unicast) { + /* Failures on dedicated (== non-shared) leave the backoff + * window nor exponent unchanged */ + if(is_shared_link) { + /* Shared link: increment backoff exponent, pick a new window */ + tsch_queue_backoff_inc(n); + } + } + } + + return in_queue; +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(tsch_tx_slot(struct pt *pt, struct rtimer *t)) +{ + /** + * TX slot: + * 1. Copy packet to radio buffer + * 2. Perform CCA if enabled + * 3. Sleep until it is time to transmit + * 4. Wait for ACK if it is a unicast packet + * 5. Extract drift if we received an E-ACK from a time source neighbor + * 6. Update CSMA parameters according to TX status + * 7. Schedule mac_call_sent_callback + **/ + + /* tx status */ + static uint8_t mac_tx_status; + /* is the packet in its neighbor's queue? */ + uint8_t in_queue; + static int dequeued_index; + static int packet_ready = 1; + + PT_BEGIN(pt); + + TSCH_DEBUG_TX_EVENT(); + + /* First check if we have space to store a newly dequeued packet (in case of + * successful Tx or Drop) */ + dequeued_index = ringbufindex_peek_put(&dequeued_ringbuf); + if(dequeued_index != -1) { + /* is this a data packet? */ + static uint8_t is_data; + if(current_packet == NULL || current_packet->qb == NULL) { + mac_tx_status = MAC_TX_ERR_FATAL; + } else { + /* packet payload */ + static void *packet; +#if TSCH_SECURITY_ENABLED + /* encrypted payload */ + static uint8_t encrypted_packet[TSCH_PACKET_MAX_LEN]; +#endif /* TSCH_SECURITY_ENABLED */ + /* packet payload length */ + static uint8_t packet_len; + /* packet seqno */ + static uint8_t seqno; + /* is this a broadcast packet? (wait for ack?) */ + static uint8_t is_broadcast; + static rtimer_clock_t tx_start_time; + +#if CCA_ENABLED + static uint8_t cca_status; +#endif + + /* get payload */ + packet = queuebuf_dataptr(current_packet->qb); + packet_len = queuebuf_datalen(current_packet->qb); + /* is this a broadcast packet? (wait for ack?) */ + is_broadcast = current_neighbor->is_broadcast; + is_data = ((((uint8_t *)(packet))[0]) & 7) == FRAME802154_DATAFRAME; + /* read seqno from payload */ + seqno = ((uint8_t *)(packet))[2]; + /* if this is an EB, then update its Sync-IE */ + if(current_neighbor == n_eb) { + packet_ready = tsch_packet_update_eb(packet, packet_len, current_packet->tsch_sync_ie_offset); + } else { + packet_ready = 1; + } + +#if TSCH_SECURITY_ENABLED + if(tsch_is_pan_secured) { + /* If we are going to encrypt, we need to generate the output in a separate buffer and keep + * the original untouched. This is to allow for future retransmissions. */ + int with_encryption = queuebuf_attr(current_packet->qb, PACKETBUF_ATTR_SECURITY_LEVEL) & 0x4; + packet_len += tsch_security_secure_frame(packet, with_encryption ? encrypted_packet : packet, current_packet->header_len, + packet_len - current_packet->header_len, ¤t_asn); + if(with_encryption) { + packet = encrypted_packet; + } + } +#endif /* TSCH_SECURITY_ENABLED */ + + /* prepare packet to send: copy to radio buffer */ + if(packet_ready && NETSTACK_RADIO.prepare(packet, packet_len) == 0) { /* 0 means success */ + static rtimer_clock_t tx_duration; + +#if CCA_ENABLED + cca_status = 1; + /* delay before CCA */ + TSCH_SCHEDULE_AND_YIELD(pt, t, current_slot_start, TS_CCA_OFFSET, "cca"); + TSCH_DEBUG_TX_EVENT(); + NETSTACK_RADIO.on(); + /* CCA */ + BUSYWAIT_UNTIL_ABS(!(cca_status |= NETSTACK_RADIO.channel_clear()), + current_slot_start, TS_CCA_OFFSET + TS_CCA); + TSCH_DEBUG_TX_EVENT(); + /* there is not enough time to turn radio off */ + /* NETSTACK_RADIO.off(); */ + if(cca_status == 0) { + mac_tx_status = MAC_TX_COLLISION; + } else +#endif /* CCA_ENABLED */ + { + /* delay before TX */ + TSCH_SCHEDULE_AND_YIELD(pt, t, current_slot_start, tsch_timing[tsch_ts_tx_offset] - RADIO_DELAY_BEFORE_TX, "TxBeforeTx"); + TSCH_DEBUG_TX_EVENT(); + /* send packet already in radio tx buffer */ + mac_tx_status = NETSTACK_RADIO.transmit(packet_len); + /* Save tx timestamp */ + tx_start_time = current_slot_start + tsch_timing[tsch_ts_tx_offset]; + /* calculate TX duration based on sent packet len */ + tx_duration = TSCH_PACKET_DURATION(packet_len); + /* limit tx_time to its max value */ + tx_duration = MIN(tx_duration, tsch_timing[tsch_ts_max_tx]); + /* turn tadio off -- will turn on again to wait for ACK if needed */ + NETSTACK_RADIO.off(); + + if(mac_tx_status == RADIO_TX_OK) { + if(!is_broadcast) { + uint8_t ackbuf[TSCH_PACKET_MAX_LEN]; + int ack_len; + rtimer_clock_t ack_start_time; + int is_time_source; + radio_value_t radio_rx_mode; + struct ieee802154_ies ack_ies; + uint8_t ack_hdrlen; + frame802154_t frame; + + /* Entering promiscuous mode so that the radio accepts the enhanced ACK */ + NETSTACK_RADIO.get_value(RADIO_PARAM_RX_MODE, &radio_rx_mode); + NETSTACK_RADIO.set_value(RADIO_PARAM_RX_MODE, radio_rx_mode & (~RADIO_RX_MODE_ADDRESS_FILTER)); + /* Unicast: wait for ack after tx: sleep until ack time */ + TSCH_SCHEDULE_AND_YIELD(pt, t, current_slot_start, + tsch_timing[tsch_ts_tx_offset] + tx_duration + tsch_timing[tsch_ts_rx_ack_delay] - RADIO_DELAY_BEFORE_RX, "TxBeforeAck"); + TSCH_DEBUG_TX_EVENT(); + NETSTACK_RADIO.on(); + /* Wait for ACK to come */ + BUSYWAIT_UNTIL_ABS(NETSTACK_RADIO.receiving_packet(), + tx_start_time, tx_duration + tsch_timing[tsch_ts_rx_ack_delay] + tsch_timing[tsch_ts_ack_wait]); + TSCH_DEBUG_TX_EVENT(); + + ack_start_time = RTIMER_NOW(); + + /* Wait for ACK to finish */ + BUSYWAIT_UNTIL_ABS(!NETSTACK_RADIO.receiving_packet(), + ack_start_time, tsch_timing[tsch_ts_max_ack]); + TSCH_DEBUG_TX_EVENT(); + NETSTACK_RADIO.off(); + + /* Leaving promiscuous mode */ + NETSTACK_RADIO.get_value(RADIO_PARAM_RX_MODE, &radio_rx_mode); + NETSTACK_RADIO.set_value(RADIO_PARAM_RX_MODE, radio_rx_mode | RADIO_RX_MODE_ADDRESS_FILTER); + + /* Read ack frame */ + ack_len = NETSTACK_RADIO.read((void *)ackbuf, sizeof(ackbuf)); + + is_time_source = 0; + /* The radio driver should return 0 if no valid packets are in the rx buffer */ + if(ack_len > 0) { + is_time_source = current_neighbor != NULL && current_neighbor->is_time_source; + if(tsch_packet_parse_eack(ackbuf, ack_len, seqno, + &frame, &ack_ies, &ack_hdrlen) == 0) { + ack_len = 0; + } + +#if TSCH_SECURITY_ENABLED + if(ack_len != 0) { + if(!tsch_security_parse_frame(ackbuf, ack_hdrlen, ack_len - ack_hdrlen - tsch_security_mic_len(&frame), + &frame, ¤t_neighbor->addr, ¤t_asn)) { + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "!failed to authenticate ACK")); + ack_len = 0; + } + } else { + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "!failed to parse ACK")); + } +#endif /* TSCH_SECURITY_ENABLED */ + } + + if(ack_len != 0) { + if(is_time_source) { + int32_t eack_time_correction = US_TO_RTIMERTICKS(ack_ies.ie_time_correction); + if(eack_time_correction > SYNC_IE_BOUND) { + drift_correction = SYNC_IE_BOUND; + } else if(eack_time_correction < -SYNC_IE_BOUND) { + drift_correction = -SYNC_IE_BOUND; + } else { + drift_correction = eack_time_correction; + } + if(drift_correction != eack_time_correction) { + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "!truncated dr %d %d", (int)eack_time_correction, (int)drift_correction); + ); + } + drift_neighbor = current_neighbor; + /* Keep track of sync time */ + last_sync_asn = current_asn; + tsch_schedule_keepalive(); + } + mac_tx_status = MAC_TX_OK; + } else { + mac_tx_status = MAC_TX_NOACK; + } + } else { + mac_tx_status = MAC_TX_OK; + } + } else { + mac_tx_status = MAC_TX_ERR; + } + } + } + } + + current_packet->transmissions++; + current_packet->ret = mac_tx_status; + + /* Post TX: Update neighbor state */ + in_queue = update_neighbor_state(current_neighbor, current_packet, current_link, mac_tx_status); + + /* The packet was dequeued, add it to dequeued_ringbuf for later processing */ + if(in_queue == 0) { + dequeued_array[dequeued_index] = current_packet; + ringbufindex_put(&dequeued_ringbuf); + } + + /* Log every tx attempt */ + TSCH_LOG_ADD(tsch_log_tx, + log->tx.mac_tx_status = mac_tx_status; + log->tx.num_tx = current_packet->transmissions; + log->tx.datalen = queuebuf_datalen(current_packet->qb); + log->tx.drift = drift_correction; + log->tx.drift_used = drift_neighbor != NULL; + log->tx.is_data = is_data; + log->tx.sec_level = queuebuf_attr(current_packet->qb, PACKETBUF_ATTR_SECURITY_LEVEL); + log->tx.dest = TSCH_LOG_ID_FROM_LINKADDR(queuebuf_addr(current_packet->qb, PACKETBUF_ADDR_RECEIVER)); + ); + + /* Poll process for later processing of packet sent events and logs */ + process_poll(&tsch_pending_events_process); + } + + TSCH_DEBUG_TX_EVENT(); + + PT_END(pt); +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(tsch_rx_slot(struct pt *pt, struct rtimer *t)) +{ + /** + * RX slot: + * 1. Check if it is used for TIME_KEEPING + * 2. Sleep and wake up just before expected RX time (with a guard time: TS_LONG_GT) + * 3. Check for radio activity for the guard time: TS_LONG_GT + * 4. Prepare and send ACK if needed + * 5. Drift calculated in the ACK callback registered with the radio driver. Use it if receiving from a time source neighbor. + **/ + + struct tsch_neighbor *n; + static linkaddr_t source_address; + static linkaddr_t destination_address; + static int16_t input_index; + static int input_queue_drop = 0; + + PT_BEGIN(pt); + + TSCH_DEBUG_RX_EVENT(); + + input_index = ringbufindex_peek_put(&input_ringbuf); + if(input_index == -1) { + input_queue_drop++; + } else { + static struct input_packet *current_input; + /* Estimated drift based on RX time */ + static int32_t estimated_drift; + /* Rx timestamps */ + static rtimer_clock_t rx_start_time; + static rtimer_clock_t expected_rx_time; + static rtimer_clock_t packet_duration; + + expected_rx_time = current_slot_start + tsch_timing[tsch_ts_tx_offset]; + /* Default start time: expected Rx time */ + rx_start_time = expected_rx_time; + + current_input = &input_array[input_index]; + + /* Wait before starting to listen */ + TSCH_SCHEDULE_AND_YIELD(pt, t, current_slot_start, tsch_timing[tsch_ts_rx_offset] - RADIO_DELAY_BEFORE_RX, "RxBeforeListen"); + TSCH_DEBUG_RX_EVENT(); + + /* Start radio for at least guard time */ + NETSTACK_RADIO.on(); + if(!NETSTACK_RADIO.receiving_packet()) { + /* Check if receiving within guard time */ + BUSYWAIT_UNTIL_ABS(NETSTACK_RADIO.receiving_packet(), + current_slot_start, tsch_timing[tsch_ts_rx_offset] + tsch_timing[tsch_ts_rx_wait]); + TSCH_DEBUG_RX_EVENT(); + /* Save packet timestamp */ + rx_start_time = RTIMER_NOW(); + } + if(!NETSTACK_RADIO.receiving_packet() && !NETSTACK_RADIO.pending_packet()) { + NETSTACK_RADIO.off(); + /* no packets on air */ + } else { + /* Wait until packet is received, turn radio off */ + BUSYWAIT_UNTIL_ABS(!NETSTACK_RADIO.receiving_packet(), + current_slot_start, tsch_timing[tsch_ts_rx_offset] + tsch_timing[tsch_ts_rx_wait] + tsch_timing[tsch_ts_max_tx]); + TSCH_DEBUG_RX_EVENT(); + NETSTACK_RADIO.off(); + +#if TSCH_RESYNC_WITH_SFD_TIMESTAMPS + /* At the end of the reception, get an more accurate estimate of SFD arrival time */ + NETSTACK_RADIO.get_object(RADIO_PARAM_LAST_PACKET_TIMESTAMP, &rx_start_time, sizeof(rtimer_clock_t)); +#endif + + if(NETSTACK_RADIO.pending_packet()) { + static int frame_valid; + static int header_len; + static frame802154_t frame; + radio_value_t radio_last_rssi; + + NETSTACK_RADIO.get_value(RADIO_PARAM_LAST_RSSI, &radio_last_rssi); + /* Read packet */ + current_input->len = NETSTACK_RADIO.read((void *)current_input->payload, TSCH_PACKET_MAX_LEN); + current_input->rx_asn = current_asn; + current_input->rssi = (signed)radio_last_rssi; + header_len = frame802154_parse((uint8_t *)current_input->payload, current_input->len, &frame); + frame_valid = header_len > 0 && + frame802154_check_dest_panid(&frame) && + frame802154_extract_linkaddr(&frame, &source_address, &destination_address); + + packet_duration = TSCH_PACKET_DURATION(current_input->len); + +#if TSCH_SECURITY_ENABLED + /* Decrypt and verify incoming frame */ + if(frame_valid) { + if(tsch_security_parse_frame( + current_input->payload, header_len, current_input->len - header_len - tsch_security_mic_len(&frame), + &frame, &source_address, ¤t_asn)) { + current_input->len -= tsch_security_mic_len(&frame); + } else { + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "!failed to authenticate frame %u", current_input->len)); + frame_valid = 0; + } + } else { + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "!failed to parse frame %u %u", header_len, current_input->len)); + frame_valid = 0; + } +#endif /* TSCH_SECURITY_ENABLED */ + + if(frame_valid) { + if(linkaddr_cmp(&destination_address, &linkaddr_node_addr) + || linkaddr_cmp(&destination_address, &linkaddr_null)) { + int do_nack = 0; + estimated_drift = ((int32_t)expected_rx_time - (int32_t)rx_start_time); + +#ifdef TSCH_CALLBACK_DO_NACK + if(frame.fcf.ack_required) { + do_nack = TSCH_CALLBACK_DO_NACK(current_link, + &source_address, &destination_address); + } +#endif + + if(frame.fcf.ack_required) { + static uint8_t ack_buf[TSCH_PACKET_MAX_LEN]; + static int ack_len; + + /* Build ACK frame */ + ack_len = tsch_packet_create_eack(ack_buf, sizeof(ack_buf), + &source_address, frame.seq, (int16_t)RTIMERTICKS_TO_US(estimated_drift), do_nack); + +#if TSCH_SECURITY_ENABLED + if(tsch_is_pan_secured) { + /* Secure ACK frame. There is only header and header IEs, therefore data len == 0. */ + ack_len += tsch_security_secure_frame(ack_buf, ack_buf, ack_len, 0, ¤t_asn); + } +#endif /* TSCH_SECURITY_ENABLED */ + + /* Copy to radio buffer */ + NETSTACK_RADIO.prepare((const void *)ack_buf, ack_len); + + /* Wait for time to ACK and transmit ACK */ + TSCH_SCHEDULE_AND_YIELD(pt, t, rx_start_time, + packet_duration + tsch_timing[tsch_ts_tx_ack_delay] - RADIO_DELAY_BEFORE_TX, "RxBeforeAck"); + TSCH_DEBUG_RX_EVENT(); + NETSTACK_RADIO.transmit(ack_len); + } + + /* If the sender is a time source, proceed to clock drift compensation */ + n = tsch_queue_get_nbr(&source_address); + if(n != NULL && n->is_time_source) { + /* Keep track of last sync time */ + last_sync_asn = current_asn; + /* Save estimated drift */ + drift_correction = -estimated_drift; + drift_neighbor = n; + tsch_schedule_keepalive(); + } + + /* Add current input to ringbuf */ + ringbufindex_put(&input_ringbuf); + + /* Log every reception */ + TSCH_LOG_ADD(tsch_log_rx, + log->rx.src = TSCH_LOG_ID_FROM_LINKADDR((linkaddr_t*)&frame.src_addr); + log->rx.is_unicast = frame.fcf.ack_required; + log->rx.datalen = current_input->len; + log->rx.drift = drift_correction; + log->rx.drift_used = drift_neighbor != NULL; + log->rx.is_data = frame.fcf.frame_type == FRAME802154_DATAFRAME; + log->rx.sec_level = frame.aux_hdr.security_control.security_level; + log->rx.estimated_drift = estimated_drift; + ); + } else { + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "!not for us %x:%x:%x:%x", + destination_address.u8[4], destination_address.u8[5], + destination_address.u8[6], destination_address.u8[7]); + ); + } + + /* Poll process for processing of pending input and logs */ + process_poll(&tsch_pending_events_process); + } + } + } + + if(input_queue_drop != 0) { + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "!queue full skipped %u", input_queue_drop); + ); + input_queue_drop = 0; + } + } + + TSCH_DEBUG_RX_EVENT(); + + PT_END(pt); +} +/*---------------------------------------------------------------------------*/ +/* Protothread for slot operation, called from rtimer interrupt + * and scheduled from tsch_schedule_slot_operation */ +static +PT_THREAD(tsch_slot_operation(struct rtimer *t, void *ptr)) +{ + TSCH_DEBUG_INTERRUPT(); + PT_BEGIN(&slot_operation_pt); + + /* Loop over all active slots */ + while(tsch_is_associated) { + + if(current_link == NULL || tsch_lock_requested) { /* Skip slot operation if there is no link + or if there is a pending request for getting the lock */ + /* Issue a log whenever skipping a slot */ + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "!skipped slot %u %u %u", + tsch_locked, + tsch_lock_requested, + current_link == NULL); + ); + + } else { + uint8_t current_channel; + TSCH_DEBUG_SLOT_START(); + tsch_in_slot_operation = 1; + /* Get a packet ready to be sent */ + current_packet = get_packet_and_neighbor_for_link(current_link, ¤t_neighbor); + /* There is no packet to send, and this link does not have Rx flag. Instead of doing + * nothing, switch to the backup link (has Rx flag) if any. */ + if(current_packet == NULL && !(current_link->link_options & LINK_OPTION_RX) && backup_link != NULL) { + current_link = backup_link; + current_packet = get_packet_and_neighbor_for_link(current_link, ¤t_neighbor); + } + /* Hop channel */ + current_channel = tsch_calculate_channel(¤t_asn, current_link->channel_offset); + NETSTACK_RADIO.set_value(RADIO_PARAM_CHANNEL, current_channel); + /* Reset drift correction */ + drift_correction = 0; + drift_neighbor = NULL; + /* Decide whether it is a TX/RX/IDLE or OFF slot */ + /* Actual slot operation */ + if(current_packet != NULL) { + /* We have something to transmit, do the following: + * 1. send + * 2. update_backoff_state(current_neighbor) + * 3. post tx callback + **/ + static struct pt slot_tx_pt; + PT_SPAWN(&slot_operation_pt, &slot_tx_pt, tsch_tx_slot(&slot_tx_pt, t)); + } else if((current_link->link_options & LINK_OPTION_RX)) { + /* Listen */ + static struct pt slot_rx_pt; + PT_SPAWN(&slot_operation_pt, &slot_rx_pt, tsch_rx_slot(&slot_rx_pt, t)); + } + TSCH_DEBUG_SLOT_END(); + } + + /* End of slot operation, schedule next slot or resynchronize */ + + /* Do we need to resynchronize? i.e., wait for EB again */ + if(!tsch_is_coordinator && (ASN_DIFF(current_asn, last_sync_asn) > + (100 * TSCH_CLOCK_TO_SLOTS(TSCH_DESYNC_THRESHOLD / 100, tsch_timing[tsch_ts_timeslot_length])))) { + TSCH_LOG_ADD(tsch_log_message, + snprintf(log->message, sizeof(log->message), + "! leaving the network, last sync %u", + (unsigned)ASN_DIFF(current_asn, last_sync_asn)); + ); + tsch_disassociate(); + } else { + /* backup of drift correction for printing debug messages */ + /* int32_t drift_correction_backup = drift_correction; */ + uint16_t timeslot_diff = 0; + rtimer_clock_t prev_slot_start; + /* Time to next wake up */ + rtimer_clock_t time_to_next_active_slot; + /* Schedule next wakeup skipping slots if missed deadline */ + do { + if(current_link != NULL + && current_link->link_options & LINK_OPTION_TX + && current_link->link_options & LINK_OPTION_SHARED) { + /* Decrement the backoff window for all neighbors able to transmit over + * this Tx, Shared link. */ + tsch_queue_update_all_backoff_windows(¤t_link->addr); + } + + /* Get next active link */ + current_link = tsch_schedule_get_next_active_link(¤t_asn, ×lot_diff, &backup_link); + if(current_link == NULL) { + /* There is no next link. Fall back to default + * behavior: wake up at the next slot. */ + timeslot_diff = 1; + } + /* Update ASN */ + ASN_INC(current_asn, timeslot_diff); + /* Time to next wake up */ + time_to_next_active_slot = timeslot_diff * tsch_timing[tsch_ts_timeslot_length] + drift_correction; + drift_correction = 0; + drift_neighbor = NULL; + /* Update current slot start */ + prev_slot_start = current_slot_start; + current_slot_start += time_to_next_active_slot; + } while(!tsch_schedule_slot_operation(t, prev_slot_start, time_to_next_active_slot, 1, "main")); + } + + tsch_in_slot_operation = 0; + PT_YIELD(&slot_operation_pt); + } + + PT_END(&slot_operation_pt); +} +/*---------------------------------------------------------------------------*/ +/* Set global time before starting slot operation, + * with a rtimer time and an ASN */ +void +tsch_slot_operation_start(void) +{ + static struct rtimer slot_operation_timer; + rtimer_clock_t time_to_next_active_slot; + rtimer_clock_t prev_slot_start; + TSCH_DEBUG_INIT(); + do { + uint16_t timeslot_diff; + /* Get next active link */ + current_link = tsch_schedule_get_next_active_link(¤t_asn, ×lot_diff, &backup_link); + if(current_link == NULL) { + /* There is no next link. Fall back to default + * behavior: wake up at the next slot. */ + timeslot_diff = 1; + } + /* Update ASN */ + ASN_INC(current_asn, timeslot_diff); + /* Time to next wake up */ + time_to_next_active_slot = timeslot_diff * tsch_timing[tsch_ts_timeslot_length]; + /* Update current slot start */ + prev_slot_start = current_slot_start; + current_slot_start += time_to_next_active_slot; + } while(!tsch_schedule_slot_operation(&slot_operation_timer, prev_slot_start, time_to_next_active_slot, 1, "association")); +} +/*---------------------------------------------------------------------------*/ +/* Start actual slot operation */ +void +tsch_slot_operation_sync(rtimer_clock_t next_slot_start, + struct asn_t *next_slot_asn) +{ + current_slot_start = next_slot_start; + current_asn = *next_slot_asn; + last_sync_asn = current_asn; + current_link = NULL; +} +/*---------------------------------------------------------------------------*/ diff --git a/core/net/mac/tsch/tsch-slot-operation.h b/core/net/mac/tsch/tsch-slot-operation.h new file mode 100644 index 000000000..64382fcc8 --- /dev/null +++ b/core/net/mac/tsch/tsch-slot-operation.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2015, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +#ifndef __TSCH_SLOT_OPERATION_H__ +#define __TSCH_SLOT_OPERATION_H__ + +/********** Includes **********/ + +#include "contiki.h" +#include "lib/ringbufindex.h" +#include "net/mac/tsch/tsch-packet.h" +#include "net/mac/tsch/tsch-private.h" + +/******** Configuration *******/ + +/* Size of the ring buffer storing dequeued outgoing packets (only an array of pointers). + * Must be power of two, and greater or equal to QUEUEBUF_NUM */ +#ifdef TSCH_CONF_DEQUEUED_ARRAY_SIZE +#define TSCH_DEQUEUED_ARRAY_SIZE TSCH_CONF_DEQUEUED_ARRAY_SIZE +#else +#define TSCH_DEQUEUED_ARRAY_SIZE 16 +#endif + +/* Size of the ring buffer storing incoming packets. + * Must be power of two */ +#ifdef TSCH_CONF_MAX_INCOMING_PACKETS +#define TSCH_MAX_INCOMING_PACKETS TSCH_CONF_MAX_INCOMING_PACKETS +#else +#define TSCH_MAX_INCOMING_PACKETS 4 +#endif + +/* Use SFD timestamp for synchronization? By default we merely rely on rtimer and busy wait + * until SFD is high, which we found to provide greater accuracy on JN516x and CC2420. + * Note: for association, however, we always use SFD timestamp to know the time of arrival + * of the EB (because we do not busy-wait for the whole scanning process) + * */ +#ifdef TSCH_CONF_RESYNC_WITH_SFD_TIMESTAMPS +#define TSCH_RESYNC_WITH_SFD_TIMESTAMPS TSCH_CONF_RESYNC_WITH_SFD_TIMESTAMPS +#else +#define TSCH_RESYNC_WITH_SFD_TIMESTAMPS 0 +#endif + +/*********** Callbacks *********/ + +/* Called by TSCH form interrupt after receiving a frame, enabled upper-layer to decide + * whether to ACK or NACK */ +#ifdef TSCH_CALLBACK_DO_NACK +int TSCH_CALLBACK_DO_NACK(struct tsch_link *link, linkaddr_t *src, linkaddr_t *dst); +#endif + +/************ Types ***********/ + +/* Stores data about an incoming packet */ +struct input_packet { + uint8_t payload[TSCH_PACKET_MAX_LEN]; /* Packet payload */ + struct asn_t rx_asn; /* ASN when the packet was received */ + int len; /* Packet len */ + uint16_t rssi; /* RSSI for this packet */ +}; + +/***** External Variables *****/ + +/* A ringbuf storing outgoing packets after they were dequeued. + * Will be processed layer by tsch_tx_process_pending */ +extern struct ringbufindex dequeued_ringbuf; +extern struct tsch_packet *dequeued_array[TSCH_DEQUEUED_ARRAY_SIZE]; +/* A ringbuf storing incoming packets. + * Will be processed layer by tsch_rx_process_pending */ +extern struct ringbufindex input_ringbuf; +extern struct input_packet input_array[TSCH_MAX_INCOMING_PACKETS]; + +/********** Functions *********/ + +/* Returns a 802.15.4 channel from an ASN and channel offset */ +uint8_t tsch_calculate_channel(struct asn_t *asn, uint8_t channel_offset); +/* Is TSCH locked? */ +int tsch_is_locked(void); +/* Lock TSCH (no link operation) */ +int tsch_get_lock(void); +/* Release TSCH lock */ +void tsch_release_lock(void); +/* Set global time before starting slot operation, + * with a rtimer time and an ASN */ +void tsch_slot_operation_sync(rtimer_clock_t next_slot_start, + struct asn_t *next_slot_asn); +/* Start actual slot operation */ +void tsch_slot_operation_start(void); + +#endif /* __TSCH_SLOT_OPERATION_H__ */ diff --git a/core/net/mac/tsch/tsch.c b/core/net/mac/tsch/tsch.c new file mode 100644 index 000000000..50a375595 --- /dev/null +++ b/core/net/mac/tsch/tsch.c @@ -0,0 +1,1029 @@ +/* + * Copyright (c) 2015, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +/** + * \file + * IEEE 802.15.4 TSCH MAC implementation. + * Does not use any RDC layer. Should be used with nordc. + * \author + * Simon Duquennoy + * Beshr Al Nahas + * + */ + +#include "contiki.h" +#include "dev/radio.h" +#include "net/netstack.h" +#include "net/packetbuf.h" +#include "net/queuebuf.h" +#include "net/mac/framer-802154.h" +#include "net/mac/tsch/tsch.h" +#include "net/mac/tsch/tsch-slot-operation.h" +#include "net/mac/tsch/tsch-queue.h" +#include "net/mac/tsch/tsch-private.h" +#include "net/mac/tsch/tsch-log.h" +#include "net/mac/tsch/tsch-packet.h" +#include "net/mac/tsch/tsch-security.h" +#include "lib/random.h" + +#if FRAME802154_VERSION < FRAME802154_IEEE802154E_2012 +#error TSCH: FRAME802154_VERSION must be at least FRAME802154_IEEE802154E_2012 +#endif + +#if TSCH_LOG_LEVEL >= 1 +#define DEBUG DEBUG_PRINT +#else /* TSCH_LOG_LEVEL */ +#define DEBUG DEBUG_NONE +#endif /* TSCH_LOG_LEVEL */ +#include "net/ip/uip-debug.h" + +/* Use to collect link statistics even on Keep-Alive, even though they were + * not sent from an upper layer and don't have a valid packet_sent callback */ +#ifndef TSCH_LINK_NEIGHBOR_CALLBACK +void uip_ds6_link_neighbor_callback(int status, int numtx); +#define TSCH_LINK_NEIGHBOR_CALLBACK(dest, status, num) uip_ds6_link_neighbor_callback(status, num) +#endif /* TSCH_LINK_NEIGHBOR_CALLBACK */ + +/* 802.15.4 duplicate frame detection */ +struct seqno { + linkaddr_t sender; + uint8_t seqno; +}; + +/* Size of the sequence number history */ +#ifdef NETSTACK_CONF_MAC_SEQNO_HISTORY +#define MAX_SEQNOS NETSTACK_CONF_MAC_SEQNO_HISTORY +#else /* NETSTACK_CONF_MAC_SEQNO_HISTORY */ +#define MAX_SEQNOS 8 +#endif /* NETSTACK_CONF_MAC_SEQNO_HISTORY */ + +/* Seqno history */ +static struct seqno received_seqnos[MAX_SEQNOS]; + +/* Let TSCH select a time source with no help of an upper layer. + * We do so using statistics from incoming EBs */ +#if TSCH_AUTOSELECT_TIME_SOURCE +int best_neighbor_eb_count; +struct eb_stat { + int rx_count; + int jp; +}; +NBR_TABLE(struct eb_stat, eb_stats); +#endif /* TSCH_AUTOSELECT_TIME_SOURCE */ + +/* TSCH channel hopping sequence */ +uint8_t tsch_hopping_sequence[TSCH_HOPPING_SEQUENCE_MAX_LEN]; +struct asn_divisor_t tsch_hopping_sequence_length; + +/* Default TSCH timeslot timing (in micro-second) */ +static const uint16_t tsch_default_timing_us[tsch_ts_elements_count] = { + TSCH_DEFAULT_TS_CCA_OFFSET, + TSCH_DEFAULT_TS_CCA, + TSCH_DEFAULT_TS_TX_OFFSET, + TSCH_DEFAULT_TS_RX_OFFSET, + TSCH_DEFAULT_TS_RX_ACK_DELAY, + TSCH_DEFAULT_TS_TX_ACK_DELAY, + TSCH_DEFAULT_TS_RX_WAIT, + TSCH_DEFAULT_TS_ACK_WAIT, + TSCH_DEFAULT_TS_RX_TX, + TSCH_DEFAULT_TS_MAX_ACK, + TSCH_DEFAULT_TS_MAX_TX, + TSCH_DEFAULT_TS_TIMESLOT_LENGTH, +}; +/* TSCH timeslot timing (in rtimer ticks) */ +rtimer_clock_t tsch_timing[tsch_ts_elements_count]; + +/* 802.15.4 broadcast MAC address */ +const linkaddr_t tsch_broadcast_address = { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } }; +/* Address used for the EB virtual neighbor queue */ +const linkaddr_t tsch_eb_address = { { 0, 0, 0, 0, 0, 0, 0, 0 } }; + +/* Is TSCH started? */ +int tsch_is_started = 0; +/* Has TSCH initialization failed? */ +int tsch_is_initialized = 0; +/* Are we coordinator of the TSCH network? */ +int tsch_is_coordinator = 0; +/* Are we associated to a TSCH network? */ +int tsch_is_associated = 0; +/* Is the PAN running link-layer security? */ +int tsch_is_pan_secured = TSCH_SECURITY_ENABLED; +/* The current Absolute Slot Number (ASN) */ +struct asn_t current_asn; +/* Device rank or join priority: + * For PAN coordinator: 0 -- lower is better */ +uint8_t tsch_join_priority; +/* The current TSCH sequence number, used for both data and EBs */ +static uint8_t tsch_packet_seqno = 0; +/* Current period for EB output */ +static clock_time_t tsch_current_eb_period; + +/* timer for sending keepalive messages */ +static struct ctimer keepalive_timer; + +/* TSCH processes and protothreads */ +PT_THREAD(tsch_scan(struct pt *pt)); +PROCESS(tsch_process, "TSCH: main process"); +PROCESS(tsch_send_eb_process, "TSCH: send EB process"); +PROCESS(tsch_pending_events_process, "TSCH: pending events process"); + +/* Other function prototypes */ +static void packet_input(void); + +/* Getters and setters */ + +/*---------------------------------------------------------------------------*/ +void +tsch_set_coordinator(int enable) +{ + tsch_is_coordinator = enable; + tsch_set_eb_period(TSCH_EB_PERIOD); +} +/*---------------------------------------------------------------------------*/ +void +tsch_set_pan_secured(int enable) +{ + tsch_is_pan_secured = TSCH_SECURITY_ENABLED && enable; +} +/*---------------------------------------------------------------------------*/ +void +tsch_set_join_priority(uint8_t jp) +{ + tsch_join_priority = jp; +} +/*---------------------------------------------------------------------------*/ +void +tsch_set_eb_period(uint32_t period) +{ + tsch_current_eb_period = period; +} +/*---------------------------------------------------------------------------*/ +static void +tsch_reset(void) +{ + int i; + frame802154_set_pan_id(0xffff); + /* First make sure pending packet callbacks are sent etc */ + process_post_synch(&tsch_pending_events_process, PROCESS_EVENT_POLL, NULL); + /* Empty all neighbor queues */ + /* tsch_queue_flush_all(); */ + /* Remove unused neighbors */ + tsch_queue_free_unused_neighbors(); + tsch_queue_update_time_source(NULL); + /* Initialize global variables */ + tsch_join_priority = 0xff; + ASN_INIT(current_asn, 0, 0); + current_link = NULL; + /* Reset timeslot timing to defaults */ + for(i = 0; i < tsch_ts_elements_count; i++) { + tsch_timing[i] = US_TO_RTIMERTICKS(tsch_default_timing_us[i]); + } +#ifdef TSCH_CALLBACK_LEAVING_NETWORK + TSCH_CALLBACK_LEAVING_NETWORK(); +#endif +#if TSCH_AUTOSELECT_TIME_SOURCE + best_neighbor_eb_count = 0; + nbr_table_register(eb_stats, NULL); + tsch_set_eb_period(TSCH_EB_PERIOD); +#endif +} + +/* TSCH keep-alive functions */ + +/*---------------------------------------------------------------------------*/ +/* Tx callback for keepalive messages */ +static void +keepalive_packet_sent(void *ptr, int status, int transmissions) +{ +#ifdef TSCH_LINK_NEIGHBOR_CALLBACK + TSCH_LINK_NEIGHBOR_CALLBACK(packetbuf_addr(PACKETBUF_ADDR_RECEIVER), status, transmissions); +#endif + PRINTF("TSCH: KA sent to %u, st %d-%d\n", + TSCH_LOG_ID_FROM_LINKADDR(packetbuf_addr(PACKETBUF_ADDR_RECEIVER)), status, transmissions); + tsch_schedule_keepalive(); +} +/*---------------------------------------------------------------------------*/ +/* Prepare and send a keepalive message */ +static void +keepalive_send() +{ + if(tsch_is_associated) { + struct tsch_neighbor *n = tsch_queue_get_time_source(); + /* Simply send an empty packet */ + packetbuf_clear(); + packetbuf_set_addr(PACKETBUF_ADDR_RECEIVER, &n->addr); + NETSTACK_LLSEC.send(keepalive_packet_sent, NULL); + PRINTF("TSCH: sending KA to %u\n", + TSCH_LOG_ID_FROM_LINKADDR(&n->addr)); + } +} +/*---------------------------------------------------------------------------*/ +/* Set ctimer to send a keepalive message after expiration of TSCH_KEEPALIVE_TIMEOUT */ +void +tsch_schedule_keepalive() +{ + /* Pick a delay in the range [TSCH_KEEPALIVE_TIMEOUT*0.9, TSCH_KEEPALIVE_TIMEOUT[ */ + if(!tsch_is_coordinator && tsch_is_associated) { + unsigned long delay = (TSCH_KEEPALIVE_TIMEOUT - TSCH_KEEPALIVE_TIMEOUT / 10) + + random_rand() % (TSCH_KEEPALIVE_TIMEOUT / 10); + ctimer_set(&keepalive_timer, delay, keepalive_send, NULL); + } +} +/*---------------------------------------------------------------------------*/ +static void +eb_input(struct input_packet *current_input) +{ + /* PRINTF("TSCH: EB received\n"); */ + frame802154_t frame; + /* Verify incoming EB (does its ASN match our Rx time?), + * and update our join priority. */ + struct ieee802154_ies eb_ies; + + if(tsch_packet_parse_eb(current_input->payload, current_input->len, + &frame, &eb_ies, NULL, 1)) { + /* PAN ID check and authentication done at rx time */ + +#if TSCH_AUTOSELECT_TIME_SOURCE + if(!tsch_is_coordinator) { + /* Maintain EB received counter for every neighbor */ + struct eb_stat *stat = (struct eb_stat *)nbr_table_get_from_lladdr(eb_stats, &frame.src_addr); + if(stat == NULL) { + stat = (struct eb_stat *)nbr_table_add_lladdr(eb_stats, &frame.src_addr); + } + if(stat != NULL) { + stat->rx_count++; + stat->jp = eb_ies.join_priority; + best_neighbor_eb_count = MAX(best_neighbor_eb_count, stat->rx_count); + } + /* Select best time source */ + struct eb_stat *best_stat = NULL; + stat = nbr_table_head(eb_stats); + while(stat != NULL) { + /* Is neighbor eligible as a time source? */ + if(stat->rx_count > best_neighbor_eb_count / 2) { + if(best_stat == NULL || + stat->jp < best_stat->jp) { + best_stat = stat; + } + } + stat = nbr_table_next(eb_stats, stat); + } + /* Update time source */ + if(best_stat != NULL) { + tsch_queue_update_time_source(nbr_table_get_lladdr(eb_stats, best_stat)); + tsch_join_priority = best_stat->jp + 1; + } + } +#endif + + struct tsch_neighbor *n = tsch_queue_get_time_source(); + /* Did the EB come from our time source? */ + if(n != NULL && linkaddr_cmp((linkaddr_t *)&frame.src_addr, &n->addr)) { + /* Check for ASN drift */ + int32_t asn_diff = ASN_DIFF(current_input->rx_asn, eb_ies.ie_asn); + if(asn_diff != 0) { + /* We disagree with our time source's ASN -- leave the network */ + PRINTF("TSCH:! ASN drifted by %ld, leaving the network\n", asn_diff); + tsch_disassociate(); + } + + if(eb_ies.ie_join_priority >= TSCH_MAX_JOIN_PRIORITY) { + /* Join priority unacceptable. Leave network. */ + PRINTF("TSCH:! EB JP too high %u, leaving the network\n", + eb_ies.ie_join_priority); + tsch_disassociate(); + } else { +#if TSCH_AUTOSELECT_TIME_SOURCE + /* Update join priority */ + if(tsch_join_priority != eb_ies.ie_join_priority + 1) { + PRINTF("TSCH: update JP from EB %u -> %u\n", + tsch_join_priority, eb_ies.ie_join_priority + 1); + tsch_join_priority = eb_ies.ie_join_priority + 1; + } +#endif /* TSCH_AUTOSELECT_TIME_SOURCE */ + } + } + } +} + +/*---------------------------------------------------------------------------*/ +/* Process pending input packet(s) */ +static void +tsch_rx_process_pending() +{ + int16_t input_index; + /* Loop on accessing (without removing) a pending input packet */ + while((input_index = ringbufindex_peek_get(&input_ringbuf)) != -1) { + struct input_packet *current_input = &input_array[input_index]; + frame802154_t frame; + uint8_t ret = frame802154_parse(current_input->payload, current_input->len, &frame); + int is_data = ret && frame.fcf.frame_type == FRAME802154_DATAFRAME; + int is_eb = ret + && frame.fcf.frame_version == FRAME802154_IEEE802154E_2012 + && frame.fcf.frame_type == FRAME802154_BEACONFRAME; + + if(is_data) { + /* Skip EBs and other control messages */ + /* Copy to packetbuf for processing */ + packetbuf_copyfrom(current_input->payload, current_input->len); + packetbuf_set_attr(PACKETBUF_ATTR_RSSI, current_input->rssi); + } + + /* Remove input from ringbuf */ + ringbufindex_get(&input_ringbuf); + + if(is_data) { + /* Pass to upper layers */ + packet_input(); + } else if(is_eb) { + eb_input(current_input); + } + } +} + +/*---------------------------------------------------------------------------*/ +/* Pass sent packets to upper layer */ +static void +tsch_tx_process_pending() +{ + int16_t dequeued_index; + /* Loop on accessing (without removing) a pending input packet */ + while((dequeued_index = ringbufindex_peek_get(&dequeued_ringbuf)) != -1) { + struct tsch_packet *p = dequeued_array[dequeued_index]; + /* Put packet into packetbuf for packet_sent callback */ + queuebuf_to_packetbuf(p->qb); + /* Call packet_sent callback */ + mac_call_sent_callback(p->sent, p->ptr, p->ret, p->transmissions); + /* Free packet queuebuf */ + tsch_queue_free_packet(p); + /* Free all unused neighbors */ + tsch_queue_free_unused_neighbors(); + /* Remove dequeued packet from ringbuf */ + ringbufindex_get(&dequeued_ringbuf); + } +} + +/* Setup TSCH as a coordinator */ +static void +tsch_start_coordinator(void) +{ + frame802154_set_pan_id(IEEE802154_PANID); + /* Initialize hopping sequence as default */ + memcpy(tsch_hopping_sequence, TSCH_DEFAULT_HOPPING_SEQUENCE, sizeof(TSCH_DEFAULT_HOPPING_SEQUENCE)); + ASN_DIVISOR_INIT(tsch_hopping_sequence_length, sizeof(TSCH_DEFAULT_HOPPING_SEQUENCE)); +#if TSCH_SCHEDULE_WITH_6TISCH_MINIMAL + tsch_schedule_create_minimal(); +#endif + + tsch_is_associated = 1; + tsch_join_priority = 0; + + PRINTF("TSCH: starting as coordinator, asn-%x.%lx\n", + current_asn.ms1b, current_asn.ls4b); + + /* Start only after some initial delay */ + tsch_slot_operation_sync( + RTIMER_NOW() + TSCH_CLOCK_TO_TICKS(CLOCK_SECOND / 10), + ¤t_asn); +} + +/* Leave the TSCH network */ +void +tsch_disassociate(void) +{ + if(tsch_is_associated == 1) { + tsch_is_associated = 0; + process_post(&tsch_process, PROCESS_EVENT_POLL, NULL); + PRINTF("TSCH: leaving the network\n"); + } +} + +/* Attempt to associate to a network form an incoming EB */ +static int +tsch_associate(const struct input_packet *input_eb, rtimer_clock_t timestamp) +{ + frame802154_t frame; + struct ieee802154_ies ies; + uint8_t hdrlen; + int i; + + if(input_eb == NULL || tsch_packet_parse_eb(input_eb->payload, input_eb->len, + &frame, &ies, &hdrlen, 0) == 0) { + PRINTF("TSCH:! failed to parse EB (len %u)\n", input_eb->len); + return 0; + } + + current_asn = ies.ie_asn; + tsch_join_priority = ies.ie_join_priority + 1; + +#if TSCH_JOIN_SECURED_ONLY + if(frame.fcf.security_enabled == 0) { + PRINTF("TSCH:! parse_eb: EB is not secured\n"); + return 0; + } +#endif /* TSCH_JOIN_SECURED_ONLY */ + +#if TSCH_SECURITY_ENABLED + if(!tsch_security_parse_frame(input_eb->payload, hdrlen, + input_eb->len - hdrlen - tsch_security_mic_len(&frame), + &frame, (linkaddr_t*)&frame.src_addr, ¤t_asn)) { + PRINTF("TSCH:! parse_eb: failed to authenticate\n"); + return 0; + } +#endif /* TSCH_SECURITY_ENABLED */ + +#if !TSCH_SECURITY_ENABLED + if(frame.fcf.security_enabled == 1) { + PRINTF("TSCH:! parse_eb: we do not support security, but EB is secured\n"); + return 0; + } +#endif /* !TSCH_SECURITY_ENABLED */ + +#if TSCH_JOIN_MY_PANID_ONLY + /* Check if the EB comes from the PAN ID we expect */ + if(frame.src_pid != IEEE802154_PANID) { + PRINTF("TSCH:! parse_eb: PAN ID %x != %x\n", frame.src_pid, IEEE802154_PANID); + return 0; + } +#endif /* TSCH_JOIN_MY_PANID_ONLY */ + + /* There was no join priority (or 0xff) in the EB, do not join */ + if(ies.ie_join_priority == 0xff) { + PRINTF("TSCH:! parse_eb: no join priority\n"); + return 0; + } + + /* TSCH timeslot timing */ + for(i = 0; i < tsch_ts_elements_count; i++) { + if(ies.ie_tsch_timeslot_id == 0) { + tsch_timing[i] = US_TO_RTIMERTICKS(tsch_default_timing_us[i]); + } else { + tsch_timing[i] = US_TO_RTIMERTICKS(ies.ie_tsch_timeslot[i]); + } + } + + /* TSCH hopping sequence */ + if(ies.ie_channel_hopping_sequence_id == 0) { + memcpy(tsch_hopping_sequence, TSCH_DEFAULT_HOPPING_SEQUENCE, sizeof(TSCH_DEFAULT_HOPPING_SEQUENCE)); + ASN_DIVISOR_INIT(tsch_hopping_sequence_length, sizeof(TSCH_DEFAULT_HOPPING_SEQUENCE)); + } else { + if(ies.ie_hopping_sequence_len <= sizeof(tsch_hopping_sequence)) { + memcpy(tsch_hopping_sequence, ies.ie_hopping_sequence_list, ies.ie_hopping_sequence_len); + ASN_DIVISOR_INIT(tsch_hopping_sequence_length, ies.ie_hopping_sequence_len); + } else { + PRINTF("TSCH:! parse_eb: hopping sequence too long (%u)\n", ies.ie_hopping_sequence_len); + return 0; + } + } + +#if TSCH_CHECK_TIME_AT_ASSOCIATION > 0 + /* Divide by 4k and multiply again to avoid integer overflow */ + uint32_t expected_asn = 4096 * TSCH_CLOCK_TO_SLOTS(clock_time() / 4096, tsch_timing_timeslot_length); /* Expected ASN based on our current time*/ + int32_t asn_threshold = TSCH_CHECK_TIME_AT_ASSOCIATION * 60ul * TSCH_CLOCK_TO_SLOTS(CLOCK_SECOND, tsch_timing_timeslot_length); + int32_t asn_diff = (int32_t)current_asn.ls4b - expected_asn; + if(asn_diff > asn_threshold) { + PRINTF("TSCH:! EB ASN rejected %lx %lx %ld\n", + current_asn.ls4b, expected_asn, asn_diff); + return 0; + } +#endif + +#if TSCH_INIT_SCHEDULE_FROM_EB + /* Create schedule */ + if(ies.ie_tsch_slotframe_and_link.num_slotframes == 0) { +#if TSCH_SCHEDULE_WITH_6TISCH_MINIMAL + PRINTF("TSCH: parse_eb: no schedule, setting up minimal schedule\n"); + tsch_schedule_create_minimal(); +#else + PRINTF("TSCH: parse_eb: no schedule\n"); +#endif + } else { + /* First, empty current schedule */ + tsch_schedule_remove_all_slotframes(); + /* We support only 0 or 1 slotframe in this IE */ + int num_links = ies.ie_tsch_slotframe_and_link.num_links; + if(num_links <= FRAME802154E_IE_MAX_LINKS) { + int i; + struct tsch_slotframe *sf = tsch_schedule_add_slotframe( + ies.ie_tsch_slotframe_and_link.slotframe_handle, + ies.ie_tsch_slotframe_and_link.slotframe_size); + for(i = 0; i < num_links; i++) { + tsch_schedule_add_link(sf, + ies.ie_tsch_slotframe_and_link.links[i].link_options, + LINK_TYPE_ADVERTISING, &tsch_broadcast_address, + ies.ie_tsch_slotframe_and_link.links[i].timeslot, ies.ie_tsch_slotframe_and_link.links[i].channel_offset); + } + } else { + PRINTF("TSCH:! parse_eb: too many links in schedule (%u)\n", num_links); + return 0; + } + } +#endif /* TSCH_INIT_SCHEDULE_FROM_EB */ + + if(tsch_join_priority < TSCH_MAX_JOIN_PRIORITY) { + struct tsch_neighbor *n; + + /* Add coordinator to list of neighbors, lock the entry */ + n = tsch_queue_add_nbr((linkaddr_t *)&frame.src_addr); + + if(n != NULL) { + tsch_queue_update_time_source((linkaddr_t *)&frame.src_addr); + +#ifdef TSCH_CALLBACK_JOINING_NETWORK + TSCH_CALLBACK_JOINING_NETWORK(); +#endif + + /* Set PANID */ + frame802154_set_pan_id(frame.src_pid); + + /* Synchronize on EB */ + tsch_slot_operation_sync(timestamp - tsch_timing[tsch_ts_tx_offset], ¤t_asn); + + /* Update global flags */ + tsch_is_associated = 1; + tsch_is_pan_secured = frame.fcf.security_enabled; + + /* Association done, schedule keepalive messages */ + tsch_schedule_keepalive(); + + PRINTF("TSCH: association done, sec %u, PAN ID %x, asn-%x.%lx, jp %u, timeslot id %u, hopping id %u, slotframe len %u with %u links, from ", + tsch_is_pan_secured, + frame.src_pid, + current_asn.ms1b, current_asn.ls4b, tsch_join_priority, + ies.ie_tsch_timeslot_id, + ies.ie_channel_hopping_sequence_id, + ies.ie_tsch_slotframe_and_link.slotframe_size, + ies.ie_tsch_slotframe_and_link.num_links); + PRINTLLADDR((const uip_lladdr_t *)&frame.src_addr); + PRINTF("\n"); + + return 1; + } + } + PRINTF("TSCH:! did not associate.\n"); + return 0; +} + +/* Processes and protothreads used by TSCH */ + +/*---------------------------------------------------------------------------*/ +/* Scanning protothread, called by tsch_process: + * Listen to different channels, and when receiving an EB, + * attempt to associate. + */ +PT_THREAD(tsch_scan(struct pt *pt)) +{ + PT_BEGIN(pt); + + static struct input_packet input_eb; + static struct etimer scan_timer; + + ASN_INIT(current_asn, 0, 0); + + etimer_set(&scan_timer, CLOCK_SECOND / TSCH_ASSOCIATION_POLL_FREQUENCY); + + while(!tsch_is_associated && !tsch_is_coordinator) { + /* Hop to any channel offset */ + static int current_channel = 0; + /* Time when we started scanning on current_channel */ + static clock_time_t current_channel_since = 0; + + /* We are not coordinator, try to associate */ + rtimer_clock_t t0; + int is_packet_pending = 0; + clock_time_t now_seconds = clock_seconds(); + + /* Switch to a (new) channel for scanning */ + if(current_channel == 0 || now_seconds != current_channel_since) { + /* Pick a channel at random in TSCH_JOIN_HOPPING_SEQUENCE */ + uint8_t scan_channel = TSCH_JOIN_HOPPING_SEQUENCE[ + random_rand() % sizeof(TSCH_JOIN_HOPPING_SEQUENCE)]; + if(current_channel != scan_channel) { + NETSTACK_RADIO.set_value(RADIO_PARAM_CHANNEL, scan_channel); + current_channel = scan_channel; + PRINTF("TSCH: scanning on channel %u\n", scan_channel); + } + current_channel_since = now_seconds; + } + + /* Turn radio on and wait for EB */ + NETSTACK_RADIO.on(); + + is_packet_pending = NETSTACK_RADIO.pending_packet(); + if(!is_packet_pending && NETSTACK_RADIO.receiving_packet()) { + /* If we are currently receiving a packet, wait until end of reception */ + t0 = RTIMER_NOW(); + BUSYWAIT_UNTIL_ABS((is_packet_pending = NETSTACK_RADIO.pending_packet()), t0, RTIMER_SECOND / 100); + } + + if(is_packet_pending) { + /* Save packet timestamp */ + NETSTACK_RADIO.get_object(RADIO_PARAM_LAST_PACKET_TIMESTAMP, &t0, sizeof(rtimer_clock_t)); + + /* Read packet */ + input_eb.len = NETSTACK_RADIO.read(input_eb.payload, TSCH_PACKET_MAX_LEN); + + /* Parse EB and attempt to associate */ + PRINTF("TSCH: association: received packet (%u bytes) on channel %u\n", input_eb.len, current_channel); + + tsch_associate(&input_eb, t0); + } + + if(tsch_is_associated) { + /* End of association, turn the radio off */ + NETSTACK_RADIO.off(); + } else if(!tsch_is_coordinator) { + /* Go back to scanning */ + etimer_reset(&scan_timer); + PT_WAIT_UNTIL(pt, etimer_expired(&scan_timer)); + } + } + + PT_END(pt); +} + +/*---------------------------------------------------------------------------*/ +/* The main TSCH process */ +PROCESS_THREAD(tsch_process, ev, data) +{ + static struct pt scan_pt; + + PROCESS_BEGIN(); + + while(1) { + + while(!tsch_is_associated) { + if(tsch_is_coordinator) { + /* We are coordinator, start operating now */ + tsch_start_coordinator(); + } else { + /* Start scanning, will attempt to join when receiving an EB */ + PROCESS_PT_SPAWN(&scan_pt, tsch_scan(&scan_pt)); + } + } + + /* We are part of a TSCH network, start slot operation */ + tsch_slot_operation_start(); + + /* Yield our main process. Slot operation will re-schedule itself + * as long as we are associated */ + PROCESS_YIELD_UNTIL(!tsch_is_associated); + + /* Will need to re-synchronize */ + tsch_reset(); + } + + PROCESS_END(); +} + +/*---------------------------------------------------------------------------*/ +/* A periodic process to send TSCH Enhanced Beacons (EB) */ +PROCESS_THREAD(tsch_send_eb_process, ev, data) +{ + static struct etimer eb_timer; + + PROCESS_BEGIN(); + + /* Wait until association */ + etimer_set(&eb_timer, CLOCK_SECOND / 10); + while(!tsch_is_associated) { + PROCESS_WAIT_UNTIL(etimer_expired(&eb_timer)); + etimer_reset(&eb_timer); + } + + /* Set an initial delay except for coordinator, which should send an EB asap */ + if(!tsch_is_coordinator) { + etimer_set(&eb_timer, random_rand() % TSCH_EB_PERIOD); + PROCESS_WAIT_UNTIL(etimer_expired(&eb_timer)); + } + + while(1) { + unsigned long delay; + + if(tsch_is_associated && tsch_current_eb_period > 0) { + /* Enqueue EB only if there isn't already one in queue */ + if(tsch_queue_packet_count(&tsch_eb_address) == 0) { + int eb_len; + uint8_t hdr_len = 0; + uint8_t tsch_sync_ie_offset; + /* Prepare the EB packet and schedule it to be sent */ + packetbuf_clear(); + /* We don't use seqno 0 */ + if(++tsch_packet_seqno == 0) { + tsch_packet_seqno++; + } + packetbuf_set_attr(PACKETBUF_ATTR_FRAME_TYPE, FRAME802154_BEACONFRAME); + packetbuf_set_attr(PACKETBUF_ATTR_MAC_SEQNO, tsch_packet_seqno); +#if TSCH_SECURITY_ENABLED + if(tsch_is_pan_secured) { + /* Set security level, key id and index */ + packetbuf_set_attr(PACKETBUF_ATTR_SECURITY_LEVEL, TSCH_SECURITY_KEY_SEC_LEVEL_EB); + packetbuf_set_attr(PACKETBUF_ATTR_KEY_ID_MODE, FRAME802154_1_BYTE_KEY_ID_MODE); /* Use 1-byte key index */ + packetbuf_set_attr(PACKETBUF_ATTR_KEY_INDEX, TSCH_SECURITY_KEY_INDEX_EB); + } +#endif /* TSCH_SECURITY_ENABLED */ + eb_len = tsch_packet_create_eb(packetbuf_dataptr(), PACKETBUF_SIZE, + tsch_packet_seqno, &hdr_len, &tsch_sync_ie_offset); + if(eb_len != 0) { + struct tsch_packet *p; + packetbuf_set_datalen(eb_len); + /* Enqueue EB packet */ + if(!(p = tsch_queue_add_packet(&tsch_eb_address, NULL, NULL))) { + PRINTF("TSCH:! could not enqueue EB packet\n"); + } else { + PRINTF("TSCH: enqueue EB packet %u %u\n", eb_len, hdr_len); + p->tsch_sync_ie_offset = tsch_sync_ie_offset; + p->header_len = hdr_len; + } + } + } + } + if(tsch_current_eb_period > 0) { + /* Next EB transmission with a random delay + * within [tsch_current_eb_period*0.75, tsch_current_eb_period[ */ + delay = (tsch_current_eb_period - tsch_current_eb_period / 4) + + random_rand() % (tsch_current_eb_period / 4); + } else { + delay = TSCH_EB_PERIOD; + } + etimer_set(&eb_timer, delay); + PROCESS_WAIT_UNTIL(etimer_expired(&eb_timer)); + } + PROCESS_END(); +} + +/*---------------------------------------------------------------------------*/ +/* A process that is polled from interrupt and calls tx/rx input + * callbacks, outputs pending logs. */ +PROCESS_THREAD(tsch_pending_events_process, ev, data) +{ + PROCESS_BEGIN(); + while(1) { + PROCESS_YIELD_UNTIL(ev == PROCESS_EVENT_POLL); + tsch_rx_process_pending(); + tsch_tx_process_pending(); + tsch_log_process_pending(); + } + PROCESS_END(); +} + +/* Functions from the Contiki MAC layer driver interface */ + +/*---------------------------------------------------------------------------*/ +static void +tsch_init(void) +{ + radio_value_t radio_rx_mode; + radio_value_t radio_tx_mode; + rtimer_clock_t t; + + /* Radio Rx mode */ + if(NETSTACK_RADIO.get_value(RADIO_PARAM_RX_MODE, &radio_rx_mode) != RADIO_RESULT_OK) { + printf("TSCH:! radio does not support getting RADIO_PARAM_RX_MODE. Abort init.\n"); + return; + } + /* Disable radio in frame filtering */ + radio_rx_mode &= ~RADIO_RX_MODE_ADDRESS_FILTER; + /* Unset autoack */ + radio_rx_mode &= ~RADIO_RX_MODE_AUTOACK; + /* Set radio in poll mode */ + radio_rx_mode |= RADIO_RX_MODE_POLL_MODE; + if(NETSTACK_RADIO.set_value(RADIO_PARAM_RX_MODE, radio_rx_mode) != RADIO_RESULT_OK) { + printf("TSCH:! radio does not support setting required RADIO_PARAM_RX_MODE. Abort init.\n"); + return; + } + + /* Radio Tx mode */ + if(NETSTACK_RADIO.get_value(RADIO_PARAM_TX_MODE, &radio_tx_mode) != RADIO_RESULT_OK) { + printf("TSCH:! radio does not support getting RADIO_PARAM_TX_MODE. Abort init.\n"); + return; + } + /* Unset CCA */ + radio_tx_mode &= ~RADIO_TX_MODE_SEND_ON_CCA; + if(NETSTACK_RADIO.set_value(RADIO_PARAM_TX_MODE, radio_tx_mode) != RADIO_RESULT_OK) { + printf("TSCH:! radio does not support setting required RADIO_PARAM_TX_MODE. Abort init.\n"); + return; + } + /* Test setting channel */ + if(NETSTACK_RADIO.set_value(RADIO_PARAM_CHANNEL, TSCH_DEFAULT_HOPPING_SEQUENCE[0]) != RADIO_RESULT_OK) { + printf("TSCH:! radio does not support setting channel. Abort init.\n"); + return; + } + /* Test getting timestamp */ + if(NETSTACK_RADIO.get_object(RADIO_PARAM_LAST_PACKET_TIMESTAMP, &t, sizeof(rtimer_clock_t)) != RADIO_RESULT_OK) { + printf("TSCH:! radio does not support getting last packet timestamp. Abort init.\n"); + return; + } + /* Check max hopping sequence length vs default sequence length */ + if(TSCH_HOPPING_SEQUENCE_MAX_LEN < sizeof(TSCH_DEFAULT_HOPPING_SEQUENCE)) { + printf("TSCH:! TSCH_HOPPING_SEQUENCE_MAX_LEN < sizeof(TSCH_DEFAULT_HOPPING_SEQUENCE). Abort init.\n"); + } + + /* Init TSCH sub-modules */ + tsch_reset(); + tsch_queue_init(); + tsch_schedule_init(); + tsch_log_init(); + ringbufindex_init(&input_ringbuf, TSCH_MAX_INCOMING_PACKETS); + ringbufindex_init(&dequeued_ringbuf, TSCH_DEQUEUED_ARRAY_SIZE); + + tsch_is_initialized = 1; + +#if TSCH_AUTOSTART + /* Start TSCH operation. + * If TSCH_AUTOSTART is not set, one needs to call NETSTACK_MAC.on() to start TSCH. */ + NETSTACK_MAC.on(); +#endif /* TSCH_AUTOSTART */ +} +/*---------------------------------------------------------------------------*/ +/* Function send for TSCH-MAC, puts the packet in packetbuf in the MAC queue */ +static void +send_packet(mac_callback_t sent, void *ptr) +{ + int ret = MAC_TX_DEFERRED; + int packet_count_before; + int hdr_len = 0; + const linkaddr_t *addr = packetbuf_addr(PACKETBUF_ADDR_RECEIVER); + + if(!tsch_is_associated) { + if(!tsch_is_initialized) { + PRINTF("TSCH:! not initialized (see earlier logs), drop outgoing packet\n"); + } else { + PRINTF("TSCH:! not associated, drop outgoing packet\n"); + } + ret = MAC_TX_ERR; + mac_call_sent_callback(sent, ptr, ret, 1); + return; + } + + /* PACKETBUF_ATTR_MAC_SEQNO cannot be zero, due to a pecuilarity + in framer-802154.c. */ + if(++tsch_packet_seqno == 0) { + tsch_packet_seqno++; + } + + /* Ask for ACK if we are sending anything other than broadcast */ + if(!linkaddr_cmp(addr, &linkaddr_null)) { + packetbuf_set_attr(PACKETBUF_ATTR_MAC_ACK, 1); + } else { + /* Broadcast packets shall be added to broadcast queue + * The broadcast address in Contiki is linkaddr_null which is equal + * to tsch_eb_address */ + addr = &tsch_broadcast_address; + } + + packetbuf_set_attr(PACKETBUF_ATTR_FRAME_TYPE, FRAME802154_DATAFRAME); + packetbuf_set_attr(PACKETBUF_ATTR_MAC_SEQNO, tsch_packet_seqno); + +#if TSCH_SECURITY_ENABLED + if(tsch_is_pan_secured) { + /* Set security level, key id and index */ + packetbuf_set_attr(PACKETBUF_ATTR_SECURITY_LEVEL, TSCH_SECURITY_KEY_SEC_LEVEL_OTHER); + packetbuf_set_attr(PACKETBUF_ATTR_KEY_ID_MODE, FRAME802154_1_BYTE_KEY_ID_MODE); /* Use 1-byte key index */ + packetbuf_set_attr(PACKETBUF_ATTR_KEY_INDEX, TSCH_SECURITY_KEY_INDEX_OTHER); + } +#endif /* TSCH_SECURITY_ENABLED */ + + packet_count_before = tsch_queue_packet_count(addr); + + if((hdr_len = NETSTACK_FRAMER.create_and_secure()) < 0) { + PRINTF("TSCH:! can't send packet due to framer error\n"); + ret = MAC_TX_ERR; + } else { + struct tsch_packet *p; + /* Enqueue packet */ + p = tsch_queue_add_packet(addr, sent, ptr); + if(p == NULL) { + PRINTF("TSCH:! can't send packet !tsch_queue_add_packet\n"); + ret = MAC_TX_ERR; + } else { + p->header_len = hdr_len; + PRINTF("TSCH: send packet to %u with seqno %u, queue %u %u, len %u %u\n", + TSCH_LOG_ID_FROM_LINKADDR(addr), tsch_packet_seqno, + packet_count_before, + tsch_queue_packet_count(addr), + p->header_len, + queuebuf_datalen(p->qb)); + } + } + if(ret != MAC_TX_DEFERRED) { + mac_call_sent_callback(sent, ptr, ret, 1); + } +} +/*---------------------------------------------------------------------------*/ +static void +packet_input(void) +{ + int frame_parsed = 1; + + frame_parsed = NETSTACK_FRAMER.parse(); + + if(frame_parsed < 0) { + PRINTF("TSCH:! failed to parse %u\n", packetbuf_datalen()); + } else { + int duplicate = 0; + + /* Seqno of 0xffff means no seqno */ + if(packetbuf_attr(PACKETBUF_ATTR_MAC_SEQNO) != 0xffff) { + /* Check for duplicate packet by comparing the sequence number + of the incoming packet with the last few ones we saw. */ + int i; + for(i = 0; i < MAX_SEQNOS; ++i) { + if(packetbuf_attr(PACKETBUF_ATTR_MAC_SEQNO) == received_seqnos[i].seqno && + linkaddr_cmp(packetbuf_addr(PACKETBUF_ADDR_SENDER), + &received_seqnos[i].sender)) { + /* Drop the packet. */ + PRINTF("TSCH:! drop dup ll from %u seqno %u\n", + TSCH_LOG_ID_FROM_LINKADDR(packetbuf_addr(PACKETBUF_ADDR_SENDER)), + packetbuf_attr(PACKETBUF_ATTR_MAC_SEQNO)); + duplicate = 1; + } + } + if(!duplicate) { + for(i = MAX_SEQNOS - 1; i > 0; --i) { + memcpy(&received_seqnos[i], &received_seqnos[i - 1], + sizeof(struct seqno)); + } + received_seqnos[0].seqno = packetbuf_attr(PACKETBUF_ATTR_MAC_SEQNO); + linkaddr_copy(&received_seqnos[0].sender, + packetbuf_addr(PACKETBUF_ADDR_SENDER)); + } + } + + if(!duplicate) { + PRINTF("TSCH: received from %u with seqno %u\n", + TSCH_LOG_ID_FROM_LINKADDR(packetbuf_addr(PACKETBUF_ADDR_SENDER)), + packetbuf_attr(PACKETBUF_ATTR_MAC_SEQNO)); + NETSTACK_LLSEC.input(); + } + } +} +/*---------------------------------------------------------------------------*/ +static int +turn_on(void) +{ + if(tsch_is_initialized == 1 && tsch_is_started == 0) { + tsch_is_started = 1; + /* Process tx/rx callback and log messages whenever polled */ + process_start(&tsch_pending_events_process, NULL); + /* periodically send TSCH EBs */ + process_start(&tsch_send_eb_process, NULL); + /* try to associate to a network or start one if setup as coordinator */ + process_start(&tsch_process, NULL); + PRINTF("TSCH: starting as %s\n", tsch_is_coordinator ? "coordinator" : "node"); + return 1; + } + return 0; +} +/*---------------------------------------------------------------------------*/ +static int +turn_off(int keep_radio_on) +{ + return 1; +} +/*---------------------------------------------------------------------------*/ +static unsigned short +channel_check_interval(void) +{ + return 0; +} +/*---------------------------------------------------------------------------*/ +const struct mac_driver tschmac_driver = { + "TSCH", + tsch_init, + send_packet, + packet_input, + turn_on, + turn_off, + channel_check_interval, +}; +/*---------------------------------------------------------------------------*/ diff --git a/core/net/mac/tsch/tsch.h b/core/net/mac/tsch/tsch.h new file mode 100644 index 000000000..31b5d41f9 --- /dev/null +++ b/core/net/mac/tsch/tsch.h @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2015, SICS Swedish ICT. + * All rights reserved. + * + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +#ifndef __TSCH_H__ +#define __TSCH_H__ + +/********** Includes **********/ + +#include "contiki.h" +#include "net/mac/mac.h" +#include "net/mac/tsch/tsch-security.h" + +/******** Configuration *******/ + +/* Max time before sending a unicast keep-alive message to the time source */ +#ifdef TSCH_CONF_KEEPALIVE_TIMEOUT +#define TSCH_KEEPALIVE_TIMEOUT TSCH_CONF_KEEPALIVE_TIMEOUT +#else +/* Time to desynch assuming a drift of 40 PPM (80 PPM between two nodes) and guard time of +/-1ms: 12.5s. */ +#define TSCH_KEEPALIVE_TIMEOUT (12 * CLOCK_SECOND) +#endif + +/* Max time without synchronization before leaving the PAN */ +#ifdef TSCH_CONF_DESYNC_THRESHOLD +#define TSCH_DESYNC_THRESHOLD TSCH_CONF_DESYNC_THRESHOLD +#else +#define TSCH_DESYNC_THRESHOLD (4 * TSCH_KEEPALIVE_TIMEOUT) +#endif + +/* Period between two consecutive EBs */ +#ifdef TSCH_CONF_EB_PERIOD +#define TSCH_EB_PERIOD TSCH_CONF_EB_PERIOD +#else +#define TSCH_EB_PERIOD (4 * CLOCK_SECOND) +#endif + +/* Max acceptable join priority */ +#ifdef TSCH_CONF_MAX_JOIN_PRIORITY +#define TSCH_MAX_JOIN_PRIORITY TSCH_CONF_MAX_JOIN_PRIORITY +#else +#define TSCH_MAX_JOIN_PRIORITY 32 +#endif + +/* Start TSCH automatically after init? If not, the upper layers + * must call NETSTACK_MAC.on() to start it. Useful when the + * application needs to control when the nodes are to start + * scanning or advertising.*/ +#ifdef TSCH_CONF_AUTOSTART +#define TSCH_AUTOSTART TSCH_CONF_AUTOSTART +#else +#define TSCH_AUTOSTART 1 +#endif + +/* Join only secured networks? (discard EBs with security disabled) */ +#ifdef TSCH_CONF_JOIN_SECURED_ONLY +#define TSCH_JOIN_SECURED_ONLY TSCH_CONF_JOIN_SECURED_ONLY +#else +/* By default, set if TSCH_SECURITY_ENABLED is also non-zero */ +#define TSCH_JOIN_SECURED_ONLY TSCH_SECURITY_ENABLED +#endif + +/* By default, join any PAN ID. Otherwise, wait for an EB from IEEE802154_PANID */ +#ifdef TSCH_CONF_JOIN_MY_PANID_ONLY +#define TSCH_JOIN_MY_PANID_ONLY TSCH_CONF_JOIN_MY_PANID_ONLY +#else +#define TSCH_JOIN_MY_PANID_ONLY 0 +#endif + +/* The radio polling frequency (in Hz) during association process */ +#ifdef TSCH_CONF_ASSOCIATION_POLL_FREQUENCY +#define TSCH_ASSOCIATION_POLL_FREQUENCY TSCH_CONF_ASSOCIATION_POLL_FREQUENCY +#else +#define TSCH_ASSOCIATION_POLL_FREQUENCY 100 +#endif + +/* When associating, check ASN against our own uptime (time in minutes).. + * Useful to force joining only with nodes started roughly at the same time. + * Set to the max number of minutes acceptable. */ +#ifdef TSCH_CONF_CHECK_TIME_AT_ASSOCIATION +#define TSCH_CHECK_TIME_AT_ASSOCIATION TSCH_CONF_CHECK_TIME_AT_ASSOCIATION +#else +#define TSCH_CHECK_TIME_AT_ASSOCIATION 0 +#endif + +/* By default: initialize schedule from EB when associating, using the + * slotframe and links Information Element */ +#ifdef TSCH_CONF_INIT_SCHEDULE_FROM_EB +#define TSCH_INIT_SCHEDULE_FROM_EB TSCH_CONF_INIT_SCHEDULE_FROM_EB +#else +#define TSCH_INIT_SCHEDULE_FROM_EB 1 +#endif + +/* An ad-hoc mechanism to have TSCH select its time source without the + * help of an upper-layer, simply by collecting statistics on received + * EBs and their join priority. Disabled by default as we recomment + * mapping the time source on the RPL preferred parent + * (via tsch_rpl_callback_parent_switch) */ +#ifdef TSCH_CONF_AUTOSELECT_TIME_SOURCE +#define TSCH_AUTOSELECT_TIME_SOURCE TSCH_CONF_AUTOSELECT_TIME_SOURCE +#else +#define TSCH_AUTOSELECT_TIME_SOURCE 0 +#endif /* TSCH_CONF_EB_AUTOSELECT */ + +/*********** Callbacks *********/ + +/* Called by TSCH when joining a network */ +#ifdef TSCH_CALLBACK_JOINING_NETWORK +void TSCH_CALLBACK_JOINING_NETWORK(); +#endif + +/* Called by TSCH when leaving a network */ +#ifdef TSCH_CALLBACK_LEAVING_NETWORK +void TSCH_CALLBACK_LEAVING_NETWORK(); +#endif + +/***** External Variables *****/ + +/* Are we coordinator of the TSCH network? */ +extern int tsch_is_coordinator; +/* Are we associated to a TSCH network? */ +extern int tsch_is_associated; +/* Is the PAN running link-layer security? */ +extern int tsch_is_pan_secured; +/* The TSCH MAC driver */ +extern const struct mac_driver tschmac_driver; + +/********** Functions *********/ + +/* The the TSCH join priority */ +void tsch_set_join_priority(uint8_t jp); +/* The the period at which EBs are sent */ +void tsch_set_eb_period(uint32_t period); +/* Set the node as PAN coordinator */ +void tsch_set_coordinator(int enable); +/* Set the pan as secured or not */ +void tsch_set_pan_secured(int enable); + +#endif /* __TSCH_H__ */ diff --git a/core/net/packetbuf.h b/core/net/packetbuf.h index 3c1839bf1..12b0d9dce 100644 --- a/core/net/packetbuf.h +++ b/core/net/packetbuf.h @@ -55,6 +55,7 @@ #include "contiki-conf.h" #include "net/linkaddr.h" #include "net/llsec/llsec802154.h" +#include "net/mac/tsch/tsch-conf.h" /** * \brief The size of the packetbuf, in bytes @@ -313,6 +314,10 @@ enum { PACKETBUF_ATTR_MAC_SEQNO, PACKETBUF_ATTR_MAC_ACK, PACKETBUF_ATTR_IS_CREATED_AND_SECURED, +#if TSCH_WITH_LINK_SELECTOR + PACKETBUF_ATTR_TSCH_SLOTFRAME, + PACKETBUF_ATTR_TSCH_TIMESLOT, +#endif /* TSCH_WITH_LINK_SELECTOR */ /* Scope 1 attributes: used between two neighbors only. */ #if PACKETBUF_WITH_PACKET_TYPE @@ -329,14 +334,16 @@ enum { PACKETBUF_ATTR_FRAME_TYPE, #if LLSEC802154_SECURITY_LEVEL PACKETBUF_ATTR_SECURITY_LEVEL, +#endif /* LLSEC802154_SECURITY_LEVEL */ +#if LLSEC802154_USES_FRAME_COUNTER PACKETBUF_ATTR_FRAME_COUNTER_BYTES_0_1, PACKETBUF_ATTR_FRAME_COUNTER_BYTES_2_3, +#endif /* LLSEC802154_USES_FRAME_COUNTER */ #if LLSEC802154_USES_EXPLICIT_KEYS PACKETBUF_ATTR_KEY_ID_MODE, PACKETBUF_ATTR_KEY_INDEX, PACKETBUF_ATTR_KEY_SOURCE_BYTES_0_1, #endif /* LLSEC802154_USES_EXPLICIT_KEYS */ -#endif /* LLSEC802154_SECURITY_LEVEL */ /* Scope 2 attributes: used between end-to-end nodes. */ #if NETSTACK_CONF_WITH_RIME @@ -362,10 +369,15 @@ enum { #if !LLSEC802154_SECURITY_LEVEL enum { PACKETBUF_ATTR_SECURITY_LEVEL, +}; +#endif /* LLSEC802154_SECURITY_LEVEL */ + +#if !LLSEC802154_USES_FRAME_COUNTER +enum { PACKETBUF_ATTR_FRAME_COUNTER_BYTES_0_1, PACKETBUF_ATTR_FRAME_COUNTER_BYTES_2_3 }; -#endif /* LLSEC802154_SECURITY_LEVEL */ +#endif /* LLSEC802154_USES_FRAME_COUNTER */ /* Define surrogates when not using explicit keys */ #if !LLSEC802154_USES_EXPLICIT_KEYS