1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #11022 from jia200x/pr/gnrc_lorawan

gnrc_lorawan: add support for GNRC based LoRaWAN stack
This commit is contained in:
MichelRottleuthner 2019-11-27 09:25:20 +01:00 committed by GitHub
commit bf676d4728
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 3085 additions and 4 deletions

View File

@ -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)))

View File

@ -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;

View File

@ -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

View File

@ -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 \
#

View File

@ -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 <if_pid> 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

View File

@ -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 <jose.alamos@haw-hamburg.de>
* @}
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 <if_pid> <payload> [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;
}

View File

@ -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)))

View File

@ -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
}
}

View File

@ -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 <jose.alamos@haw-hamburg.de>
* @author Francisco Molina <femolina@uc.cl>
*/
#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 */
/** @} */

View File

@ -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 <jose.alamos@haw-hamburg.de>
*/
#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 */

View File

@ -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

View File

@ -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 <jose.alamos@haw-hamburg.de>
*/
#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 */
/** @} */

View File

@ -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 <jose.alamos@haw-hamburg.de>
*/
#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 */
/** @} */

View File

@ -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

View File

@ -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.
*

View File

@ -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",
};

View File

@ -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

View File

@ -0,0 +1,3 @@
MODULE = gnrc_lorawan
include $(RIOTBASE)/Makefile.base

View File

@ -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 <jose.alamos@haw-hamburg.de>
* @}
*/
#include <stdio.h>
#include <string.h>
#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);
}

View File

@ -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 <jose.alamos@haw-hamburg.de>
* @author Francisco Molina <femolina@uc.cl>
*/
#include <stdio.h>
#include <string.h>
#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);
}
/** @} */

View File

@ -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 <jose.alamos@haw-hamburg.de>
*/
#include <stdio.h>
#include <string.h>
#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);
}
}
/** @} */

View File

@ -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 <jose.alamos@haw-hamburg.de>
*
* @}
*/
#include <stdio.h>
#include <string.h>
#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;
}
}

View File

@ -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 <jose.alamos@haw-hamburg.de>
*/
#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;
}
/** @} */

View File

@ -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 <jose.alamos@haw-hamburg.de>
*/
#ifndef GNRC_LORAWAN_INTERNAL_H
#define GNRC_LORAWAN_INTERNAL_H
#include <stdio.h>
#include <string.h>
#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 */
/** @} */

View File

@ -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

View File

@ -0,0 +1,3 @@
MODULE := gnrc_netif_lorawan
include $(RIOTBASE)/Makefile.base

View File

@ -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 <jose.alamos@haw-hamburg.de>
*/
#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;i<size;i++) {
dst[size-i-1] = src[i];
}
}
static void _init(gnrc_netif_t *netif)
{
gnrc_netif_default_init(netif);
netif->dev->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;
}
/** @} */

View File

@ -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);

View File

@ -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);