pamoja.lorawan
Idiomatic LoRaWAN facade.
A long-range public-band link is wide open, so LoRaWAN wraps every frame in two guarantees: a message integrity code keyed to the network proves the frame is authentic and intact, and the payload is encrypted to the application so only its owner can read it. This builds and verifies exactly that.
1"""Idiomatic LoRaWAN facade. 2 3A long-range public-band link is wide open, so LoRaWAN wraps every frame in two 4guarantees: a message integrity code keyed to the network proves the frame is 5authentic and intact, and the payload is encrypted to the application so only its 6owner can read it. This builds and verifies exactly that. 7""" 8 9from __future__ import annotations 10 11import enum 12 13from pamoja._native import ( 14 LorawanDevice, 15 LorawanGrant, 16 LorawanHeader, 17 LorawanJoinAccept, 18 LorawanJoinRequest, 19 LorawanRxData, 20 LorawanSession, 21) 22from pamoja._native import lorawan_parse_header as _parse_header 23from pamoja._native import lorawan_parse_join_request as _parse_join_request 24 25__all__ = [ 26 "Device", 27 "Direction", 28 "Grant", 29 "Header", 30 "JoinAccept", 31 "JoinRequest", 32 "MessageType", 33 "RxData", 34 "Session", 35 "device", 36 "grant", 37 "parse_header", 38 "parse_join_request", 39 "session", 40] 41 42#: An activated session: a device address and its two session keys. 43Session = LorawanSession 44#: The root credentials over-the-air activation is built on. 45Device = LorawanDevice 46#: An accepted join: the network settings, and the session it grants. 47JoinAccept = LorawanJoinAccept 48#: A decoded data frame, with its payload decrypted. 49RxData = LorawanRxData 50#: What a frame says about itself before any key is involved. 51Header = LorawanHeader 52#: A join-request a device broadcast, with its integrity already verified. 53JoinRequest = LorawanJoinRequest 54#: What a network grants a device that joined. 55Grant = LorawanGrant 56 57 58class MessageType(str, enum.Enum): 59 """What kind of message a frame is, read from its header.""" 60 61 #: A device asking to join a network. 62 JOIN_REQUEST = "JoinRequest" 63 #: A network admitting a device. 64 JOIN_ACCEPT = "JoinAccept" 65 #: Data from a device that does not need acknowledging. 66 UNCONFIRMED_UP = "UnconfirmedUp" 67 #: Data from a device that asks to be acknowledged. 68 CONFIRMED_UP = "ConfirmedUp" 69 #: Data to a device that does not need acknowledging. 70 UNCONFIRMED_DOWN = "UnconfirmedDown" 71 #: Data to a device that asks to be acknowledged. 72 CONFIRMED_DOWN = "ConfirmedDown" 73 74 75class Direction(str, enum.Enum): 76 """The direction a frame travelled, which its MIC and encryption fold in.""" 77 78 #: From an end device up to the network. 79 UPLINK = "Uplink" 80 #: From the network down to an end device. 81 DOWNLINK = "Downlink" 82 83 84def session(dev_addr: int, nwk_skey: bytes, app_skey: bytes) -> LorawanSession: 85 """Create a session for a device already activated by personalization. 86 87 :param dev_addr: The device address the network assigned. 88 :param nwk_skey: The 16-byte network session key, which authenticates frames. 89 :param app_skey: The 16-byte application session key, which encrypts payloads. 90 :returns: The session, ready to encode and decode data frames. 91 :raises PamojaError: If either key is not 16 bytes. 92 """ 93 return LorawanSession(dev_addr, bytes(nwk_skey), bytes(app_skey)) 94 95 96def device(dev_eui: bytes, app_eui: bytes, app_key: bytes) -> LorawanDevice: 97 """Create a device holding the root credentials for over-the-air activation. 98 99 :param dev_eui: The 8-byte device EUI. 100 :param app_eui: The 8-byte application (join) EUI. 101 :param app_key: The 16-byte application key the join exchange is secured with. 102 :returns: The device, ready to build a join request. 103 :raises PamojaError: If any credential is the wrong length. 104 """ 105 return LorawanDevice(bytes(dev_eui), bytes(app_eui), bytes(app_key)) 106 107 108def parse_header(data: bytes) -> Header: 109 """Read a frame far enough to route it, without any key. 110 111 A receiver holding many sessions uses this to find which one a frame belongs 112 to: the device address travels in the clear, so it can be read before the 113 session that would verify the frame is even known. 114 115 Nothing this reports is authenticated. Treat it as a routing hint until 116 :meth:`Session.decode` has verified the frame. 117 118 :param data: The raw frame as it came off the radio. 119 :returns: What the header says the frame is. 120 :raises PamojaError: If the frame is truncated or carries a message type this 121 build does not read. 122 """ 123 return _parse_header(bytes(data)) 124 125 126def parse_join_request(data: bytes, app_key: bytes) -> JoinRequest: 127 """Verify a join-request and read the identifiers out of it. 128 129 This is the network side of activation: it proves the request came from a 130 holder of the application key before reporting who sent it. 131 132 :param data: The raw join-request as it came off the radio. 133 :param app_key: The 16-byte application root key the device shares. 134 :returns: The verified request. 135 :raises PamojaError: If the MIC does not verify or the frame is not a 136 join-request. 137 """ 138 return _parse_join_request(bytes(data), bytes(app_key)) 139 140 141def grant( 142 app_nonce: int, 143 net_id: int, 144 dev_addr: int, 145 dl_settings: int = 0, 146 rx_delay: int = 0, 147 cflist: bytes | None = None, 148) -> Grant: 149 """Describe what this network grants a device that joined. 150 151 :param app_nonce: A nonce this network must not reuse for the device, since 152 the session keys are derived from it; low 24 bits only. 153 :param net_id: The network identifier; low 24 bits only. 154 :param dev_addr: The address to assign the device. 155 :param dl_settings: The downlink settings byte. 156 :param rx_delay: The delay before the first receive window, in seconds. 157 :param cflist: The optional 16-byte channel list. 158 :returns: The grant, which signs its own join-accept and derives the session. 159 :raises PamojaError: If the channel list is not 16 bytes. 160 """ 161 return Grant( 162 app_nonce, 163 net_id, 164 dev_addr, 165 dl_settings, 166 rx_delay, 167 None if cflist is None else bytes(cflist), 168 )
76class Direction(str, enum.Enum): 77 """The direction a frame travelled, which its MIC and encryption fold in.""" 78 79 #: From an end device up to the network. 80 UPLINK = "Uplink" 81 #: From the network down to an end device. 82 DOWNLINK = "Downlink"
The direction a frame travelled, which its MIC and encryption fold in.
59class MessageType(str, enum.Enum): 60 """What kind of message a frame is, read from its header.""" 61 62 #: A device asking to join a network. 63 JOIN_REQUEST = "JoinRequest" 64 #: A network admitting a device. 65 JOIN_ACCEPT = "JoinAccept" 66 #: Data from a device that does not need acknowledging. 67 UNCONFIRMED_UP = "UnconfirmedUp" 68 #: Data from a device that asks to be acknowledged. 69 CONFIRMED_UP = "ConfirmedUp" 70 #: Data to a device that does not need acknowledging. 71 UNCONFIRMED_DOWN = "UnconfirmedDown" 72 #: Data to a device that asks to be acknowledged. 73 CONFIRMED_DOWN = "ConfirmedDown"
What kind of message a frame is, read from its header.
97def device(dev_eui: bytes, app_eui: bytes, app_key: bytes) -> LorawanDevice: 98 """Create a device holding the root credentials for over-the-air activation. 99 100 :param dev_eui: The 8-byte device EUI. 101 :param app_eui: The 8-byte application (join) EUI. 102 :param app_key: The 16-byte application key the join exchange is secured with. 103 :returns: The device, ready to build a join request. 104 :raises PamojaError: If any credential is the wrong length. 105 """ 106 return LorawanDevice(bytes(dev_eui), bytes(app_eui), bytes(app_key))
Create a device holding the root credentials for over-the-air activation.
Parameters
- dev_eui: The 8-byte device EUI.
- app_eui: The 8-byte application (join) EUI.
- app_key: The 16-byte application key the join exchange is secured with. :returns: The device, ready to build a join request.
Raises
- PamojaError: If any credential is the wrong length.
142def grant( 143 app_nonce: int, 144 net_id: int, 145 dev_addr: int, 146 dl_settings: int = 0, 147 rx_delay: int = 0, 148 cflist: bytes | None = None, 149) -> Grant: 150 """Describe what this network grants a device that joined. 151 152 :param app_nonce: A nonce this network must not reuse for the device, since 153 the session keys are derived from it; low 24 bits only. 154 :param net_id: The network identifier; low 24 bits only. 155 :param dev_addr: The address to assign the device. 156 :param dl_settings: The downlink settings byte. 157 :param rx_delay: The delay before the first receive window, in seconds. 158 :param cflist: The optional 16-byte channel list. 159 :returns: The grant, which signs its own join-accept and derives the session. 160 :raises PamojaError: If the channel list is not 16 bytes. 161 """ 162 return Grant( 163 app_nonce, 164 net_id, 165 dev_addr, 166 dl_settings, 167 rx_delay, 168 None if cflist is None else bytes(cflist), 169 )
Describe what this network grants a device that joined.
Parameters
- app_nonce: A nonce this network must not reuse for the device, since the session keys are derived from it; low 24 bits only.
- net_id: The network identifier; low 24 bits only.
- dev_addr: The address to assign the device.
- dl_settings: The downlink settings byte.
- rx_delay: The delay before the first receive window, in seconds.
- cflist: The optional 16-byte channel list. :returns: The grant, which signs its own join-accept and derives the session.
Raises
- PamojaError: If the channel list is not 16 bytes.
109def parse_header(data: bytes) -> Header: 110 """Read a frame far enough to route it, without any key. 111 112 A receiver holding many sessions uses this to find which one a frame belongs 113 to: the device address travels in the clear, so it can be read before the 114 session that would verify the frame is even known. 115 116 Nothing this reports is authenticated. Treat it as a routing hint until 117 :meth:`Session.decode` has verified the frame. 118 119 :param data: The raw frame as it came off the radio. 120 :returns: What the header says the frame is. 121 :raises PamojaError: If the frame is truncated or carries a message type this 122 build does not read. 123 """ 124 return _parse_header(bytes(data))
Read a frame far enough to route it, without any key.
A receiver holding many sessions uses this to find which one a frame belongs to: the device address travels in the clear, so it can be read before the session that would verify the frame is even known.
Nothing this reports is authenticated. Treat it as a routing hint until
Session.decode() has verified the frame.
Parameters
- data: The raw frame as it came off the radio. :returns: What the header says the frame is.
Raises
- PamojaError: If the frame is truncated or carries a message type this build does not read.
127def parse_join_request(data: bytes, app_key: bytes) -> JoinRequest: 128 """Verify a join-request and read the identifiers out of it. 129 130 This is the network side of activation: it proves the request came from a 131 holder of the application key before reporting who sent it. 132 133 :param data: The raw join-request as it came off the radio. 134 :param app_key: The 16-byte application root key the device shares. 135 :returns: The verified request. 136 :raises PamojaError: If the MIC does not verify or the frame is not a 137 join-request. 138 """ 139 return _parse_join_request(bytes(data), bytes(app_key))
Verify a join-request and read the identifiers out of it.
This is the network side of activation: it proves the request came from a holder of the application key before reporting who sent it.
Parameters
- data: The raw join-request as it came off the radio.
- app_key: The 16-byte application root key the device shares. :returns: The verified request.
Raises
- PamojaError: If the MIC does not verify or the frame is not a join-request.
85def session(dev_addr: int, nwk_skey: bytes, app_skey: bytes) -> LorawanSession: 86 """Create a session for a device already activated by personalization. 87 88 :param dev_addr: The device address the network assigned. 89 :param nwk_skey: The 16-byte network session key, which authenticates frames. 90 :param app_skey: The 16-byte application session key, which encrypts payloads. 91 :returns: The session, ready to encode and decode data frames. 92 :raises PamojaError: If either key is not 16 bytes. 93 """ 94 return LorawanSession(dev_addr, bytes(nwk_skey), bytes(app_skey))
Create a session for a device already activated by personalization.
Parameters
- dev_addr: The device address the network assigned.
- nwk_skey: The 16-byte network session key, which authenticates frames.
- app_skey: The 16-byte application session key, which encrypts payloads. :returns: The session, ready to encode and decode data frames.
Raises
- PamojaError: If either key is not 16 bytes.