mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-15 20:33:39 +00:00
lora/lorawan: completed functions needed for Join OTAA process
Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
+15
-10
@@ -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
@@ -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
@@ -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[:])
|
||||||
|
|||||||
Reference in New Issue
Block a user