lora/lorawan: completed functions needed for Join OTAA process

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram
2023-01-12 13:19:18 +01:00
committed by Ron Evans
parent 999f42dc03
commit 4bc8ecdbdd
3 changed files with 44 additions and 29 deletions
+15 -10
View File
@@ -6,10 +6,22 @@ import (
"tinygo.org/x/drivers/lora" "tinygo.org/x/drivers/lora"
) )
var (
ErrNoJoinAcceptReceived = errors.New("no JoinAccept packet received")
ErrNoRadioAttached = errors.New("no LoRa radio attached")
ErrInvalidEuiLength = errors.New("invalid EUI length")
ErrInvalidAppKeyLength = errors.New("invalid AppKey length")
ErrInvalidPacketLength = errors.New("invalid packet length")
ErrInvalidDevAddrLength = errors.New("invalid DevAddr length")
ErrInvalidMic = errors.New("invalid Mic")
ErrFrmPayloadTooLarge = errors.New("FRM payload too large")
)
const LORA_RXTX_TIMEOUT = 1000 const LORA_RXTX_TIMEOUT = 1000
var ( var (
ActiveRadio lora.Radio ActiveRadio lora.Radio
Retries = 15
) )
func UseRadio(r lora.Radio) { func UseRadio(r lora.Radio) {
@@ -23,7 +35,7 @@ func Join(otaa *Otaa, session *Session) error {
var resp []uint8 var resp []uint8
if ActiveRadio == nil { if ActiveRadio == nil {
return errors.New("no LoRa radio attached") return ErrNoRadioAttached
} }
otaa.Init() otaa.Init()
@@ -42,9 +54,8 @@ func Join(otaa *Otaa, session *Session) error {
} }
// Wait for JoinAccept // Wait for JoinAccept
//println("lorawan: Wait for JOINACCEPT for 10s")
ActiveRadio.SetIqMode(1) // IQ Inverted ActiveRadio.SetIqMode(1) // IQ Inverted
for i := 0; i < 15; i++ { for i := 0; i < Retries; i++ {
resp, err = ActiveRadio.Rx(LORA_RXTX_TIMEOUT) resp, err = ActiveRadio.Rx(LORA_RXTX_TIMEOUT)
if err != nil { if err != nil {
return err return err
@@ -54,19 +65,13 @@ func Join(otaa *Otaa, session *Session) error {
} }
} }
if resp == nil { if resp == nil {
return errors.New("no JoinAccept packet received") return ErrNoJoinAcceptReceived
} }
//println("lorawan: Received packet: len=", len(resp), "payload=", bytesToHexString(resp))
err = otaa.DecodeJoinAccept(resp, session) err = otaa.DecodeJoinAccept(resp, session)
if err != nil { if err != nil {
return err return err
} }
// println("lorawan: Valid JOINACCEPT, now connected")
// println("lorawan: | DevAddr: ", bytesToHexString(r.Session.DevAddr[:]), " (LSB)")
// println("lorawan: | NetID : ", bytesToHexString(r.Otaa.NetID[:]))
// println("lorawan: | NwkSKey: ", bytesToHexString(r.Session.NwkSKey[:]))
// println("lorawan: | AppSKey: ", bytesToHexString(r.Session.AppSKey[:]))
return nil return nil
} }
+19 -16
View File
@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"crypto/aes" "crypto/aes"
"encoding/hex" "encoding/hex"
"errors"
) )
// Otaa is used to store Over The Air Activation data of a LoRaWAN session // Otaa is used to store Over The Air Activation data of a LoRaWAN session
@@ -12,8 +11,8 @@ type Otaa struct {
DevEUI [8]uint8 DevEUI [8]uint8
AppEUI [8]uint8 AppEUI [8]uint8
AppKey [16]uint8 AppKey [16]uint8
DevNonce [2]uint8 devNonce [2]uint8
AppNonce [3]uint8 appNonce [3]uint8
NetID [3]uint8 NetID [3]uint8
buf []uint8 buf []uint8
} }
@@ -24,8 +23,8 @@ func (o *Otaa) Init() {
// TODO: handle error // TODO: handle error
rnd, _ := GetRand16() rnd, _ := GetRand16()
o.DevNonce[0] = rnd[0] o.devNonce[0] = rnd[0]
o.DevNonce[1] = rnd[1] o.devNonce[1] = rnd[1]
} }
// Set configures the Otaa AppEUI, DevEUI, AppKey for the device // Set configures the Otaa AppEUI, DevEUI, AppKey for the device
@@ -38,7 +37,7 @@ func (o *Otaa) Set(appEUI []uint8, devEUI []uint8, appKey []uint8) {
// SetAppEUI configures the Otaa AppEUI // SetAppEUI configures the Otaa AppEUI
func (o *Otaa) SetAppEUI(appEUI []uint8) error { func (o *Otaa) SetAppEUI(appEUI []uint8) error {
if len(appEUI) != 8 { if len(appEUI) != 8 {
return errors.New("invalid length") return ErrInvalidEuiLength
} }
copy(o.AppEUI[:], appEUI) copy(o.AppEUI[:], appEUI)
@@ -53,7 +52,7 @@ func (o *Otaa) GetAppEUI() string {
// SetDevEUI configures the Otaa DevEUI // SetDevEUI configures the Otaa DevEUI
func (o *Otaa) SetDevEUI(devEUI []uint8) error { func (o *Otaa) SetDevEUI(devEUI []uint8) error {
if len(devEUI) != 8 { if len(devEUI) != 8 {
return errors.New("invalid length") return ErrInvalidEuiLength
} }
copy(o.DevEUI[:], devEUI) copy(o.DevEUI[:], devEUI)
@@ -68,7 +67,7 @@ func (o *Otaa) GetDevEUI() string {
// SetAppKey configures the Otaa AppKey // SetAppKey configures the Otaa AppKey
func (o *Otaa) SetAppKey(appKey []uint8) error { func (o *Otaa) SetAppKey(appKey []uint8) error {
if len(appKey) != 16 { if len(appKey) != 16 {
return errors.New("invalid length") return ErrInvalidAppKeyLength
} }
copy(o.AppKey[:], appKey) copy(o.AppKey[:], appKey)
@@ -80,6 +79,10 @@ func (o *Otaa) GetAppKey() string {
return hex.EncodeToString(o.AppKey[:]) return hex.EncodeToString(o.AppKey[:])
} }
func (o *Otaa) GetNetID() string {
return hex.EncodeToString(o.NetID[:])
}
// GenerateJoinRequest Generates a LoraWAN Join request // GenerateJoinRequest Generates a LoraWAN Join request
func (o *Otaa) GenerateJoinRequest() ([]uint8, error) { func (o *Otaa) GenerateJoinRequest() ([]uint8, error) {
// TODO: Add checks // TODO: Add checks
@@ -87,7 +90,7 @@ func (o *Otaa) GenerateJoinRequest() ([]uint8, error) {
o.buf = append(o.buf, 0x00) o.buf = append(o.buf, 0x00)
o.buf = append(o.buf, reverseBytes(o.AppEUI[:])...) o.buf = append(o.buf, reverseBytes(o.AppEUI[:])...)
o.buf = append(o.buf, reverseBytes(o.DevEUI[:])...) o.buf = append(o.buf, reverseBytes(o.DevEUI[:])...)
o.buf = append(o.buf, o.DevNonce[:]...) o.buf = append(o.buf, o.devNonce[:]...)
mic := genPayloadMIC(o.buf, o.AppKey) mic := genPayloadMIC(o.buf, o.AppKey)
o.buf = append(o.buf, mic[:]...) o.buf = append(o.buf, mic[:]...)
@@ -97,21 +100,21 @@ func (o *Otaa) GenerateJoinRequest() ([]uint8, error) {
// DecodeJoinAccept Decodes a Lora Join Accept packet // DecodeJoinAccept Decodes a Lora Join Accept packet
func (o *Otaa) DecodeJoinAccept(phyPload []uint8, s *Session) error { func (o *Otaa) DecodeJoinAccept(phyPload []uint8, s *Session) error {
if len(phyPload) < 12 { if len(phyPload) < 12 {
return errors.New("Bad packet") return ErrInvalidPacketLength
} }
data := phyPload[1:] // Remove trailing 0x20 data := phyPload[1:] // Remove trailing 0x20
// Prepare AES Cipher // Prepare AES Cipher
block, err := aes.NewCipher(o.AppKey[:]) block, err := aes.NewCipher(o.AppKey[:])
if err != nil { if err != nil {
return errors.New("Lora Cipher error 1") return err
} }
buf := make([]byte, len(data)) buf := make([]byte, len(data))
for k := 0; k < len(data)/aes.BlockSize; k++ { for k := 0; k < len(data)/aes.BlockSize; k++ {
block.Encrypt(buf[k*aes.BlockSize:], data[k*aes.BlockSize:]) block.Encrypt(buf[k*aes.BlockSize:], data[k*aes.BlockSize:])
} }
copy(o.AppNonce[:], buf[0:3]) copy(o.appNonce[:], buf[0:3])
copy(o.NetID[:], buf[3:6]) copy(o.NetID[:], buf[3:6])
copy(s.DevAddr[:], buf[6:10]) copy(s.DevAddr[:], buf[6:10])
s.DLSettings = buf[10] s.DLSettings = buf[10]
@@ -124,7 +127,7 @@ func (o *Otaa) DecodeJoinAccept(phyPload []uint8, s *Session) error {
dataMic := []byte{} dataMic := []byte{}
dataMic = append(dataMic, phyPload[0]) dataMic = append(dataMic, phyPload[0])
dataMic = append(dataMic, o.AppNonce[:]...) dataMic = append(dataMic, o.appNonce[:]...)
dataMic = append(dataMic, o.NetID[:]...) dataMic = append(dataMic, o.NetID[:]...)
dataMic = append(dataMic, s.DevAddr[:]...) dataMic = append(dataMic, s.DevAddr[:]...)
dataMic = append(dataMic, s.DLSettings) dataMic = append(dataMic, s.DLSettings)
@@ -132,15 +135,15 @@ func (o *Otaa) DecodeJoinAccept(phyPload []uint8, s *Session) error {
dataMic = append(dataMic, s.CFList[:]...) dataMic = append(dataMic, s.CFList[:]...)
computedMic := genPayloadMIC(dataMic[:], o.AppKey) computedMic := genPayloadMIC(dataMic[:], o.AppKey)
if !bytes.Equal(computedMic[:], rxMic[:]) { if !bytes.Equal(computedMic[:], rxMic[:]) {
return errors.New("invalid Mic") return ErrInvalidMic
} }
// Generate NwkSKey // Generate NwkSKey
// NwkSKey = aes128_encrypt(AppKey, 0x01|AppNonce|NetID|DevNonce|pad16) // NwkSKey = aes128_encrypt(AppKey, 0x01|AppNonce|NetID|DevNonce|pad16)
sKey := []byte{} sKey := []byte{}
sKey = append(sKey, 0x01) sKey = append(sKey, 0x01)
sKey = append(sKey, o.AppNonce[:]...) sKey = append(sKey, o.appNonce[:]...)
sKey = append(sKey, o.NetID[:]...) sKey = append(sKey, o.NetID[:]...)
sKey = append(sKey, o.DevNonce[:]...) sKey = append(sKey, o.devNonce[:]...)
for i := 0; i < 7; i++ { for i := 0; i < 7; i++ {
sKey = append(sKey, 0x00) // PAD to 16 sKey = append(sKey, 0x00) // PAD to 16
} }
+10 -3
View File
@@ -4,7 +4,6 @@ import (
"crypto/aes" "crypto/aes"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"errors"
"math" "math"
) )
@@ -23,7 +22,7 @@ type Session struct {
// SetDevAddr configures the Session DevAddr // SetDevAddr configures the Session DevAddr
func (s *Session) SetDevAddr(devAddr []uint8) error { func (s *Session) SetDevAddr(devAddr []uint8) error {
if len(devAddr) != 4 { if len(devAddr) != 4 {
return errors.New("invalid length") return ErrInvalidDevAddrLength
} }
copy(s.DevAddr[:], devAddr) copy(s.DevAddr[:], devAddr)
@@ -35,6 +34,14 @@ func (s *Session) GetDevAddr() string {
return hex.EncodeToString(s.DevAddr[:]) return hex.EncodeToString(s.DevAddr[:])
} }
func (s *Session) GetNwkSKey() string {
return hex.EncodeToString(s.NwkSKey[:])
}
func (s *Session) GetAppSKey() string {
return hex.EncodeToString(s.AppSKey[:])
}
// GenMessage Forge an uplink message // GenMessage Forge an uplink message
func (s *Session) GenMessage(dir uint8, payload []uint8) ([]uint8, error) { func (s *Session) GenMessage(dir uint8, payload []uint8) ([]uint8, error) {
var buf []uint8 var buf []uint8
@@ -75,7 +82,7 @@ func (s *Session) genFRMPayload(dir uint8, fCnt uint32, payload []byte, isFOpts
k++ k++
} }
if k > math.MaxUint8 { if k > math.MaxUint8 {
return nil, errors.New("Payload too big !") return nil, ErrFrmPayloadTooLarge
} }
encrypted := make([]byte, 0, k*16) encrypted := make([]byte, 0, k*16)
cipher, err := aes.NewCipher(s.AppSKey[:]) cipher, err := aes.NewCipher(s.AppSKey[:])