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