86890704fd
todo: add documentation & wireshark dissector
695 lines
27 KiB
Python
Executable file
695 lines
27 KiB
Python
Executable file
# This file is part of Scapy
|
|
# Scapy 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
|
|
# any later version.
|
|
#
|
|
# Scapy 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 Scapy. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# scapy.contrib.description = LoRa PHY to WAN Layer
|
|
# scapy.contrib.status = loads
|
|
|
|
|
|
"""
|
|
Copyright (C) 2020 Sebastien Dudek (@FlUxIuS @PentHertz)
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
from scapy.packet import Packet
|
|
from scapy.fields import BitField, ByteEnumField, ByteField, \
|
|
ConditionalField, IntField, LEShortField, PacketListField, \
|
|
StrFixedLenField, X3BytesField, XByteField, XIntField, \
|
|
XShortField, BitFieldLenField, LEX3BytesField, XBitField, \
|
|
BitEnumField, XLEIntField, StrField, PacketField
|
|
|
|
|
|
class FCtrl_DownLink(Packet):
|
|
name = "FCtrl_DownLink"
|
|
fields_desc = [BitField("ADR", 0, 1),
|
|
BitField("ADRACKReq", 0, 1),
|
|
BitField("ACK", 0, 1),
|
|
BitField("FPending", 0, 1),
|
|
BitFieldLenField("FOptsLen", 0, 4)]
|
|
|
|
# pylint: disable=R0201
|
|
def extract_padding(self, p):
|
|
return "", p
|
|
|
|
|
|
class FCtrl_UpLink(Packet):
|
|
name = "FCtrl_UpLink"
|
|
fields_desc = [BitField("ADR", 0, 1),
|
|
BitField("ADRACKReq", 0, 1),
|
|
BitField("ACK", 0, 1),
|
|
BitField("ClassB", 0, 1),
|
|
BitFieldLenField("FOptsLen", 0, 4)]
|
|
|
|
# pylint: disable=R0201
|
|
def extract_padding(self, p):
|
|
return "", p
|
|
|
|
|
|
class DevAddrElem(Packet):
|
|
name = "DevAddrElem"
|
|
fields_desc = [XByteField("NwkID", 0x0),
|
|
LEX3BytesField("NwkAddr", b"\x00" * 3)]
|
|
|
|
|
|
CIDs_up = {0x01: "ResetInd",
|
|
0x02: "LinkCheckReq",
|
|
0x03: "LinkADRReq",
|
|
0x04: "DutyCycleReq",
|
|
0x05: "RXParamSetupReq",
|
|
0x06: "DevStatusReq",
|
|
0x07: "NewChannelReq",
|
|
0x08: "RXTimingSetupReq",
|
|
0x09: "TxParamSetupReq", # LoRa 1.1 specs
|
|
0x0A: "DlChannelReq",
|
|
0x0B: "RekeyInd",
|
|
0x0C: "ADRParamSetupReq",
|
|
0x0D: "DeviceTimeReq",
|
|
0x0E: "ForceRejoinReq",
|
|
0x0F: "RejoinParamSetupReq"} # end of LoRa 1.1 specs
|
|
|
|
|
|
CIDs_down = {0x01: "ResetConf",
|
|
0x02: "LinkCheckAns",
|
|
0x03: "LinkADRAns",
|
|
0x04: "DutyCycleAns",
|
|
0x05: "RXParamSetupAns",
|
|
0x06: "DevStatusAns",
|
|
0x07: "NewChannelAns",
|
|
0x08: "RXTimingSetupAns",
|
|
0x09: "TxParamSetupAns", # LoRa 1.1 specs here
|
|
0x0A: "DlChannelAns",
|
|
0x0B: "RekeyConf",
|
|
0x0C: "ADRParamSetupAns",
|
|
0x0D: "DeviceTimeAns",
|
|
0x0F: "RejoinParamSetupAns"} # end of LoRa 1.1 specs
|
|
|
|
|
|
class ResetInd(Packet):
|
|
name = "ResetInd"
|
|
fields_desc = [ByteField("Dev_version", 0)]
|
|
|
|
|
|
class ResetConf(Packet):
|
|
name = "ResetConf"
|
|
fields_desc = [ByteField("Serv_version", 0)]
|
|
|
|
|
|
class LinkCheckReq(Packet):
|
|
name = "LinkCheckReq"
|
|
|
|
|
|
class LinkCheckAns(Packet):
|
|
name = "LinkCheckAns"
|
|
fields_desc = [ByteField("Margin", 0),
|
|
ByteField("GwCnt", 0)]
|
|
|
|
|
|
class DataRate_TXPower(Packet):
|
|
name = "DataRate_TXPower"
|
|
fields_desc = [XBitField("DataRate", 0, 4),
|
|
XBitField("TXPower", 0, 4)]
|
|
|
|
|
|
class Redundancy(Packet):
|
|
name = "Redundancy"
|
|
fields_desc = [XBitField("RFU", 0, 1),
|
|
XBitField("ChMaskCntl", 0, 3),
|
|
XBitField("NbTrans", 0, 4)]
|
|
|
|
|
|
class LinkADRReq(Packet):
|
|
name = "LinkADRReq"
|
|
fields_desc = [DataRate_TXPower,
|
|
XShortField("ChMask", 0),
|
|
Redundancy]
|
|
|
|
|
|
class LinkADRAns_Status(Packet):
|
|
name = "LinkADRAns_Status"
|
|
fields_desc = [BitField("RFU", 0, 5),
|
|
BitField("PowerACK", 0, 1),
|
|
BitField("DataRate", 0, 1),
|
|
BitField("ChannelMaskACK", 0, 1)]
|
|
|
|
|
|
class LinkADRAns(Packet):
|
|
name = "LinkADRAns"
|
|
fields_desc = [PacketField("status",
|
|
LinkADRAns_Status(),
|
|
LinkADRAns_Status)]
|
|
|
|
|
|
class DutyCyclePL(Packet):
|
|
name = "DutyCyclePL"
|
|
fields_desc = [BitField("MaxDCycle", 0, 4)]
|
|
|
|
|
|
class DutyCycleReq(Packet):
|
|
name = "DutyCycleReq"
|
|
fields_desc = [DutyCyclePL]
|
|
|
|
|
|
class DutyCycleAns(Packet):
|
|
name = "DutyCycleAns"
|
|
fields_desc = []
|
|
|
|
|
|
class DLsettings(Packet):
|
|
name = "DLsettings"
|
|
fields_desc = [BitField("OptNeg", 0, 1),
|
|
XBitField("RX1DRoffset", 0, 3),
|
|
XBitField("RX2_Data_rate", 0, 4)]
|
|
|
|
|
|
class RXParamSetupReq(Packet):
|
|
name = "RXParamSetupReq"
|
|
fields_desc = [DLsettings,
|
|
X3BytesField("Frequency", 0)]
|
|
|
|
|
|
class RXParamSetupAns_Status(Packet):
|
|
name = "RXParamSetupAns_Status"
|
|
fields_desc = [XBitField("RFU", 0, 5),
|
|
BitField("RX1DRoffsetACK", 0, 1),
|
|
BitField("RX2DatarateACK", 0, 1),
|
|
BitField("ChannelACK", 0, 1)]
|
|
|
|
|
|
class RXParamSetupAns(Packet):
|
|
name = "RXParamSetupAns"
|
|
fields_desc = [RXParamSetupAns_Status]
|
|
|
|
|
|
Battery_state = {0: "End-device connected to external source",
|
|
255: "Battery level unknown"}
|
|
|
|
|
|
class DevStatusReq(Packet):
|
|
name = "DevStatusReq"
|
|
fields_desc = [ByteEnumField("Battery", 0, Battery_state),
|
|
ByteField("Margin", 0)]
|
|
|
|
|
|
class DevStatusAns_Status(Packet):
|
|
name = "DevStatusAns_Status"
|
|
fields_desc = [XBitField("RFU", 0, 2),
|
|
XBitField("Margin", 0, 6)]
|
|
|
|
|
|
class DevStatusAns(Packet):
|
|
name = "DevStatusAns"
|
|
fields_desc = [DevStatusAns_Status]
|
|
|
|
|
|
class DrRange(Packet):
|
|
name = "DrRange"
|
|
fields_desc = [XBitField("MaxDR", 0, 4),
|
|
XBitField("MinDR", 0, 4)]
|
|
|
|
|
|
class NewChannelReq(Packet):
|
|
name = "NewChannelReq"
|
|
fields_desc = [ByteField("ChIndex", 0),
|
|
X3BytesField("Freq", 0),
|
|
DrRange]
|
|
|
|
|
|
class NewChannelAns_Status(Packet):
|
|
name = "NewChannelAns_Status"
|
|
fields_desc = [XBitField("RFU", 0, 6),
|
|
BitField("Dataraterangeok", 0, 1),
|
|
BitField("Channelfrequencyok", 0, 1)]
|
|
|
|
|
|
class NewChannelAns(Packet):
|
|
name = "NewChannelAns"
|
|
fields_desc = [NewChannelAns_Status]
|
|
|
|
|
|
class RXTimingSetupReq_Settings(Packet):
|
|
name = "RXTimingSetupReq_Settings"
|
|
fields_desc = [XBitField("RFU", 0, 4),
|
|
XBitField("Del", 0, 4)]
|
|
|
|
|
|
class RXTimingSetupReq(Packet):
|
|
name = "RXTimingSetupReq"
|
|
fields_desc = [RXTimingSetupReq_Settings]
|
|
|
|
|
|
class RXTimingSetupAns(Packet):
|
|
name = "RXTimingSetupAns"
|
|
fields_desc = []
|
|
|
|
|
|
# Specific commands for LoRa 1.1 here
|
|
|
|
MaxEIRPs = {0: "8 dbm",
|
|
1: "10 dbm",
|
|
2: "12 dbm",
|
|
3: "13 dbm",
|
|
4: "14 dbm",
|
|
5: "16 dbm",
|
|
6: "18 dbm",
|
|
7: "20 dbm",
|
|
8: "21 dbm",
|
|
9: "24 dbm",
|
|
10: "26 dbm",
|
|
11: "27 dbm",
|
|
12: "29 dbm",
|
|
13: "30 dbm",
|
|
14: "33 dbm",
|
|
15: "36 dbm"}
|
|
|
|
|
|
DwellTimes = {0: "No limit",
|
|
1: "400 ms"}
|
|
|
|
|
|
class EIRP_DwellTime(Packet):
|
|
name = "EIRP_DwellTime"
|
|
fields_desc = [BitField("RFU", 0b0, 2),
|
|
BitEnumField("DownlinkDwellTime", 0b0, 1, DwellTimes),
|
|
BitEnumField("UplinkDwellTime", 0b0, 1, DwellTimes),
|
|
BitEnumField("MaxEIRP", 0b0000, 4, MaxEIRPs)]
|
|
|
|
|
|
class TxParamSetupReq(Packet):
|
|
name = "TxParamSetupReq"
|
|
fields_desc = [EIRP_DwellTime]
|
|
|
|
|
|
class TxParamSetupAns(Packet):
|
|
name = "TxParamSetupAns"
|
|
fields_desc = []
|
|
|
|
|
|
class DlChannelReq(Packet):
|
|
name = "DlChannelReq"
|
|
fields_desc = [ByteField("ChIndex", 0),
|
|
X3BytesField("Freq", 0)]
|
|
|
|
|
|
class DlChannelAns(Packet):
|
|
name = "DlChannelAns"
|
|
fields_desc = [ByteField("Status", 0)]
|
|
|
|
|
|
class DevLoraWANversion(Packet):
|
|
name = "DevLoraWANversion"
|
|
fields_desc = [BitField("RFU", 0b0000, 4),
|
|
BitField("Minor", 0b0001, 4)]
|
|
|
|
|
|
class RekeyInd(Packet):
|
|
name = "RekeyInd"
|
|
fields_desc = [PacketListField("LoRaWANversion", b"",
|
|
DevLoraWANversion, length_from=lambda pkt:1)]
|
|
|
|
|
|
class RekeyConf(Packet):
|
|
name = "RekeyConf"
|
|
fields_desc = [ByteField("ServerVersion", 0)]
|
|
|
|
|
|
class ADRparam(Packet):
|
|
name = "ADRparam"
|
|
fields_desc = [BitField("Limit_exp", 0b0000, 4),
|
|
BitField("Delay_exp", 0b0000, 4)]
|
|
|
|
|
|
class ADRParamSetupReq(Packet):
|
|
name = "ADRParamSetupReq"
|
|
fields_desc = [ADRparam]
|
|
|
|
|
|
class ADRParamSetupAns(Packet):
|
|
name = "ADRParamSetupReq"
|
|
fields_desc = []
|
|
|
|
|
|
class DeviceTimeReq(Packet):
|
|
name = "DeviceTimeReq"
|
|
fields_desc = []
|
|
|
|
|
|
class DeviceTimeAns(Packet):
|
|
name = "DeviceTimeAns"
|
|
fields_desc = [IntField("SecondsSinceEpoch", 0),
|
|
ByteField("FracSecond", 0x00)]
|
|
|
|
|
|
class ForceRejoinReq(Packet):
|
|
name = "ForceRejoinReq"
|
|
fields_desc = [BitField("RFU", 0, 2),
|
|
BitField("Period", 0, 3),
|
|
BitField("Max_Retries", 0, 3),
|
|
BitField("RFU", 0, 1),
|
|
BitField("RejoinType", 0, 3),
|
|
BitField("DR", 0, 4)]
|
|
|
|
|
|
class RejoinParamSetupReq(Packet):
|
|
name = "RejoinParamSetupReq"
|
|
fields_desc = [BitField("MaxTimeN", 0, 4),
|
|
BitField("MaxCountN", 0, 4)]
|
|
|
|
|
|
class RejoinParamSetupAns(Packet):
|
|
name = "RejoinParamSetupAns"
|
|
fields_desc = [BitField("RFU", 0, 7),
|
|
BitField("TimeOK", 0, 1)]
|
|
|
|
|
|
# End of specific 1.1 commands
|
|
|
|
|
|
class MACCommand_up(Packet):
|
|
name = "MACCommand_up"
|
|
fields_desc = [ByteEnumField("CID", 0, CIDs_up),
|
|
ConditionalField(PacketListField("Reset", b"",
|
|
ResetInd,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x01)),
|
|
ConditionalField(PacketListField("LinkCheck", b"",
|
|
LinkCheckReq,
|
|
length_from=lambda pkt:0),
|
|
lambda pkt:(pkt.CID == 0x02)),
|
|
ConditionalField(PacketListField("LinkADR", b"",
|
|
LinkADRReq,
|
|
length_from=lambda pkt:4),
|
|
lambda pkt:(pkt.CID == 0x03)),
|
|
ConditionalField(PacketListField("DutyCycle", b"",
|
|
DutyCycleReq,
|
|
length_from=lambda pkt:4),
|
|
lambda pkt:(pkt.CID == 0x04)),
|
|
ConditionalField(PacketListField("RXParamSetup", b"",
|
|
RXParamSetupReq,
|
|
length_from=lambda pkt:4),
|
|
lambda pkt:(pkt.CID == 0x05)),
|
|
ConditionalField(PacketListField("DevStatus", b"",
|
|
DevStatusReq,
|
|
length_from=lambda pkt:2),
|
|
lambda pkt:(pkt.CID == 0x06)),
|
|
ConditionalField(PacketListField("NewChannel", b"",
|
|
NewChannelReq,
|
|
length_from=lambda pkt:5),
|
|
lambda pkt:(pkt.CID == 0x07)),
|
|
ConditionalField(PacketListField("RXTimingSetup", b"",
|
|
RXTimingSetupReq,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x08)),
|
|
# specific to 1.1 from here
|
|
ConditionalField(PacketListField("TxParamSetup", b"",
|
|
TxParamSetupReq,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x09)),
|
|
ConditionalField(PacketListField("DlChannel", b"",
|
|
DlChannelReq,
|
|
length_from=lambda pkt:4),
|
|
lambda pkt:(pkt.CID == 0x0A)),
|
|
ConditionalField(PacketListField("Rekey", b"",
|
|
RekeyInd,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x0B)),
|
|
ConditionalField(PacketListField("ADRParamSetup", b"",
|
|
ADRParamSetupReq,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x0C)),
|
|
ConditionalField(PacketListField("DeviceTime", b"",
|
|
DeviceTimeReq,
|
|
length_from=lambda pkt:0),
|
|
lambda pkt:(pkt.CID == 0x0D)),
|
|
ConditionalField(PacketListField("ForceRejoin", b"",
|
|
ForceRejoinReq,
|
|
length_from=lambda pkt:2),
|
|
lambda pkt:(pkt.CID == 0x0E)),
|
|
ConditionalField(PacketListField("RejoinParamSetup", b"",
|
|
RejoinParamSetupReq,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x0F))]
|
|
|
|
# pylint: disable=R0201
|
|
def extract_padding(self, p):
|
|
return "", p
|
|
|
|
|
|
class MACCommand_down(Packet):
|
|
name = "MACCommand_down"
|
|
fields_desc = [ByteEnumField("CID", 0, CIDs_up),
|
|
ConditionalField(PacketListField("Reset", b"",
|
|
ResetConf,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x01)),
|
|
ConditionalField(PacketListField("LinkCheck", b"",
|
|
LinkCheckAns,
|
|
length_from=lambda pkt:2),
|
|
lambda pkt:(pkt.CID == 0x02)),
|
|
ConditionalField(PacketListField("LinkADR", b"",
|
|
LinkADRAns,
|
|
length_from=lambda pkt:0),
|
|
lambda pkt:(pkt.CID == 0x03)),
|
|
ConditionalField(PacketListField("DutyCycle", b"",
|
|
DutyCycleAns,
|
|
length_from=lambda pkt:4),
|
|
lambda pkt:(pkt.CID == 0x04)),
|
|
ConditionalField(PacketListField("RXParamSetup", b"",
|
|
RXParamSetupAns,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x05)),
|
|
ConditionalField(PacketListField("DevStatusAns", b"",
|
|
RXParamSetupAns,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x06)),
|
|
ConditionalField(PacketListField("NewChannel", b"",
|
|
NewChannelAns,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x07)),
|
|
ConditionalField(PacketListField("RXTimingSetup", b"",
|
|
RXTimingSetupAns,
|
|
length_from=lambda pkt:0),
|
|
lambda pkt:(pkt.CID == 0x08)),
|
|
ConditionalField(PacketListField("TxParamSetup", b"",
|
|
TxParamSetupAns,
|
|
length_from=lambda pkt:0),
|
|
lambda pkt:(pkt.CID == 0x09)),
|
|
ConditionalField(PacketListField("DlChannel", b"",
|
|
DlChannelAns,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x0A)),
|
|
ConditionalField(PacketListField("Rekey", b"",
|
|
RekeyConf,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x0B)),
|
|
ConditionalField(PacketListField("ADRParamSetup", b"",
|
|
ADRParamSetupAns,
|
|
length_from=lambda pkt:0),
|
|
lambda pkt:(pkt.CID == 0x0C)),
|
|
ConditionalField(PacketListField("DeviceTime", b"",
|
|
DeviceTimeAns,
|
|
length_from=lambda pkt:5),
|
|
lambda pkt:(pkt.CID == 0x0D)),
|
|
ConditionalField(PacketListField("RejoinParamSetup", b"",
|
|
RejoinParamSetupAns,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.CID == 0x0F))]
|
|
|
|
|
|
class FOpts(Packet):
|
|
name = "FOpts"
|
|
fields_desc = [ConditionalField(PacketListField("FOpts_up", b"",
|
|
# UL piggy MAC Command
|
|
MACCommand_up,
|
|
length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501
|
|
lambda pkt:(pkt.FCtrl[0].FOptsLen > 0 and
|
|
pkt.MType & 0b1 == 0 and
|
|
pkt.MType >= 0b010)),
|
|
ConditionalField(PacketListField("FOpts_down", b"",
|
|
# DL piggy MAC Command
|
|
MACCommand_down,
|
|
length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501
|
|
lambda pkt:(pkt.FCtrl[0].FOptsLen > 0 and
|
|
pkt.MType & 0b1 == 1 and
|
|
pkt.MType <= 0b101))]
|
|
|
|
|
|
def FOptsDownShow(pkt):
|
|
try:
|
|
if pkt.FCtrl[0].FOptsLen > 0 and pkt.MType & 0b1 == 1 and pkt.MType <= 0b101: # noqa: E501
|
|
return True
|
|
return False
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def FOptsUpShow(pkt):
|
|
try:
|
|
if pkt.FCtrl[0].FOptsLen > 0 and pkt.MType & 0b1 == 0 and pkt.MType >= 0b010: # noqa: E501
|
|
return True
|
|
return False
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
class FHDR(Packet):
|
|
name = "FHDR"
|
|
fields_desc = [ConditionalField(PacketListField("DevAddr", b"", DevAddrElem, # noqa: E501
|
|
length_from=lambda pkt:4),
|
|
lambda pkt:(pkt.MType >= 0b010 and
|
|
pkt.MType <= 0b101)),
|
|
ConditionalField(PacketListField("FCtrl", b"",
|
|
FCtrl_DownLink,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.MType & 0b1 == 1 and
|
|
pkt.MType <= 0b101)),
|
|
ConditionalField(PacketListField("FCtrl", b"",
|
|
FCtrl_UpLink,
|
|
length_from=lambda pkt:1),
|
|
lambda pkt:(pkt.MType & 0b1 == 0 and
|
|
pkt.MType >= 0b010)),
|
|
ConditionalField(LEShortField("FCnt", 0),
|
|
lambda pkt:(pkt.MType >= 0b010 and
|
|
pkt.MType <= 0b101)),
|
|
ConditionalField(PacketListField("FOpts_up", b"",
|
|
MACCommand_up,
|
|
length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501
|
|
FOptsUpShow),
|
|
ConditionalField(PacketListField("FOpts_down", b"",
|
|
MACCommand_down,
|
|
length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501
|
|
FOptsDownShow)]
|
|
|
|
|
|
FPorts = {0: "NwkSKey"} # anything else is AppSKey
|
|
|
|
|
|
JoinReqTypes = {0xFF: "Join-request",
|
|
0x00: "Rejoin-request type 0",
|
|
0x01: "Rejoin-request type 1",
|
|
0x02: "Rejoin-request type 2"}
|
|
|
|
|
|
class Join_Request(Packet):
|
|
name = "Join_Request"
|
|
fields_desc = [StrFixedLenField("AppEUI", b"\x00" * 8, 8),
|
|
StrFixedLenField("DevEUI", b"\00" * 8, 8),
|
|
LEShortField("DevNonce", 0x0000)]
|
|
|
|
|
|
class Join_Accept(Packet):
|
|
name = "Join_Accept"
|
|
dcflist = False
|
|
fields_desc = [LEX3BytesField("JoinAppNonce", 0),
|
|
LEX3BytesField("NetID", 0),
|
|
XLEIntField("DevAddr", 0),
|
|
DLsettings,
|
|
XByteField("RxDelay", 0),
|
|
ConditionalField(StrFixedLenField("CFList", b"\x00" * 16, 16), # noqa: E501
|
|
lambda pkt:(Join_Accept.dcflist is True))]
|
|
|
|
# pylint: disable=R0201
|
|
def extract_padding(self, p):
|
|
return "", p
|
|
|
|
def __init__(self, packet=""): # CFList calculated with rest of packet len
|
|
if len(packet) > 18:
|
|
Join_Accept.dcflist = True
|
|
super(Join_Accept, self).__init__(packet)
|
|
|
|
|
|
RejoinType = {0: "NetID+DevEUI",
|
|
1: "JoinEUI+DevEUI",
|
|
2: "NetID+DevEUI"}
|
|
|
|
|
|
class RejoinReq(Packet): # LoRa 1.1 specs
|
|
name = "RejoinReq"
|
|
fields_desc = [ByteField("Type", 0),
|
|
X3BytesField("NetID", 0),
|
|
StrFixedLenField("DevEUI", b"\x00" * 8),
|
|
XShortField("RJcount0", 0)]
|
|
|
|
|
|
class FRMPayload(Packet):
|
|
name = "FRMPayload"
|
|
fields_desc = [ConditionalField(StrField("DataPayload", "", remain=4), # Downlink # noqa: E501
|
|
lambda pkt:(pkt.MType == 0b101 or
|
|
pkt.MType == 0b011)),
|
|
ConditionalField(StrField("DataPayload", "", remain=6), # Uplink # noqa: E501
|
|
lambda pkt:(pkt.MType == 0b100 or
|
|
pkt.MType == 0b010)),
|
|
ConditionalField(PacketListField("Join_Request_Field", b"",
|
|
Join_Request,
|
|
length_from=lambda pkt:18),
|
|
lambda pkt:(pkt.MType == 0b000)),
|
|
ConditionalField(PacketListField("Join_Accept_Field", b"",
|
|
Join_Accept,
|
|
count_from=lambda pkt:1),
|
|
lambda pkt:(pkt.MType == 0b001 and
|
|
LoRa.encrypted is False)),
|
|
ConditionalField(StrField("Join_Accept_Encrypted", 0),
|
|
lambda pkt:(pkt.MType == 0b001 and LoRa.encrypted is True)), # noqa: E501
|
|
ConditionalField(PacketListField("ReJoin_Request_Field", b"", # noqa: E501
|
|
RejoinReq,
|
|
length_from=lambda pkt:14),
|
|
lambda pkt:(pkt.MType == 0b111))]
|
|
|
|
|
|
class MACPayload(Packet):
|
|
name = "MACPayload"
|
|
eFPort = False
|
|
fields_desc = [FHDR,
|
|
ConditionalField(ByteEnumField("FPort", 0, FPorts),
|
|
lambda pkt:(pkt.MType >= 0b010 and
|
|
pkt.MType <= 0b101 and
|
|
pkt.FCtrl[0].FOptsLen == 0)),
|
|
FRMPayload]
|
|
|
|
|
|
MTypes = {0b000: "Join-request",
|
|
0b001: "Join-accept",
|
|
0b010: "Unconfirmed Data Up",
|
|
0b011: "Unconfirmed Data Down",
|
|
0b100: "Confirmed Data Up",
|
|
0b101: "Confirmed Data Down",
|
|
0b110: "Rejoin-request", # Only in LoRa 1.1 specs
|
|
0b111: "Proprietary"}
|
|
|
|
|
|
class MHDR(Packet): # Same for 1.0 as for 1.1
|
|
name = "MHDR"
|
|
fields_desc = [BitEnumField("MType", 0b000, 3, MTypes),
|
|
BitField("RFU", 0b000, 3),
|
|
BitField("Major", 0b00, 2)]
|
|
|
|
|
|
class PHYPayload(Packet):
|
|
name = "PHYPayload"
|
|
fields_desc = [MHDR,
|
|
MACPayload,
|
|
ConditionalField(XIntField("MIC", 0),
|
|
lambda pkt:(pkt.MType != 0b001 or
|
|
LoRa.encrypted is False))]
|
|
|
|
|
|
class LoRa(Packet): # default frame (unclear specs => taken from https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5677147/) # noqa: E501
|
|
name = "LoRa"
|
|
version = "1.1" # default version to parse
|
|
encrypted = True
|
|
fields_desc = [XBitField("Preamble", 0, 4),
|
|
XBitField("PHDR", 0, 16),
|
|
XBitField("PHDR_CRC", 0, 4),
|
|
PHYPayload,
|
|
ConditionalField(XShortField("CRC", 0),
|
|
lambda pkt:(pkt.MType & 0b1 == 0))]
|