def unpack_jreq(pdu:bytes) -> Msg: """Unpack JREQ message and extract fields without verifying the integrity of the message.. This call helps locate the device key and then the integrity must be checked by calling verifyJoinRequest. Return None if message is malformed, otherwise return mapping with frame fields. """ mhdr, aeui, deui, devnonce, mic = struct.unpack("<BQQHi", pdu[0:23]) return { 'msgtype' : 'jreq', 'MHdr' : mhdr, 'JoinEUI' : rtlib.Eui(aeui), 'DevEUI' : rtlib.Eui(deui), 'DevNonce' : devnonce, 'MIC' : mic }
def pack_jreq (nwkkey:bytes, joineui:rtlib.Eui=None, deveui:rtlib.Eui=None, devnonce:int=0, mhdr:int=Major.V1|FrmType.JREQ) -> bytes: """Pack parameters into a JOIN REQUEST pdu with the following layout: | 1 | 8 | 8 | 2 | 4 | bytes - all fields little endian | mhdr| joineui | deveui | devnonce | MIC | | < - - - - - - - - - - - - - - - ->| input to MIC """ if joineui is None: joineui = rtlib.Eui("00-00-00-00-00-00-00-00") if deveui is None: deveui = rtlib.Eui("00-00-00-00-00-00-00-00") pdu = struct.pack("<BqqHi", mhdr, joineui.as_int(), deveui.as_int(), devnonce & 0xFFFF, 0) mic = crypto.calcMicJoin(nwkkey,pdu) return pdu[0:-4] + struct.pack("<i", mic)
def mic_join_request(sstate: State, jrmsg: Msg) -> int: """Use the sstate['NwkKey'] to append or verify the MIC of the jrmsg. jsmsg must contain the following fields: 'MHdr', 'JoinEUI', 'DevEUI' and 'DevNonce'. The method computes a MIC over an encoding of the join request message and either sets the field 'MIC' if it is absent or compare the computed value with the value of this field. If they don't match a VerifyError is raised. """ pdu = pack_jreq(to_bytes(sstate['NwkKey']), rtlib.Eui(jrmsg['JoinEUI']), rtlib.Eui(jrmsg['DevEUI']), jrmsg['DevNonce'], jrmsg.get('MHdr', Major.V1 | FrmType.JREQ)) mic = struct.unpack("<i", pdu[-4:])[0] if 'MIC' in jrmsg: if jrmsg['MIC'] != mic: raise VerifyError('Verify of join request failed') else: jrmsg['MIC'] = mic return mic
def join(pdu:bytes, region:ld.Region, *, pdevnonce:int=-1, nwkkey:bytes=b'@ABCDEFGHIJKLMNO', appnonce:int=0, netid:int=1, devaddr:Optional[int]=None, dlset:Optional[int]=None, rxdly:int=0, **kwargs:Any) -> Tuple[bytes,Session]: jreq = lm.unpack_jreq(pdu) deveui = rt.Eui(jreq['DevEUI']) if devaddr is None: devaddr = numpy.int32(crc32(struct.pack('q', deveui))) if dlset is None: dlset = lm.DLSettings.pack(0, region.RX2DR, False) rx1droff, rx2dr, optneg = lm.DLSettings.unpack(dlset) lm.verify_jreq(nwkkey, pdu) cflist = region.get_cflist() devnonce = jreq['DevNonce'] if pdevnonce >= devnonce: raise ValueError('DevNonce is not strictly increasing') nwkskey = lc.crypto.derive(nwkkey, devnonce, appnonce, netid, lm.KD_NwkSKey) appskey = lc.crypto.derive(nwkkey, devnonce, appnonce, netid, lm.KD_AppSKey) return lm.pack_jacc(nwkkey, appnonce, netid, devaddr, dlset, rxdly, cflist, devnonce=devnonce), { 'deveui' : deveui, 'devaddr' : devaddr, 'nwkkey' : nwkkey, 'nwkskey' : nwkskey, 'appskey' : appskey, 'fcntup' : 0, 'fcntdn' : 0, 'rx1delay' : max(rxdly, 1), 'rx1droff' : rx1droff, 'rx2dr' : rx2dr, 'rx2freq' : region.RX2Freq, 'devnonce' : devnonce, 'region' : region, }
def process_join(self, msg: LoraMsg, rx2: bool = False, **kwargs: Any) -> None: m = lm.unpack_nomic(msg.pdu) assert m['msgtype'] == 'jreq' nwkkey = kwargs.setdefault('nwkkey', b'@ABCDEFGHIJKLMNO') appnonce = kwargs.setdefault('appnonce', 0) netid = kwargs.setdefault('netid', 1) devaddr = kwargs.setdefault( 'devaddr', numpy.int32(crc32(rt.Eui(m['DevEUI']).as_bytes()))) rxdly = kwargs.setdefault('rxdly', 0) (rx1droff, rx2dr, optneg) = lm.DLSettings.unpack( kwargs.setdefault('dlset', lm.DLSettings.pack(0, self.region.RX2DR, False))) lm.verify_jreq(nwkkey, msg.pdu) # check channel validity self.getupch(msg) # check that devnonce is increasing ldn = self.session.get('devnonce') devnonce = m['DevNonce'] if ldn is not None and ldn >= devnonce: raise ValueError('DevNonce is not strictly increasing') nwkskey = lc.crypto.derive(nwkkey, devnonce, appnonce, netid, lm.KD_NwkSKey) appskey = lc.crypto.derive(nwkkey, devnonce, appnonce, netid, lm.KD_AppSKey) jacc = lm.pack_jacc(**kwargs) rxdelay = ld.JaccRxDelay if rx2: rxdelay += 1 (freq, rps) = self.dn_rx2() else: (freq, rps) = self.up2dn_rx1(msg.freq, msg.rps, 0) self.add_dn(LoraMsg(msg.xend + rxdelay, jacc, freq, rps, 14)) if self.sm and self.session: self.sm.remove(self.session) self.session = { 'deveui': m['DevEUI'], 'devaddr': devaddr, 'nwkkey': nwkkey, 'nwkskey': nwkskey, 'appskey': appskey, 'fcntup': 0, 'fcntdn': 0, 'rx1delay': max(rxdly, 1), 'rx1droff': rx1droff, 'rx2dr': rx2dr, 'devnonce': devnonce, } if self.sm: self.sm.add(self.session)