diff options
author | Erik de Jong <erikdejong@gmail.com> | 2017-06-05 17:59:03 +0200 |
---|---|---|
committer | Anders Broman <a.broman58@gmail.com> | 2017-06-12 03:26:42 +0000 |
commit | 8c316f3b98db8149735062ab03803cfad7e7de58 (patch) | |
tree | 02a6d7807b2095c15ca64140c422cad5d81740d2 /epan | |
parent | 9d2f07e316752b1497af8230964fb693646f6d0d (diff) |
Dissector for LoRaWAN
Initial implementation of LoRaWAN dissector based on LoRa Alliance specification.
Features:
- Frame dissection for fields as per documentation
- Payload decryption
- MIC verification
Not implemented:
- Region specific information (frequencies etc)
- Statistics
Bug: 13775
Change-Id: I6031755dfd582dd78ed7c2566cdb390c577c9078
Reviewed-on: https://code.wireshark.org/review/22017
Petri-Dish: Anders Broman <a.broman58@gmail.com>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Anders Broman <a.broman58@gmail.com>
Diffstat (limited to 'epan')
-rw-r--r-- | epan/dissectors/CMakeLists.txt | 1 | ||||
-rw-r--r-- | epan/dissectors/Makefile.am | 1 | ||||
-rw-r--r-- | epan/dissectors/packet-lorawan.c | 1371 |
3 files changed, 1373 insertions, 0 deletions
diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt index 4b8923947c..f10850f384 100644 --- a/epan/dissectors/CMakeLists.txt +++ b/epan/dissectors/CMakeLists.txt @@ -857,6 +857,7 @@ set(DISSECTOR_SRC packet-lon.c packet-loop.c packet-loratap.c + packet-lorawan.c packet-lpd.c packet-lsc.c packet-lsd.c diff --git a/epan/dissectors/Makefile.am b/epan/dissectors/Makefile.am index 85f5002208..4a8e20675c 100644 --- a/epan/dissectors/Makefile.am +++ b/epan/dissectors/Makefile.am @@ -878,6 +878,7 @@ DISSECTOR_SRC = \ packet-lon.c \ packet-loop.c \ packet-loratap.c \ + packet-lorawan.c \ packet-lpd.c \ packet-lsc.c \ packet-lsd.c \ diff --git a/epan/dissectors/packet-lorawan.c b/epan/dissectors/packet-lorawan.c new file mode 100644 index 0000000000..b6d964f80b --- /dev/null +++ b/epan/dissectors/packet-lorawan.c @@ -0,0 +1,1371 @@ +/* packet-lorawan.c + * Dissector routines for the LoRaWAN protocol + * By Erik de Jong <erikdejong@gmail.com> + * Copyright 2017 Erik de Jong + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include <epan/packet.h> +#include <epan/expert.h> +#include <epan/prefs.h> +#include <epan/uat.h> +#include <epan/strutil.h> +#include <wsutil/wsgcrypt.h> + +void proto_reg_handoff_lorawan(void); +void proto_register_lorawan(void); + +static int proto_lorawan = -1; +static int hf_lorawan_mac_header_type = -1; +static int hf_lorawan_mac_header_mtype_type = -1; +static int hf_lorawan_mac_header_major_type = -1; +static int hf_lorawan_mac_commands_type = -1; +static int hf_lorawan_mac_command_uplink_type = -1; +static int hf_lorawan_mac_command_downlink_type = -1; +static int hf_lorawan_mac_command_down_link_check_ans_type = -1; +static int hf_lorawan_mac_command_down_link_check_ans_margin_type = -1; +static int hf_lorawan_mac_command_down_link_check_ans_gwcnt_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_datarate_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_txpower_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel1_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel2_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel3_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel4_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel5_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel6_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel7_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel8_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel9_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel10_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel11_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel12_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel13_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel14_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel15_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel16_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_channel_mask_control_type = -1; +static int hf_lorawan_mac_command_down_link_adr_req_repetitions_type = -1; +static int hf_lorawan_mac_command_up_link_adr_ans_txpower_type = -1; +static int hf_lorawan_mac_command_up_link_adr_ans_datarate_type = -1; +static int hf_lorawan_mac_command_up_link_adr_ans_channel_mask_type = -1; +static int hf_lorawan_mac_command_down_dutycycle_type = -1; +static int hf_lorawan_mac_command_down_rx_setup_req_rx1droffset_type = -1; +static int hf_lorawan_mac_command_down_rx_setup_req_rx2datarate_type = -1; +static int hf_lorawan_mac_command_down_rx_setup_req_frequency_type = -1; +static int hf_lorawan_mac_command_up_rx_setup_ans_type = -1; +static int hf_lorawan_mac_command_up_rx_setup_ans_rx1droffset_type = -1; +static int hf_lorawan_mac_command_up_rx_setup_ans_rx2datarate_type = -1; +static int hf_lorawan_mac_command_up_rx_setup_ans_frequency_type = -1; +static int hf_lorawan_mac_command_up_device_status_ans_battery_type = -1; +static int hf_lorawan_mac_command_up_device_status_ans_margin_type = -1; +static int hf_lorawan_mac_command_down_new_channel_req_index_type = -1; +static int hf_lorawan_mac_command_down_new_channel_req_frequency_type = -1; +static int hf_lorawan_mac_command_down_new_channel_req_drrange_max_type = -1; +static int hf_lorawan_mac_command_down_new_channel_req_drrange_min_type = -1; +static int hf_lorawan_mac_command_up_new_channel_ans_type = -1; +static int hf_lorawan_mac_command_up_new_channel_ans_datarate_type = -1; +static int hf_lorawan_mac_command_up_new_channel_ans_frequency_type = -1; +static int hf_lorawan_mac_command_down_rx_timing_req_delay_type = -1; +static int hf_lorawan_join_request_appeui_type = -1; +static int hf_lorawan_join_request_deveui_type = -1; +static int hf_lorawan_join_request_devnonce_type = -1; +static int hf_lorawan_join_accept_appnonce_type = -1; +static int hf_lorawan_join_accept_netid_type = -1; +static int hf_lorawan_join_accept_devaddr_type = -1; +static int hf_lorawan_join_accept_dlsettings_rx1droffset_type = -1; +static int hf_lorawan_join_accept_dlsettings_rx2dr_type = -1; +static int hf_lorawan_join_accept_rxdelay_type = -1; +static int hf_lorawan_join_accept_cflist_type = -1; +static int hf_lorawan_frame_header_type = -1; +static int hf_lorawan_frame_header_address_type = -1; +static int hf_lorawan_frame_header_frame_control_adr_type = -1; +static int hf_lorawan_frame_header_frame_control_adrackreq_type = -1; +static int hf_lorawan_frame_header_frame_control_ack_type = -1; +static int hf_lorawan_frame_header_frame_control_fpending_type = -1; +static int hf_lorawan_frame_header_frame_control_foptslen_type = -1; +static int hf_lorawan_frame_header_frame_control_type = -1; +static int hf_lorawan_frame_header_frame_counter_type = -1; +static int hf_lorawan_frame_fport_type = -1; +static int hf_lorawan_frame_payload = -1; +static int hf_lorawan_frame_payload_decrypted = -1; +static int hf_lorawan_mic_type = -1; + +static gint ett_lorawan = -1; +static gint ett_lorawan_mac_header = -1; +static gint ett_lorawan_mac_commands = -1; +static gint ett_lorawan_mac_command = -1; +static gint ett_lorawan_mac_command_link_check_ans = -1; +static gint ett_lorawan_mac_command_link_adr_req_channel = -1; +static gint ett_lorawan_mac_command_rx_setup_ans = -1; +static gint ett_lorawan_mac_command_new_channel_ans = -1; +static gint ett_lorawan_join_request = -1; +static gint ett_lorawan_join_accept = -1; +static gint ett_lorawan_frame_header = -1; +static gint ett_lorawan_frame_header_control = -1; +static gint ett_lorawan_frame_payload_decrypted = -1; + +#define LORAWAN_MAC_MTYPE_MASK 0xE0 +#define LORAWAN_MAC_MTYPE(mtype) (((mtype) & LORAWAN_MAC_MTYPE_MASK) >> 5) + +#define LORAWAN_MAC_MTYPE_JOINREQUEST 0 +#define LORAWAN_MAC_MTYPE_JOINACCEPT 1 +#define LORAWAN_MAC_MTYPE_UNCONFIRMEDDATAUP 2 +#define LORAWAN_MAC_MTYPE_UNCONFIRMEDDATADOWN 3 +#define LORAWAN_MAC_MTYPE_CONFIRMEDDATAUP 4 +#define LORAWAN_MAC_MTYPE_CONFIRMEDDATADOWN 5 +#define LORAWAN_MAC_MTYPE_PROPRIETARY 7 + +#define LORAWAN_MAC_MAJOR_MASK 0x03 +#define LORAWAN_MAC_MAJOR(major) ((major) & LORAWAN_MAC_MAJOR_MASK) + +#define LORAWAN_MAC_MAJOR_R1 0 + +#define LORAWAN_MAC_COMMAND_UP_LINK_CHECK_REQ 2 +#define LORAWAN_MAC_COMMAND_UP_LINK_ADR_ANS 3 +#define LORAWAN_MAC_COMMAND_UP_DUTY_ANS 4 +#define LORAWAN_MAC_COMMAND_UP_RX_SETUP_ANS 5 +#define LORAWAN_MAC_COMMAND_UP_DEV_STATUS_ANS 6 +#define LORAWAN_MAC_COMMAND_UP_NEW_CHANNEL_ANS 7 +#define LORAWAN_MAC_COMMAND_UP_RX_TIMING_ANS 8 + +#define LORAWAN_MAC_COMMAND_DOWN_LINK_CHECK_ANS 2 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ 3 +#define LORAWAN_MAC_COMMAND_DOWN_DUTY_REQ 4 +#define LORAWAN_MAC_COMMAND_DOWN_RX_SETUP_REQ 5 +#define LORAWAN_MAC_COMMAND_DOWN_DEV_STATUS_REQ 6 +#define LORAWAN_MAC_COMMAND_DOWN_NEW_CHANNEL_REQ 7 +#define LORAWAN_MAC_COMMAND_DOWN_RX_TIMING_REQ 8 + +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_DATARATE_MASK 0xF0 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_TXPOWER_MASK 0x0F +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_1_MASK 0x0001 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_2_MASK 0x0002 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_3_MASK 0x0004 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_4_MASK 0x0008 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_5_MASK 0x0010 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_6_MASK 0x0020 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_7_MASK 0x0040 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_8_MASK 0x0080 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_9_MASK 0x0100 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_10_MASK 0x0200 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_11_MASK 0x0400 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_12_MASK 0x0800 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_13_MASK 0x1000 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_14_MASK 0x2000 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_15_MASK 0x4000 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_16_MASK 0x8000 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHMASKCNTL_MASK 0x70 +#define LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_NBREP_MASK 0x0F +#define LORAWAN_MAC_COMMAND_UP_LINK_ADR_ANS_TXPOWER_MASK 0x04 +#define LORAWAN_MAC_COMMAND_UP_LINK_ADR_ANS_DATARATE_MASK 0x02 +#define LORAWAN_MAC_COMMAND_UP_LINK_ADR_ANS_CHANNEL_MASK 0x01 +#define LORAWAN_MAC_COMMAND_DOWN_RX_SETUP_RX1DROFFSET_MASK 0x70 +#define LORAWAN_MAC_COMMAND_DOWN_RX_SETUP_RX2DATARATE_MASK 0x0F +#define LORAWAN_MAC_COMMAND_UP_RX_SETUP_ANS_TXPOWER_MASK 0x04 +#define LORAWAN_MAC_COMMAND_UP_RX_SETUP_ANS_DATARATE_MASK 0x02 +#define LORAWAN_MAC_COMMAND_UP_RX_SETUP_ANS_CHANNEL_MASK 0x01 +#define LORAWAN_MAC_COMMAND_UP_DEVICE_STATUS_ANS_MARGIN_MASK 0x3F +#define LORAWAN_MAC_COMMAND_DOWN_NEW_CHANNEL_REQ_DRRANGE_MAX_MASK 0xF0 +#define LORAWAN_MAC_COMMAND_DOWN_NEW_CHANNEL_REQ_DRRANGE_MIN_MASK 0x0F +#define LORAWAN_MAC_COMMAND_UP_NEW_CHANNEL_ANS_DATARATE_MASK 0x02 +#define LORAWAN_MAC_COMMAND_UP_NEW_CHANNEL_ANS_FREQUENCY_MASK 0x01 +#define LORAWAN_MAC_COMMAND_DOWN_RX_TIMING_REQ_DELAY_MASK 0x0F + +#define LORAWAN_JOIN_ACCEPT_RX1DROFFSET_MASK 0x70 +#define LORAWAN_JOIN_ACCEPT_RX2DR_MASK 0x0F + +#define LORAWAN_FRAME_FOPTSLEN_MASK 0x0F + +#define LORAWAN_AES_BLOCK_LENGTH 16 +#define LORAWAN_AES_PADDEDSIZE(length) (length + (16 - (length % 16))) + +static expert_field ei_lorawan_invalid_crc = EI_INIT; +static expert_field ei_lorawan_unverified_crc = EI_INIT; +static expert_field ei_lorawan_decrypting_error = EI_INIT; + +static const value_string lorawan_mtypenames[] = { + { LORAWAN_MAC_MTYPE_JOINREQUEST, "Join Request" }, + { LORAWAN_MAC_MTYPE_JOINACCEPT, "Join Accept" }, + { LORAWAN_MAC_MTYPE_UNCONFIRMEDDATAUP, "Unconfirmed Data Up" }, + { LORAWAN_MAC_MTYPE_UNCONFIRMEDDATADOWN, "Unconfirmed Data Down" }, + { LORAWAN_MAC_MTYPE_CONFIRMEDDATAUP, "Confirmed Data Up" }, + { LORAWAN_MAC_MTYPE_CONFIRMEDDATADOWN, "Confirmed Data Down" }, + { LORAWAN_MAC_MTYPE_PROPRIETARY, "Proprietary" }, + { 0, NULL } +}; + +static const value_string lorawan_majornames[] = { + { LORAWAN_MAC_MAJOR_R1, "LoRaWAN R1" }, + { 0, NULL } +}; + +static const value_string lorawan_mac_uplink_commandnames[] = { + { LORAWAN_MAC_COMMAND_UP_LINK_CHECK_REQ, "Network validation request" }, + { LORAWAN_MAC_COMMAND_UP_LINK_ADR_ANS, "Data rate adjustment response" }, + { LORAWAN_MAC_COMMAND_UP_DUTY_ANS, "Duty-cycle rate set response" }, + { LORAWAN_MAC_COMMAND_UP_RX_SETUP_ANS, "Reception slots set response" }, + { LORAWAN_MAC_COMMAND_UP_DEV_STATUS_ANS, "Status response" }, + { LORAWAN_MAC_COMMAND_UP_NEW_CHANNEL_ANS, "Channel creation/modification response" }, + { LORAWAN_MAC_COMMAND_UP_RX_TIMING_ANS, "Reception slots timing set response" }, + { 0, NULL } +}; + +static const value_string lorawan_mac_downlink_commandnames[] = { + { LORAWAN_MAC_COMMAND_DOWN_LINK_CHECK_ANS, "Network validation response" }, + { LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ, "Data rate adjustment request" }, + { LORAWAN_MAC_COMMAND_DOWN_DUTY_REQ, "Duty-cycle rate set request" }, + { LORAWAN_MAC_COMMAND_DOWN_RX_SETUP_REQ, "Reception slots set request" }, + { LORAWAN_MAC_COMMAND_DOWN_DEV_STATUS_REQ, "Status request" }, + { LORAWAN_MAC_COMMAND_DOWN_NEW_CHANNEL_REQ, "Channel creation/modification request" }, + { LORAWAN_MAC_COMMAND_DOWN_RX_TIMING_REQ, "Reception slots timing set request" }, + { 0, NULL } +}; + + +typedef struct _device_encryption_keys_t { + gchar *dev_addr_string; + gchar *nwkskey_string; + gchar *appskey_string; + gchar *appeui_string; + guint32 dev_addr; + GByteArray *nwkskey; + GByteArray *appskey; + GByteArray *appeui; +} device_encryption_keys_t; + +static device_encryption_keys_t *device_encryption_keys = NULL; +static guint device_encryption_num_keys = 0; + +static gboolean +device_encryption_keys_update_cb(void *r, char **err) +{ + device_encryption_keys_t *rec = (device_encryption_keys_t *)r; + + if (rec->dev_addr_string == NULL) { + *err = g_strdup("Device address can't be empty"); + return FALSE; + } + GByteArray *addr = g_byte_array_new(); + if (!hex_str_to_bytes(rec->dev_addr_string, addr, FALSE)) { + g_byte_array_free(addr, TRUE); + *err = g_strdup("Device address must be hexadecimal"); + return FALSE; + } + if (addr->len != 4) { + g_byte_array_free(addr, TRUE); + *err = g_strdup("Device address must be 4 bytes hexadecimal"); + return FALSE; + } + rec->dev_addr = *(guint32*)addr->data; + g_byte_array_free(addr, TRUE); + + if (rec->nwkskey_string == NULL) { + *err = g_strdup("Network key can't be empty"); + return FALSE; + } + if (!rec->nwkskey) { + rec->nwkskey = g_byte_array_new(); + } + if (!hex_str_to_bytes(rec->nwkskey_string, rec->nwkskey, FALSE)) { + *err = g_strdup("Network encryption key must be hexadecimal"); + return FALSE; + } + if (rec->nwkskey->len != 16) { + *err = g_strdup("Network encryption key must be 16 bytes hexadecimal"); + return FALSE; + } + + if (rec->appskey_string == NULL) { + *err = g_strdup("Application key can't be empty"); + return FALSE; + } + if (!rec->appskey) { + rec->appskey = g_byte_array_new(); + } + if (!hex_str_to_bytes(rec->appskey_string, rec->appskey, FALSE)) { + *err = g_strdup("Application encryption key must be hexadecimal"); + return FALSE; + } + if (rec->appskey->len != 16) { + *err = g_strdup("Application encryption key must be 16 bytes hexadecimal"); + return FALSE; + } + + if (rec->appeui_string == NULL) { + *err = g_strdup("Application eui can't be empty"); + return FALSE; + } + if (!rec->appeui) { + rec->appeui = g_byte_array_new(); + } + if (!hex_str_to_bytes(rec->appeui_string, rec->appeui, FALSE)) { + *err = g_strdup("Application eui must be hexadecimal"); + return FALSE; + } + if (rec->appeui->len != 8) { + *err = g_strdup("Application eui must be 8 bytes hexadecimal"); + return FALSE; + } + + *err = NULL; + return TRUE; +} + +static void * +device_encryption_keys_copy_cb(void *n, const void *o, size_t siz _U_) +{ + device_encryption_keys_t* new_rec = (device_encryption_keys_t*)n; + const device_encryption_keys_t* old_rec = (const device_encryption_keys_t*)o; + + if (old_rec->dev_addr_string) { + new_rec->dev_addr_string = g_strdup(old_rec->dev_addr_string); + GByteArray *addr = g_byte_array_new(); + if (hex_str_to_bytes(new_rec->dev_addr_string, addr, FALSE)) { + if (addr->len == 4) { + new_rec->dev_addr = *(guint32*)addr->data; + } else { + new_rec->dev_addr = 0; + } + } + g_byte_array_free(addr, TRUE); + } else { + new_rec->dev_addr_string = NULL; + new_rec->dev_addr = 0; + } + + if (old_rec->nwkskey_string) { + new_rec->nwkskey_string = g_strdup(old_rec->nwkskey_string); + new_rec->nwkskey = g_byte_array_new(); + hex_str_to_bytes(new_rec->nwkskey_string, new_rec->nwkskey, FALSE); + } else { + new_rec->nwkskey_string = NULL; + new_rec->nwkskey = NULL; + } + + if (old_rec->appskey_string) { + new_rec->appskey_string = g_strdup(old_rec->appskey_string); + new_rec->appskey = g_byte_array_new(); + hex_str_to_bytes(new_rec->appskey_string, new_rec->appskey, FALSE); + } else { + new_rec->appskey_string = NULL; + new_rec->appskey = NULL; + } + + if (old_rec->appeui_string) { + new_rec->appeui_string = g_strdup(old_rec->appeui_string); + new_rec->appeui = g_byte_array_new(); + hex_str_to_bytes(new_rec->appeui_string, new_rec->appeui, FALSE); + } else { + new_rec->appeui_string = NULL; + new_rec->appeui = NULL; + } + + return new_rec; +} + +static void +device_encryption_keys_free_cb(void *r) +{ + device_encryption_keys_t *rec = (device_encryption_keys_t*)r; + + g_free(rec->dev_addr_string); + g_free(rec->nwkskey_string); + g_byte_array_free(rec->nwkskey, TRUE); + g_free(rec->appskey_string); + g_byte_array_free(rec->appskey, TRUE); + g_free(rec->appeui_string); + g_byte_array_free(rec->appeui, TRUE); +} + +UAT_CSTRING_CB_DEF(device_encryption_keys, dev_addr_string, device_encryption_keys_t) +UAT_CSTRING_CB_DEF(device_encryption_keys, nwkskey_string, device_encryption_keys_t) +UAT_CSTRING_CB_DEF(device_encryption_keys, appskey_string, device_encryption_keys_t) +UAT_CSTRING_CB_DEF(device_encryption_keys, appeui_string, device_encryption_keys_t) + +static device_encryption_keys_t *get_encryption_keys_dev_address(guint32 dev_addr) +{ + guint i; + for (i = 0; i < device_encryption_num_keys; i++) { + if (device_encryption_keys[i].dev_addr == dev_addr) { + return &device_encryption_keys[i]; + } + } + return NULL; +} + +static device_encryption_keys_t *get_encryption_keys_app_eui(const guint8 *appeui) +{ + guint i; + for (i = 0; i < device_encryption_num_keys; i++) { + if (device_encryption_keys[i].appeui) { + if (!memcmp(device_encryption_keys[i].appeui->data, appeui, 8)) { + return &device_encryption_keys[i]; + } + } + } + return NULL; +} + +static guint32 +calculate_mic(const guint8 *in, guint8 length, const guint8 *key) +{ + /* + * cmac = aes128_cmac(key, in) + * MIC = cmac[0..3] + */ + gcry_mac_hd_t mac_hd; + guint32 mac; + size_t read_digest_length = 4; + + if (gcry_mac_open(&mac_hd, GCRY_MAC_CMAC_AES, 0, NULL)) { + return 0; + } + if (gcry_mac_setkey(mac_hd, key, 16)) { + gcry_mac_close(mac_hd); + return 0; + } + /* Pass in the message */ + if (gcry_mac_write(mac_hd, in, length)) { + gcry_mac_close(mac_hd); + return 0; + } + /* Read out the digest */ + if (gcry_mac_read(mac_hd, &mac, &read_digest_length)) { + gcry_mac_close(mac_hd); + return 0; + } + /* Now close the mac handle */ + gcry_mac_close(mac_hd); + return mac; +} + +/* length should be a multiple of 16, in should be padded to get to a multiple of 16 */ +static gboolean +decrypt_lorawan_frame_payload(const guint8 *in, gint length, guint8 *out, const guint8 * key, guint8 dir, guint32 dev_addr, guint32 fcnt) +{ + gcry_cipher_hd_t cipher; + guint8 iv[LORAWAN_AES_BLOCK_LENGTH] = {0x01, 0x00, 0x00, 0x00, 0x00, dir, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; + memcpy(iv + 6, &dev_addr, 4); + memcpy(iv + 10, &fcnt, 4); + if (gcry_cipher_open(&cipher, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0)) { + return FALSE; + } + if (gcry_cipher_setkey(cipher, key, LORAWAN_AES_BLOCK_LENGTH)) { + gcry_cipher_close(cipher); + return FALSE; + } + if (gcry_cipher_setctr(cipher, iv, 16)) { + gcry_cipher_close(cipher); + return FALSE; + } + if (gcry_cipher_encrypt(cipher, out, length, in, length)) { + gcry_cipher_close(cipher); + return FALSE; + } + gcry_cipher_close(cipher); + return TRUE; +} + +static int +dissect_lorawan_mac_commands(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree _U_, gboolean uplink) +{ + proto_item *ti, *tf; + proto_tree *mac_command_tree, *field_tree; + guint8 command; + gint32 current_offset = 0; + + static const int * link_adr_ans_flags[] = { + &hf_lorawan_mac_command_up_link_adr_ans_txpower_type, + &hf_lorawan_mac_command_up_link_adr_ans_datarate_type, + &hf_lorawan_mac_command_up_link_adr_ans_channel_mask_type, + NULL + }; + static const int * link_adr_req_channel_flags[] = { + &hf_lorawan_mac_command_down_link_adr_req_channel1_type, + &hf_lorawan_mac_command_down_link_adr_req_channel2_type, + &hf_lorawan_mac_command_down_link_adr_req_channel3_type, + &hf_lorawan_mac_command_down_link_adr_req_channel4_type, + &hf_lorawan_mac_command_down_link_adr_req_channel5_type, + &hf_lorawan_mac_command_down_link_adr_req_channel6_type, + &hf_lorawan_mac_command_down_link_adr_req_channel7_type, + &hf_lorawan_mac_command_down_link_adr_req_channel8_type, + &hf_lorawan_mac_command_down_link_adr_req_channel9_type, + &hf_lorawan_mac_command_down_link_adr_req_channel10_type, + &hf_lorawan_mac_command_down_link_adr_req_channel11_type, + &hf_lorawan_mac_command_down_link_adr_req_channel12_type, + &hf_lorawan_mac_command_down_link_adr_req_channel13_type, + &hf_lorawan_mac_command_down_link_adr_req_channel14_type, + &hf_lorawan_mac_command_down_link_adr_req_channel15_type, + &hf_lorawan_mac_command_down_link_adr_req_channel16_type, + NULL + }; + static const int * rx_setup_ans_flags[] = { + &hf_lorawan_mac_command_up_rx_setup_ans_rx1droffset_type, + &hf_lorawan_mac_command_up_rx_setup_ans_rx2datarate_type, + &hf_lorawan_mac_command_up_rx_setup_ans_frequency_type, + NULL + }; + static const int * new_channel_ans_flags[] = { + &hf_lorawan_mac_command_up_new_channel_ans_datarate_type, + &hf_lorawan_mac_command_up_new_channel_ans_frequency_type, + NULL + }; + + ti = proto_tree_add_item(tree, hf_lorawan_mac_commands_type, tvb, 0, -1, ENC_NA); + mac_command_tree = proto_item_add_subtree(ti, ett_lorawan_mac_commands); + + do { + command = tvb_get_guint8(tvb, current_offset); + if (uplink) { + tf = proto_tree_add_item(mac_command_tree, hf_lorawan_mac_command_uplink_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + proto_item_append_text(tf, " (%s)", val_to_str(command, lorawan_mac_uplink_commandnames, "RFU")); + switch (command) { + case LORAWAN_MAC_COMMAND_UP_LINK_CHECK_REQ: + case LORAWAN_MAC_COMMAND_UP_DUTY_ANS: + case LORAWAN_MAC_COMMAND_UP_RX_TIMING_ANS: + /* No payload */ + break; + case LORAWAN_MAC_COMMAND_UP_LINK_ADR_ANS: + field_tree = proto_item_add_subtree(tf, ett_lorawan_mac_command); + proto_tree_add_bitmask(field_tree, tvb, current_offset, hf_lorawan_mac_command_down_link_check_ans_type, ett_lorawan_mac_command_link_check_ans, link_adr_ans_flags, ENC_NA); + current_offset++; + break; + case LORAWAN_MAC_COMMAND_UP_RX_SETUP_ANS: + field_tree = proto_item_add_subtree(tf, ett_lorawan_mac_command); + proto_tree_add_bitmask(field_tree, tvb, current_offset, hf_lorawan_mac_command_up_rx_setup_ans_type, ett_lorawan_mac_command_rx_setup_ans, rx_setup_ans_flags, ENC_NA); + current_offset++; + break; + case LORAWAN_MAC_COMMAND_UP_DEV_STATUS_ANS: + field_tree = proto_item_add_subtree(tf, ett_lorawan_mac_command); + proto_tree_add_item(field_tree, hf_lorawan_mac_command_up_device_status_ans_battery_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + proto_tree_add_item(field_tree, hf_lorawan_mac_command_up_device_status_ans_margin_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + break; + case LORAWAN_MAC_COMMAND_UP_NEW_CHANNEL_ANS: + field_tree = proto_item_add_subtree(tf, ett_lorawan_mac_command); + proto_tree_add_bitmask(field_tree, tvb, current_offset, hf_lorawan_mac_command_up_new_channel_ans_type, ett_lorawan_mac_command_new_channel_ans, new_channel_ans_flags, ENC_NA); + current_offset++; + break; + default: + /* End on unknown mac command because command lengths are not explicitly given */ + return tvb_captured_length(tvb); + break; + } + } else { + tf = proto_tree_add_item(mac_command_tree, hf_lorawan_mac_command_downlink_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + proto_item_append_text(tf, " (%s)", val_to_str(command, lorawan_mac_downlink_commandnames, "RFU")); + switch (command) { + case LORAWAN_MAC_COMMAND_DOWN_LINK_CHECK_ANS: + field_tree = proto_item_add_subtree(tf, ett_lorawan_mac_command); + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_link_check_ans_margin_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_link_check_ans_gwcnt_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + break; + case LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ: + field_tree = proto_item_add_subtree(tf, ett_lorawan_mac_command); + /* Region specific */ + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_link_adr_req_datarate_type, tvb, current_offset, 1, ENC_NA); + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_link_adr_req_txpower_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + proto_tree_add_bitmask(field_tree, tvb, current_offset, hf_lorawan_mac_command_down_link_adr_req_channel_type, ett_lorawan_mac_command_link_adr_req_channel, link_adr_req_channel_flags, ENC_LITTLE_ENDIAN); + current_offset += 2; + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_link_adr_req_channel_mask_control_type, tvb, current_offset, 1, ENC_NA); + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_link_adr_req_repetitions_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + break; + case LORAWAN_MAC_COMMAND_DOWN_DUTY_REQ: + field_tree = proto_item_add_subtree(tf, ett_lorawan_mac_command); + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_dutycycle_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + break; + case LORAWAN_MAC_COMMAND_DOWN_RX_SETUP_REQ: + field_tree = proto_item_add_subtree(tf, ett_lorawan_mac_command); + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_rx_setup_req_rx1droffset_type, tvb, current_offset, 1, ENC_NA); + /* Region specific */ + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_rx_setup_req_rx2datarate_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_rx_setup_req_frequency_type, tvb, current_offset, 3, ENC_LITTLE_ENDIAN); + current_offset += 3; + break; + case LORAWAN_MAC_COMMAND_DOWN_DEV_STATUS_REQ: + /* No payload */ + break; + case LORAWAN_MAC_COMMAND_DOWN_NEW_CHANNEL_REQ: + field_tree = proto_item_add_subtree(tf, ett_lorawan_mac_command); + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_new_channel_req_index_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_new_channel_req_frequency_type, tvb, current_offset, 3, ENC_LITTLE_ENDIAN); + current_offset += 3; + /* Region specific */ + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_new_channel_req_drrange_max_type, tvb, current_offset, 1, ENC_NA); + /* Region specific */ + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_new_channel_req_drrange_min_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + break; + case LORAWAN_MAC_COMMAND_DOWN_RX_TIMING_REQ: + field_tree = proto_item_add_subtree(tf, ett_lorawan_mac_command); + proto_tree_add_item(field_tree, hf_lorawan_mac_command_down_rx_timing_req_delay_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + break; + default: + /* End on unknown mac command because command lengths are not explicitly given */ + return tvb_captured_length(tvb); + break; + } + } + } while (tvb_captured_length_remaining(tvb, current_offset)); + return tvb_captured_length(tvb); +} + +static int +dissect_lorawan(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_) +{ + proto_item *ti, *tf; + proto_tree *lorawan_tree, *field_tree, *frame_payload_decrypted_tree; + gint32 current_offset = 0; + guint8 mac_mtype; + guint8 fopts_length = 0; + guint8 frmpayload_length = 0; + guint8 fport; + guint32 dev_address; + guint32 fcnt; + gint frame_length; + gboolean uplink = TRUE; + device_encryption_keys_t *encryption_keys = NULL; + + static const int * flags[] = { + &hf_lorawan_frame_header_frame_control_adr_type, + &hf_lorawan_frame_header_frame_control_adrackreq_type, + &hf_lorawan_frame_header_frame_control_ack_type, + &hf_lorawan_frame_header_frame_control_fpending_type, + &hf_lorawan_frame_header_frame_control_foptslen_type, + NULL + }; + + col_set_str(pinfo->cinfo, COL_PROTOCOL, "LoRaWAN"); + col_clear(pinfo->cinfo,COL_INFO); + ti = proto_tree_add_item(tree, proto_lorawan, tvb, 0, -1, ENC_NA); + lorawan_tree = proto_item_add_subtree(ti, ett_lorawan); + + /* MAC header */ + tf = proto_tree_add_item(lorawan_tree, hf_lorawan_mac_header_type, tvb, current_offset, 1, ENC_NA); + mac_mtype = LORAWAN_MAC_MTYPE(tvb_get_guint8(tvb, current_offset)); + proto_item_append_text(tf, " (Message Type: %s, Major Version: %s)", val_to_str(mac_mtype, lorawan_mtypenames, "RFU"), val_to_str(LORAWAN_MAC_MAJOR(tvb_get_guint8(tvb, current_offset)), lorawan_majornames, "RFU")); + + if (mac_mtype == LORAWAN_MAC_MTYPE_JOINREQUEST) { + field_tree = proto_item_add_subtree(tf, ett_lorawan_join_request); + proto_tree_add_item(field_tree, hf_lorawan_join_request_appeui_type, tvb, current_offset, 8, ENC_NA); + current_offset += 8; + proto_tree_add_item(field_tree, hf_lorawan_join_request_deveui_type, tvb, current_offset, 8, ENC_NA); + current_offset += 8; + proto_tree_add_item(field_tree, hf_lorawan_join_request_devnonce_type, tvb, current_offset, 2, ENC_NA); + current_offset += 2; + /* MIC + * cmac = aes128_cmac(AppKey, msg) + * MIC = cmac[0..3] + */ + encryption_keys = get_encryption_keys_app_eui(tvb_get_ptr(tvb, current_offset - 18, 8)); + if (encryption_keys) { + if (calculate_mic(tvb_get_ptr(tvb, 0, current_offset), current_offset, encryption_keys->appskey->data) != tvb_get_guint32(tvb, current_offset, ENC_LITTLE_ENDIAN)) { + proto_tree_add_expert_format(lorawan_tree, pinfo, &ei_lorawan_invalid_crc, tvb, current_offset, 4, "Invalid CRC"); + } + } else { + proto_tree_add_expert_format(lorawan_tree, pinfo, &ei_lorawan_unverified_crc, tvb, current_offset, 4, "Unverified CRC"); + } + proto_tree_add_item(lorawan_tree, hf_lorawan_mic_type, tvb, current_offset, 4, ENC_LITTLE_ENDIAN); + return tvb_captured_length(tvb); + } else if (mac_mtype == LORAWAN_MAC_MTYPE_JOINACCEPT) { + field_tree = proto_item_add_subtree(tf, ett_lorawan_join_accept); + proto_tree_add_item(field_tree, hf_lorawan_join_accept_appnonce_type, tvb, current_offset, 3, ENC_NA); + current_offset += 3; + proto_tree_add_item(field_tree, hf_lorawan_join_accept_netid_type, tvb, current_offset, 3, ENC_NA); + current_offset += 3; + proto_tree_add_item(field_tree, hf_lorawan_join_accept_devaddr_type, tvb, current_offset, 4, ENC_LITTLE_ENDIAN); + dev_address = tvb_get_guint32(tvb, current_offset, ENC_LITTLE_ENDIAN); + current_offset += 4; + proto_tree_add_item(field_tree, hf_lorawan_join_accept_dlsettings_rx1droffset_type, tvb, current_offset, 1, ENC_NA); + proto_tree_add_item(field_tree, hf_lorawan_join_accept_dlsettings_rx2dr_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + proto_tree_add_item(field_tree, hf_lorawan_join_accept_rxdelay_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + if (tvb_captured_length(tvb) - current_offset > 4) { + proto_tree_add_item(field_tree, hf_lorawan_join_accept_cflist_type, tvb, current_offset, 16, ENC_NA); + current_offset += 16; + } + /* MIC + * cmac = aes128_cmac(AppKey, msg) + * MIC = cmac[0..3] + */ + encryption_keys = get_encryption_keys_dev_address(dev_address); + if (encryption_keys) { + if (calculate_mic(tvb_get_ptr(tvb, 0, current_offset), current_offset, encryption_keys->appskey->data) != tvb_get_guint32(tvb, current_offset, ENC_LITTLE_ENDIAN)) { + proto_tree_add_expert_format(lorawan_tree, pinfo, &ei_lorawan_invalid_crc, tvb, current_offset, 4, "Invalid CRC"); + } + } else { + proto_tree_add_expert_format(lorawan_tree, pinfo, &ei_lorawan_unverified_crc, tvb, current_offset, 4, "Unverified CRC"); + } + proto_tree_add_item(lorawan_tree, hf_lorawan_mic_type, tvb, current_offset, 4, ENC_LITTLE_ENDIAN); + return tvb_captured_length(tvb); + } else if ((mac_mtype >= LORAWAN_MAC_MTYPE_UNCONFIRMEDDATAUP) && (mac_mtype <= LORAWAN_MAC_MTYPE_CONFIRMEDDATADOWN)) { + if (mac_mtype & 1) { + uplink = FALSE; + } + field_tree = proto_item_add_subtree(tf, ett_lorawan_mac_header); + proto_tree_add_item(field_tree, hf_lorawan_mac_header_major_type, tvb, current_offset, 1, ENC_NA); + proto_tree_add_item(field_tree, hf_lorawan_mac_header_mtype_type, tvb, current_offset, 1, ENC_NA); + current_offset++; + + fopts_length = (tvb_get_guint8(tvb, current_offset + 4) & LORAWAN_FRAME_FOPTSLEN_MASK); + /* Frame header */ + tf = proto_tree_add_item(lorawan_tree, hf_lorawan_frame_header_type, tvb, current_offset, 7 + fopts_length, ENC_NA); + field_tree = proto_item_add_subtree(tf, ett_lorawan_frame_header); + proto_tree_add_item(field_tree, hf_lorawan_frame_header_address_type, tvb, current_offset, 4, ENC_LITTLE_ENDIAN); + dev_address = tvb_get_guint32(tvb, current_offset, ENC_LITTLE_ENDIAN); + current_offset += 4; + proto_tree_add_bitmask(field_tree, tvb, current_offset, hf_lorawan_frame_header_frame_control_type, ett_lorawan_frame_header_control, flags, ENC_NA); + current_offset++; + proto_tree_add_item(field_tree, hf_lorawan_frame_header_frame_counter_type, tvb, current_offset, 2, ENC_LITTLE_ENDIAN); + fcnt = tvb_get_guint16(tvb, current_offset, ENC_LITTLE_ENDIAN); + current_offset += 2; + + /* + * If fopts_length > 0 then MAC commands are present in fopts field and port cannot be 0 + * If fopts_length == 0 then port can be any value + * If port == 0 then MAC commands are in frame payload + */ + + if (fopts_length > 0) { + tvbuff_t *next_tvb = tvb_new_subset_length(tvb, current_offset, fopts_length); + current_offset += dissect_lorawan_mac_commands(next_tvb, pinfo, tree, uplink); + } + + proto_tree_add_item(lorawan_tree, hf_lorawan_frame_fport_type, tvb, current_offset, 1, ENC_NA); + fport = tvb_get_guint8(tvb, current_offset); + current_offset++; + + if ((fopts_length > 0) && (fport == 0)) { + /* TODO?: error, not allowed */ + } + + frmpayload_length = tvb_captured_length_remaining(tvb, current_offset) - 4; + ti = proto_tree_add_item(lorawan_tree, hf_lorawan_frame_payload, tvb, current_offset, frmpayload_length, ENC_NA); + encryption_keys = get_encryption_keys_dev_address(dev_address); + if (encryption_keys) { + guint8 padded_length = LORAWAN_AES_PADDEDSIZE(frmpayload_length); + guint8 *decrypted_buffer = (guint8*)wmem_alloc0(pinfo->pool, padded_length); + guint8 *encrypted_buffer = (guint8*)wmem_alloc0(pinfo->pool, padded_length); + memcpy(encrypted_buffer, tvb_get_ptr(tvb, current_offset, frmpayload_length), frmpayload_length); + if (decrypt_lorawan_frame_payload(encrypted_buffer, padded_length, decrypted_buffer, encryption_keys->appskey->data, 0, dev_address, fcnt)) { + tvbuff_t *next_tvb = tvb_new_child_real_data(tvb, decrypted_buffer,frmpayload_length, frmpayload_length); + add_new_data_source(pinfo, next_tvb, "Decrypted payload"); + frame_payload_decrypted_tree = proto_item_add_subtree(ti, ett_lorawan_frame_payload_decrypted); + if (fport == 0) { + current_offset += dissect_lorawan_mac_commands(next_tvb, pinfo, tree, uplink); + } else { + /* + * fport values 0x01 - 0xDF are application specific + * fport values 0xE0 - 0xFF are reserved for future extensions + */ + proto_tree_add_bytes(frame_payload_decrypted_tree, hf_lorawan_frame_payload_decrypted, next_tvb, 0, frmpayload_length, decrypted_buffer); + current_offset += frmpayload_length; + } + } else { + proto_tree_add_expert_format(lorawan_tree, pinfo, &ei_lorawan_decrypting_error, tvb, current_offset, 4, "Decrypting error"); + current_offset += frmpayload_length; + } + } else { + current_offset += frmpayload_length; + } + } else if (mac_mtype == LORAWAN_MAC_MTYPE_PROPRIETARY) { + current_offset = tvb_captured_length(tvb) - 4; + } else { + /* RFU */ + current_offset = tvb_captured_length(tvb) - 4; + } + + /* + * MIC + * cmac = aes128_cmac(NwkSKey, B0 | msg) + * MIC = cmac[0..3] + * B0 = 0x49 | 0x00 | 0x00 | 0x00 | 0x00 | dir | devAddr | fcntup/fcntdown | len(msg) + */ + frame_length = current_offset; + if (encryption_keys) { + guint8 *msg = (guint8 *)wmem_alloc0(wmem_packet_scope(), frame_length + 16); + msg[0] = 0x49; + msg[5] = uplink ? 0 : 1; + memcpy(msg + 6, &dev_address, 4); + memcpy(msg + 10, &fcnt, 4); + msg[15] = frame_length; + memcpy(msg + 16, tvb_get_ptr(tvb, 0, frame_length), frame_length); + if (calculate_mic(msg, frame_length + 16, encryption_keys->nwkskey->data) != tvb_get_guint32(tvb, current_offset, ENC_LITTLE_ENDIAN)) { + proto_tree_add_expert_format(lorawan_tree, pinfo, &ei_lorawan_invalid_crc, tvb, current_offset, 4, "Invalid CRC"); + } + } else { + proto_tree_add_expert_format(lorawan_tree, pinfo, &ei_lorawan_unverified_crc, tvb, current_offset, 4, "Unverified CRC"); + } + proto_tree_add_item(lorawan_tree, hf_lorawan_mic_type, tvb, current_offset, 4, ENC_LITTLE_ENDIAN); + return tvb_captured_length(tvb); +} + +void +proto_register_lorawan(void) +{ + static hf_register_info hf[] = { + { &hf_lorawan_mac_header_type, + { "MAC Header", "lorawan.mhdr", + FT_NONE, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_header_mtype_type, + { "Message Type", "lorawan.mhdr.mtype", + FT_UINT8, BASE_DEC, + VALS(lorawan_mtypenames), LORAWAN_MAC_MTYPE_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_header_major_type, + { "Major Version", "lorawan.mhdr.major", + FT_UINT8, BASE_DEC, + VALS(lorawan_majornames), LORAWAN_MAC_MAJOR_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_commands_type, + { "MAC Commands", "lorawan.mac_commands", + FT_NONE, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_uplink_type, + { "Uplink Command", "lorawan.mac_command_uplink", + FT_UINT8, BASE_DEC, + VALS(lorawan_mac_uplink_commandnames), 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_downlink_type, + { "Downlink Command", "lorawan.mac_command_downlink", + FT_UINT8, BASE_DEC, + VALS(lorawan_mac_downlink_commandnames), 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_check_ans_type, + { "Link Check Answer", "lorawan.link_check_answer", + FT_UINT8, BASE_HEX, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_check_ans_margin_type, + { "Demodulation Margin", "lorawan.link_check_answer.margin", + FT_UINT8, BASE_DEC|BASE_UNIT_STRING, + &units_decibels, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_check_ans_gwcnt_type, + { "Gateway Count", "lorawan.link_check_answer.gwcnt", + FT_UINT8, BASE_DEC, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_datarate_type, + { "Data Rate", "lorawan.link_adr_request.datarate", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_DATARATE_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_txpower_type, + { "Transmit Power", "lorawan.link_adr_request.txpower", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_TXPOWER_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel_type, + { "Channel 1", "lorawan.link_adr_request.channel", + FT_UINT16, BASE_HEX, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel1_type, + { "Channel 1", "lorawan.link_adr_request.channel.1", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_1_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel2_type, + { "Channel 2", "lorawan.link_adr_request.channel.2", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_2_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel3_type, + { "Channel 3", "lorawan.link_adr_request.channel.3", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_3_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel4_type, + { "Channel 4", "lorawan.link_adr_request.channel.4", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_4_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel5_type, + { "Channel 5", "lorawan.link_adr_request.channel.5", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_5_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel6_type, + { "Channel 6", "lorawan.link_adr_request.channel.6", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_6_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel7_type, + { "Channel 7", "lorawan.link_adr_request.channel.7", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_7_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel8_type, + { "Channel 8", "lorawan.link_adr_request.channel.8", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_8_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel9_type, + { "Channel 9", "lorawan.link_adr_request.channel.9", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_9_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel10_type, + { "Channel 10", "lorawan.link_adr_request.channel.10", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_10_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel11_type, + { "Channel 11", "lorawan.link_adr_request.channel.11", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_11_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel12_type, + { "Channel 12", "lorawan.link_adr_request.channel.12", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_12_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel13_type, + { "Channel 13", "lorawan.link_adr_request.channel.13", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_13_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel14_type, + { "Channel 14", "lorawan.link_adr_request.channel.14", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_14_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel15_type, + { "Channel 15", "lorawan.link_adr_request.channel.15", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_15_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel16_type, + { "Channel 16", "lorawan.link_adr_request.channel.16", + FT_BOOLEAN, 16, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHANNEL_16_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_channel_mask_control_type, + { "Channel Mask Control", "lorawan.link_adr_request.chmaskctl", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_CHMASKCNTL_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_link_adr_req_repetitions_type, + { "Number Of Repetitions", "lorawan.link_adr_request.nbrep", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_MAC_COMMAND_DOWN_LINK_ADR_REQ_NBREP_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_link_adr_ans_txpower_type, + { "Transmit Power Ack", "lorawan.link_adr_response.txpower", + FT_BOOLEAN, 8, + NULL, LORAWAN_MAC_COMMAND_UP_LINK_ADR_ANS_TXPOWER_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_link_adr_ans_datarate_type, + { "Data Rate Ack", "lorawan.link_adr_response.datarate", + FT_BOOLEAN, 8, + NULL, LORAWAN_MAC_COMMAND_UP_LINK_ADR_ANS_DATARATE_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_link_adr_ans_channel_mask_type, + { "Channel Mask Ack", "lorawan.link_adr_response.channelmask", + FT_BOOLEAN, 8, + NULL, LORAWAN_MAC_COMMAND_UP_LINK_ADR_ANS_CHANNEL_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_dutycycle_type, + { "Duty Cycle", "lorawan.dutycycle_request.dutycycle", + FT_UINT8, BASE_DEC, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_rx_setup_req_rx1droffset_type, + { "RX1 Datarate Offset", "lorawan.rx_setup_request.rx1droffset", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_MAC_COMMAND_DOWN_RX_SETUP_RX1DROFFSET_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_rx_setup_req_rx2datarate_type, + { "RX2 Datarate", "lorawan.rx_setup_request.rx2datarate", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_MAC_COMMAND_DOWN_RX_SETUP_RX2DATARATE_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_rx_setup_req_frequency_type, + { "Frequency", "lorawan.rx_setup_request.frequency", + FT_UINT24, BASE_DEC|BASE_UNIT_STRING, + &units_hz, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_rx_setup_ans_type, + { "RX Setup Response", "lorawan.rx_setup_response", + FT_UINT8, BASE_HEX, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_rx_setup_ans_rx1droffset_type, + { "RX1 Datarate Offset Ack", "lorawan.rx_setup_response.rx1droffset", + FT_BOOLEAN, 8, + NULL, LORAWAN_MAC_COMMAND_UP_RX_SETUP_ANS_TXPOWER_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_rx_setup_ans_rx2datarate_type, + { "RX2 Datarate Ack", "lorawan.rx_setup_response.rx2datarate", + FT_BOOLEAN, 8, + NULL, LORAWAN_MAC_COMMAND_UP_RX_SETUP_ANS_DATARATE_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_rx_setup_ans_frequency_type, + { "Frequency Ack", "lorawan.rx_setup_response.frequency", + FT_BOOLEAN, 8, + NULL, LORAWAN_MAC_COMMAND_UP_RX_SETUP_ANS_CHANNEL_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_device_status_ans_battery_type, + { "Battery Level", "lorawan.device_status_response.battery", + FT_UINT8, BASE_DEC, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_device_status_ans_margin_type, + { "Margin", "lorawan.device_status_response.margin", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_MAC_COMMAND_UP_DEVICE_STATUS_ANS_MARGIN_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_new_channel_req_index_type, + { "Index", "lorawan.new_channel_request.index", + FT_UINT8, BASE_DEC, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_new_channel_req_frequency_type, + { "Frequency", "lorawan.new_channel_request.frequency", + FT_UINT24, BASE_DEC|BASE_UNIT_STRING, + &units_hz, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_new_channel_req_drrange_max_type, + { "Maximum Data Rate", "lorawan.new_channel_request.drrange_max", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_MAC_COMMAND_DOWN_NEW_CHANNEL_REQ_DRRANGE_MAX_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_new_channel_req_drrange_min_type, + { "Minimum Data Rate", "lorawan.new_channel_request.drrange_min", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_MAC_COMMAND_DOWN_NEW_CHANNEL_REQ_DRRANGE_MIN_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_new_channel_ans_type, + { "New Channel Response", "lorawan.new_channel_response", + FT_UINT8, BASE_HEX, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_new_channel_ans_datarate_type, + { "Datarate Ack", "lorawan.new_channel_response.datarate", + FT_BOOLEAN, 8, + NULL, LORAWAN_MAC_COMMAND_UP_NEW_CHANNEL_ANS_DATARATE_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_up_new_channel_ans_frequency_type, + { "Frequency Ack", "lorawan.new_channel_response.frequency", + FT_BOOLEAN, 8, + NULL, LORAWAN_MAC_COMMAND_UP_NEW_CHANNEL_ANS_FREQUENCY_MASK, + NULL, HFILL } + }, + { &hf_lorawan_mac_command_down_rx_timing_req_delay_type, + { "Delay", "lorawan.rx_timing_request.delay", + FT_UINT8, BASE_DEC|BASE_UNIT_STRING, + &units_seconds, LORAWAN_MAC_COMMAND_DOWN_RX_TIMING_REQ_DELAY_MASK, + NULL, HFILL } + }, + { &hf_lorawan_join_request_appeui_type, + { "AppEUI", "lorawan.join_request.appeui", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_join_request_deveui_type, + { "DevEUI", "lorawan.join_request.deveui", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_join_request_devnonce_type, + { "Device Nonce", "lorawan.join_request.devnonce", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_join_accept_appnonce_type, + { "Application Nonce", "lorawan.join_accept.appnonce", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_join_accept_netid_type, + { "Net ID", "lorawan.join_accept.netid", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_join_accept_devaddr_type, + { "Device Address", "lorawan.join_accept.devaddr", + FT_UINT32, BASE_HEX, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_join_accept_dlsettings_rx1droffset_type, + { "RX1 Datarate Offset", "lorawan.join_accept.rx1droffset", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_JOIN_ACCEPT_RX1DROFFSET_MASK, + NULL, HFILL } + }, + { &hf_lorawan_join_accept_dlsettings_rx2dr_type, + { "RX2 Datarate", "lorawan.join_accept.rx2datarate", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_JOIN_ACCEPT_RX2DR_MASK, + NULL, HFILL } + }, + { &hf_lorawan_join_accept_rxdelay_type, + { "RX Delay", "lorawan.join_accept.rxdelay", + FT_UINT8, BASE_DEC|BASE_UNIT_STRING, + &units_seconds, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_join_accept_cflist_type, + { "Channel Frequency List", "lorawan.join_accept.cflist", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_frame_header_type, + { "Frame Header", "lorawan.fhdr", + FT_NONE, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_frame_header_address_type, + { "Device Address", "lorawan.fhdr.devaddr", + FT_UINT32, BASE_HEX, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_frame_header_frame_control_type, + { "Frame Control", "lorawan.fhdr.fctrl", + FT_UINT8, BASE_HEX, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_frame_header_frame_control_adr_type, + { "Adaptive Data Rate", "lorawan.fhdr.fctrl.adr", + FT_BOOLEAN, 8, + NULL, 0x80, + NULL, HFILL } + }, + { &hf_lorawan_frame_header_frame_control_adrackreq_type, + { "ADR Acknowledgement Request", "lorawan.fhdr.fctrl.adrackreq", + FT_BOOLEAN, 8, + NULL, 0x40, + NULL, HFILL } + }, + { &hf_lorawan_frame_header_frame_control_ack_type, + { "ACK", "lorawan.fhdr.fctrl.ack", + FT_BOOLEAN, 8, + NULL, 0x20, + NULL, HFILL } + }, + { &hf_lorawan_frame_header_frame_control_fpending_type, + { "Frame Pending", "lorawan.fhdr.fctrl.fpending", + FT_BOOLEAN, 8, + NULL, 0x10, + NULL, HFILL } + }, + { &hf_lorawan_frame_header_frame_control_foptslen_type, + { "Frame Options Length", "lorawan.fhdr.fctrl.foptslen", + FT_UINT8, BASE_DEC, + NULL, LORAWAN_FRAME_FOPTSLEN_MASK, + NULL, HFILL } + }, + { &hf_lorawan_frame_header_frame_counter_type, + { "Frame Counter", "lorawan.fhdr.fcnt", + FT_UINT16, BASE_DEC, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_frame_fport_type, + { "FPort", "lorawan.fport", + FT_UINT8, BASE_HEX, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_frame_payload, + { "Frame Payload", "lorawan.frmpayload", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_frame_payload_decrypted, + { "Decrypted Frame Payload", "lorawan.frmpayload_decrypted", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_lorawan_mic_type, + { "Message Integrity Code", "lorawan.mic", + FT_UINT32, BASE_HEX, + NULL, 0x0, + NULL, HFILL } + } + }; + + /* Setup protocol subtree array */ + static gint *ett[] = { + &ett_lorawan, + &ett_lorawan_mac_header, + &ett_lorawan_mac_commands, + &ett_lorawan_mac_command, + &ett_lorawan_mac_command_link_check_ans, + &ett_lorawan_mac_command_link_adr_req_channel, + &ett_lorawan_mac_command_rx_setup_ans, + &ett_lorawan_mac_command_new_channel_ans, + &ett_lorawan_join_request, + &ett_lorawan_join_accept, + &ett_lorawan_frame_header, + &ett_lorawan_frame_header_control, + &ett_lorawan_frame_payload_decrypted + }; + + static ei_register_info ei[] = { + { &ei_lorawan_invalid_crc, { "lorawan.invalid_crc", PI_MALFORMED, PI_WARN, "CRC does not match data", EXPFILL }}, + { &ei_lorawan_unverified_crc, { "lorawan.unverified_crc", PI_MALFORMED, PI_WARN, "CRC could not be verified because of missing encryption keys", EXPFILL }}, + { &ei_lorawan_decrypting_error, { "lorawan.decrypting_error", PI_MALFORMED, PI_WARN, "Error decrypting payload", EXPFILL }}, + }; + + expert_module_t* expert_lorawan; + + proto_lorawan = proto_register_protocol ( + "LoRaWAN Protocol", /* name */ + "LoRaWAN", /* short name */ + "lorawan" /* abbrev */ + ); + + proto_register_field_array(proto_lorawan, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + + expert_lorawan = expert_register_protocol(proto_lorawan); + expert_register_field_array(expert_lorawan, ei, array_length(ei)); + + static uat_field_t device_encryption_keys_uat_fields[] = { + UAT_FLD_CSTRING(device_encryption_keys, dev_addr_string, "Device Address", "LoRaWAN Device Address"), + UAT_FLD_CSTRING(device_encryption_keys, nwkskey_string, "Network Key", "LoRaWAN Network Key"), + UAT_FLD_CSTRING(device_encryption_keys, appskey_string, "Application Key", "LoRaWAN Application Key"), + UAT_FLD_CSTRING(device_encryption_keys, appeui_string, "Application EUI", "LoRaWAN Application EUI"), + UAT_END_FIELDS + }; + + uat_t *device_encryption_keys_uat = uat_new("LoRaWAN Encryption Keys", + sizeof(device_encryption_keys_t), + "encryption_keys_lorawan", + TRUE, + &device_encryption_keys, + &device_encryption_num_keys, + UAT_AFFECTS_DISSECTION|UAT_AFFECTS_FIELDS, + NULL, + device_encryption_keys_copy_cb, + device_encryption_keys_update_cb, + device_encryption_keys_free_cb, + NULL, + NULL, + device_encryption_keys_uat_fields + ); + + module_t *lorawan_module; + lorawan_module = prefs_register_protocol(proto_lorawan, proto_reg_handoff_lorawan); + prefs_register_uat_preference(lorawan_module, "encryption_keys_lorawan", "LoRaWAN Encryption Keys", + "A table to define encryption keys for LoRaWAN devices", + device_encryption_keys_uat); +} + +void +proto_reg_handoff_lorawan(void) +{ + static dissector_handle_t lorawan_handle; + lorawan_handle = create_dissector_handle(dissect_lorawan, proto_lorawan); + dissector_add_uint("loratap.syncword", 0x34, lorawan_handle); +} + +/* + * Editor modelines - http://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ |