This is a parser tool of the LoRaWAN PHY payload.
Please use lorawan-parser (this software) instead of lorawan_phy_parser, which is not maintained anymore.
- supporting v1.0, v1.0.3, v1.1.
- decoding encrypted message, v1.0, v1.0.3 only. (v1.1 not yet)
- calculating NwkSKey, AppSKey from the join messages with AppKey.
- calculating MIC based on the wire message so that you can check the integrity.
- Python 3.x is required.
- pycryptodome
usage: lorawan-parser.py [-h] [--appkey APPKEY] [--join-request JOIN_R]
[--join-accept JOIN_A] [--nwkskey NWKSKEY]
[--appskey APPSKEY] [--input-file INPUT_FILE]
[--upper-fcnt UPPER_FCNT] [--lorawan-version VERSION]
[--string-type STRING_TYPE] [-v] [-d]
[PHY_PDU_HEXSTR [PHY_PDU_HEXSTR ...]]
LoRaWAN PHY Payload parser. The input must be hex strings. You can use stdin
to pass the string.
positional arguments:
PHY_PDU_HEXSTR a series or multiple of hex string. (default: None)
optional arguments:
-h, --help show this help message and exit
--appkey APPKEY, --AppKey APPKEY
specify AppKey. (default: None)
--join-request JOIN_R
specify the hex string of the Join Request. (default:
None)
--join-accept JOIN_A specify the hex string of the Join Accept/Response.
(default: None)
--nwkskey NWKSKEY, --NwkSKey NWKSKEY
specify NwkSKey(v1.0.x). (default: None)
--appskey APPSKEY, --AppSKey APPSKEY
specify AppSKey. (default: None)
--input-file INPUT_FILE
specify a file or stdin to read the messages.
(default: None)
--upper-fcnt UPPER_FCNT
specify the most significant 16-bit of the FCnt in
hex. (default: 0000)
--lorawan-version VERSION
specify the version of LoRaWAN; 1.0, 1.0.3, or 1.1
(default: 1.0.3)
--string-type STRING_TYPE
specify the type of string of phy_pdu, either hexstr
or base64. (default: hexstr)
-v enable verbose mode. (default: False)
-d increase debug mode. (default: [])
You can put the hex string of the LoRaWAN PHY Payload into the arguments. The following string are decoded as a same string. i.e. '01 02 03 04'
01020304
0102 0304
01.02.03.04
0x01 0x02 0x03 0x04
0x01,0x02,0x03,0x04
0102,0304
The separater is a new line code. If you make a file containing some lines of the hex string like below.
01020304
05060708
0a0b0c0d
The parser can decode each of them separately. Don't forget to specify "-" or "stdin" as the argument to pass the content of the file from the stdin. e.g.
cat file | lorawan-parser.py --from-file -
It will parse the hex string of LoRaWAN PHY Payload and show the result. The following three examples show Join Request, Join Response.
e.g. Join Request
% lorawan-parser.py \
--appkey BEEF000102030405060708090A0B0C0D \
00050403020100efbe050403020100efbe87bcc54c5b5f
=== PHYPayload ===
## MHDR : x 00
MType : Join Request
RFU : b 000
Major : LoRaWAN R1
## JoinReq : x 050403020100efbe050403020100efbe87bc
AppEUI : x beef000102030405
DevEUI : x beef000102030405
DevNonce : x bc87
## MIC
MIC in frame : x 5f5b4cc5
MIC Derived : x 5f5b4cc5
e.g. Join Response
% lorawan-parser.py \
--appkey BEEF000102030405060708090A0B0C0D \
20b46c0022dae91f38e172ac0312d4bc5b
=== PHYPayload ===
## MHDR : x 20
MType : Join Accept
RFU : b 000
Major : LoRaWAN R1
## JoinAccept : x b46c0022dae91f38e172ac03
AppNonce : x 4c5247
NetID : x 000015
NwkID : b 0000000
DevAddr : x 2bbe0934
DLSettings : x 02
RFU : b 0
RX1DROffset : 0
RX2DataRate : 2
RxDelay : 1 sec
## MIC
MIC in frame : x 223195d7
MIC Derived : x 223195d7
If you can put a pair of messages of Join Request and Join Response in OTAA mode, it will calculate both NwkSkey and AppSkey with AppKey. And, it will decrypt all encrypted field and calculate MIC. The -v option always shows the detail.
e.g. Unconfirmed Data Up
% lorawan-parser.py -v \
--appkey BEEF000102030405060708090A0B0C0D \
--join-request 00050403020100efbe050403020100efbe87bcc54c5b5f \
--join-accept 20b46c0022dae91f38e172ac0312d4bc5b \
403409be2b80000002c7fb8963476d5bf4090e6b867a40b597047241eb80aef79df6
## Calculated Keys
NwkSKey: x 70ff6652c80bcee90b21f2d74bf336b2
AppSkey: x 51ebd6666d77121b3782ef59a252e013
=== PHYPayload ===
PDU : x 403409be2b80000002c7fb8963476d5bf4090e6b867a40b597047241eb80aef79df6
## MHDR : x 40 [b 01000000]
MType : Unconfirmed Data Up [b 010]
RFU : b 000
Major : LoRaWAN R1 [b 00]
## MACPayload : x 3409be2b80000002c7fb8963476d5bf4090e6b867a40b597047241eb80
FHDR : x 3409be2b800000
DevAddr : x 2bbe0934 [x 3409be2b]
FCtrl : x 80 [b 10000000]
ADR : b 1
ADRACKReq : b 0
ACK : b 0
RFU : b 0
FOptsLen : 0 [b 0000]
FCnt : 0 [x 00000000]
FPort : 2 [x 02]
## FRMPayload (Application Data) : x c7fb8963476d5bf4090e6b867a40b597047241eb80
AppData : x 8105000a14033e0000000000000e7e09420a8c0000
## MIC
MIC in frame : x f69df7ae [x aef79df6]
MIC Derived : x f69df7ae
If you use the -v option for the message containing MAC Commands, you can see a short description of each command like below:
% lorawan-parser.py -v \
--nwkskey=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB \
--appskey=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB \
40,B2,00,EF,BE,9A,03,00,03,07,05,07,07,03,07,03,10,04,02,09,AA,50,B1,F0,79,2F,31,AD,9C,63,78,5F,83,8D,95,C6,FE,81,D2,97,09,BA,3C,6E,19,B7,71,43,7C, -v
=== PHYPayload ===
PDU : x 40b200efbe9a0300030705070703070310040209aa50b1f0792f31ad9c63785f838d95c6fe81d29709ba3c6e19b771437c
## MHDR : x 40 [b 01000000]
MType : Unconfirmed Data Up [b 010]
RFU : b 000
Major : LoRaWAN R1 [b 00]
## MACPayload : x b200efbe9a0300030705070703070310040209aa50b1f0792f31ad9c63785f838d95c6fe81d29709ba3c6e19
FHDR : x b200efbe9a030003070507070307031004
DevAddr : x beef00b2 [x b200efbe]
FCtrl : x 9a [b 10011010]
ADR : b 1
ADRACKReq : b 0
ACK : b 0
RFU : b 1
FOptsLen : 10 [b 1010]
FCnt : 3 [x 00000003]
FOpts : x 03070507070307031004
## MAC Command (No. CMD CID DIR [MSG])
1. LinkADRAns : 0x03 Uplink [x 0705]
Status : x 07 [b 00000111]
RFU : b 00000
Power ACK : 1
* DETAIL: The device is able to operate at or below the requested
power level,, or the TXPower field of the request was
set to 15, meaning it shall be ignored
Data_rate_ACK : 1
* DETAIL: The data rate was successfully set or the DataRate
field of the request was set to 15, meaning it was
ignored
Channel_mask_ACK : 1
* DETAIL: The channel mask sent was successfully interpreted. All
currently defined channel states were set according to
the mask.
2. RXParamSetupAns : 0x05 Uplink [x 0707]
Status : x 07 [b 00000111]
RFU : b 00000
RX1DRoffset ACK : b 1
* DETAIL: RX1DRoffset was successfully set.
RX2 Data rate ACK : b 1
* DETAIL: RX2 slot channel was successfully set.
Channel ACK : b 1
* DETAIL: RX2 slot channel was successfully set.
3. NewChannelAns : 0x07 Uplink [x 0307]
Status : x 03 [b 00000011]
RFU : b 000000
Data rate range ok : b 1
* DETAIL: The data rate range is compatible with the
possibilities of the end-device.
Channel frequency ok : b 1
* DETAIL: The device is able to use this frequency.
4. NewChannelAns : 0x07 Uplink [x 0310]
Status : x 03 [b 00000011]
RFU : b 000000
Data rate range ok : b 1
* DETAIL: The data rate range is compatible with the
possibilities of the end-device.
Channel frequency ok : b 1
* DETAIL: The device is able to use this frequency.
5. PingSlotInfoReq : 0x10 Uplink [x 04]
PingSlotParam : x 04
RFU : b 00000
Periodicity : 4 [b 100]
pingSlotPeriod : 16 sec
* DETAIL: Periodicity = 0 means that the end-device opens a ping
slot every second. Periodicity = 7, every 128 seconds
which is the maximum ping period supported by the
LoRaWAN Class B specification.
FPort : 2 [x 02]
## FRMPayload (Application Data) : x 09aa50b1f0792f31ad9c63785f838d95c6fe81d29709ba3c6e19
AppData : x 000009c400000000000000000000000000000000000000000000
## MIC
MIC in frame : x 7c4371b7 [x b771437c]
MIC Derived : x 7c4371b7
The --separator option allows to add another separator when you want to decode the multiple frames. Normally, the separator is a single newline. If you want to add one more newline, the command line is like below:
lorawan-parser.py --input-file frame.log --separator ""