diff --git a/Makefile.dep b/Makefile.dep index 6ebede633f..5e74feb159 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -43,6 +43,15 @@ ifneq (,$(filter gnrc_gomach,$(USEMODULE))) FEATURES_REQUIRED += periph_rtt endif +ifneq (,$(filter gnrc_lorawan,$(USEMODULE))) + USEMODULE += xtimer + USEMODULE += random + USEMODULE += hashes + USEMODULE += crypto + USEMODULE += netdev_layer + USEMODULE += gnrc_neterr +endif + ifneq (,$(filter nhdp,$(USEMODULE))) USEMODULE += sock_udp USEMODULE += xtimer @@ -158,6 +167,9 @@ ifneq (,$(filter gnrc_netif,$(USEMODULE))) ifneq (,$(filter netdev_eth,$(USEMODULE))) USEMODULE += gnrc_netif_ethernet endif + ifneq (,$(filter gnrc_lorawan,$(USEMODULE))) + USEMODULE += gnrc_netif_lorawan + endif endif ifneq (,$(filter ieee802154 nrfmin esp_now cc110x gnrc_sixloenc,$(USEMODULE))) diff --git a/drivers/include/net/netdev.h b/drivers/include/net/netdev.h index ab88ccca37..e0da1304ee 100644 --- a/drivers/include/net/netdev.h +++ b/drivers/include/net/netdev.h @@ -246,6 +246,12 @@ typedef enum { NETDEV_EVENT_CRC_ERROR, /**< wrong CRC */ NETDEV_EVENT_FHSS_CHANGE_CHANNEL, /**< channel changed */ NETDEV_EVENT_CAD_DONE, /**< channel activity detection done */ + NETDEV_EVENT_MLME_CONFIRM, /**< MAC MLME confirm event */ + NETDEV_EVENT_MLME_INDICATION, /**< MAC MLME indication event */ + NETDEV_EVENT_MCPS_CONFIRM, /**< MAC MCPS confirm event */ + NETDEV_EVENT_MCPS_INDICATION, /**< MAC MCPS indication event */ + NETDEV_EVENT_MLME_GET_BUFFER, /**< MAC layer requests MLME buffer */ + NETDEV_EVENT_MCPS_GET_BUFFER, /**< MAC layer requests MCPS buffer */ /* expand this list if needed */ } netdev_event_t; diff --git a/examples/gnrc_lorawan/Makefile b/examples/gnrc_lorawan/Makefile new file mode 100644 index 0000000000..6834583cd0 --- /dev/null +++ b/examples/gnrc_lorawan/Makefile @@ -0,0 +1,60 @@ +# name of your application +APPLICATION = gnrc_lorawan + +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += gnrc_netdev_default +USEMODULE += auto_init_gnrc_netif +USEMODULE += gnrc_lorawan +USEMODULE += gnrc_pktdump + +BOARD ?= b-l072z-lrwan1 +RIOTBASE ?= ../../ + +# Turn on developer helpers +DEVELHELP ?= 1 + +# use SX1276 by default +DRIVER ?= sx1276 + +USEMODULE += $(DRIVER) + +# Required for the cipher module */ +CFLAGS += -DCRYPTO_AES +# +# We can reduce the size of the packet buffer for LoRaWAN, since there's no IP +# support. This will reduce RAM consumption. +CFLAGS += -DGNRC_PKTBUF_SIZE=512 + + +########################### COMPILE TIME CONFIGURATION ######################## +# NOTE: The following options can be configured on runtime as well using +# `ifconfig` + +# OTAA compile time configuration keys +CFLAGS += -DLORAMAC_APP_KEY_DEFAULT=\{0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\} +CFLAGS += -DLORAMAC_APP_EUI_DEFAULT=\{0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\} +CFLAGS += -DLORAMAC_DEV_EUI_DEFAULT=\{0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\} + +# Uncomment and replace with proper keys for joining with ABP +# NOTE: This values will be overriten in case of OTAA. +#CFLAGS += -DLORAMAC_DEV_ADDR_DEFAULT=\{0x00\,0x00\,0x00\,0x00\} +#CFLAGS += -DLORAMAC_NWK_SKEY_DEFAULT=\{0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\} +#CFLAGS += -DLORAMAC_APP_SKEY_DEFAULT=\{0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\} + +# Comment/uncomment as necessary +CFLAGS += -DLORAMAC_DEFAULT_JOIN_PROCEDURE=LORAMAC_JOIN_OTAA +#CFLAGS += -DLORAMAC_DEFAULT_JOIN_PROCEDURE=LORAMAC_JOIN_ABP + +# Uncomment to set the highest DR for the EU868 in order to maximize throughput. +# If uncommented, the default value (DR0) is used. +# Note this value is also used for the OTAA. +#CFLAGS += -DLORAMAC_DEFAULT_DR=LORAMAC_DR_5 + +# Set the default RX2 datarate to DR3 (used by The Things Network) +CFLAGS += -DLORAMAC_DEFAULT_RX2_DR=LORAMAC_DR_3 + +# Set default messages to unconfirmable +CFLAGS += -DLORAMAC_DEFAULT_TX_MODE=LORAMAC_TX_CNF + +include $(RIOTBASE)/Makefile.include diff --git a/examples/gnrc_lorawan/Makefile.ci b/examples/gnrc_lorawan/Makefile.ci new file mode 100644 index 0000000000..6deb8724c9 --- /dev/null +++ b/examples/gnrc_lorawan/Makefile.ci @@ -0,0 +1,20 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega328p \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-l031k6 \ + stm32f030f4-demo \ + stm32f0discovery \ + msb-430 \ + msb-430h \ + telosb \ + waspmote-pro \ + wsn430-v1_3b \ + wsn430-v1_4 \ + z1 \ +# diff --git a/examples/gnrc_lorawan/README.md b/examples/gnrc_lorawan/README.md new file mode 100644 index 0000000000..1ce7dfb2d9 --- /dev/null +++ b/examples/gnrc_lorawan/README.md @@ -0,0 +1,145 @@ +GNRC LoRaWAN application +============================= + +This application is a showcase for testing GNRC LoRaWAN stack. You should be +able to send and receive LoRaWAN packets and perform basic LoRaWAN commands +(Link Check). + +The MAC layers still doesn't implement any duty cycle restriction mechanism. +However, it respects the retransmission procedure. + +Only Class A and EU868 region are supported so far. + +Usage +===== + +It's necessary to join the LoRaWAN network either via OTAA or ABP. +All keys, addresses and EUIs are in network endian (big endian). + +## OTAA + +Join by OTAA is set by default. +Set the Application Key, Device EUI and Application EUI using ifconfig. Assuming +the interface pid is 3: + +``` +ifconfig 3 set deveui AAAAAAAAAAAAAAAA +ifconfig 3 set appeui BBBBBBBBBBBBBBBB +ifconfig 3 set appkey CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +ifconfig 3 up +``` + +Wait for 5-6 seconds. Type `ifconfig`. The link status should be `up`: + +``` +Iface 3 HWaddr: 26:01:27:2F Frequency: 868500000Hz BW: 125kHz SF: 7 + CR: 4/5 Link: up + TX-Power: 14dBm State: SLEEP Demod margin.: 0 Num gateways.: 0 + IQ_INVERT + RX_SINGLE OTAA + +``` + +## ABP + +Deactivate OTAA using ifconfig and set the AppSKey, NwkSKey and DevAddr; + +``` +ifconfig 3 -otaa +ifconfig 3 set appskey DDDDDDDDDDDDDDDD +ifconfig 3 set nwkskey EEEEEEEEEEEEEEEE +ifconfig 3 set addr FFFFFFFF +ifconfig 3 up +``` + +The join by ABP occurs immediately. + +Alternatively all keys can be set using CFLAGS so it's only required to +select join mode and type `ifconfig up`. + +E.g in the application Makefile: + +``` +CFLAGS += -DLORAMAC_DEV_EUI_DEFAULT=\{0xAA\,0xAA\,0xAA\,0xAA\,0xAA\,0xAA\,0xAA\,0xAA\} +CFLAGS += -DLORAMAC_APP_EUI_DEFAULT=\{0xBB\,0xBB\,0xBB\,0xBB\,0xBB\,0xBB\,0xBB\,0xBB\} +CFLAGS += -DLORAMAC_APP_KEY_DEFAULT=\{0xCC\,0xCC\,0xCC\,0xCC\,0xCC\,0xCC\,0xCC\,0xCC\,0xCC\,0xCC\,0xCC\,0xCC\,0xCC\,0xCC\,0xCC\,0xCC\} +CFLAGS += -DLORAMAC_APP_SKEY_DEFAULT=\{0xDD\,0xDD\,0xDD\,0xDD\,0xDD\,0xDD\,0xDD\,0xDD\,0xDD\,0xDD\,0xDD\,0xDD\,0xDD\,0xDD\,0xDD\,0xDD\} +CFLAGS += -DLORAMAC_NWK_SKEY_DEFAULT=\{0xEE\,0xEE\,0xEE\,0xEE\,0xEE\,0xEE\,0xEE\,0xEE\,0xEE\,0xEE\,0xEE\,0xEE\,0xEE\,0xEE\,0xEE\,0xEE\} +CFLAGS += -DLORAMAC_DEV_ADDR_DEFAULT=\{0xFF\,0xFF\,0xFF\,0xFF\} +``` + +## Send data + +After join, send data using `send` command. E.g to send "Hello RIOT!" to port 2: + +``` +send 3 "Hello RIOT!" 2 +``` + +## Changing datarate of transmission +Use `ifconfig` to change the datarate of the transmission. E.g to set the DR to +2: + +``` +ifconfig 3 set dr 2 +``` + +## Perform a Link Check + +Use `ifconfig` to request a Link Check on the next transmission: + +``` +ifconfig 3 link_check +``` + +Send some data. The result of the Link Check request can be seen with +`ifconfig`. + +``` +ifconfig 3 link_check +send 3 "Join the RIOT!" +``` + +Check demodulation margin and number of gateways using `ifconfig` + +``` +ifconfig +Iface 3 HWaddr: 26:01:2C:EA Frequency: 867500000Hz BW: 125kHz SF: 7 + CR: 4/5 Link: up + TX-Power: 14dBm State: SLEEP Demod margin.: 14 Num gateways.: 2 + IQ_INVERT + RX_SINGLE OTAA + +``` + +## Confirmable and unconfirmable messages + +Use `ifconfig` to set the `ack_req` flag. With this flag on, messages are +confirmable. + +E.g send confirmable messages: + +``` +ifconfig 3 ack_req +send "My confirmable message" +``` + +And unconfirmable messages: + +``` +ifconfig 3 -ack_req +send "My unconfirmable message" +``` + +Current state and future plans +============ + +The current GNRC LoRaWAN stack is still in an experimental state. It's still +not compliant with the LoRaWAN specification because some features like duty +cycle restrictions and some FOps are missing. Work in progress. + +Next steps: +- Add other regions (US915, etc) +- Add Adaptive Data Rate +- Add Duty Cycle restrictions +- Add support for RTC diff --git a/examples/gnrc_lorawan/main.c b/examples/gnrc_lorawan/main.c new file mode 100644 index 0000000000..e620e4fbf4 --- /dev/null +++ b/examples/gnrc_lorawan/main.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * @file + * @brief Test application for GNRC LoRaWAN + * + * @author José Ignacio Alamos + * @} + */ + +#include +#include +#include +#include + +#include "thread.h" +#include "xtimer.h" +#include "shell.h" +#include "shell_commands.h" + +#include "board.h" + +#include "net/gnrc/netapi.h" +#include "net/gnrc/netif.h" + +#include "net/gnrc/pktbuf.h" +#include "net/gnrc/pktdump.h" +#include "net/gnrc/netreg.h" + +#define LORAWAN_PORT (2U) + +static void _usage(void) +{ + puts("usage: send [port]"); +} + +int tx_cmd(int argc, char **argv) +{ + gnrc_pktsnip_t *pkt; + uint8_t port = LORAWAN_PORT; /* Default: 2 */ + int interface; + + if(argc < 3) { + _usage(); + return 1; + } + + interface = atoi(argv[1]); + /* handle optional parameters */ + if (argc > 3) { + port = atoi(argv[3]); + if (port == 0 || port >= 224) { + printf("error: invalid port given '%d', " + "port can only be between 1 and 223\n", port); + return 1; + } + } + + pkt = gnrc_pktbuf_add(NULL, argv[2], strlen(argv[2]), GNRC_NETTYPE_UNDEF); + + /* register for returned packet status */ + if (gnrc_neterr_reg(pkt) != 0) { + puts("Can not register for error reporting"); + return 0; + } + + gnrc_netapi_set(interface, NETOPT_LORAWAN_TX_PORT, 0, &port, sizeof(port)); + gnrc_netapi_send(interface, pkt); + + msg_t msg; + /* wait for packet status and check */ + msg_receive(&msg); + if ((msg.type != GNRC_NETERR_MSG_TYPE) || + (msg.content.value != GNRC_NETERR_SUCCESS)) { + puts("Error sending packet (not joined?)"); + } + else { + puts("Successfully sent packet"); + } + return 0; +} + +static const shell_command_t shell_commands[] = { + { "send", "Send LoRaWAN data", tx_cmd}, + { NULL, NULL, NULL } +}; + + +int main(void) +{ + /* start the shell */ + puts("Initialization successful - starting the shell now"); + gnrc_netreg_entry_t dump = GNRC_NETREG_ENTRY_INIT_PID(LORAWAN_PORT, + gnrc_pktdump_pid); + gnrc_netreg_register(GNRC_NETTYPE_LORAWAN, &dump); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + return 0; +} diff --git a/sys/Makefile.include b/sys/Makefile.include index cde195f739..f156b65224 100644 --- a/sys/Makefile.include +++ b/sys/Makefile.include @@ -6,6 +6,10 @@ ifneq (,$(filter gnrc_sixlowpan_frag_rb,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/sys/net/gnrc/network_layer/sixlowpan/frag endif +ifneq (,$(filter gnrc_lorawan,$(USEMODULE))) + USEMODULE_INCLUDES += $(RIOTBASE)/sys/net/gnrc/link_layer/lorawan/include +endif + ifneq (,$(filter gnrc_sock,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/sys/net/gnrc/sock/include ifneq (,$(filter gnrc_ipv6,$(USEMODULE))) diff --git a/sys/auto_init/netif/auto_init_sx127x.c b/sys/auto_init/netif/auto_init_sx127x.c index d6fffebc17..46536e8481 100644 --- a/sys/auto_init/netif/auto_init_sx127x.c +++ b/sys/auto_init/netif/auto_init_sx127x.c @@ -21,7 +21,11 @@ #include "log.h" #include "board.h" +#ifdef MODULE_GNRC_LORAWAN +#include "net/gnrc/netif/lorawan_base.h" +#else #include "net/gnrc/netif/raw.h" +#endif #include "net/gnrc.h" #include "sx127x.h" @@ -56,8 +60,16 @@ void auto_init_sx127x(void) #endif sx127x_setup(&sx127x_devs[i], &sx127x_params[i]); +#ifdef MODULE_GNRC_LORAWAN + /* Currently only one lora device is supported */ + assert(SX127X_NUMOF == 1); + + gnrc_netif_lorawan_create(sx127x_stacks[i], SX127X_STACKSIZE, SX127X_PRIO, + "sx127x", (netdev_t *)&sx127x_devs[i]); +#else gnrc_netif_raw_create(sx127x_stacks[i], SX127X_STACKSIZE, SX127X_PRIO, "sx127x", (netdev_t *)&sx127x_devs[i]); +#endif } } diff --git a/sys/include/net/gnrc/lorawan.h b/sys/include/net/gnrc/lorawan.h new file mode 100644 index 0000000000..3c321b9cb4 --- /dev/null +++ b/sys/include/net/gnrc/lorawan.h @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2017 Fundación Inria Chile + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup net_gnrc_lorawan GNRC LoRaWAN + * @ingroup net_gnrc + * @brief GNRC LoRaWAN stack implementation + * + * @{ + * + * @file + * @brief GNRC LoRaWAN API definition + * + * @author José Ignacio Alamos + * @author Francisco Molina + */ +#ifndef NET_GNRC_LORAWAN_H +#define NET_GNRC_LORAWAN_H + +#include "gnrc_lorawan_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief maximum timer drift in percentage + * + * @note this is only a workaround to compensate inaccurate timers. + * + * E.g a value of 0.1 means there's a positive drift of 0.1% (set timeout to + * 1000 ms => triggers after 1001 ms) + */ +#ifndef CONFIG_GNRC_LORAWAN_TIMER_DRIFT +#define CONFIG_GNRC_LORAWAN_TIMER_DRIFT 1 +#endif + +/** + * @brief the minimum symbols to detect a LoRa preamble + */ +#ifndef CONFIG_GNRC_LORAWAN_MIN_SYMBOLS_TIMEOUT +#define CONFIG_GNRC_LORAWAN_MIN_SYMBOLS_TIMEOUT 30 +#endif + +#define GNRC_LORAWAN_REQ_STATUS_SUCCESS (0) /**< MLME or MCPS request successful status */ +#define GNRC_LORAWAN_REQ_STATUS_DEFERRED (1) /**< the MLME or MCPS confirm message is asynchronous */ + +/** + * @brief MCPS events + */ +typedef enum { + MCPS_EVENT_RX, /**< MCPS RX event */ + MCPS_EVENT_NO_RX, /**< MCPS no RX event */ + MCPS_EVENT_ACK_TIMEOUT /**< MCPS retrans event */ +} mcps_event_t; + +/** + * @brief LoRaWAN activation mechanism + */ +typedef enum { + MLME_ACTIVATION_NONE, /**< MAC layer is not activated */ + MLME_ACTIVATION_ABP, /**< MAC layer activated by ABP */ + MLME_ACTIVATION_OTAA /**< MAC layer activated by OTAA */ +} mlme_activation_t; + +/** + * @brief MAC Information Base attributes + */ +typedef enum { + MIB_ACTIVATION_METHOD /**< type is activation method */ +} mlme_mib_type_t; + +/** + * @brief MLME primitive types + */ +typedef enum { + MLME_JOIN, /**< join a LoRaWAN network */ + MLME_LINK_CHECK, /**< perform a Link Check */ + MLME_RESET, /**< reset the MAC layer */ + MLME_SET, /**< set the MIB */ + MLME_GET, /**< get the MIB */ + MLME_SCHEDULE_UPLINK /**< schedule uplink indication */ +} mlme_type_t; + +/** + * @brief MCPS primitive types + */ +typedef enum { + MCPS_CONFIRMED, /**< confirmed data */ + MCPS_UNCONFIRMED /**< unconfirmed data */ +} mcps_type_t; + +/** + * @brief MAC Information Base descriptor for MLME Request-Confirm + */ +typedef struct { + mlme_mib_type_t type; /**< MIB attribute identifier */ + union { + mlme_activation_t activation; /**< holds activation mechanism */ + }; +} mlme_mib_t; + +/** + * @brief MAC (sub) Layer Management Entity (MLME) request representation + */ +typedef struct { + union { + mlme_lorawan_join_t join; /**< Join Data holder */ + mlme_mib_t mib; /**< MIB holder */ + }; + mlme_type_t type; /**< type of the MLME request */ +} mlme_request_t; + +/** + * @brief Mac Common Part Sublayer (MCPS) request representation + */ +typedef struct { + union { + mcps_data_t data; /**< MCPS data holder */ + }; + mcps_type_t type; /**< type of the MCPS request */ +} mcps_request_t; + +/** + * @brief MAC (sub) Layer Management Entity (MLME) confirm representation + */ +typedef struct { + int16_t status; /**< status of the MLME confirm */ + mlme_type_t type; /**< type of the MLME confirm */ + union { + mlme_link_req_confirm_t link_req; /**< Link Check confirmation data */ + mlme_mib_t mib; /**< MIB confirmation data */ + }; +} mlme_confirm_t; + +/** + * @brief Mac Common Part Sublayer (MCPS) confirm representation + */ +typedef struct { + void *data; /**< data of the MCPS confirm */ + int16_t status; /**< status of the MCPS confirm */ + mcps_type_t type; /**< type of the MCPS confirm */ +} mcps_confirm_t; + +/** + * @brief Mac Common Part Sublayer (MCPS) indication representation + */ +typedef struct { + mcps_type_t type; /**< type of the MCPS indication */ + union { + mcps_data_t data; /**< MCPS Data holder */ + }; +} mcps_indication_t; + +/** + * @brief MAC (sub) Layer Management Entity (MLME) indication representation + */ +typedef struct { + mlme_type_t type; /**< type of the MLME indication */ +} mlme_indication_t; + +/** + * @brief Indicate the MAC layer there was a timeout event + * + * @param[in] mac pointer to the MAC descriptor + */ +void gnrc_lorawan_event_timeout(gnrc_lorawan_t *mac); + +/** + * @brief Indicate the MAC layer when the transmission finished + * + * @param[in] mac pointer to the MAC descriptor + */ +void gnrc_lorawan_event_tx_complete(gnrc_lorawan_t *mac); + +/** + * @brief Init GNRC LoRaWAN + * + * @param[in] mac pointer to the MAC descriptor + * @param[in] nwkskey buffer to store the NwkSKey. Should be at least 16 bytes long + * @param[in] appskey buffer to store the AppsKey. Should be at least 16 bytes long + */ +void gnrc_lorawan_init(gnrc_lorawan_t *mac, uint8_t *nwkskey, uint8_t *appskey); + +/** + * @brief Perform a MLME request + * + * @param[in] mac pointer to the MAC descriptor + * @param[in] mlme_request the MLME request + * @param[out] mlme_confirm the MLME confirm. `mlme_confirm->status` could either + * be GNRC_LORAWAN_REQ_STATUS_SUCCESS if the request was OK, + * GNRC_LORAWAN_REQ_STATUS_DEFERRED if the confirmation is deferred + * or an standard error number + */ +void gnrc_lorawan_mlme_request(gnrc_lorawan_t *mac, const mlme_request_t *mlme_request, + mlme_confirm_t *mlme_confirm); + +/** + * @brief Perform a MCPS request + * + * @param[in] mac pointer to the MAC descriptor + * @param[in] mcps_request the MCPS request + * @param[out] mcps_confirm the MCPS confirm. `mlme_confirm->status` could either + * be GNRC_LORAWAN_REQ_STATUS_SUCCESS if the request was OK, + * GNRC_LORAWAN_REQ_STATUS_DEFERRED if the confirmation is deferred + * or an standard error number + */ +void gnrc_lorawan_mcps_request(gnrc_lorawan_t *mac, const mcps_request_t *mcps_request, + mcps_confirm_t *mcps_confirm); + +/** + * @brief Fetch a LoRaWAN packet from the radio. + * + * To be called on radio RX done event. + * + * @param[in] mac pointer to the MAC descriptor + */ +void gnrc_lorawan_recv(gnrc_lorawan_t *mac); + +/** + * @brief Setup GNRC LoRaWAN netdev layers + * + * @param mac pointer to the MAC descriptor + * @param lower pointer to the lower netdev device (radio) + */ +void gnrc_lorawan_setup(gnrc_lorawan_t *mac, netdev_t *lower); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_GNRC_LORAWAN_H */ +/** @} */ diff --git a/sys/include/net/gnrc/lorawan/region.h b/sys/include/net/gnrc/lorawan/region.h new file mode 100644 index 0000000000..8ed2794550 --- /dev/null +++ b/sys/include/net/gnrc/lorawan/region.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup net_gnrc_lorawan + * @{ + * + * @file + * @brief GNRC LoRaWAN region specific functions + * + * @author José Ignacio Alamos + */ +#ifndef NET_GNRC_LORAWAN_REGION_H +#define NET_GNRC_LORAWAN_REGION_H + +#include "kernel_defines.h" +#include "net/gnrc/lorawan.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Default LoRaWAN channels for current region (EU868) + */ +static const uint32_t gnrc_lorawan_default_channels[] = { + 868100000UL, + 868300000UL, + 868500000UL +}; + +#define GNRC_LORAWAN_DEFAULT_CHANNELS_NUMOF \ + ARRAY_SIZE(gnrc_lorawan_default_channels) /**< Number of default channels */ + +/** + * @brief Process Channel Frequency list frame + * + * @param[in] mac pointer to the MAC descriptor + * @param[in] cflist the CFList to be processed + */ +void gnrc_lorawan_process_cflist(gnrc_lorawan_t *mac, uint8_t *cflist); + +/** + * @brief Get the datarate of the first reception windows + * + * @param[in] dr_up the datarate of the transmission + * @param[in] dr_offset the offset of the first reception window + * + * @return datarate + */ +uint8_t gnrc_lorawan_rx1_get_dr_offset(uint8_t dr_up, uint8_t dr_offset); + +/** + * @brief Check if a datarate is valid in the current region + * + * @param[in] dr the datarate to be checked + * + * @return true if datarate is valid + * @return false otherwise + */ +bool gnrc_lorawan_validate_dr(uint8_t dr); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_GNRC_LORAWAN_REGION_H */ diff --git a/sys/include/net/gnrc/netif.h b/sys/include/net/gnrc/netif.h index ff1b2f39f0..5865ebd2d0 100644 --- a/sys/include/net/gnrc/netif.h +++ b/sys/include/net/gnrc/netif.h @@ -34,6 +34,9 @@ #include "net/gnrc/netapi.h" #include "net/gnrc/pkt.h" #include "net/gnrc/netif/conf.h" +#ifdef MODULE_GNRC_LORAWAN +#include "net/gnrc/netif/lorawan.h" +#endif #ifdef MODULE_GNRC_SIXLOWPAN #include "net/gnrc/netif/6lo.h" #endif @@ -76,6 +79,9 @@ typedef struct { #ifdef MODULE_NETSTATS_L2 netstats_t stats; /**< transceiver's statistics */ #endif +#if defined(MODULE_GNRC_LORAWAN) || DOXYGEN + gnrc_netif_lorawan_t lorawan; /**< LoRaWAN component */ +#endif #if defined(MODULE_GNRC_IPV6) || DOXYGEN gnrc_netif_ipv6_t ipv6; /**< IPv6 component */ #endif diff --git a/sys/include/net/gnrc/netif/lorawan.h b/sys/include/net/gnrc/netif/lorawan.h new file mode 100644 index 0000000000..e823ef1e32 --- /dev/null +++ b/sys/include/net/gnrc/netif/lorawan.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup net_gnrc_netif + * @{ + * + * @file + * @brief LoRaWAN adaption for @ref net_gnrc_netif + * + * @author Jose Ignacio Alamos + */ +#ifndef NET_GNRC_NETIF_LORAWAN_H +#define NET_GNRC_NETIF_LORAWAN_H + +#include "net/gnrc/lorawan.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief A Link Check request was scheduled + */ +#define GNRC_NETIF_LORAWAN_FLAGS_LINK_CHECK (0x1U) + +/** + * @brief GNRC LoRaWAN interface descriptor + */ +typedef struct { + uint8_t nwkskey[LORAMAC_NWKSKEY_LEN]; /**< network SKey buffer */ + uint8_t appskey[LORAMAC_APPSKEY_LEN]; /**< App SKey buffer */ + uint8_t appkey[LORAMAC_APPKEY_LEN]; /**< App Key buffer */ + uint8_t deveui[LORAMAC_DEVEUI_LEN]; /**< Device EUI buffer */ + uint8_t appeui[LORAMAC_APPEUI_LEN]; /**< App EUI buffer */ + gnrc_lorawan_t mac; /**< gnrc lorawan mac descriptor */ + uint8_t flags; /**< flags for the LoRaWAN interface */ + uint8_t demod_margin; /**< value of last demodulation margin */ + uint8_t num_gateways; /**< number of gateways of last link check */ + uint8_t datarate; /**< LoRaWAN datarate for the next transmission */ + uint8_t port; /**< LoRaWAN port for the next transmission */ + uint8_t ack_req; /**< Request ACK in the next transmission */ + uint8_t otaa; /**< wether the next transmission is OTAA or not */ +} gnrc_netif_lorawan_t; + +#ifdef __cplusplus +} +#endif + +#endif /* NET_GNRC_NETIF_LORAWAN_H */ +/** @} */ diff --git a/sys/include/net/gnrc/netif/lorawan_base.h b/sys/include/net/gnrc/netif/lorawan_base.h new file mode 100644 index 0000000000..4e0abaf173 --- /dev/null +++ b/sys/include/net/gnrc/netif/lorawan_base.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup net_gnrc_netif + * @{ + * + * @file + * @brief LoRaWAN base @ref net_gnrc_netif header + * + * @author Jose Ignacio Alamos + */ +#ifndef NET_GNRC_NETIF_LORAWAN_BASE_H +#define NET_GNRC_NETIF_LORAWAN_BASE_H + +#include "net/gnrc/netif.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Creates a raw network interface + * + * @param[in] stack The stack for the network interface's thread. + * @param[in] stacksize Size of @p stack. + * @param[in] priority Priority for the network interface's thread. + * @param[in] name Name for the network interface. May be NULL. + * @param[in] dev Device for the interface. + * + * @see @ref gnrc_netif_create() + * + * @return The network interface on success. + * @return NULL, on error. + */ +gnrc_netif_t *gnrc_netif_lorawan_create(char *stack, int stacksize, char priority, + char *name, netdev_t *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_GNRC_NETIF_LORAWAN_BASE_H */ +/** @} */ diff --git a/sys/include/net/gnrc/nettype.h b/sys/include/net/gnrc/nettype.h index 70b58aafaf..a9dc4f72a9 100644 --- a/sys/include/net/gnrc/nettype.h +++ b/sys/include/net/gnrc/nettype.h @@ -119,6 +119,10 @@ typedef enum { GNRC_NETTYPE_NDN, /**< Protocol is NDN */ #endif +#ifdef MODULE_GNRC_LORAWAN + GNRC_NETTYPE_LORAWAN, /**< Protocol is LoRaWAN */ +#endif + /** * @{ * @name Testing diff --git a/sys/include/net/netopt.h b/sys/include/net/netopt.h index 73dcbbd900..794e07cd87 100644 --- a/sys/include/net/netopt.h +++ b/sys/include/net/netopt.h @@ -670,6 +670,29 @@ typedef enum { /* add more options if needed */ + /** + * @brief (@ref netopt_enable_t) Enable or disable OTAA activation (LoRaWAN) + */ + NETOPT_OTAA, + + /** + * @brief (uint8_t) Get the demodulation margin of the last Link Check request. + */ + NETOPT_DEMOD_MARGIN, + + /** + * @brief (uint8_t) Get the number of gateways of the last Link Check request. + */ + NETOPT_NUM_GATEWAYS, + + /** + * @brief (@ref netopt_enable_t) Perform a Link Check request (LoRaWAN) + * + * When set, the next transmission will request a Link Check and will + * be received on the next downlink + */ + NETOPT_LINK_CHECK, + /** * @brief maximum number of options defined here. * diff --git a/sys/net/crosslayer/netopt/netopt.c b/sys/net/crosslayer/netopt/netopt.c index db0a75bca9..e9f420f1bb 100644 --- a/sys/net/crosslayer/netopt/netopt.c +++ b/sys/net/crosslayer/netopt/netopt.c @@ -109,6 +109,10 @@ static const char *_netopt_strmap[] = { [NETOPT_SYNCWORD] = "NETOPT_SYNCWORD", [NETOPT_RANDOM] = "NETOPT_RANDOM", [NETOPT_RX_SYMBOL_TIMEOUT] = "NETOPT_RX_SYMBOL_TIMEOUT", + [NETOPT_OTAA] = "NETOPT_OTAA", + [NETOPT_DEMOD_MARGIN] = "NETOPT_DEMOD_MARGIN", + [NETOPT_NUM_GATEWAYS] = "NETOPT_NUM_GATEWAYS", + [NETOPT_LINK_CHECK] = "NETOPT_LINK_CHECK", [NETOPT_NUMOF] = "NETOPT_NUMOF", }; diff --git a/sys/net/gnrc/Makefile b/sys/net/gnrc/Makefile index cea412af33..f2a2956131 100644 --- a/sys/net/gnrc/Makefile +++ b/sys/net/gnrc/Makefile @@ -58,6 +58,9 @@ endif ifneq (,$(filter gnrc_pktbuf_malloc,$(USEMODULE))) DIRS += pktbuf_malloc endif +ifneq (,$(filter gnrc_lorawan,$(USEMODULE))) + DIRS += link_layer/lorawan +endif ifneq (,$(filter gnrc_gomach,$(USEMODULE))) DIRS += link_layer/gomach endif diff --git a/sys/net/gnrc/link_layer/lorawan/Makefile b/sys/net/gnrc/link_layer/lorawan/Makefile new file mode 100644 index 0000000000..d06929c43e --- /dev/null +++ b/sys/net/gnrc/link_layer/lorawan/Makefile @@ -0,0 +1,3 @@ +MODULE = gnrc_lorawan + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan.c b/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan.c new file mode 100644 index 0000000000..36f0d24736 --- /dev/null +++ b/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan.c @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @author José Ignacio Alamos + * @} + */ +#include +#include +#include "net/lora.h" +#include "net/gnrc/lorawan.h" +#include "errno.h" +#include "net/gnrc/pktbuf.h" + +#include "net/lorawan/hdr.h" +#include "net/loramac.h" +#include "net/gnrc/lorawan/region.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* This factor is used for converting "real" seconds into microcontroller + * microseconds. This is done in order to correct timer drift. + */ +#define _DRIFT_FACTOR (int) (US_PER_SEC * 100 / (100 + CONFIG_GNRC_LORAWAN_TIMER_DRIFT)) + +#define GNRC_LORAWAN_DL_RX2_DR_MASK (0x0F) /**< DL Settings DR Offset mask */ +#define GNRC_LORAWAN_DL_RX2_DR_POS (0) /**< DL Settings DR Offset pos */ +#define GNRC_LORAWAN_DL_DR_OFFSET_MASK (0x70) /**< DL Settings RX2 DR mask */ +#define GNRC_LORAWAN_DL_DR_OFFSET_POS (4) /**< DL Settings RX2 DR pos */ + +static inline void gnrc_lorawan_mlme_reset(gnrc_lorawan_t *mac) +{ + mac->mlme.activation = MLME_ACTIVATION_NONE; + mac->mlme.pending_mlme_opts = 0; + mac->rx_delay = (LORAMAC_DEFAULT_RX1_DELAY/MS_PER_SEC); + mac->mlme.nid = LORAMAC_DEFAULT_NETID; +} + +static inline void gnrc_lorawan_mlme_backoff_init(gnrc_lorawan_t *mac) +{ + mac->mlme.backoff_msg.type = MSG_TYPE_MLME_BACKOFF_EXPIRE; + mac->mlme.backoff_state = 0; + + gnrc_lorawan_mlme_backoff_expire(mac); +} + +static inline void gnrc_lorawan_mcps_reset(gnrc_lorawan_t *mac) +{ + mac->mcps.ack_requested = false; + mac->mcps.waiting_for_ack = false; + mac->mcps.fcnt = 0; + mac->mcps.fcnt_down = 0; +} + +static inline void _set_rx2_dr(gnrc_lorawan_t *mac, uint8_t rx2_dr) +{ + mac->dl_settings &= ~GNRC_LORAWAN_DL_RX2_DR_MASK; + mac->dl_settings |= (rx2_dr << GNRC_LORAWAN_DL_RX2_DR_POS) & + GNRC_LORAWAN_DL_RX2_DR_MASK; +} + +static void _sleep_radio(gnrc_lorawan_t *mac) +{ + netopt_state_t state = NETOPT_STATE_SLEEP; + + netdev_set_pass((netdev_t *) mac, NETOPT_STATE, &state, sizeof(state)); +} + +void gnrc_lorawan_init(gnrc_lorawan_t *mac, uint8_t *nwkskey, uint8_t *appskey) +{ + mac->nwkskey = nwkskey; + mac->appskey = appskey; + mac->busy = false; + gnrc_lorawan_mlme_backoff_init(mac); + gnrc_lorawan_reset(mac); +} + +void gnrc_lorawan_reset(gnrc_lorawan_t *mac) +{ + uint8_t cr = LORA_CR_4_5; + + netdev_set_pass(&mac->netdev, NETOPT_CODING_RATE, &cr, sizeof(cr)); + + uint8_t syncword = LORAMAC_DEFAULT_PUBLIC_NETWORK ? LORA_SYNCWORD_PUBLIC + : LORA_SYNCWORD_PRIVATE; + netdev_set_pass(&mac->netdev, NETOPT_SYNCWORD, &syncword, sizeof(syncword)); + + /* Continuous reception */ + uint32_t rx_timeout = 0; + netdev_set_pass(&mac->netdev, NETOPT_RX_TIMEOUT, &rx_timeout, sizeof(rx_timeout)); + + _set_rx2_dr(mac, LORAMAC_DEFAULT_RX2_DR); + + mac->toa = 0; + gnrc_lorawan_mcps_reset(mac); + gnrc_lorawan_mlme_reset(mac); + gnrc_lorawan_channels_init(mac); +} + +static void _config_radio(gnrc_lorawan_t *mac, uint32_t channel_freq, uint8_t dr, int rx) +{ + if (channel_freq != 0) { + netdev_set_pass(&mac->netdev, NETOPT_CHANNEL_FREQUENCY, &channel_freq, sizeof(channel_freq)); + } + + netopt_enable_t iq_invert = rx; + netdev_set_pass(&mac->netdev, NETOPT_IQ_INVERT, &iq_invert, sizeof(iq_invert)); + + gnrc_lorawan_set_dr(mac, dr); + + if (rx) { + /* Switch to single listen mode */ + const netopt_enable_t single = true; + netdev_set_pass(&mac->netdev, NETOPT_SINGLE_RECEIVE, &single, sizeof(single)); + const uint16_t timeout = CONFIG_GNRC_LORAWAN_MIN_SYMBOLS_TIMEOUT; + netdev_set_pass(&mac->netdev, NETOPT_RX_SYMBOL_TIMEOUT, &timeout, sizeof(timeout)); + } +} + +static void _configure_rx_window(gnrc_lorawan_t *mac, uint32_t channel_freq, uint8_t dr) +{ + _config_radio(mac, channel_freq, dr, true); +} + +void gnrc_lorawan_open_rx_window(gnrc_lorawan_t *mac) +{ + mac->msg.type = MSG_TYPE_TIMEOUT; + /* Switch to RX state */ + if (mac->state == LORAWAN_STATE_RX_1) { + xtimer_set_msg(&mac->rx, _DRIFT_FACTOR, &mac->msg, thread_getpid()); + } + uint8_t state = NETOPT_STATE_RX; + netdev_set_pass(&mac->netdev, NETOPT_STATE, &state, sizeof(state)); +} + +void gnrc_lorawan_event_tx_complete(gnrc_lorawan_t *mac) +{ + mac->msg.type = MSG_TYPE_TIMEOUT; + mac->state = LORAWAN_STATE_RX_1; + + int rx_1; + /* if the MAC is not activated, then this is a Join Request */ + rx_1 = mac->mlme.activation == MLME_ACTIVATION_NONE ? + LORAMAC_DEFAULT_JOIN_DELAY1 : mac->rx_delay; + + xtimer_set_msg(&mac->rx, rx_1 * _DRIFT_FACTOR, &mac->msg, thread_getpid()); + + uint8_t dr_offset = (mac->dl_settings & GNRC_LORAWAN_DL_DR_OFFSET_MASK) >> + GNRC_LORAWAN_DL_DR_OFFSET_POS; + _configure_rx_window(mac, 0, gnrc_lorawan_rx1_get_dr_offset(mac->last_dr, dr_offset)); + + _sleep_radio(mac); +} + +void gnrc_lorawan_event_timeout(gnrc_lorawan_t *mac) +{ + (void) mac; + switch (mac->state) { + case LORAWAN_STATE_RX_1: + _configure_rx_window(mac, LORAMAC_DEFAULT_RX2_FREQ, mac->dl_settings & GNRC_LORAWAN_DL_RX2_DR_MASK); + mac->state = LORAWAN_STATE_RX_2; + break; + case LORAWAN_STATE_RX_2: + gnrc_lorawan_mlme_no_rx(mac); + gnrc_lorawan_mcps_event(mac, MCPS_EVENT_NO_RX, 0); + mac->state = LORAWAN_STATE_IDLE; + gnrc_lorawan_mac_release(mac); + break; + default: + assert(false); + break; + } + _sleep_radio(mac); +} + +/* This function uses a precomputed table to calculate time on air without + * using floating point arithmetics */ +static uint32_t lora_time_on_air(size_t payload_size, uint8_t dr, uint8_t cr) +{ + assert(dr <= LORAMAC_DR_6); + uint8_t _K[6][4] = { { 0, 1, 5, 5 }, + { 0, 1, 4, 5 }, + { 1, 5, 5, 5 }, + { 1, 4, 5, 4 }, + { 1, 3, 4, 4 }, + { 1, 2, 4, 3 } }; + + uint32_t t_sym = 1 << (15 - dr); + uint32_t t_preamble = (t_sym << 3) + (t_sym << 2) + (t_sym >> 2); + + int index = (dr < LORAMAC_DR_6) ? dr : LORAMAC_DR_5; + uint8_t n0 = _K[index][0]; + int nb_symbols; + + uint8_t offset = _K[index][1]; + if (payload_size < offset) { + nb_symbols = 8 + n0 * cr; + } + else { + uint8_t c1 = _K[index][2]; + uint8_t c2 = _K[index][3]; + uint8_t pos = (payload_size - offset) % (c1 + c2); + uint8_t cycle = (payload_size - offset) / (c1 + c2); + nb_symbols = 8 + (n0 + 2 * cycle + 1 + (pos > (c1 - 1))) * cr; + } + + uint32_t t_payload = t_sym * nb_symbols; + return t_preamble + t_payload; +} + +void gnrc_lorawan_send_pkt(gnrc_lorawan_t *mac, gnrc_pktsnip_t *pkt, uint8_t dr) +{ + mac->state = LORAWAN_STATE_TX; + + iolist_t iolist = { + .iol_base = pkt->data, + .iol_len = pkt->size, + .iol_next = (iolist_t *) pkt->next + }; + + uint32_t chan = gnrc_lorawan_pick_channel(mac); + _config_radio(mac, chan, dr, false); + + mac->last_dr = dr; + + uint8_t cr; + netdev_get_pass(&mac->netdev, NETOPT_CODING_RATE, &cr, sizeof(cr)); + + mac->toa = lora_time_on_air(gnrc_pkt_len(pkt), dr, cr + 4); + + if (netdev_send_pass(&mac->netdev, &iolist) == -ENOTSUP) { + DEBUG("gnrc_lorawan: Cannot send: radio is still transmitting"); + } + +} + +void gnrc_lorawan_process_pkt(gnrc_lorawan_t *mac, gnrc_pktsnip_t *pkt) +{ + mac->state = LORAWAN_STATE_IDLE; + xtimer_remove(&mac->rx); + + uint8_t *p = pkt->data; + + uint8_t mtype = (*p & MTYPE_MASK) >> 5; + switch (mtype) { + case MTYPE_JOIN_ACCEPT: + gnrc_lorawan_mlme_process_join(mac, pkt); + break; + case MTYPE_CNF_DOWNLINK: + case MTYPE_UNCNF_DOWNLINK: + gnrc_lorawan_mcps_process_downlink(mac, pkt); + break; + default: + gnrc_pktbuf_release(pkt); + break; + } + + gnrc_lorawan_mac_release(mac); +} + +int gnrc_lorawan_netdev_get(netdev_t *dev, netopt_t opt, void *value, size_t max_len) +{ + int res = 0; + gnrc_lorawan_t *mac = (gnrc_lorawan_t *) dev; + uint32_t tmp; + + switch (opt) { + case NETOPT_ADDRESS: + assert(max_len >= sizeof(mac->dev_addr)); + tmp = byteorder_swapl(mac->dev_addr.u32); + memcpy(value, &tmp, sizeof(mac->dev_addr)); + res = sizeof(mac->dev_addr); + break; + default: + res = netdev_get_pass(dev, opt, value, max_len); + break; + } + return res; +} + +int gnrc_lorawan_netdev_set(netdev_t *dev, netopt_t opt, const void *value, size_t len) +{ + gnrc_lorawan_t *mac = (gnrc_lorawan_t *) dev; + uint32_t tmp; + + if (mac->busy) { + return -EBUSY; + } + + switch (opt) { + case NETOPT_ADDRESS: + assert(len == sizeof(uint32_t)); + tmp = byteorder_swapl(*((uint32_t *) value)); + memcpy(&mac->dev_addr, &tmp, sizeof(uint32_t)); + break; + case NETOPT_LORAWAN_RX2_DR: + assert(len == sizeof(uint8_t)); + _set_rx2_dr(mac, *((uint8_t *) value)); + break; + default: + netdev_set_pass(dev, opt, value, len); + break; + } + return 0; +} + +const netdev_driver_t gnrc_lorawan_driver = { + .init = netdev_init_pass, + .send = netdev_send_pass, + .recv = netdev_recv_pass, + .get = gnrc_lorawan_netdev_get, + .set = gnrc_lorawan_netdev_set, + .isr = netdev_isr_pass, +}; + +void gnrc_lorawan_setup(gnrc_lorawan_t *mac, netdev_t *lower) +{ + mac->netdev.driver = &gnrc_lorawan_driver; + mac->netdev.lower = lower; + lower->context = mac; +} + +void gnrc_lorawan_recv(gnrc_lorawan_t *mac) +{ + int bytes_expected = netdev_recv_pass((netdev_t *) mac, NULL, 0, 0); + int nread; + struct netdev_radio_rx_info rx_info; + gnrc_pktsnip_t *pkt = gnrc_pktbuf_add(NULL, NULL, bytes_expected, GNRC_NETTYPE_UNDEF); + if (pkt == NULL) { + DEBUG("_recv_ieee802154: cannot allocate pktsnip.\n"); + /* Discard packet on netdev device */ + netdev_recv_pass((netdev_t *) mac, NULL, bytes_expected, NULL); + return; + } + nread = netdev_recv_pass((netdev_t *) mac, pkt->data, bytes_expected, &rx_info); + _sleep_radio(mac); + if (nread <= 0) { + gnrc_pktbuf_release(pkt); + return; + } + + gnrc_lorawan_process_pkt(mac, pkt); +} diff --git a/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_crypto.c b/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_crypto.c new file mode 100644 index 0000000000..f6b74ca817 --- /dev/null +++ b/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_crypto.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @author José Ignacio Alamos + * @author Francisco Molina + */ +#include +#include + +#include "hashes/cmac.h" +#include "crypto/ciphers.h" + +#include "net/gnrc/lorawan.h" +#include "byteorder.h" +#include "net/lorawan/hdr.h" + +#define MIC_B0_START (0x49) +#define CRYPT_B0_START (0x01) +#define DIR_MASK (0x1) +#define SBIT_MASK (0xF) + +#define APP_SKEY_B0_START (0x1) +#define NWK_SKEY_B0_START (0x2) + +static cmac_context_t CmacContext; +static uint8_t digest[LORAMAC_APPKEY_LEN]; +static cipher_t AesContext; + +typedef struct __attribute__((packed)) { + uint8_t fb; + uint32_t u8_pad; + uint8_t dir; + le_uint32_t dev_addr; + le_uint32_t fcnt; + uint8_t u32_pad; + uint8_t len; +} lorawan_block_t; + +void gnrc_lorawan_calculate_join_mic(const iolist_t *io, const uint8_t *key, le_uint32_t *out) +{ + cmac_init(&CmacContext, key, LORAMAC_APPKEY_LEN); + while (io != NULL) { + cmac_update(&CmacContext, io->iol_base, io->iol_len); + io = io->iol_next; + } + cmac_final(&CmacContext, digest); + + memcpy(out, digest, sizeof(le_uint32_t)); +} + +void gnrc_lorawan_calculate_mic(const le_uint32_t *dev_addr, uint32_t fcnt, + uint8_t dir, iolist_t *pkt, const uint8_t *nwkskey, le_uint32_t *out) +{ + lorawan_block_t block; + + block.fb = MIC_B0_START; + block.u8_pad = 0; + block.dir = dir & DIR_MASK; + + memcpy(&block.dev_addr, dev_addr, sizeof(le_uint32_t)); + + block.fcnt = byteorder_btoll(byteorder_htonl(fcnt)); + + block.u32_pad = 0; + + block.len = iolist_size(pkt); + + iolist_t io = { .iol_base = &block, .iol_len = sizeof(block), + .iol_next = pkt }; + gnrc_lorawan_calculate_join_mic(&io, nwkskey, out); +} + +void gnrc_lorawan_encrypt_payload(iolist_t *iolist, const le_uint32_t *dev_addr, uint32_t fcnt, uint8_t dir, const uint8_t *appskey) +{ + uint8_t s_block[16]; + uint8_t a_block[16]; + + memset(s_block, 0, sizeof(s_block)); + memset(a_block, 0, sizeof(a_block)); + + lorawan_block_t *block = (lorawan_block_t *) a_block; + + cipher_init(&AesContext, CIPHER_AES_128, appskey, LORAMAC_APPKEY_LEN); + + block->fb = CRYPT_B0_START; + + block->u8_pad = 0; + block->dir = dir & DIR_MASK; + + block->dev_addr = *dev_addr; + block->fcnt = byteorder_btoll(byteorder_htonl(fcnt)); + + block->u32_pad = 0; + + int c = 0; + for (iolist_t *io = iolist; io != NULL; io = io->iol_next) { + for (unsigned i = 0; i < io->iol_len; i++) { + uint8_t *v = io->iol_base; + + if ((c & SBIT_MASK) == 0) { + block->len = (c >> 4) + 1; + cipher_encrypt(&AesContext, a_block, s_block); + } + + v[i] = v[i] ^ s_block[c & SBIT_MASK]; + c++; + } + } +} + +void gnrc_lorawan_decrypt_join_accept(const uint8_t *key, uint8_t *pkt, int has_clist, uint8_t *out) +{ + cipher_init(&AesContext, CIPHER_AES_128, key, LORAMAC_APPKEY_LEN); + cipher_encrypt(&AesContext, pkt, out); + + if (has_clist) { + cipher_encrypt(&AesContext, pkt + LORAMAC_APPKEY_LEN, out + LORAMAC_APPKEY_LEN); + } +} + +void gnrc_lorawan_generate_session_keys(const uint8_t *app_nonce, const uint8_t *dev_nonce, const uint8_t *appkey, uint8_t *nwkskey, uint8_t *appskey) +{ + uint8_t buf[LORAMAC_APPSKEY_LEN]; + + memset(buf, 0, sizeof(buf)); + + cipher_init(&AesContext, CIPHER_AES_128, appkey, LORAMAC_APPSKEY_LEN); + + /* net_id comes right after app_nonce */ + memcpy(buf + 1, app_nonce, GNRC_LORAWAN_APP_NONCE_SIZE + GNRC_LORAWAN_NET_ID_SIZE); + memcpy(buf + 1 + GNRC_LORAWAN_APP_NONCE_SIZE + GNRC_LORAWAN_NET_ID_SIZE, dev_nonce, GNRC_LORAWAN_DEV_NONCE_SIZE); + + /* Calculate Application Session Key */ + buf[0] = APP_SKEY_B0_START; + cipher_encrypt(&AesContext, buf, nwkskey); + + /* Calculate Network Session Key */ + buf[0] = NWK_SKEY_B0_START; + cipher_encrypt(&AesContext, buf, appskey); +} + +/** @} */ diff --git a/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_mcps.c b/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_mcps.c new file mode 100644 index 0000000000..48fd6628fd --- /dev/null +++ b/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_mcps.c @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @author José Ignacio Alamos + */ +#include +#include +#include "net/lora.h" +#include "net/gnrc/lorawan.h" +#include "net/gnrc/lorawan/region.h" +#include "errno.h" +#include "net/gnrc/pktbuf.h" + +#include "net/lorawan/hdr.h" + +#include "random.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define _16_UPPER_BITMASK 0xFFFF0000 +#define _16_LOWER_BITMASK 0xFFFF + +int gnrc_lorawan_mic_is_valid(gnrc_pktsnip_t *mic, uint8_t *nwkskey) +{ + le_uint32_t calc_mic; + + assert(mic->size == MIC_SIZE); + assert(mic->next->data); + lorawan_hdr_t *lw_hdr = (lorawan_hdr_t *) mic->next->data; + + uint32_t fcnt = byteorder_ntohs(byteorder_ltobs(lw_hdr->fcnt)); + gnrc_lorawan_calculate_mic(&lw_hdr->addr, fcnt, GNRC_LORAWAN_DIR_DOWNLINK, (iolist_t *) mic->next, nwkskey, &calc_mic); + return calc_mic.u32 == ((le_uint32_t *) mic->data)->u32; +} + +uint32_t gnrc_lorawan_fcnt_stol(uint32_t fcnt_down, uint16_t s_fcnt) +{ + uint32_t u32_fcnt = (fcnt_down & _16_UPPER_BITMASK) | s_fcnt; + + if (fcnt_down + LORAMAC_DEFAULT_MAX_FCNT_GAP >= _16_LOWER_BITMASK + && s_fcnt < (fcnt_down & _16_LOWER_BITMASK)) { + u32_fcnt += _16_LOWER_BITMASK; + } + return u32_fcnt; +} + +void gnrc_lorawan_mcps_process_downlink(gnrc_lorawan_t *mac, gnrc_pktsnip_t *pkt) +{ + gnrc_pktsnip_t *hdr, *data, *fopts = NULL, *fport = NULL; + int release = true; + int error = true; + + /* mark MIC */ + if (!(data = gnrc_pktbuf_mark(pkt, (pkt->size - MIC_SIZE > 0) ? pkt->size - MIC_SIZE : 0, GNRC_NETTYPE_UNDEF))) { + DEBUG("gnrc_lorawan: failed to mark MIC\n"); + goto out; + } + + /* NOTE: MIC is in pkt */ + if (!gnrc_lorawan_mic_is_valid(pkt, mac->nwkskey)) { + DEBUG("gnrc_lorawan: invalid MIC\n"); + goto out; + } + + /* remove snip */ + pkt = gnrc_pktbuf_remove_snip(pkt, pkt); + + if (!(hdr = gnrc_pktbuf_mark(pkt, sizeof(lorawan_hdr_t), GNRC_NETTYPE_UNDEF))) { + DEBUG("gnrc_lorawan: failed to allocate hdr\n"); + goto out; + } + + int _fopts_length = lorawan_hdr_get_frame_opts_len((lorawan_hdr_t *) hdr->data); + if (_fopts_length && !(fopts = gnrc_pktbuf_mark(pkt, _fopts_length, GNRC_NETTYPE_UNDEF))) { + DEBUG("gnrc_lorawan: failed to allocate fopts\n"); + goto out; + } + + if (pkt->size && !(fport = gnrc_pktbuf_mark(pkt, 1, GNRC_NETTYPE_UNDEF))) { + DEBUG("gnrc_lorawan: failed to allocate fport\n"); + goto out; + } + + assert(pkt != NULL && fport->data); + + int fopts_in_payload = *((uint8_t *) fport->data) == 0; + if (fopts && fopts_in_payload) { + DEBUG("gnrc_lorawan: packet with fopts and port == 0. Drop\n"); + goto out; + } + + lorawan_hdr_t *lw_hdr = hdr->data; + + if (lw_hdr->addr.u32 != mac->dev_addr.u32) { + DEBUG("gnrc_lorawan: received packet with wrong dev addr. Drop\n"); + goto out; + } + + uint32_t fcnt = gnrc_lorawan_fcnt_stol(mac->mcps.fcnt_down, lw_hdr->fcnt.u16); + if (mac->mcps.fcnt_down > fcnt || mac->mcps.fcnt_down + + LORAMAC_DEFAULT_MAX_FCNT_GAP < fcnt) { + goto out; + } + + mac->mcps.fcnt_down = fcnt; + error = false; + + int ack_req = lorawan_hdr_get_mtype(lw_hdr) == MTYPE_CNF_DOWNLINK; + if (ack_req) { + mac->mcps.ack_requested = true; + } + + iolist_t payload = { .iol_base = pkt->data, .iol_len = pkt->size }; + if (pkt->data) { + gnrc_lorawan_encrypt_payload(&payload, &lw_hdr->addr, byteorder_ntohs(byteorder_ltobs(lw_hdr->fcnt)), GNRC_LORAWAN_DIR_DOWNLINK, fopts_in_payload ? mac->nwkskey : mac->appskey); + } + + /* if there are fopts, it's either an empty packet or application payload */ + if (fopts) { + gnrc_lorawan_process_fopts(mac, fopts->data, fopts->size); + } + else if (fopts_in_payload) { + gnrc_lorawan_process_fopts(mac, pkt->data, pkt->size); + } + + gnrc_lorawan_mcps_event(mac, MCPS_EVENT_RX, lorawan_hdr_get_ack(lw_hdr)); + if (pkt->data && *((uint8_t *) fport->data) != 0) { + pkt->type = GNRC_NETTYPE_LORAWAN; + release = false; + + mcps_indication_t *mcps_indication = gnrc_lorawan_mcps_allocate(mac); + mcps_indication->type = ack_req; + mcps_indication->data.pkt = pkt; + mcps_indication->data.port = *((uint8_t *) fport->data); + mac->netdev.event_callback((netdev_t *) mac, NETDEV_EVENT_MCPS_INDICATION); + } + + if (lorawan_hdr_get_frame_pending(lw_hdr)) { + mlme_indication_t *mlme_indication = gnrc_lorawan_mlme_allocate(mac); + mlme_indication->type = MLME_SCHEDULE_UPLINK; + mac->netdev.event_callback((netdev_t *) mac, NETDEV_EVENT_MLME_INDICATION); + } + +out: + if (error) { + gnrc_lorawan_mcps_event(mac, MCPS_EVENT_NO_RX, 0); + } + + if (release) { + DEBUG("gnrc_lorawan: release packet\n"); + gnrc_pktbuf_release(pkt); + } +} + +size_t gnrc_lorawan_build_hdr(uint8_t mtype, le_uint32_t *dev_addr, uint32_t fcnt, uint8_t ack, uint8_t fopts_length, lorawan_buffer_t *buf) +{ + assert(fopts_length < 16); + lorawan_hdr_t *lw_hdr = (lorawan_hdr_t *) buf->data; + + lw_hdr->mt_maj = 0; + lorawan_hdr_set_mtype(lw_hdr, mtype); + lorawan_hdr_set_maj(lw_hdr, MAJOR_LRWAN_R1); + + lw_hdr->addr = *dev_addr; + lw_hdr->fctrl = 0; + + lorawan_hdr_set_ack(lw_hdr, ack); + lorawan_hdr_set_frame_opts_len(lw_hdr, fopts_length); + + lw_hdr->fcnt = byteorder_btols(byteorder_htons(fcnt)); + + buf->index += sizeof(lorawan_hdr_t); + + return sizeof(lorawan_hdr_t); +} + +gnrc_pktsnip_t *gnrc_lorawan_build_uplink(gnrc_lorawan_t *mac, gnrc_pktsnip_t *payload, int confirmed_data, uint8_t port) +{ + /* Encrypt payload (it's block encryption so we can use the same buffer!) */ + gnrc_lorawan_encrypt_payload((iolist_t *) payload, &mac->dev_addr, mac->mcps.fcnt, GNRC_LORAWAN_DIR_UPLINK, port ? mac->appskey : mac->nwkskey); + + /* We try to allocate the whole header with fopts at once */ + uint8_t fopts_length = gnrc_lorawan_build_options(mac, NULL); + + gnrc_pktsnip_t *mac_hdr = gnrc_pktbuf_add(payload, NULL, sizeof(lorawan_hdr_t) + fopts_length + 1, GNRC_NETTYPE_UNDEF); + + if (!mac_hdr) { + gnrc_pktbuf_release_error(payload, -ENOBUFS); + return NULL; + } + + gnrc_pktsnip_t *mic = gnrc_pktbuf_add(NULL, NULL, MIC_SIZE, GNRC_NETTYPE_UNDEF); + if (!mic) { + gnrc_pktbuf_release_error(mac_hdr, -ENOBUFS); + return NULL; + } + + lorawan_buffer_t buf = { + .data = (uint8_t *) mac_hdr->data, + .size = mac_hdr->size, + .index = 0 + }; + + gnrc_lorawan_build_hdr(confirmed_data ? MTYPE_CNF_UPLINK : MTYPE_UNCNF_UPLINK, + &mac->dev_addr, mac->mcps.fcnt, mac->mcps.ack_requested, fopts_length, &buf); + + gnrc_lorawan_build_options(mac, &buf); + + assert(buf.index == mac_hdr->size - 1); + + buf.data[buf.index++] = port; + + gnrc_lorawan_calculate_mic(&mac->dev_addr, mac->mcps.fcnt, GNRC_LORAWAN_DIR_UPLINK, + (iolist_t *) mac_hdr, mac->nwkskey, mic->data); + + LL_APPEND(payload, mic); + + return mac_hdr; +} + +static void _end_of_tx(gnrc_lorawan_t *mac, int type, int status) +{ + mac->mcps.waiting_for_ack = false; + + mcps_confirm_t *mcps_confirm = gnrc_lorawan_mcps_allocate(mac); + + mcps_confirm->type = type; + mcps_confirm->status = status; + mac->netdev.event_callback((netdev_t *) mac, NETDEV_EVENT_MCPS_CONFIRM); + + mac->mcps.fcnt += 1; +} + +void gnrc_lorawan_mcps_event(gnrc_lorawan_t *mac, int event, int data) +{ + if (mac->mlme.activation == MLME_ACTIVATION_NONE) { + return; + } + + if (event == MCPS_EVENT_ACK_TIMEOUT) { + gnrc_lorawan_send_pkt(mac, mac->mcps.outgoing_pkt, mac->last_dr); + } + else { + int state = mac->mcps.waiting_for_ack ? MCPS_CONFIRMED : MCPS_UNCONFIRMED; + if (state == MCPS_CONFIRMED && ((event == MCPS_EVENT_RX && !data) || + event == MCPS_EVENT_NO_RX)) { + if (mac->mcps.nb_trials-- == 0) { + _end_of_tx(mac, MCPS_CONFIRMED, -ETIMEDOUT); + } + } + else { + _end_of_tx(mac, state, GNRC_LORAWAN_REQ_STATUS_SUCCESS); + } + + mac->msg.type = MSG_TYPE_MCPS_ACK_TIMEOUT; + if (mac->mcps.outgoing_pkt) { + xtimer_set_msg(&mac->rx, 1000000 + random_uint32_range(0, 2000000), &mac->msg, thread_getpid()); + } + } +} + +void gnrc_lorawan_mcps_request(gnrc_lorawan_t *mac, const mcps_request_t *mcps_request, mcps_confirm_t *mcps_confirm) +{ + int release = true; + gnrc_pktsnip_t *pkt = mcps_request->data.pkt; + + if (mac->mlme.activation == MLME_ACTIVATION_NONE) { + DEBUG("gnrc_lorawan_mcps: LoRaWAN not activated\n"); + mcps_confirm->status = -ENOTCONN; + goto out; + } + + if (!gnrc_lorawan_mac_acquire(mac)) { + mcps_confirm->status = -EBUSY; + goto out; + } + + if (mcps_request->data.port < LORAMAC_PORT_MIN || + mcps_request->data.port > LORAMAC_PORT_MAX) { + mcps_confirm->status = -EBADMSG; + goto out; + } + + if (!gnrc_lorawan_validate_dr(mcps_request->data.dr)) { + mcps_confirm->status = -EINVAL; + goto out; + } + + int waiting_for_ack = mcps_request->type == MCPS_CONFIRMED; + if (!(pkt = gnrc_lorawan_build_uplink(mac, pkt, waiting_for_ack, mcps_request->data.port))) { + /* This function releases the pkt if fails */ + release = false; + mcps_confirm->status = -ENOBUFS; + goto out; + } + + if ((gnrc_pkt_len(pkt) - MIC_SIZE - 1) > gnrc_lorawan_region_mac_payload_max(mcps_request->data.dr)) { + mcps_confirm->status = -EMSGSIZE; + goto out; + } + + release = false; + mac->mcps.waiting_for_ack = waiting_for_ack; + mac->mcps.ack_requested = false; + + mac->mcps.nb_trials = LORAMAC_DEFAULT_RETX; + + assert(mac->mcps.outgoing_pkt == NULL); + mac->mcps.outgoing_pkt = pkt; + + gnrc_lorawan_send_pkt(mac, pkt, mcps_request->data.dr); + mcps_confirm->status = GNRC_LORAWAN_REQ_STATUS_DEFERRED; +out: + + if (mcps_confirm->status != GNRC_LORAWAN_REQ_STATUS_DEFERRED) { + gnrc_lorawan_mac_release(mac); + } + + if (release) { + gnrc_pktbuf_release_error(pkt, mcps_confirm->status); + } +} + +/** @} */ diff --git a/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_mlme.c b/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_mlme.c new file mode 100644 index 0000000000..c1c8cdd5c9 --- /dev/null +++ b/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_mlme.c @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @author José Ignacio Alamos + * + * @} + */ +#include +#include +#include "net/lora.h" +#include "net/gnrc/lorawan.h" +#include "net/gnrc/lorawan/region.h" +#include "errno.h" +#include "net/gnrc/pktbuf.h" +#include "random.h" + +#include "net/lorawan/hdr.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static gnrc_pktsnip_t *_build_join_req_pkt(uint8_t *appeui, uint8_t *deveui, uint8_t *appkey, uint8_t *dev_nonce) +{ + gnrc_pktsnip_t *pkt = gnrc_pktbuf_add(NULL, NULL, sizeof(lorawan_join_request_t), GNRC_NETTYPE_UNDEF); + + if (pkt) { + lorawan_join_request_t *hdr = (lorawan_join_request_t *) pkt->data; + + hdr->mt_maj = 0; + lorawan_hdr_set_mtype((lorawan_hdr_t *) hdr, MTYPE_JOIN_REQUEST); + lorawan_hdr_set_maj((lorawan_hdr_t *) hdr, MAJOR_LRWAN_R1); + + le_uint64_t l_appeui = *((le_uint64_t *) appeui); + le_uint64_t l_deveui = *((le_uint64_t *) deveui); + + hdr->app_eui = l_appeui; + hdr->dev_eui = l_deveui; + + le_uint16_t l_dev_nonce = *((le_uint16_t *) dev_nonce); + hdr->dev_nonce = l_dev_nonce; + + iolist_t io = { .iol_base = pkt->data, .iol_len = JOIN_REQUEST_SIZE - MIC_SIZE, + .iol_next = NULL }; + gnrc_lorawan_calculate_join_mic(&io, appkey, &hdr->mic); + } + + return pkt; +} + +static int gnrc_lorawan_send_join_request(gnrc_lorawan_t *mac, uint8_t *deveui, + uint8_t *appeui, uint8_t *appkey, uint8_t dr) +{ + netdev_t *dev = mac->netdev.lower; + + /* Dev Nonce */ + uint32_t random_number; + dev->driver->get(dev, NETOPT_RANDOM, &random_number, sizeof(random_number)); + + mac->mlme.dev_nonce[0] = random_number & 0xFF; + mac->mlme.dev_nonce[1] = (random_number >> 8) & 0xFF; + + /* build join request */ + gnrc_pktsnip_t *pkt = _build_join_req_pkt(appeui, deveui, appkey, mac->mlme.dev_nonce); + if (!pkt) { + return -ENOBUFS; + } + + /* We need a random delay for join request. Otherwise there might be + * network congestion if a group of nodes start at the same time */ + xtimer_usleep(random_uint32() & GNRC_LORAWAN_JOIN_DELAY_U32_MASK); + gnrc_lorawan_send_pkt(mac, pkt, dr); + + mac->mlme.backoff_budget -= mac->toa; + gnrc_pktbuf_release(pkt); + + return GNRC_LORAWAN_REQ_STATUS_DEFERRED; +} + +void gnrc_lorawan_mlme_process_join(gnrc_lorawan_t *mac, gnrc_pktsnip_t *pkt) +{ + int status; + + if (mac->mlme.activation != MLME_ACTIVATION_NONE) { + status = -EBADMSG; + goto out; + } + + if (pkt->size != GNRC_LORAWAN_JOIN_ACCEPT_MAX_SIZE - CFLIST_SIZE && + pkt->size != GNRC_LORAWAN_JOIN_ACCEPT_MAX_SIZE) { + status = -EBADMSG; + goto out; + } + + /* Substract 1 from join accept max size, since the MHDR was already read */ + uint8_t out[GNRC_LORAWAN_JOIN_ACCEPT_MAX_SIZE - 1]; + uint8_t has_cflist = (pkt->size - 1) >= CFLIST_SIZE; + gnrc_lorawan_decrypt_join_accept(mac->appskey, ((uint8_t *) pkt->data) + 1, + has_cflist, out); + memcpy(((uint8_t *) pkt->data) + 1, out, pkt->size - 1); + + iolist_t io = { .iol_base = pkt->data, .iol_len = pkt->size - MIC_SIZE, + .iol_next = NULL }; + le_uint32_t mic; + le_uint32_t *expected_mic = (le_uint32_t *) (((uint8_t *) pkt->data) + pkt->size - MIC_SIZE); + gnrc_lorawan_calculate_join_mic(&io, mac->appskey, &mic); + if (mic.u32 != expected_mic->u32) { + DEBUG("gnrc_lorawan_mlme: wrong MIC.\n"); + status = -EBADMSG; + goto out; + } + + lorawan_join_accept_t *ja_hdr = (lorawan_join_accept_t *) pkt->data; + gnrc_lorawan_generate_session_keys(ja_hdr->app_nonce, mac->mlme.dev_nonce, mac->appskey, mac->nwkskey, mac->appskey); + + le_uint32_t le_nid; + le_nid.u32 = 0; + memcpy(&le_nid, ja_hdr->net_id, 3); + mac->mlme.nid = byteorder_ntohl(byteorder_ltobl(le_nid)); + /* Copy devaddr */ + memcpy(&mac->dev_addr, ja_hdr->dev_addr, sizeof(mac->dev_addr)); + + mac->dl_settings = ja_hdr->dl_settings; + + /* delay 0 maps to 1 second */ + mac->rx_delay = ja_hdr->rx_delay ? ja_hdr->rx_delay : 1; + + gnrc_lorawan_process_cflist(mac, out + sizeof(lorawan_join_accept_t) - 1); + mac->mlme.activation = MLME_ACTIVATION_OTAA; + status = GNRC_LORAWAN_REQ_STATUS_SUCCESS; + +out: + gnrc_pktbuf_release(pkt); + mlme_confirm_t *mlme_confirm = gnrc_lorawan_mlme_allocate(mac); + mlme_confirm->type = MLME_JOIN; + mlme_confirm->status = status; + + mac->netdev.event_callback((netdev_t *) mac, NETDEV_EVENT_MLME_CONFIRM); +} + +void gnrc_lorawan_mlme_backoff_expire(gnrc_lorawan_t *mac) +{ + uint8_t counter = mac->mlme.backoff_state & 0x1F; + uint8_t state = mac->mlme.backoff_state >> 5; + + if (counter == 0) { + switch (state) { + case GNRC_LORAWAN_BACKOFF_STATE_1: + counter = GNRC_LORAWAN_BACKOFF_TIME_1; + state = GNRC_LORAWAN_BACKOFF_STATE_2; + mac->mlme.backoff_budget = GNRC_LORAWAN_BACKOFF_BUDGET_1; + break; + case GNRC_LORAWAN_BACKOFF_STATE_2: + counter = GNRC_LORAWAN_BACKOFF_TIME_2; + state = GNRC_LORAWAN_BACKOFF_STATE_3; + mac->mlme.backoff_budget = GNRC_LORAWAN_BACKOFF_BUDGET_2; + break; + case GNRC_LORAWAN_BACKOFF_STATE_3: + default: + counter = GNRC_LORAWAN_BACKOFF_TIME_3; + mac->mlme.backoff_budget = GNRC_LORAWAN_BACKOFF_BUDGET_3; + break; + } + } + + counter--; + mac->mlme.backoff_state = state << 5 | (counter & 0x1F); + xtimer_set_msg(&mac->mlme.backoff_timer, + GNRC_LORAWAN_BACKOFF_WINDOW_TICK, + &mac->mlme.backoff_msg, thread_getpid()); +} + +static void _mlme_set(gnrc_lorawan_t *mac, const mlme_request_t *mlme_request, + mlme_confirm_t *mlme_confirm) +{ + mlme_confirm->status = -EINVAL; + switch(mlme_request->mib.type) { + case MIB_ACTIVATION_METHOD: + if(mlme_request->mib.activation != MLME_ACTIVATION_OTAA) { + mlme_confirm->status = GNRC_LORAWAN_REQ_STATUS_SUCCESS; + mac->mlme.activation = mlme_request->mib.activation; + } + break; + default: + break; + } +} + +static void _mlme_get(gnrc_lorawan_t *mac, const mlme_request_t *mlme_request, + mlme_confirm_t *mlme_confirm) +{ + switch(mlme_request->mib.type) { + case MIB_ACTIVATION_METHOD: + mlme_confirm->status = GNRC_LORAWAN_REQ_STATUS_SUCCESS; + mlme_confirm->mib.activation = mac->mlme.activation; + break; + default: + mlme_confirm->status = -EINVAL; + break; + } +} + +void gnrc_lorawan_mlme_request(gnrc_lorawan_t *mac, const mlme_request_t *mlme_request, + mlme_confirm_t *mlme_confirm) +{ + switch (mlme_request->type) { + case MLME_JOIN: + if(mac->mlme.activation != MLME_ACTIVATION_NONE) { + mlme_confirm->status = -EINVAL; + break; + } + if (!gnrc_lorawan_mac_acquire(mac)) { + mlme_confirm->status = -EBUSY; + break; + } + + if (mac->mlme.backoff_budget < 0) { + mlme_confirm->status = -EDQUOT; + break; + } + memcpy(mac->appskey, mlme_request->join.appkey, LORAMAC_APPKEY_LEN); + mlme_confirm->status = gnrc_lorawan_send_join_request(mac, mlme_request->join.deveui, + mlme_request->join.appeui, mlme_request->join.appkey, mlme_request->join.dr); + break; + case MLME_LINK_CHECK: + mac->mlme.pending_mlme_opts |= GNRC_LORAWAN_MLME_OPTS_LINK_CHECK_REQ; + mlme_confirm->status = GNRC_LORAWAN_REQ_STATUS_DEFERRED; + break; + case MLME_SET: + _mlme_set(mac, mlme_request, mlme_confirm); + break; + case MLME_GET: + _mlme_get(mac, mlme_request, mlme_confirm); + break; + case MLME_RESET: + gnrc_lorawan_reset(mac); + mlme_confirm->status = GNRC_LORAWAN_REQ_STATUS_SUCCESS; + break; + default: + break; + } +} + +int _fopts_mlme_link_check_req(lorawan_buffer_t *buf) +{ + if (buf) { + assert(buf->index + GNRC_LORAWAN_CID_SIZE <= buf->size); + buf->data[buf->index++] = GNRC_LORAWAN_CID_LINK_CHECK_ANS; + } + + return GNRC_LORAWAN_CID_SIZE; +} + +static void _mlme_link_check_ans(gnrc_lorawan_t *mac, uint8_t *p) +{ + mlme_confirm_t *mlme_confirm = gnrc_lorawan_mlme_allocate(mac); + mlme_confirm->link_req.margin = p[1]; + mlme_confirm->link_req.num_gateways = p[2]; + + mlme_confirm->type = MLME_LINK_CHECK; + mlme_confirm->status = GNRC_LORAWAN_REQ_STATUS_SUCCESS; + mac->netdev.event_callback(&mac->netdev, NETDEV_EVENT_MLME_CONFIRM); + + mac->mlme.pending_mlme_opts &= ~GNRC_LORAWAN_MLME_OPTS_LINK_CHECK_REQ; +} + +void gnrc_lorawan_process_fopts(gnrc_lorawan_t *mac, uint8_t *fopts, size_t size) +{ + if (!fopts || !size) { + return; + } + + uint8_t ret = 0; + void (*cb)(gnrc_lorawan_t*, uint8_t *p) = NULL; + + for(uint8_t pos = 0; pos < size; pos += ret) { + switch (fopts[pos]) { + case GNRC_LORAWAN_CID_LINK_CHECK_ANS: + ret += GNRC_LORAWAN_FOPT_LINK_CHECK_ANS_SIZE; + cb = _mlme_link_check_ans; + break; + default: + return; + } + + if(pos + ret > size) { + return; + } + + cb(mac, &fopts[pos]); + } +} + +uint8_t gnrc_lorawan_build_options(gnrc_lorawan_t *mac, lorawan_buffer_t *buf) +{ + size_t size = 0; + + if(mac->mlme.pending_mlme_opts & GNRC_LORAWAN_MLME_OPTS_LINK_CHECK_REQ) { + size += _fopts_mlme_link_check_req(buf); + } + + return size; +} + +void gnrc_lorawan_mlme_no_rx(gnrc_lorawan_t *mac) +{ + mlme_confirm_t *mlme_confirm = gnrc_lorawan_mlme_allocate(mac); + + mlme_confirm->status = -ETIMEDOUT; + + if (mac->mlme.activation == MLME_ACTIVATION_NONE) { + mlme_confirm->type = MLME_JOIN; + mac->netdev.event_callback(&mac->netdev, NETDEV_EVENT_MLME_CONFIRM); + } + else if (mac->mlme.pending_mlme_opts & GNRC_LORAWAN_MLME_OPTS_LINK_CHECK_REQ) { + mlme_confirm->type = MLME_LINK_CHECK; + mac->netdev.event_callback(&mac->netdev, NETDEV_EVENT_MLME_CONFIRM); + mac->mlme.pending_mlme_opts &= ~GNRC_LORAWAN_MLME_OPTS_LINK_CHECK_REQ; + } +} diff --git a/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_region.c b/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_region.c new file mode 100644 index 0000000000..b845626375 --- /dev/null +++ b/sys/net/gnrc/link_layer/lorawan/gnrc_lorawan_region.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @author José Ignacio Alamos + */ +#include "net/gnrc/lorawan/region.h" + +#define GNRC_LORAWAN_DATARATES_NUMOF (6U) + +static uint8_t dr_sf[GNRC_LORAWAN_DATARATES_NUMOF] = +{ LORA_SF12, LORA_SF11, LORA_SF10, LORA_SF9, LORA_SF8, LORA_SF7 }; +static uint8_t dr_bw[GNRC_LORAWAN_DATARATES_NUMOF] = +{ LORA_BW_125_KHZ, LORA_BW_125_KHZ, LORA_BW_125_KHZ, LORA_BW_125_KHZ, + LORA_BW_125_KHZ, LORA_BW_125_KHZ }; + +int gnrc_lorawan_set_dr(gnrc_lorawan_t *mac, uint8_t datarate) +{ + netdev_t *dev = mac->netdev.lower; + + if (!gnrc_lorawan_validate_dr(datarate)) { + return -EINVAL; + } + uint8_t bw = dr_bw[datarate]; + uint8_t sf = dr_sf[datarate]; + + dev->driver->set(dev, NETOPT_BANDWIDTH, &bw, sizeof(bw)); + dev->driver->set(dev, NETOPT_SPREADING_FACTOR, &sf, sizeof(sf)); + + return 0; +} + +uint8_t gnrc_lorawan_rx1_get_dr_offset(uint8_t dr_up, uint8_t dr_offset) +{ + return (dr_up > dr_offset) ? (dr_up - dr_offset) : 0; +} + +static size_t _get_num_used_channels(gnrc_lorawan_t *mac) +{ + size_t count = 0; + + for (unsigned i = 0; i < GNRC_LORAWAN_MAX_CHANNELS; i++) { + if (mac->channel[i]) { + count++; + } + } + return count; +} + +static uint32_t _get_nth_channel(gnrc_lorawan_t *mac, size_t n) +{ + int i = 0; + uint32_t channel = 0; + + while (n) { + if (mac->channel[i]) { + n--; + channel = mac->channel[i]; + i++; + } + } + return channel; +} + +void gnrc_lorawan_channels_init(gnrc_lorawan_t *mac) +{ + for (unsigned i = 0; i < GNRC_LORAWAN_DEFAULT_CHANNELS_NUMOF; i++) { + mac->channel[i] = gnrc_lorawan_default_channels[i]; + } + + for (unsigned i = GNRC_LORAWAN_DEFAULT_CHANNELS_NUMOF; + i < GNRC_LORAWAN_MAX_CHANNELS; i++) { + mac->channel[i] = 0; + } +} + +uint32_t gnrc_lorawan_pick_channel(gnrc_lorawan_t *mac) +{ + netdev_t *netdev = mac->netdev.lower; + uint32_t random_number; + + netdev->driver->get(netdev, NETOPT_RANDOM, &random_number, + sizeof(random_number)); + + return _get_nth_channel(mac, + 1 + (random_number % _get_num_used_channels(mac))); +} + +void gnrc_lorawan_process_cflist(gnrc_lorawan_t *mac, uint8_t *cflist) +{ + /* TODO: Check CFListType to 0 */ + for (unsigned i = GNRC_LORAWAN_DEFAULT_CHANNELS_NUMOF; i < 8; i++) { + le_uint32_t cl; + cl.u32 = 0; + memcpy(&cl, cflist, GNRC_LORAWAN_CFLIST_ENTRY_SIZE); + mac->channel[i] = byteorder_ntohl(byteorder_ltobl(cl)) * 100; + cflist += GNRC_LORAWAN_CFLIST_ENTRY_SIZE; + } +} + +uint8_t gnrc_lorawan_region_mac_payload_max(uint8_t datarate) +{ + if (datarate < 3) { + return GNRC_LORAWAN_MAX_PAYLOAD_1; + } + else if (datarate == 3) { + return GNRC_LORAWAN_MAX_PAYLOAD_2; + } + else { + return GNRC_LORAWAN_MAX_PAYLOAD_3; + } +} + +bool gnrc_lorawan_validate_dr(uint8_t dr) +{ + if (dr < GNRC_LORAWAN_DATARATES_NUMOF) { + return true; + } + return false; +} + +/** @} */ diff --git a/sys/net/gnrc/link_layer/lorawan/include/gnrc_lorawan_internal.h b/sys/net/gnrc/link_layer/lorawan/include/gnrc_lorawan_internal.h new file mode 100644 index 0000000000..e9dedbe1bd --- /dev/null +++ b/sys/net/gnrc/link_layer/lorawan/include/gnrc_lorawan_internal.h @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup net_gnrc_lorawan + * @{ + * + * @file + * @brief GNRC LoRaWAN internal header + * + * @author Jose Ignacio Alamos + */ +#ifndef GNRC_LORAWAN_INTERNAL_H +#define GNRC_LORAWAN_INTERNAL_H + +#include +#include +#include "iolist.h" +#include "net/lora.h" +#include "net/lorawan/hdr.h" +#include "net/gnrc/pktbuf.h" +#include "xtimer.h" +#include "msg.h" +#include "net/netdev.h" +#include "net/netdev/layer.h" +#include "net/loramac.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MSG_TYPE_TIMEOUT (0x3457) /**< Timeout message type */ +#define MSG_TYPE_MCPS_ACK_TIMEOUT (0x3458) /**< ACK timeout message type */ +#define MSG_TYPE_MLME_BACKOFF_EXPIRE (0x3459) /**< Backoff timer expiration message type */ + +#define MTYPE_MASK 0xE0 /**< MHDR mtype mask */ +#define MTYPE_JOIN_REQUEST 0x0 /**< Join Request type */ +#define MTYPE_JOIN_ACCEPT 0x1 /**< Join Accept type */ +#define MTYPE_UNCNF_UPLINK 0x2 /**< Unconfirmed uplink type */ +#define MTYPE_UNCNF_DOWNLINK 0x3 /**< Unconfirmed downlink type */ +#define MTYPE_CNF_UPLINK 0x4 /**< Confirmed uplink type */ +#define MTYPE_CNF_DOWNLINK 0x5 /**< Confirmed downlink type */ +#define MTYPE_REJOIN_REQ 0x6 /**< Re-join request type */ +#define MTYPE_PROPIETARY 0x7 /**< Propietary frame type */ + +#define MAJOR_MASK 0x3 /**< Major mtype mask */ +#define MAJOR_LRWAN_R1 0x0 /**< LoRaWAN R1 version type */ + +#define JOIN_REQUEST_SIZE (23U) /**< Join Request size in bytes */ +#define MIC_SIZE (4U) /**< MIC size in bytes */ +#define CFLIST_SIZE (16U) /**< Channel Frequency list size in bytes */ + +#define GNRC_LORAWAN_MAX_CHANNELS (16U) /**< Maximum number of channels */ + +#define LORAWAN_STATE_IDLE (0) /**< MAC state machine in idle */ +#define LORAWAN_STATE_RX_1 (1) /**< MAC state machine in RX1 */ +#define LORAWAN_STATE_RX_2 (2) /**< MAC state machine in RX2 */ +#define LORAWAN_STATE_TX (3) /**< MAC state machine in TX */ + +#define GNRC_LORAWAN_DIR_UPLINK (0U) /**< uplink frame direction */ +#define GNRC_LORAWAN_DIR_DOWNLINK (1U) /**< downlink frame direction */ + +#define GNRC_LORAWAN_BACKOFF_WINDOW_TICK (3600000000LL) /**< backoff expire tick in usecs (set to 1 second) */ + +#define GNRC_LORAWAN_BACKOFF_BUDGET_1 (36000000LL) /**< budget of time on air during the first hour */ +#define GNRC_LORAWAN_BACKOFF_BUDGET_2 (36000000LL) /**< budget of time on air between 1-10 hours after boot */ +#define GNRC_LORAWAN_BACKOFF_BUDGET_3 (8700000LL) /**< budget of time on air every 24 hours */ + +#define GNRC_LORAWAN_MLME_OPTS_LINK_CHECK_REQ (1 << 0) /**< Internal Link Check request flag */ + +#define GNRC_LORAWAN_CID_SIZE (1U) /**< size of Command ID in FOps */ +#define GNRC_LORAWAN_CID_LINK_CHECK_ANS (0x02) /**< Link Check CID */ + +#define GNRC_LORAWAN_FOPT_LINK_CHECK_ANS_SIZE (3U) /**< size of Link check answer */ + +#define GNRC_LORAWAN_JOIN_DELAY_U32_MASK (0x1FFFFF) /**< mask for detecting overflow in frame counter */ + +#define GNRC_LORAWAN_MAX_PAYLOAD_1 (59U) /**< max MAC payload in DR0, DR1 and DR2 */ +#define GNRC_LORAWAN_MAX_PAYLOAD_2 (123U) /**< max MAC payload in DR3 */ +#define GNRC_LORAWAN_MAX_PAYLOAD_3 (250U) /**< max MAC payload above DR3 */ + +#define GNRC_LORAWAN_CFLIST_ENTRY_SIZE (3U) /**< size of Channel Frequency list */ +#define GNRC_LORAWAN_JOIN_ACCEPT_MAX_SIZE (33U) /**< max size of Join Accept frame */ + +#define GNRC_LORAWAN_BACKOFF_STATE_1 (0U) /**< backoff state during the first hour after boot */ +#define GNRC_LORAWAN_BACKOFF_STATE_2 (1U) /**< backoff state between 1-10 hours after boot */ +#define GNRC_LORAWAN_BACKOFF_STATE_3 (2U) /**< backoff state past 11 hours after boot */ + +#define GNRC_LORAWAN_BACKOFF_TIME_1 (1U) /**< duration of first backoff state (in hours) */ +#define GNRC_LORAWAN_BACKOFF_TIME_2 (10U) /**< duration of second backoff state (in hours) */ +#define GNRC_LORAWAN_BACKOFF_TIME_3 (24U) /**< duration of third backoff state (in hours) */ + +#define GNRC_LORAWAN_APP_NONCE_SIZE (3U) /**< App Nonce size */ +#define GNRC_LORAWAN_NET_ID_SIZE (3U) /**< Net ID size */ +#define GNRC_LORAWAN_DEV_NONCE_SIZE (2U) /**< Dev Nonce size */ + +/** + * @brief buffer helper for parsing and constructing LoRaWAN packets. + */ +typedef struct { + uint8_t *data; /**< pointer to the beginning of the buffer holding data */ + uint8_t size; /**< size of the buffer */ + uint8_t index; /**< current inxed in the buffer */ +} lorawan_buffer_t; + +/** + * @brief MLME Join Request data + */ +typedef struct { + void *deveui; /**< pointer to the Device EUI */ + void *appeui; /**< pointer to the Application EUI */ + void *appkey; /**< pointer to the Application Key */ + uint8_t dr; /**< datarate for the Join Request */ +} mlme_lorawan_join_t; + +/** + * @brief MLME Link Check confirmation data + */ +typedef struct { + uint8_t margin; /**< demodulation margin (in dB) */ + uint8_t num_gateways; /**< number of gateways */ +} mlme_link_req_confirm_t; + +/** + * @brief MCPS data + */ +typedef struct { + gnrc_pktsnip_t *pkt; /**< packet of the request */ + uint8_t port; /**< port of the request */ + uint8_t dr; /**< datarate of the request */ +} mcps_data_t; + +/** + * @brief MCPS service access point descriptor + */ +typedef struct { + uint32_t fcnt; /**< uplink framecounter */ + uint32_t fcnt_down; /**< downlink frame counter */ + gnrc_pktsnip_t *outgoing_pkt; /**< holds the outgoing packet in case of retransmissions */ + int nb_trials; /**< holds the remaining number of retransmissions */ + int ack_requested; /**< wether the network server requested an ACK */ + int waiting_for_ack; /**< true if the MAC layer is waiting for an ACK */ +} gnrc_lorawan_mcps_t; + +/** + * @brief MLME service access point descriptor + */ +typedef struct { + xtimer_t backoff_timer; /**< timer used for backoff expiration */ + msg_t backoff_msg; /**< msg for backoff expiration */ + uint8_t activation; /**< Activation mechanism of the MAC layer */ + int pending_mlme_opts; /**< holds pending mlme opts */ + uint32_t nid; /**< current Network ID */ + int32_t backoff_budget; /**< remaining Time On Air budget */ + uint8_t dev_nonce[2]; /**< Device Nonce */ + uint8_t backoff_state; /**< state in the backoff state machine */ +} gnrc_lorawan_mlme_t; + +/** + * @brief GNRC LoRaWAN mac descriptor */ +typedef struct { + netdev_t netdev; /**< netdev for the MAC layer */ + xtimer_t rx; /**< RX timer */ + msg_t msg; /**< MAC layer message descriptor */ + gnrc_lorawan_mcps_t mcps; /**< MCPS descriptor */ + gnrc_lorawan_mlme_t mlme; /**< MLME descriptor */ + void *mlme_buf; /**< pointer to MLME buffer */ + void *mcps_buf; /**< pointer to MCPS buffer */ + uint8_t *nwkskey; /**< pointer to Network SKey buffer */ + uint8_t *appskey; /**< pointer to Application SKey buffer */ + uint32_t channel[GNRC_LORAWAN_MAX_CHANNELS]; /**< channel array */ + uint32_t toa; /**< Time on Air of the last transmission */ + int busy; /**< MAC busy */ + int shutdown_req; /**< MAC Shutdown request */ + le_uint32_t dev_addr; /**< Device address */ + int state; /**< state of MAC layer */ + uint8_t dl_settings; /**< downlink settings */ + uint8_t rx_delay; /**< Delay of first reception window */ + uint8_t dr_range[GNRC_LORAWAN_MAX_CHANNELS]; /**< Datarate Range for all channels */ + uint8_t last_dr; /**< datarate of the last transmission */ +} gnrc_lorawan_t; + +/** + * @brief Encrypts LoRaWAN payload + * + * @note This function is also used for decrypting a LoRaWAN packet. The LoRaWAN server encrypts the packet using decryption, so the end device only needs to implement encryption + * + * @param[in] iolist packet iolist representation + * @param[in] dev_addr device address + * @param[in] fcnt frame counter + * @param[in] dir direction of the packet (0 if uplink, 1 if downlink) + * @param[in] appskey pointer to the Application Session Key + */ +void gnrc_lorawan_encrypt_payload(iolist_t *iolist, const le_uint32_t *dev_addr, uint32_t fcnt, uint8_t dir, const uint8_t *appskey); + +/** + * @brief Decrypts join accept message + * + * @param[in] key key to be used in the decryption + * @param[in] pkt pointer to Join Accept MAC component (next byte after the MHDR) + * @param[in] has_clist true if the Join Accept frame has CFList + * @param[out] out buffer where the decryption is stored + */ +void gnrc_lorawan_decrypt_join_accept(const uint8_t *key, uint8_t *pkt, int has_clist, uint8_t *out); + +/** + * @brief Generate LoRaWAN session keys + * + * Intended to be called after a successfull Join Request in order to generate + * NwkSKey and AppSKey + * + * @param[in] app_nonce pointer to the app_nonce of the Join Accept message + * @param[in] dev_nonce pointer to the dev_nonce buffer + * @param[in] appkey pointer to eh AppKey + * @param[out] nwkskey pointer to the NwkSKey + * @param[out] appskey pointer to the AppSKey + */ +void gnrc_lorawan_generate_session_keys(const uint8_t *app_nonce, const uint8_t *dev_nonce, const uint8_t *appkey, uint8_t *nwkskey, uint8_t *appskey); + +/** + * @brief Set datarate for the next transmission + * + * @param[in] mac pointer to the MAC descriptor + * @param[in] datarate desired datarate + * + * @return 0 on success + * @return -EINVAL if datarate is not available in the current region + */ +int gnrc_lorawan_set_dr(gnrc_lorawan_t *mac, uint8_t datarate); + +/** + * @brief build uplink frame + * + * @param[in] mac pointer to MAC descriptor + * @param[in] payload packet containing payload + * @param[in] confirmed_data true if confirmed frame + * @param[in] port MAC port + * + * @return full LoRaWAN frame including payload + * @return NULL if packet buffer is full. `payload` is released + */ +gnrc_pktsnip_t *gnrc_lorawan_build_uplink(gnrc_lorawan_t *mac, gnrc_pktsnip_t *payload, int confirmed_data, uint8_t port); + +/** + * @brief pick a random available LoRaWAN channel + * + * @param[in] mac pointer to the MAC descriptor + * + * @return a free channel + */ +uint32_t gnrc_lorawan_pick_channel(gnrc_lorawan_t *mac); + +/** + * @brief Build fopts header + * + * @param[in] mac pointer to MAC descriptor + * @param[out] buf destination buffer of fopts. If NULL, this function just returns + * the size of the expected fopts frame. + * + * @return size of the fopts frame + */ +uint8_t gnrc_lorawan_build_options(gnrc_lorawan_t *mac, lorawan_buffer_t *buf); + +/** + * @brief Process an fopts frame + * + * @param[in] mac pointer to MAC descriptor + * @param[in] fopts pointer to fopts frame + * @param[in] size size of fopts frame + */ +void gnrc_lorawan_process_fopts(gnrc_lorawan_t *mac, uint8_t *fopts, size_t size); + +/** + * @brief calculate join Message Integrity Code + * + * @param[in] io iolist representation of the packet + * @param[in] key key used to calculate the MIC + * @param[out] out calculated MIC + */ +void gnrc_lorawan_calculate_join_mic(const iolist_t *io, const uint8_t *key, le_uint32_t *out); + +/** + * @brief Calculate Message Integrity Code for a MCPS message + * + * @param[in] dev_addr the Device Address + * @param[in] fcnt frame counter + * @param[in] dir direction of the packet (0 is uplink, 1 is downlink) + * @param[in] pkt the pkt + * @param[in] nwkskey pointer to the Network Session Key + * @param[out] out calculated MIC + */ +void gnrc_lorawan_calculate_mic(const le_uint32_t *dev_addr, uint32_t fcnt, + uint8_t dir, iolist_t *pkt, const uint8_t *nwkskey, le_uint32_t *out); + +/** + * @brief Build a MCPS LoRaWAN header + * + * @param[in] mtype the MType of the header + * @param[in] dev_addr the Device Address + * @param[in] fcnt frame counter + * @param[in] ack true if ACK bit is set + * @param[in] fopts_length the length of the FOpts field + * @param[out] buf destination buffer of the hdr + * + * @return the size of the header + */ +size_t gnrc_lorawan_build_hdr(uint8_t mtype, le_uint32_t *dev_addr, uint32_t fcnt, uint8_t ack, uint8_t fopts_length, lorawan_buffer_t *buf); + +/** + * @brief Process an MCPS downlink message (confirmable or non comfirmable) + * + * @param[in] mac pointer to the MAC descriptor + * @param[in] pkt pointer to the downlink message + */ +void gnrc_lorawan_mcps_process_downlink(gnrc_lorawan_t *mac, gnrc_pktsnip_t *pkt); + +/** + * @brief Init regional channel settings. + * + * Intended to be called upon initialization + * + * @param[in] mac pointer to the MAC descriptor + */ +void gnrc_lorawan_channels_init(gnrc_lorawan_t *mac); + +/** + * @brief Reset MAC parameters + * + * @note This doesn't affect backoff timers variables. + * + * @param[in] mac pointer to the MAC layer + */ +void gnrc_lorawan_reset(gnrc_lorawan_t *mac); + +/** + * @brief Send a LoRaWAN packet + * + * @param[in] mac pointer to the MAC descriptor + * @param[in] pkt the packet to be sent + * @param[in] dr the datarate used for the transmission + */ +void gnrc_lorawan_send_pkt(gnrc_lorawan_t *mac, gnrc_pktsnip_t *pkt, uint8_t dr); + +/** + * @brief Process join accept message + * + * @param[in] mac pointer to the MAC descriptor + * @param[in] pkt the Join Accept packet + */ +void gnrc_lorawan_mlme_process_join(gnrc_lorawan_t *mac, gnrc_pktsnip_t *pkt); + +/** + * @brief Inform the MAC layer that no packet was received during reception. + * + * To be called when the radio reports "NO RX" after the second reception + * window + * + * @param[in] mac pointer to the MAC descriptor + */ +void gnrc_lorawan_mlme_no_rx(gnrc_lorawan_t *mac); + +/** + * @brief Trigger a MCPS event + * + * @param[in] mac pointer to the MAC descriptor + * @param[in] event the event to be processed. + * @param[in] data set to true if the packet contains payload + */ +void gnrc_lorawan_mcps_event(gnrc_lorawan_t *mac, int event, int data); + +/** + * @brief Get the maximum MAC payload (M value) for a given datarate. + * + * @note This function is region specific + * + * @param[in] datarate datarate + * + * @return the maximum allowed size of the packet + */ +uint8_t gnrc_lorawan_region_mac_payload_max(uint8_t datarate); + +/** + * @brief MLME Backoff expiration tick + * + * Should be called every hour in order to maintain the Time On Air budget. + * + * @param[in] mac pointer to the MAC descriptor + */ +void gnrc_lorawan_mlme_backoff_expire(gnrc_lorawan_t *mac); + +/** + * @brief Process and dispatch a full LoRaWAN packet + * + * Intended to be called right after reception from the radio + * + * @param[in] mac pointer to the MAC descriptor + * @param[in] pkt the received packet + */ +void gnrc_lorawan_process_pkt(gnrc_lorawan_t *mac, gnrc_pktsnip_t *pkt); + +/** + * @brief Open a reception window + * + * This is called by the MAC layer on timeout event. + * + * @param[in] mac pointer to the MAC descriptor + */ +void gnrc_lorawan_open_rx_window(gnrc_lorawan_t *mac); + +/** + * @brief save internal MAC state in non-volatile storage and shutdown + * the MAC layer gracefully. + * + * @param mac + */ +void gnrc_lorawan_perform_save(gnrc_lorawan_t *mac); + +/** + * @brief Acquire the MAC layer + * + * @param[in] mac pointer to the MAC descriptor + * + * @return true on success + * @return false if MAC is already acquired + */ +static inline int gnrc_lorawan_mac_acquire(gnrc_lorawan_t *mac) +{ + int _c = mac->busy; + + mac->busy = true; + return !_c; +} + +/** + * @brief Release the MAC layer + * + * @param[in] mac pointer to the MAC descriptor + */ +static inline void gnrc_lorawan_mac_release(gnrc_lorawan_t *mac) +{ + mac->busy = false; +} + +/** + * @brief Allocate memory to hold a GNRC LoRaWAN MCPS request + * + * @param[in] mac pointer to the MAC descriptor + * + * @return pointer the allocated buffer + */ +static inline void *gnrc_lorawan_mcps_allocate(gnrc_lorawan_t *mac) +{ + mac->netdev.event_callback((netdev_t *) mac, NETDEV_EVENT_MCPS_GET_BUFFER); + return mac->mcps_buf; +} + +/** + * @brief Allocate memory to hold a GNRC LoRaWAN MLME request + * + * @param[in] mac pointer to the MAC descriptor + * + * @return pointer the allocated buffer + */ +static inline void *gnrc_lorawan_mlme_allocate(gnrc_lorawan_t *mac) +{ + mac->netdev.event_callback((netdev_t *) mac, NETDEV_EVENT_MLME_GET_BUFFER); + return mac->mlme_buf; +} + + +#ifdef __cplusplus +} +#endif + +#endif /* GNRC_LORAWAN_INTERNAL_H */ +/** @} */ diff --git a/sys/net/gnrc/netif/Makefile b/sys/net/gnrc/netif/Makefile index f615ac1e31..872b604667 100644 --- a/sys/net/gnrc/netif/Makefile +++ b/sys/net/gnrc/netif/Makefile @@ -9,5 +9,8 @@ endif ifneq (,$(filter gnrc_netif_hdr,$(USEMODULE))) DIRS += hdr endif +ifneq (,$(filter gnrc_netif_lorawan,$(USEMODULE))) + DIRS += lorawan +endif include $(RIOTBASE)/Makefile.base diff --git a/sys/net/gnrc/netif/lorawan/Makefile b/sys/net/gnrc/netif/lorawan/Makefile new file mode 100644 index 0000000000..44f08f7c42 --- /dev/null +++ b/sys/net/gnrc/netif/lorawan/Makefile @@ -0,0 +1,3 @@ +MODULE := gnrc_netif_lorawan + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/gnrc/netif/lorawan/gnrc_netif_lorawan.c b/sys/net/gnrc/netif/lorawan/gnrc_netif_lorawan.c new file mode 100644 index 0000000000..9bc95a20b1 --- /dev/null +++ b/sys/net/gnrc/netif/lorawan/gnrc_netif_lorawan.c @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2018 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @author Jose Ignacio Alamos + */ + +#include "net/gnrc/pktbuf.h" +#include "net/gnrc/netif.h" +#include "net/gnrc/netif/lorawan.h" +#include "net/gnrc/netif/internal.h" +#include "net/gnrc/lorawan.h" +#include "net/netdev.h" +#include "net/lora.h" +#include "net/loramac.h" +#include "net/gnrc/netreg.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static uint8_t _nwkskey[LORAMAC_NWKSKEY_LEN] = LORAMAC_NWK_SKEY_DEFAULT; +static uint8_t _appskey[LORAMAC_APPSKEY_LEN] = LORAMAC_APP_SKEY_DEFAULT; +static uint8_t _appkey[LORAMAC_APPKEY_LEN] = LORAMAC_APP_KEY_DEFAULT; +static uint8_t _deveui[LORAMAC_DEVEUI_LEN] = LORAMAC_DEV_EUI_DEFAULT; +static uint8_t _appeui[LORAMAC_APPEUI_LEN] = LORAMAC_APP_EUI_DEFAULT; +static uint8_t _devaddr[LORAMAC_DEVADDR_LEN] = LORAMAC_DEV_ADDR_DEFAULT; + +static int _send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt); +static gnrc_pktsnip_t *_recv(gnrc_netif_t *netif); +static void _msg_handler(gnrc_netif_t *netif, msg_t *msg); +static int _get(gnrc_netif_t *netif, gnrc_netapi_opt_t *opt); +static int _set(gnrc_netif_t *netif, const gnrc_netapi_opt_t *opt); +static void _init(gnrc_netif_t *netif); + +static const gnrc_netif_ops_t lorawan_ops = { + .init = _init, + .send = _send, + .recv = _recv, + .get = _get, + .set = _set, + .msg_handler = _msg_handler +}; + +static uint8_t _mcps_buffer[sizeof(mcps_confirm_t) > sizeof(mcps_indication_t) ? + sizeof(mcps_confirm_t) : sizeof(mcps_indication_t)]; +static uint8_t _mlme_buffer[sizeof(mlme_confirm_t) > sizeof(mlme_indication_t) ? + sizeof(mlme_confirm_t) : sizeof(mlme_indication_t)]; + +static void _mlme_confirm(gnrc_netif_t *netif, mlme_confirm_t *confirm) +{ + if (confirm->type == MLME_JOIN) { + if (confirm->status == 0) { + DEBUG("gnrc_lorawan: join succeeded\n"); + } + else { + DEBUG("gnrc_lorawan: join failed\n"); + } + } + else if (confirm->type == MLME_LINK_CHECK) { + netif->lorawan.flags &= ~GNRC_NETIF_LORAWAN_FLAGS_LINK_CHECK; + netif->lorawan.demod_margin = confirm->link_req.margin; + netif->lorawan.num_gateways = confirm->link_req.num_gateways; + } +} + +static void _mac_cb(netdev_t *dev, netdev_event_t event) +{ + gnrc_lorawan_t *mac = (gnrc_lorawan_t *) dev; + + mcps_confirm_t *mcps_confirm; + mcps_indication_t *mcps_indication; + + switch (event) { + case NETDEV_EVENT_MLME_INDICATION: + /* ignore */ + break; + case NETDEV_EVENT_MCPS_INDICATION: + mcps_indication = mac->mcps_buf; + if (!gnrc_netapi_dispatch_receive(GNRC_NETTYPE_LORAWAN, mcps_indication->data.port, mcps_indication->data.pkt)) { + gnrc_pktbuf_release(mcps_indication->data.pkt); + } + break; + case NETDEV_EVENT_MLME_CONFIRM: + _mlme_confirm((gnrc_netif_t *) mac->netdev.context, mac->mlme_buf); + break; + case NETDEV_EVENT_MCPS_CONFIRM: + mcps_confirm = mac->mcps_buf; + if (mcps_confirm->status == 0) { + gnrc_pktbuf_release(mac->mcps.outgoing_pkt); + } + else { + gnrc_pktbuf_release_error(mac->mcps.outgoing_pkt, 1); + } + mac->mcps.outgoing_pkt = NULL; + break; + case NETDEV_EVENT_MLME_GET_BUFFER: + mac->mlme_buf = _mlme_buffer; + break; + case NETDEV_EVENT_MCPS_GET_BUFFER: + mac->mcps_buf = _mcps_buffer; + break; + default: + netdev_event_cb_pass(dev, event); + break; + } +} + +static void _driver_cb(netdev_t *dev, netdev_event_t event) +{ + gnrc_lorawan_t *mac = (gnrc_lorawan_t *) dev->context; + gnrc_netif_t *netif = (gnrc_netif_t *) mac->netdev.context; + + if (event == NETDEV_EVENT_ISR) { + msg_t msg = { .type = NETDEV_MSG_TYPE_EVENT, + .content = { .ptr = netif } }; + + if (msg_send(&msg, netif->pid) <= 0) { + DEBUG("gnrc_netif: possibly lost interrupt.\n"); + } + } + else { + DEBUG("gnrc_netif: event triggered -> %i\n", event); + switch (event) { + case NETDEV_EVENT_RX_COMPLETE: + gnrc_lorawan_recv(mac); + break; + case NETDEV_EVENT_TX_COMPLETE: + gnrc_lorawan_event_tx_complete(mac); + break; + case NETDEV_EVENT_RX_TIMEOUT: + gnrc_lorawan_event_timeout(mac); + break; + default: + DEBUG("gnrc_netif: warning: unhandled event %u.\n", event); + break; + } + } +} + +static void _reset(gnrc_netif_t *netif) +{ + netif->lorawan.otaa = LORAMAC_DEFAULT_JOIN_PROCEDURE == LORAMAC_JOIN_OTAA ? NETOPT_ENABLE : NETOPT_DISABLE; + netif->lorawan.datarate = LORAMAC_DEFAULT_DR; + netif->lorawan.demod_margin = 0; + netif->lorawan.num_gateways = 0; + netif->lorawan.port = LORAMAC_DEFAULT_TX_PORT; + netif->lorawan.ack_req = LORAMAC_DEFAULT_TX_MODE == LORAMAC_TX_CNF; + netif->lorawan.flags = 0; +} + +static void _memcpy_reversed(uint8_t *dst, uint8_t *src, size_t size) +{ + for(size_t i=0;idev->event_callback = _driver_cb; + netif->lorawan.mac.netdev.event_callback = _mac_cb; + netif->lorawan.mac.netdev.context = netif; + _reset(netif); + + /* Initialize default keys, address and EUIs */ + memcpy(netif->lorawan.nwkskey, _nwkskey, sizeof(_nwkskey)); + memcpy(netif->lorawan.appskey, _appskey, sizeof(_appskey)); + _memcpy_reversed(netif->lorawan.deveui, _deveui, sizeof(_deveui)); + memcpy(netif->lorawan.appkey, _appkey, sizeof(_appkey)); + _memcpy_reversed(netif->lorawan.appeui, _appeui, sizeof(_appeui)); + + gnrc_lorawan_setup(&netif->lorawan.mac, netif->dev); + netif->lorawan.mac.netdev.driver->set(&netif->lorawan.mac.netdev, NETOPT_ADDRESS, _devaddr, sizeof(_devaddr)); + gnrc_lorawan_init(&netif->lorawan.mac, netif->lorawan.nwkskey, netif->lorawan.appskey); +} + +gnrc_netif_t *gnrc_netif_lorawan_create(char *stack, int stacksize, + char priority, char *name, + netdev_t *dev) +{ + return gnrc_netif_create(stack, stacksize, priority, name, dev, + &lorawan_ops); +} + +static gnrc_pktsnip_t *_recv(gnrc_netif_t *netif) +{ + (void) netif; + /* Unused */ + return 0; +} + +static int _send(gnrc_netif_t *netif, gnrc_pktsnip_t *payload) +{ + mlme_request_t mlme_request; + mlme_confirm_t mlme_confirm; + + if (netif->lorawan.flags & GNRC_NETIF_LORAWAN_FLAGS_LINK_CHECK) { + mlme_request.type = MLME_LINK_CHECK; + gnrc_lorawan_mlme_request(&netif->lorawan.mac, &mlme_request, &mlme_confirm); + } + mcps_request_t req = { .type = netif->lorawan.ack_req ? MCPS_CONFIRMED : MCPS_UNCONFIRMED, + .data = { .pkt = payload, .port = netif->lorawan.port, + .dr = netif->lorawan.datarate } }; + mcps_confirm_t conf; + gnrc_lorawan_mcps_request(&netif->lorawan.mac, &req, &conf); + return conf.status; +} + +static void _msg_handler(gnrc_netif_t *netif, msg_t *msg) +{ + (void) netif; + (void) msg; + switch (msg->type) { + case MSG_TYPE_TIMEOUT: + gnrc_lorawan_open_rx_window(&netif->lorawan.mac); + break; + case MSG_TYPE_MCPS_ACK_TIMEOUT: + gnrc_lorawan_mcps_event(&netif->lorawan.mac, MCPS_EVENT_ACK_TIMEOUT, 0); + break; + case MSG_TYPE_MLME_BACKOFF_EXPIRE: + gnrc_lorawan_mlme_backoff_expire(&netif->lorawan.mac); + default: + break; + } +} + +static int _get(gnrc_netif_t *netif, gnrc_netapi_opt_t *opt) +{ + int res = 0; + + mlme_confirm_t mlme_confirm; + mlme_request_t mlme_request; + switch (opt->opt) { + case NETOPT_OTAA: + assert(opt->data_len >= sizeof(netopt_enable_t)); + *((netopt_enable_t *) opt->data) = netif->lorawan.otaa; + break; + case NETOPT_LINK_CONNECTED: + mlme_request.type = MLME_GET; + mlme_request.mib.type = MIB_ACTIVATION_METHOD; + gnrc_lorawan_mlme_request(&netif->lorawan.mac, &mlme_request, &mlme_confirm); + *((netopt_enable_t *) opt->data) = mlme_confirm.mib.activation != MLME_ACTIVATION_NONE; + break; + case NETOPT_LINK_CHECK: + assert(opt->data_len == sizeof(netopt_enable_t)); + *((netopt_enable_t *) opt->data) = (netif->lorawan.flags & GNRC_NETIF_LORAWAN_FLAGS_LINK_CHECK) ? + NETOPT_ENABLE : NETOPT_DISABLE; + break; + case NETOPT_NUM_GATEWAYS: + assert(opt->data_len == sizeof(uint8_t)); + *((uint8_t *) opt->data) = netif->lorawan.num_gateways; + break; + case NETOPT_DEMOD_MARGIN: + assert(opt->data_len == sizeof(uint8_t)); + *((uint8_t *) opt->data) = netif->lorawan.demod_margin; + break; + default: + res = netif->lorawan.mac.netdev.driver->get(&netif->lorawan.mac.netdev, opt->opt, opt->data, opt->data_len); + break; + } + return res; +} + +static int _set(gnrc_netif_t *netif, const gnrc_netapi_opt_t *opt) +{ + int res = 0; + mlme_confirm_t mlme_confirm; + mlme_request_t mlme_request; + + gnrc_netif_acquire(netif); + switch (opt->opt) { + case NETOPT_LORAWAN_DR: + assert(opt->data_len == sizeof(uint8_t)); + netif->lorawan.datarate = *((uint8_t *) opt->data); + break; + case NETOPT_LORAWAN_TX_PORT: + assert(opt->data_len == sizeof(uint8_t)); + netif->lorawan.port = *((uint8_t *) opt->data); + break; + case NETOPT_ACK_REQ: + assert(opt->data_len == sizeof(netopt_enable_t)); + netif->lorawan.ack_req = *((netopt_enable_t *) opt->data); + break; + case NETOPT_LORAWAN_APPKEY: + assert(opt->data_len == LORAMAC_APPKEY_LEN); + memcpy(netif->lorawan.appkey, opt->data, LORAMAC_APPKEY_LEN); + break; + case NETOPT_ADDRESS_LONG: + assert(opt->data_len == LORAMAC_DEVEUI_LEN); + _memcpy_reversed(netif->lorawan.deveui, opt->data, LORAMAC_DEVEUI_LEN); + break; + case NETOPT_LORAWAN_APPEUI: + assert(opt->data_len == LORAMAC_APPEUI_LEN); + _memcpy_reversed(netif->lorawan.appeui, opt->data, LORAMAC_APPEUI_LEN); + break; + case NETOPT_OTAA: + assert(opt->data_len == sizeof(netopt_enable_t)); + netif->lorawan.otaa = *((netopt_enable_t *) opt->data); + break; + case NETOPT_LORAWAN_APPSKEY: + assert(opt->data_len >= LORAMAC_APPSKEY_LEN); + memcpy(netif->lorawan.appskey, opt->data, LORAMAC_APPSKEY_LEN); + break; + case NETOPT_LORAWAN_NWKSKEY: + assert(opt->data_len >= LORAMAC_NWKSKEY_LEN); + memcpy(netif->lorawan.nwkskey, opt->data, LORAMAC_NWKSKEY_LEN); + break; + case NETOPT_LINK_CONNECTED: + { + netopt_enable_t en = *((netopt_enable_t *) opt->data); + if (en) { + if(netif->lorawan.otaa) { + mlme_request.type = MLME_JOIN; + mlme_request.join.deveui = netif->lorawan.deveui; + mlme_request.join.appeui = netif->lorawan.appeui; + mlme_request.join.appkey = netif->lorawan.appkey; + mlme_request.join.dr = netif->lorawan.datarate; + gnrc_lorawan_mlme_request(&netif->lorawan.mac, &mlme_request, &mlme_confirm); + } + else { + mlme_request.type = MLME_SET; + mlme_request.mib.activation = MLME_ACTIVATION_ABP; + gnrc_lorawan_mlme_request(&netif->lorawan.mac, &mlme_request, &mlme_confirm); + } + } + else { + mlme_request.type = MLME_RESET; + gnrc_lorawan_mlme_request(&netif->lorawan.mac, &mlme_request, &mlme_confirm); + res = mlme_confirm.status; + if (mlme_confirm.status == 0) { + /* reset netif as well */ + _reset(netif); + } + } + break; + } + case NETOPT_LINK_CHECK: + netif->lorawan.flags |= GNRC_NETIF_LORAWAN_FLAGS_LINK_CHECK; + break; + default: + res = netif->lorawan.mac.netdev.driver->set(&netif->lorawan.mac.netdev, opt->opt, opt->data, opt->data_len); + break; + } + gnrc_netif_release(netif); + return res; +} +/** @} */ diff --git a/sys/net/gnrc/pktdump/gnrc_pktdump.c b/sys/net/gnrc/pktdump/gnrc_pktdump.c index cd52f9797a..cea6b119af 100644 --- a/sys/net/gnrc/pktdump/gnrc_pktdump.c +++ b/sys/net/gnrc/pktdump/gnrc_pktdump.c @@ -112,6 +112,12 @@ static void _dump_snip(gnrc_pktsnip_t *pkt) od_hex_dump(pkt->data, pkt->size, OD_WIDTH_DEFAULT); break; #endif +#ifdef MODULE_GNRC_LORAWAN + case GNRC_NETTYPE_LORAWAN: + printf("NETTYPE_LORAWAN (%i)\n", pkt->type); + od_hex_dump(pkt->data, pkt->size, OD_WIDTH_DEFAULT); + break; +#endif #ifdef TEST_SUITES case GNRC_NETTYPE_TEST: printf("NETTYPE_TEST (%i)\n", pkt->type); diff --git a/sys/shell/commands/sc_gnrc_netif.c b/sys/shell/commands/sc_gnrc_netif.c index 510b609888..09b5e85537 100644 --- a/sys/shell/commands/sc_gnrc_netif.c +++ b/sys/shell/commands/sc_gnrc_netif.c @@ -26,6 +26,8 @@ #include "net/gnrc/netif.h" #include "net/gnrc/netif/hdr.h" #include "net/lora.h" +#include "net/loramac.h" +#include "fmt.h" #ifdef MODULE_NETSTATS #include "net/netstats.h" @@ -70,6 +72,8 @@ static const struct { { "rx_single", NETOPT_SINGLE_RECEIVE }, { "chan_hop", NETOPT_CHANNEL_HOP }, { "checksum", NETOPT_CHECKSUM }, + { "otaa", NETOPT_OTAA }, + { "link_check", NETOPT_LINK_CHECK }, }; /* utility functions */ @@ -169,6 +173,13 @@ static void _set_usage(char *cmd_name) " * \"bw\" - alias for channel bandwidth\n" " * \"sf\" - alias for spreading factor\n" " * \"cr\" - alias for coding rate\n" + " * \"appeui\" - sets Application EUI\n" + " * \"appkey\" - sets Application key\n" + " * \"appskey\" - sets Application session key\n" + " * \"deveui\" - sets Device EUI\n" + " * \"dr\" - sets datarate\n" + " * \"rx2_dr\" - sets datarate of RX2 (lorawan)\n" + " * \"nwkskey\" - sets Network Session Key\n" #endif " * \"power\" - TX power in dBm\n" " * \"retrans\" - max. number of retransmissions\n" @@ -219,6 +230,22 @@ static void _print_netopt(netopt_t opt) printf("long address"); break; + case NETOPT_LORAWAN_APPKEY: + printf("AppKey"); + break; + + case NETOPT_LORAWAN_APPSKEY: + printf("AppSKey"); + break; + + case NETOPT_LORAWAN_NWKSKEY: + printf("NwkSKey"); + break; + + case NETOPT_LORAWAN_APPEUI: + printf("AppEUI"); + break; + case NETOPT_SRC_LEN: printf("source address length"); break; @@ -288,10 +315,26 @@ static void _print_netopt(netopt_t opt) printf("checksum"); break; + case NETOPT_OTAA: + printf("otaa"); + break; + + case NETOPT_LINK_CHECK: + printf("link check"); + break; + case NETOPT_PHY_BUSY: printf("PHY busy"); break; + case NETOPT_LORAWAN_DR: + printf("datarate"); + break; + + case NETOPT_LORAWAN_RX2_DR: + printf("RX2 datarate"); + break; + default: /* we don't serve these options here */ break; @@ -491,6 +534,18 @@ static void _netif_list(kernel_pid_t iface) } line_thresh++; } +#ifdef MODULE_GNRC_NETIF_CMD_LORA + res = gnrc_netapi_get(iface, NETOPT_DEMOD_MARGIN, 0, &u8, sizeof(u8)); + if (res >= 0) { + printf(" Demod margin.: %u ", (unsigned) u8); + line_thresh++; + } + res = gnrc_netapi_get(iface, NETOPT_NUM_GATEWAYS, 0, &u8, sizeof(u8)); + if (res >= 0) { + printf(" Num gateways.: %u ", (unsigned) u8); + line_thresh++; + } +#endif line_thresh = _newline(0U, line_thresh); line_thresh = _netif_list_flag(iface, NETOPT_PROMISCUOUSMODE, "PROMISC ", line_thresh); @@ -507,13 +562,15 @@ static void _netif_list(kernel_pid_t iface) line_thresh = _netif_list_flag(iface, NETOPT_CSMA, "CSMA ", line_thresh); line_thresh += _LINE_THRESHOLD + 1; /* enforce linebreak after this option */ - line_thresh = _netif_list_flag(iface, NETOPT_AUTOCCA, "AUTOCCA", + line_thresh = _netif_list_flag(iface, NETOPT_AUTOCCA, "AUTOCCA ", line_thresh); - line_thresh = _netif_list_flag(iface, NETOPT_IQ_INVERT, "IQ_INVERT", + line_thresh = _netif_list_flag(iface, NETOPT_IQ_INVERT, "IQ_INVERT ", line_thresh); - line_thresh = _netif_list_flag(iface, NETOPT_SINGLE_RECEIVE, "RX_SINGLE", + line_thresh = _netif_list_flag(iface, NETOPT_SINGLE_RECEIVE, "RX_SINGLE ", line_thresh); - line_thresh = _netif_list_flag(iface, NETOPT_CHANNEL_HOP, "CHAN_HOP", + line_thresh = _netif_list_flag(iface, NETOPT_CHANNEL_HOP, "CHAN_HOP ", + line_thresh); + line_thresh = _netif_list_flag(iface, NETOPT_OTAA, "OTAA ", line_thresh); res = gnrc_netapi_get(iface, NETOPT_MAX_PDU_SIZE, 0, &u16, sizeof(u16)); if (res > 0) { @@ -823,6 +880,38 @@ static int _netif_set_flag(kernel_pid_t iface, netopt_t opt, return 0; } +#ifdef MODULE_GNRC_NETIF_CMD_LORA +static int _netif_set_lw_key(kernel_pid_t iface, netopt_t opt, char *key_str) +{ + /* This is the longest key */ + uint8_t key[LORAMAC_APPKEY_LEN]; + + size_t key_len = fmt_hex_bytes(key, key_str); + size_t expected_len; + switch(opt) { + case NETOPT_LORAWAN_APPKEY: + case NETOPT_LORAWAN_APPSKEY: + case NETOPT_LORAWAN_NWKSKEY: + /* All keys have the same length as the APP KEY */ + expected_len = LORAMAC_APPKEY_LEN; + break; + default: + /* Same rationale here */ + expected_len = LORAMAC_DEVEUI_LEN; + } + if (!key_len || key_len != expected_len) { + puts("error: unable to parse key.\n"); + return 1; + } + + gnrc_netapi_set(iface, opt, 0, &key, expected_len); + printf("success: set "); + _print_netopt(opt); + printf(" on interface %" PRIkernel_pid " to %s\n", iface, key_str); + return 0; +} +#endif + static int _netif_set_addr(kernel_pid_t iface, netopt_t opt, char *addr_str) { uint8_t addr[GNRC_NETIF_L2ADDR_MAXLEN]; @@ -1040,6 +1129,27 @@ static int _netif_set(char *cmd_name, kernel_pid_t iface, char *key, char *value else if ((strcmp("coding_rate", key) == 0) || (strcmp("cr", key) == 0)) { return _netif_set_coding_rate(iface, value); } + else if (strcmp("appeui", key) == 0) { + return _netif_set_lw_key(iface, NETOPT_LORAWAN_APPEUI, value); + } + else if (strcmp("appkey", key) == 0) { + return _netif_set_lw_key(iface, NETOPT_LORAWAN_APPKEY, value); + } + else if (strcmp("deveui", key) == 0) { + return _netif_set_addr(iface, NETOPT_ADDRESS_LONG, value); + } + else if (strcmp("appskey", key) == 0) { + return _netif_set_addr(iface, NETOPT_LORAWAN_APPSKEY, value); + } + else if (strcmp("nwkskey", key) == 0) { + return _netif_set_addr(iface, NETOPT_LORAWAN_NWKSKEY, value); + } + else if (strcmp("dr", key) == 0) { + return _netif_set_u8(iface, NETOPT_LORAWAN_DR, 0, value); + } + else if (strcmp("rx2_dr", key) == 0) { + return _netif_set_u8(iface, NETOPT_LORAWAN_RX2_DR, 0, value); + } #endif else if ((strcmp("channel", key) == 0) || (strcmp("chan", key) == 0)) { return _netif_set_u16(iface, NETOPT_CHANNEL, 0, value);