1# This file is part of Scapy 2# Scapy is free software: you can redistribute it and/or modify 3# it under the terms of the GNU General Public License as published by 4# the Free Software Foundation, either version 2 of the License, or 5# any later version. 6# 7# Scapy is distributed in the hope that it will be useful, 8# but WITHOUT ANY WARRANTY; without even the implied warranty of 9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10# GNU General Public License for more details. 11# 12# You should have received a copy of the GNU General Public License 13# along with Scapy. If not, see <http://www.gnu.org/licenses/>. 14 15# scapy.contrib.description = LoRa PHY to WAN Layer 16# scapy.contrib.status = loads 17 18""" 19 Copyright (C) 2020 Sebastien Dudek (@FlUxIuS) 20 initially developed @PentHertz 21 and improved at @Trend Micro 22""" 23 24from scapy.packet import Packet 25from scapy.fields import BitField, ByteEnumField, ByteField, \ 26 ConditionalField, IntField, LEShortField, PacketListField, \ 27 StrFixedLenField, X3BytesField, XByteField, XIntField, \ 28 XShortField, BitFieldLenField, LEX3BytesField, XBitField, \ 29 BitEnumField, XLEIntField, StrField, PacketField, \ 30 MultipleTypeField 31 32 33class FCtrl_DownLink(Packet): 34 name = "FCtrl_DownLink" 35 fields_desc = [BitField("ADR", 0, 1), 36 BitField("ADRACKReq", 0, 1), 37 BitField("ACK", 0, 1), 38 BitField("FPending", 0, 1), 39 BitFieldLenField("FOptsLen", 0, 4)] 40 41 def extract_padding(self, p): 42 return "", p 43 44 45class FCtrl_Link(Packet): 46 name = "FCtrl_UpLink" 47 fields_desc = [BitField("ADR", 0, 1), 48 BitField("ADRACKReq", 0, 1), 49 BitField("ACK", 0, 1), 50 BitField("UpClassB_DownFPending", 0, 1), 51 BitFieldLenField("FOptsLen", 0, 4)] 52 53 def extract_padding(self, p): 54 return "", p 55 56 57class FCtrl_UpLink(Packet): 58 name = "FCtrl_UpLink" 59 fields_desc = [BitField("ADR", 0, 1), 60 BitField("ADRACKReq", 0, 1), 61 BitField("ACK", 0, 1), 62 BitField("ClassB", 0, 1), 63 BitFieldLenField("FOptsLen", 0, 4)] 64 65 def extract_padding(self, p): 66 return "", p 67 68 69class DevAddrElem(Packet): 70 name = "DevAddrElem" 71 fields_desc = [XByteField("NwkID", 0x0), 72 LEX3BytesField("NwkAddr", b"\x00" * 3)] 73 74 75CIDs_up = {0x01: "ResetInd", 76 0x02: "LinkCheckReq", 77 0x03: "LinkADRReq", 78 0x04: "DutyCycleReq", 79 0x05: "RXParamSetupReq", 80 0x06: "DevStatusReq", 81 0x07: "NewChannelReq", 82 0x08: "RXTimingSetupReq", 83 0x09: "TxParamSetupReq", # LoRa 1.1 specs 84 0x0A: "DlChannelReq", 85 0x0B: "RekeyInd", 86 0x0C: "ADRParamSetupReq", 87 0x0D: "DeviceTimeReq", 88 0x0E: "ForceRejoinReq", 89 0x0F: "RejoinParamSetupReq"} # end of LoRa 1.1 specs 90 91 92CIDs_down = {0x01: "ResetConf", 93 0x02: "LinkCheckAns", 94 0x03: "LinkADRAns", 95 0x04: "DutyCycleAns", 96 0x05: "RXParamSetupAns", 97 0x06: "DevStatusAns", 98 0x07: "NewChannelAns", 99 0x08: "RXTimingSetupAns", 100 0x09: "TxParamSetupAns", # LoRa 1.1 specs here 101 0x0A: "DlChannelAns", 102 0x0B: "RekeyConf", 103 0x0C: "ADRParamSetupAns", 104 0x0D: "DeviceTimeAns", 105 0x0F: "RejoinParamSetupAns"} # end of LoRa 1.1 specs 106 107 108class ResetInd(Packet): 109 name = "ResetInd" 110 fields_desc = [ByteField("Dev_version", 0)] 111 112 113class ResetConf(Packet): 114 name = "ResetConf" 115 fields_desc = [ByteField("Serv_version", 0)] 116 117 118class LinkCheckReq(Packet): 119 name = "LinkCheckReq" 120 121 122class LinkCheckAns(Packet): 123 name = "LinkCheckAns" 124 fields_desc = [ByteField("Margin", 0), 125 ByteField("GwCnt", 0)] 126 127 128class DataRate_TXPower(Packet): 129 name = "DataRate_TXPower" 130 fields_desc = [XBitField("DataRate", 0, 4), 131 XBitField("TXPower", 0, 4)] 132 133 134class Redundancy(Packet): 135 name = "Redundancy" 136 fields_desc = [XBitField("RFU", 0, 1), 137 XBitField("ChMaskCntl", 0, 3), 138 XBitField("NbTrans", 0, 4)] 139 140 141class LinkADRReq(Packet): 142 name = "LinkADRReq" 143 fields_desc = [DataRate_TXPower, 144 XShortField("ChMask", 0), 145 Redundancy] 146 147 148class LinkADRAns_Status(Packet): 149 name = "LinkADRAns_Status" 150 fields_desc = [BitField("RFU", 0, 5), 151 BitField("PowerACK", 0, 1), 152 BitField("DataRate", 0, 1), 153 BitField("ChannelMaskACK", 0, 1)] 154 155 156class LinkADRAns(Packet): 157 name = "LinkADRAns" 158 fields_desc = [PacketField("status", 159 LinkADRAns_Status(), 160 LinkADRAns_Status)] 161 162 163class DutyCyclePL(Packet): 164 name = "DutyCyclePL" 165 fields_desc = [BitField("MaxDCycle", 0, 4)] 166 167 168class DutyCycleReq(Packet): 169 name = "DutyCycleReq" 170 fields_desc = [DutyCyclePL] 171 172 173class DutyCycleAns(Packet): 174 name = "DutyCycleAns" 175 fields_desc = [] 176 177 178class DLsettings(Packet): 179 name = "DLsettings" 180 fields_desc = [BitField("OptNeg", 0, 1), 181 XBitField("RX1DRoffset", 0, 3), 182 XBitField("RX2_Data_rate", 0, 4)] 183 184 185class RXParamSetupReq(Packet): 186 name = "RXParamSetupReq" 187 fields_desc = [DLsettings, 188 X3BytesField("Frequency", 0)] 189 190 191class RXParamSetupAns_Status(Packet): 192 name = "RXParamSetupAns_Status" 193 fields_desc = [XBitField("RFU", 0, 5), 194 BitField("RX1DRoffsetACK", 0, 1), 195 BitField("RX2DatarateACK", 0, 1), 196 BitField("ChannelACK", 0, 1)] 197 198 199class RXParamSetupAns(Packet): 200 name = "RXParamSetupAns" 201 fields_desc = [RXParamSetupAns_Status] 202 203 204Battery_state = {0: "End-device connected to external source", 205 255: "Battery level unknown"} 206 207 208class DevStatusReq(Packet): 209 name = "DevStatusReq" 210 fields_desc = [ByteEnumField("Battery", 0, Battery_state), 211 ByteField("Margin", 0)] 212 213 214class DevStatusAns_Status(Packet): 215 name = "DevStatusAns_Status" 216 fields_desc = [XBitField("RFU", 0, 2), 217 XBitField("Margin", 0, 6)] 218 219 220class DevStatusAns(Packet): 221 name = "DevStatusAns" 222 fields_desc = [DevStatusAns_Status] 223 224 225class DrRange(Packet): 226 name = "DrRange" 227 fields_desc = [XBitField("MaxDR", 0, 4), 228 XBitField("MinDR", 0, 4)] 229 230 231class NewChannelReq(Packet): 232 name = "NewChannelReq" 233 fields_desc = [ByteField("ChIndex", 0), 234 X3BytesField("Freq", 0), 235 DrRange] 236 237 238class NewChannelAns_Status(Packet): 239 name = "NewChannelAns_Status" 240 fields_desc = [XBitField("RFU", 0, 6), 241 BitField("Dataraterangeok", 0, 1), 242 BitField("Channelfrequencyok", 0, 1)] 243 244 245class NewChannelAns(Packet): 246 name = "NewChannelAns" 247 fields_desc = [NewChannelAns_Status] 248 249 250class RXTimingSetupReq_Settings(Packet): 251 name = "RXTimingSetupReq_Settings" 252 fields_desc = [XBitField("RFU", 0, 4), 253 XBitField("Del", 0, 4)] 254 255 256class RXTimingSetupReq(Packet): 257 name = "RXTimingSetupReq" 258 fields_desc = [RXTimingSetupReq_Settings] 259 260 261class RXTimingSetupAns(Packet): 262 name = "RXTimingSetupAns" 263 fields_desc = [] 264 265 266# Specific commands for LoRa 1.1 here 267 268MaxEIRPs = {0: "8 dbm", 269 1: "10 dbm", 270 2: "12 dbm", 271 3: "13 dbm", 272 4: "14 dbm", 273 5: "16 dbm", 274 6: "18 dbm", 275 7: "20 dbm", 276 8: "21 dbm", 277 9: "24 dbm", 278 10: "26 dbm", 279 11: "27 dbm", 280 12: "29 dbm", 281 13: "30 dbm", 282 14: "33 dbm", 283 15: "36 dbm"} 284 285 286DwellTimes = {0: "No limit", 287 1: "400 ms"} 288 289 290class EIRP_DwellTime(Packet): 291 name = "EIRP_DwellTime" 292 fields_desc = [BitField("RFU", 0b0, 2), 293 BitEnumField("DownlinkDwellTime", 0b0, 1, DwellTimes), 294 BitEnumField("UplinkDwellTime", 0b0, 1, DwellTimes), 295 BitEnumField("MaxEIRP", 0b0000, 4, MaxEIRPs)] 296 297 298class TxParamSetupReq(Packet): 299 name = "TxParamSetupReq" 300 fields_desc = [EIRP_DwellTime] 301 302 303class TxParamSetupAns(Packet): 304 name = "TxParamSetupAns" 305 fields_desc = [] 306 307 308class DlChannelReq(Packet): 309 name = "DlChannelReq" 310 fields_desc = [ByteField("ChIndex", 0), 311 X3BytesField("Freq", 0)] 312 313 314class DlChannelAns(Packet): 315 name = "DlChannelAns" 316 fields_desc = [ByteField("Status", 0)] 317 318 319class DevLoraWANversion(Packet): 320 name = "DevLoraWANversion" 321 fields_desc = [BitField("RFU", 0b0000, 4), 322 BitField("Minor", 0b0001, 4)] 323 324 325class RekeyInd(Packet): 326 name = "RekeyInd" 327 fields_desc = [PacketListField("LoRaWANversion", b"", 328 DevLoraWANversion, length_from=lambda pkt:1)] 329 330 331class RekeyConf(Packet): 332 name = "RekeyConf" 333 fields_desc = [ByteField("ServerVersion", 0)] 334 335 336class ADRparam(Packet): 337 name = "ADRparam" 338 fields_desc = [BitField("Limit_exp", 0b0000, 4), 339 BitField("Delay_exp", 0b0000, 4)] 340 341 342class ADRParamSetupReq(Packet): 343 name = "ADRParamSetupReq" 344 fields_desc = [ADRparam] 345 346 347class ADRParamSetupAns(Packet): 348 name = "ADRParamSetupReq" 349 fields_desc = [] 350 351 352class DeviceTimeReq(Packet): 353 name = "DeviceTimeReq" 354 fields_desc = [] 355 356 357class DeviceTimeAns(Packet): 358 name = "DeviceTimeAns" 359 fields_desc = [IntField("SecondsSinceEpoch", 0), 360 ByteField("FracSecond", 0x00)] 361 362 363class ForceRejoinReq(Packet): 364 name = "ForceRejoinReq" 365 fields_desc = [BitField("RFU", 0, 2), 366 BitField("Period", 0, 3), 367 BitField("Max_Retries", 0, 3), 368 BitField("RFU", 0, 1), 369 BitField("RejoinType", 0, 3), 370 BitField("DR", 0, 4)] 371 372 373class RejoinParamSetupReq(Packet): 374 name = "RejoinParamSetupReq" 375 fields_desc = [BitField("MaxTimeN", 0, 4), 376 BitField("MaxCountN", 0, 4)] 377 378 379class RejoinParamSetupAns(Packet): 380 name = "RejoinParamSetupAns" 381 fields_desc = [BitField("RFU", 0, 7), 382 BitField("TimeOK", 0, 1)] 383 384 385# End of specific 1.1 commands 386 387 388class MACCommand_up(Packet): 389 name = "MACCommand_up" 390 fields_desc = [ByteEnumField("CID", 0, CIDs_up), 391 ConditionalField(PacketListField("Reset", b"", 392 ResetInd, 393 length_from=lambda pkt:1), 394 lambda pkt:(pkt.CID == 0x01)), 395 ConditionalField(PacketListField("LinkCheck", b"", 396 LinkCheckReq, 397 length_from=lambda pkt:0), 398 lambda pkt:(pkt.CID == 0x02)), 399 ConditionalField(PacketListField("LinkADR", b"", 400 LinkADRReq, 401 length_from=lambda pkt:4), 402 lambda pkt:(pkt.CID == 0x03)), 403 ConditionalField(PacketListField("DutyCycle", b"", 404 DutyCycleReq, 405 length_from=lambda pkt:4), 406 lambda pkt:(pkt.CID == 0x04)), 407 ConditionalField(PacketListField("RXParamSetup", b"", 408 RXParamSetupReq, 409 length_from=lambda pkt:4), 410 lambda pkt:(pkt.CID == 0x05)), 411 ConditionalField(PacketListField("DevStatus", b"", 412 DevStatusReq, 413 length_from=lambda pkt:2), 414 lambda pkt:(pkt.CID == 0x06)), 415 ConditionalField(PacketListField("NewChannel", b"", 416 NewChannelReq, 417 length_from=lambda pkt:5), 418 lambda pkt:(pkt.CID == 0x07)), 419 ConditionalField(PacketListField("RXTimingSetup", b"", 420 RXTimingSetupReq, 421 length_from=lambda pkt:1), 422 lambda pkt:(pkt.CID == 0x08)), 423 # specific to 1.1 from here 424 ConditionalField(PacketListField("TxParamSetup", b"", 425 TxParamSetupReq, 426 length_from=lambda pkt:1), 427 lambda pkt:(pkt.CID == 0x09)), 428 ConditionalField(PacketListField("DlChannel", b"", 429 DlChannelReq, 430 length_from=lambda pkt:4), 431 lambda pkt:(pkt.CID == 0x0A)), 432 ConditionalField(PacketListField("Rekey", b"", 433 RekeyInd, 434 length_from=lambda pkt:1), 435 lambda pkt:(pkt.CID == 0x0B)), 436 ConditionalField(PacketListField("ADRParamSetup", b"", 437 ADRParamSetupReq, 438 length_from=lambda pkt:1), 439 lambda pkt:(pkt.CID == 0x0C)), 440 ConditionalField(PacketListField("DeviceTime", b"", 441 DeviceTimeReq, 442 length_from=lambda pkt:0), 443 lambda pkt:(pkt.CID == 0x0D)), 444 ConditionalField(PacketListField("ForceRejoin", b"", 445 ForceRejoinReq, 446 length_from=lambda pkt:2), 447 lambda pkt:(pkt.CID == 0x0E)), 448 ConditionalField(PacketListField("RejoinParamSetup", b"", 449 RejoinParamSetupReq, 450 length_from=lambda pkt:1), 451 lambda pkt:(pkt.CID == 0x0F))] 452 453 # pylint: disable=R0201 454 def extract_padding(self, p): 455 return "", p 456 457 458class MACCommand_down(Packet): 459 name = "MACCommand_down" 460 fields_desc = [ByteEnumField("CID", 0, CIDs_up), 461 ConditionalField(PacketListField("Reset", b"", 462 ResetConf, 463 length_from=lambda pkt:1), 464 lambda pkt:(pkt.CID == 0x01)), 465 ConditionalField(PacketListField("LinkCheck", b"", 466 LinkCheckAns, 467 length_from=lambda pkt:2), 468 lambda pkt:(pkt.CID == 0x02)), 469 ConditionalField(PacketListField("LinkADR", b"", 470 LinkADRAns, 471 length_from=lambda pkt:0), 472 lambda pkt:(pkt.CID == 0x03)), 473 ConditionalField(PacketListField("DutyCycle", b"", 474 DutyCycleAns, 475 length_from=lambda pkt:4), 476 lambda pkt:(pkt.CID == 0x04)), 477 ConditionalField(PacketListField("RXParamSetup", b"", 478 RXParamSetupAns, 479 length_from=lambda pkt:1), 480 lambda pkt:(pkt.CID == 0x05)), 481 ConditionalField(PacketListField("DevStatusAns", b"", 482 RXParamSetupAns, 483 length_from=lambda pkt:1), 484 lambda pkt:(pkt.CID == 0x06)), 485 ConditionalField(PacketListField("NewChannel", b"", 486 NewChannelAns, 487 length_from=lambda pkt:1), 488 lambda pkt:(pkt.CID == 0x07)), 489 ConditionalField(PacketListField("RXTimingSetup", b"", 490 RXTimingSetupAns, 491 length_from=lambda pkt:0), 492 lambda pkt:(pkt.CID == 0x08)), 493 ConditionalField(PacketListField("TxParamSetup", b"", 494 TxParamSetupAns, 495 length_from=lambda pkt:0), 496 lambda pkt:(pkt.CID == 0x09)), 497 ConditionalField(PacketListField("DlChannel", b"", 498 DlChannelAns, 499 length_from=lambda pkt:1), 500 lambda pkt:(pkt.CID == 0x0A)), 501 ConditionalField(PacketListField("Rekey", b"", 502 RekeyConf, 503 length_from=lambda pkt:1), 504 lambda pkt:(pkt.CID == 0x0B)), 505 ConditionalField(PacketListField("ADRParamSetup", b"", 506 ADRParamSetupAns, 507 length_from=lambda pkt:0), 508 lambda pkt:(pkt.CID == 0x0C)), 509 ConditionalField(PacketListField("DeviceTime", b"", 510 DeviceTimeAns, 511 length_from=lambda pkt:5), 512 lambda pkt:(pkt.CID == 0x0D)), 513 ConditionalField(PacketListField("RejoinParamSetup", b"", 514 RejoinParamSetupAns, 515 length_from=lambda pkt:1), 516 lambda pkt:(pkt.CID == 0x0F))] 517 518 519class FOpts(Packet): 520 name = "FOpts" 521 fields_desc = [ConditionalField(PacketListField("FOpts_up", b"", 522 # UL piggy MAC Command 523 MACCommand_up, 524 length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 525 lambda pkt:(pkt.FCtrl[0].FOptsLen > 0 and 526 pkt.MType & 0b1 == 0 and 527 pkt.MType >= 0b010)), 528 ConditionalField(PacketListField("FOpts_down", b"", 529 # DL piggy MAC Command 530 MACCommand_down, 531 length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 532 lambda pkt:(pkt.FCtrl[0].FOptsLen > 0 and 533 pkt.MType & 0b1 == 1 and 534 pkt.MType <= 0b101))] 535 536 537def FOptsDownShow(pkt): 538 try: 539 if pkt.FCtrl[0].FOptsLen > 0 and pkt.MType & 0b1 == 1 and pkt.MType <= 0b101 and (pkt.MType & 0b101 > 0): # noqa: E501 540 return True 541 return False 542 except Exception: 543 return False 544 545 546def FOptsUpShow(pkt): 547 try: 548 if pkt.FCtrl[0].FOptsLen > 0 and pkt.MType & 0b1 == 0 and pkt.MType >= 0b010 and (pkt.MType & 0b110 > 0): # noqa: E501 549 return True 550 return False 551 except Exception: 552 return False 553 554 555class FHDR(Packet): 556 name = "FHDR" 557 fields_desc = [ConditionalField(PacketListField("DevAddr", b"", DevAddrElem, # noqa: E501 558 length_from=lambda pkt:4), 559 lambda pkt:(pkt.MType >= 0b010 and 560 pkt.MType <= 0b101)), 561 ConditionalField(PacketListField("FCtrl", b"", 562 FCtrl_Link, 563 length_from=lambda pkt:1), 564 lambda pkt:((pkt.MType & 0b1 == 1 and 565 pkt.MType <= 0b101 and 566 (pkt.MType & 0b10 > 0)) or 567 (pkt.MType & 0b1 == 0 and 568 pkt.MType >= 0b010))), 569 ConditionalField(LEShortField("FCnt", 0), 570 lambda pkt:(pkt.MType >= 0b010 and 571 pkt.MType <= 0b101)), 572 ConditionalField(PacketListField("FOpts_up", b"", 573 MACCommand_up, 574 length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 575 FOptsUpShow), 576 ConditionalField(PacketListField("FOpts_down", b"", 577 MACCommand_down, 578 length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 579 FOptsDownShow)] 580 581 582FPorts = {0: "NwkSKey"} # anything else is AppSKey 583 584 585JoinReqTypes = {0xFF: "Join-request", 586 0x00: "Rejoin-request type 0", 587 0x01: "Rejoin-request type 1", 588 0x02: "Rejoin-request type 2"} 589 590 591class Join_Request(Packet): 592 name = "Join_Request" 593 fields_desc = [StrFixedLenField("AppEUI", b"\x00" * 8, 8), 594 StrFixedLenField("DevEUI", b"\00" * 8, 8), 595 LEShortField("DevNonce", 0x0000)] 596 597 598class Join_Accept(Packet): 599 name = "Join_Accept" 600 dcflist = False 601 fields_desc = [LEX3BytesField("JoinAppNonce", 0), 602 LEX3BytesField("NetID", 0), 603 XLEIntField("DevAddr", 0), 604 DLsettings, 605 XByteField("RxDelay", 0), 606 ConditionalField(StrFixedLenField("CFList", b"\x00" * 16, 16), # noqa: E501 607 lambda pkt:(Join_Accept.dcflist is True))] 608 609 def extract_padding(self, p): 610 return "", p 611 612 def __init__(self, packet=""): # CFList calculated with rest of packet len 613 if len(packet) > 18: 614 Join_Accept.dcflist = True 615 super(Join_Accept, self).__init__(packet) 616 617 618RejoinType = {0: "NetID+DevEUI", 619 1: "JoinEUI+DevEUI", 620 2: "NetID+DevEUI"} 621 622 623class RejoinReq(Packet): # LoRa 1.1 specs 624 name = "RejoinReq" 625 fields_desc = [ByteField("Type", 0), 626 X3BytesField("NetID", 0), 627 StrFixedLenField("DevEUI", b"\x00" * 8), 628 XShortField("RJcount0", 0)] 629 630 631def dpload_type(pkt): 632 if (pkt.MType == 0b101 or pkt.MType == 0b011): 633 return 0 # downlink 634 elif (pkt.MType == 0b100 or pkt.MType == 0b010): 635 return 1 # uplink 636 return None 637 638 639datapayload_list = [(StrField("DataPayload", "", remain=4), 640 lambda pkt:(dpload_type(pkt) == 0)), 641 (StrField("DataPayload", "", remain=6), 642 lambda pkt:(dpload_type(pkt) == 1))] 643 644 645class FRMPayload(Packet): 646 name = "FRMPayload" 647 fields_desc = [ConditionalField(MultipleTypeField(datapayload_list, 648 StrField("DataPayload", 649 "", remain=4)), 650 lambda pkt:(dpload_type(pkt) is not None)), 651 ConditionalField(PacketListField("Join_Request_Field", b"", 652 Join_Request, 653 length_from=lambda pkt:18), 654 lambda pkt:(pkt.MType == 0b000)), 655 ConditionalField(PacketListField("Join_Accept_Field", b"", 656 Join_Accept, 657 count_from=lambda pkt:1), 658 lambda pkt:(pkt.MType == 0b001 and 659 LoRa.encrypted is False)), 660 ConditionalField(StrField("Join_Accept_Encrypted", 0), 661 lambda pkt:(pkt.MType == 0b001 and LoRa.encrypted is True)), # noqa: E501 662 ConditionalField(PacketListField("ReJoin_Request_Field", b"", # noqa: E501 663 RejoinReq, 664 length_from=lambda pkt:14), 665 lambda pkt:(pkt.MType == 0b111))] 666 667 668class MACPayload(Packet): 669 name = "MACPayload" 670 eFPort = False 671 fields_desc = [FHDR, 672 ConditionalField(ByteEnumField("FPort", 0, FPorts), 673 lambda pkt:(pkt.MType >= 0b010 and 674 pkt.MType <= 0b101 and 675 pkt.FCtrl[0].FOptsLen == 0)), 676 FRMPayload] 677 678 679MTypes = {0b000: "Join-request", 680 0b001: "Join-accept", 681 0b010: "Unconfirmed Data Up", 682 0b011: "Unconfirmed Data Down", 683 0b100: "Confirmed Data Up", 684 0b101: "Confirmed Data Down", 685 0b110: "Rejoin-request", # Only in LoRa 1.1 specs 686 0b111: "Proprietary"} 687 688 689class MHDR(Packet): # Same for 1.0 as for 1.1 690 name = "MHDR" 691 692 fields_desc = [BitEnumField("MType", 0b000, 3, MTypes), 693 BitField("RFU", 0b000, 3), 694 BitField("Major", 0b00, 2)] 695 696 697class PHYPayload(Packet): 698 name = "PHYPayload" 699 fields_desc = [MHDR, 700 MACPayload, 701 ConditionalField(XIntField("MIC", 0), 702 lambda pkt:(pkt.MType != 0b001 or 703 LoRa.encrypted is False))] 704 705 706class LoRa(Packet): # default frame (unclear specs => taken from https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5677147/) # noqa: E501 707 name = "LoRa" 708 version = "1.1" # default version to parse 709 encrypted = True 710 711 fields_desc = [XBitField("Preamble", 0, 4), 712 XBitField("PHDR", 0, 16), 713 XBitField("PHDR_CRC", 0, 4), 714 PHYPayload, 715 ConditionalField(XShortField("CRC", 0), 716 lambda pkt:(pkt.MType & 0b1 == 0))] 717