diff --git a/lora/lorawan/adaptor.go b/lora/lorawan/adaptor.go index abebc2e..7236e0e 100644 --- a/lora/lorawan/adaptor.go +++ b/lora/lorawan/adaptor.go @@ -1,8 +1,16 @@ package lorawan -import "tinygo.org/x/drivers/lora" +import ( + "errors" -var ActiveRadio lora.Radio + "tinygo.org/x/drivers/lora" +) + +const LORA_RXTX_TIMEOUT = 1000 + +var ( + ActiveRadio lora.Radio +) func UseRadio(r lora.Radio) { if ActiveRadio != nil { @@ -11,7 +19,55 @@ func UseRadio(r lora.Radio) { ActiveRadio = r } -func Join() error { +func Join(otaa *Otaa, session *Session) error { + var resp []uint8 + + if ActiveRadio == nil { + return errors.New("no LoRa radio attached") + } + + otaa.Init() + + // Send join packet + payload, err := otaa.GenerateJoinRequest() + if err != nil { + return err + } + + ActiveRadio.SetCrc(true) + ActiveRadio.SetIqMode(0) // IQ Standard + ActiveRadio.Tx(payload, LORA_RXTX_TIMEOUT) + if err != nil { + return err + } + + // Wait for JoinAccept + //println("lorawan: Wait for JOINACCEPT for 10s") + ActiveRadio.SetIqMode(1) // IQ Inverted + for i := 0; i < 15; i++ { + resp, err = ActiveRadio.Rx(LORA_RXTX_TIMEOUT) + if err != nil { + return err + } + if resp != nil { + break + } + } + if resp == nil { + return errors.New("no JoinAccept packet received") + } + //println("lorawan: Received packet: len=", len(resp), "payload=", bytesToHexString(resp)) + + err = otaa.DecodeJoinAccept(resp, session) + if err != nil { + 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 } diff --git a/lora/lorawan/bytes.go b/lora/lorawan/bytes.go index 25f4813..e7e7d29 100644 --- a/lora/lorawan/bytes.go +++ b/lora/lorawan/bytes.go @@ -1,37 +1,23 @@ package lorawan -// revertByteArray inverts de order of a given byte slice -func revertByteArray(s []byte) []byte { +import "crypto/rand" + +// reverseBytes reverses order of a given byte slice +func reverseBytes(s []byte) []byte { + result := make([]byte, len(s)) for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { - s[i], s[j] = s[j], s[i] + result[i], result[j] = s[j], s[i] } - return s + return result } -// byteToHex return string hex representation of byte -func byteToHex(b byte) string { - bb := (b >> 4) & 0x0F - ret := "" - if bb < 10 { - ret += string(rune('0' + bb)) - } else { - ret += string(rune('A' + (bb - 10))) +// GetRand16 returns 2 random bytes +func GetRand16() ([2]uint8, error) { + randomBytes := make([]byte, 2) + _, err := rand.Read(randomBytes) + if err != nil { + return [2]uint8{}, err } - bb = (b) & 0xF - if bb < 10 { - ret += string(rune('0' + bb)) - } else { - ret += string(rune('A' + (bb - 10))) - } - return ret -} - -// BytesToHexString converts byte slice to hex string representation -func bytesToHexString(data []byte) string { - s := "" - for i := 0; i < len(data); i++ { - s += byteToHex(data[i]) - } - return s + return [2]uint8{randomBytes[0], randomBytes[1]}, nil } diff --git a/lora/lorawan/otaa.go b/lora/lorawan/otaa.go index 395714a..f3d5574 100644 --- a/lora/lorawan/otaa.go +++ b/lora/lorawan/otaa.go @@ -3,6 +3,7 @@ package lorawan import ( "bytes" "crypto/aes" + "encoding/hex" "errors" ) @@ -14,25 +15,83 @@ type Otaa struct { DevNonce [2]uint8 AppNonce [3]uint8 NetID [3]uint8 + buf []uint8 +} + +// Initialize DevNonce +func (o *Otaa) Init() { + o.buf = make([]uint8, 0) + + // TODO: handle error + rnd, _ := GetRand16() + o.DevNonce[0] = rnd[0] + o.DevNonce[1] = rnd[1] } // Set configures the Otaa AppEUI, DevEUI, AppKey for the device -func (o *Otaa) Set(appEUI [8]uint8, devEUI [8]uint8, appKey [16]uint8) { - o.AppEUI = appEUI - o.DevEUI = devEUI - o.AppKey = appKey +func (o *Otaa) Set(appEUI []uint8, devEUI []uint8, appKey []uint8) { + o.SetAppEUI(appEUI) + o.SetDevEUI(devEUI) + o.SetAppKey(appKey) +} + +// SetAppEUI configures the Otaa AppEUI +func (o *Otaa) SetAppEUI(appEUI []uint8) error { + if len(appEUI) != 8 { + return errors.New("invalid length") + } + + copy(o.AppEUI[:], appEUI) + + return nil +} + +func (o *Otaa) GetAppEUI() string { + return hex.EncodeToString(o.AppEUI[:]) +} + +// SetDevEUI configures the Otaa DevEUI +func (o *Otaa) SetDevEUI(devEUI []uint8) error { + if len(devEUI) != 8 { + return errors.New("invalid length") + } + + copy(o.DevEUI[:], devEUI) + + return nil +} + +func (o *Otaa) GetDevEUI() string { + return hex.EncodeToString(o.DevEUI[:]) +} + +// SetAppKey configures the Otaa AppKey +func (o *Otaa) SetAppKey(appKey []uint8) error { + if len(appKey) != 16 { + return errors.New("invalid length") + } + + copy(o.AppKey[:], appKey) + + return nil +} + +func (o *Otaa) GetAppKey() string { + return hex.EncodeToString(o.AppKey[:]) } // GenerateJoinRequest Generates a LoraWAN Join request -func (o *Otaa) GenerateJoinRequest(buf []uint8) error { +func (o *Otaa) GenerateJoinRequest() ([]uint8, error) { // TODO: Add checks - buf = append(buf, 0x00) - buf = append(buf, revertByteArray(o.AppEUI[:])...) - buf = append(buf, revertByteArray(o.DevEUI[:])...) - buf = append(buf, revertByteArray(o.DevNonce[:])...) - mic := genPayloadMIC(buf, o.AppKey) - buf = append(buf, mic[:]...) - return nil + o.buf = o.buf[:0] + o.buf = append(o.buf, 0x00) + o.buf = append(o.buf, reverseBytes(o.AppEUI[:])...) + o.buf = append(o.buf, reverseBytes(o.DevEUI[:])...) + o.buf = append(o.buf, reverseBytes(o.DevNonce[:])...) + mic := genPayloadMIC(o.buf, o.AppKey) + o.buf = append(o.buf, mic[:]...) + + return o.buf, nil } // DecodeJoinAccept Decodes a Lora Join Accept packet diff --git a/lora/lorawan/session.go b/lora/lorawan/session.go index 91ee64e..6ff6d1a 100644 --- a/lora/lorawan/session.go +++ b/lora/lorawan/session.go @@ -3,6 +3,7 @@ package lorawan import ( "crypto/aes" "encoding/binary" + "encoding/hex" "errors" "math" ) @@ -19,6 +20,21 @@ type Session struct { DLSettings uint8 } +// SetDevAddr configures the Session DevAddr +func (s *Session) SetDevAddr(devAddr []uint8) error { + if len(devAddr) != 4 { + return errors.New("invalid length") + } + + copy(s.DevAddr[:], devAddr) + + return nil +} + +func (s *Session) GetDevAddr() string { + return hex.EncodeToString(s.DevAddr[:]) +} + // GenMessage Forge an uplink message func (s *Session) GenMessage(dir uint8, payload []uint8) ([]uint8, error) { var buf []uint8 @@ -87,4 +103,3 @@ func (s *Session) genFRMPayload(dir uint8, fCnt uint32, payload []byte, isFOpts } return encrypted[:len(payload)], nil } -