mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-16 12:53:23 +00:00
lorawan: add initial LoRaWAN stack support
Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
import "tinygo.org/x/drivers/lora"
|
||||||
|
|
||||||
|
var ActiveRadio lora.Radio
|
||||||
|
|
||||||
|
func UseRadio(r lora.Radio) {
|
||||||
|
if ActiveRadio != nil {
|
||||||
|
panic("lorawan.ActiveRadio is already set")
|
||||||
|
}
|
||||||
|
ActiveRadio = r
|
||||||
|
}
|
||||||
|
|
||||||
|
func Join() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendUplink() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenDownlink() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
// revertByteArray inverts de order of a given byte slice
|
||||||
|
func revertByteArray(s []byte) []byte {
|
||||||
|
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"hash"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cmacHash struct {
|
||||||
|
ciph cipher.Block
|
||||||
|
k1 []byte
|
||||||
|
k2 []byte
|
||||||
|
data []byte
|
||||||
|
x []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
Size = aes.BlockSize
|
||||||
|
blockSize = Size
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
subkeyZero []byte
|
||||||
|
subkeyRb []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
subkeyZero = bytes.Repeat([]byte{0x00}, blockSize)
|
||||||
|
subkeyRb = append(bytes.Repeat([]byte{0x00}, blockSize-1), 0x87)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns an AES-CMAC hash using the supplied key. The key must be 16, 24,
|
||||||
|
// or 32 bytes long.
|
||||||
|
func NewCmac(key []byte) (hash.Hash, error) {
|
||||||
|
// Create a cipher.
|
||||||
|
ciph, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Set up the hash object.
|
||||||
|
h := &cmacHash{ciph: ciph}
|
||||||
|
h.k1, h.k2 = generateSubkeys(ciph)
|
||||||
|
h.Reset()
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Xor(dst []byte, a []byte, b []byte) error {
|
||||||
|
if len(dst) != len(a) || len(a) != len(b) {
|
||||||
|
panic("crypto/Xor: bad length")
|
||||||
|
}
|
||||||
|
for i, _ := range a {
|
||||||
|
dst[i] = a[i] ^ b[i]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) Reset() {
|
||||||
|
h.data = h.data[:0]
|
||||||
|
h.x = make([]byte, blockSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) BlockSize() int {
|
||||||
|
return h.ciph.BlockSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) Size() int {
|
||||||
|
return h.ciph.BlockSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) Sum(b []byte) []byte {
|
||||||
|
dataLen := len(h.data)
|
||||||
|
|
||||||
|
// We should have at most one block left.
|
||||||
|
if dataLen > blockSize {
|
||||||
|
panic("cmacHash err1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate M_last.
|
||||||
|
mLast := make([]byte, blockSize)
|
||||||
|
if dataLen == blockSize {
|
||||||
|
Xor(mLast, h.data, h.k1)
|
||||||
|
} else {
|
||||||
|
// TODO(jacobsa): Accept a destination buffer in common.PadBlock and
|
||||||
|
// simplify this code.
|
||||||
|
Xor(mLast, PadBlock(h.data), h.k2)
|
||||||
|
}
|
||||||
|
|
||||||
|
y := make([]byte, blockSize)
|
||||||
|
Xor(y, mLast, h.x)
|
||||||
|
|
||||||
|
result := make([]byte, blockSize)
|
||||||
|
h.ciph.Encrypt(result, y)
|
||||||
|
|
||||||
|
b = append(b, result...)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) Write(p []byte) (n int, err error) {
|
||||||
|
n = len(p)
|
||||||
|
|
||||||
|
// First step: consume enough data to expand h.data to a full block, if
|
||||||
|
// possible.
|
||||||
|
{
|
||||||
|
toConsume := blockSize - len(h.data)
|
||||||
|
if toConsume > len(p) {
|
||||||
|
toConsume = len(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.data = append(h.data, p[:toConsume]...)
|
||||||
|
p = p[toConsume:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's no data left in p, it means h.data might not be a full block.
|
||||||
|
// Even if it is, we're not sure it's the final block, which we must treat
|
||||||
|
// specially. So we must stop here.
|
||||||
|
if len(p) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// h.data is a full block and is not the last; process it.
|
||||||
|
h.writeBlocks(h.data)
|
||||||
|
h.data = h.data[:0]
|
||||||
|
|
||||||
|
// Consume any further full blocks in p that we're sure aren't the last. Note
|
||||||
|
// that we're sure that len(p) is greater than zero here.
|
||||||
|
blocksToProcess := (len(p) - 1) / blockSize
|
||||||
|
bytesToProcess := blocksToProcess * blockSize
|
||||||
|
|
||||||
|
h.writeBlocks(p[:bytesToProcess])
|
||||||
|
p = p[bytesToProcess:]
|
||||||
|
|
||||||
|
// Store the rest for later.
|
||||||
|
h.data = append(h.data, p...)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) writeBlocks(p []byte) {
|
||||||
|
y := make([]byte, blockSize)
|
||||||
|
|
||||||
|
for off := 0; off < len(p); off += blockSize {
|
||||||
|
block := p[off : off+blockSize]
|
||||||
|
|
||||||
|
xorBlock(
|
||||||
|
unsafe.Pointer(&y[0]),
|
||||||
|
unsafe.Pointer(&h.x[0]),
|
||||||
|
unsafe.Pointer(&block[0]))
|
||||||
|
|
||||||
|
h.ciph.Encrypt(h.x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func xorBlock(
|
||||||
|
dstPtr unsafe.Pointer,
|
||||||
|
aPtr unsafe.Pointer,
|
||||||
|
bPtr unsafe.Pointer) {
|
||||||
|
// Check assumptions. (These are compile-time constants, so this should
|
||||||
|
// compile out.)
|
||||||
|
const wordSize = unsafe.Sizeof(uintptr(0))
|
||||||
|
if blockSize != 4*wordSize {
|
||||||
|
panic("xorBlock err1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert.
|
||||||
|
a := (*[4]uintptr)(aPtr)
|
||||||
|
b := (*[4]uintptr)(bPtr)
|
||||||
|
dst := (*[4]uintptr)(dstPtr)
|
||||||
|
|
||||||
|
// Compute.
|
||||||
|
dst[0] = a[0] ^ b[0]
|
||||||
|
dst[1] = a[1] ^ b[1]
|
||||||
|
dst[2] = a[2] ^ b[2]
|
||||||
|
dst[3] = a[3] ^ b[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
func PadBlock(block []byte) []byte {
|
||||||
|
blockLen := len(block)
|
||||||
|
if blockLen >= aes.BlockSize {
|
||||||
|
panic("PadBlock input must be less than 16 bytes.")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]byte, aes.BlockSize)
|
||||||
|
copy(result, block)
|
||||||
|
result[blockLen] = 0x80
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the supplied cipher, whose block size must be 16 bytes, return two
|
||||||
|
// subkeys that can be used in MAC generation. See section 5.3 of NIST SP
|
||||||
|
// 800-38B. Note that the other NIST-approved block size of 8 bytes is not
|
||||||
|
// supported by this function.
|
||||||
|
func generateSubkeys(ciph cipher.Block) (k1 []byte, k2 []byte) {
|
||||||
|
if ciph.BlockSize() != blockSize {
|
||||||
|
panic("generateSubkeys requires a cipher with a block size of 16 bytes.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1
|
||||||
|
l := make([]byte, blockSize)
|
||||||
|
ciph.Encrypt(l, subkeyZero)
|
||||||
|
|
||||||
|
// Step 2: Derive the first subkey.
|
||||||
|
if Msb(l) == 0 {
|
||||||
|
// TODO(jacobsa): Accept a destination buffer in ShiftLeft and then hoist
|
||||||
|
// the allocation in the else branch below.
|
||||||
|
k1 = ShiftLeft(l)
|
||||||
|
} else {
|
||||||
|
k1 = make([]byte, blockSize)
|
||||||
|
Xor(k1, ShiftLeft(l), subkeyRb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Derive the second subkey.
|
||||||
|
if Msb(k1) == 0 {
|
||||||
|
k2 = ShiftLeft(k1)
|
||||||
|
} else {
|
||||||
|
k2 = make([]byte, blockSize)
|
||||||
|
Xor(k2, ShiftLeft(k1), subkeyRb)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShiftLeft(b []byte) []byte {
|
||||||
|
l := len(b)
|
||||||
|
if l == 0 {
|
||||||
|
panic("shiftLeft requires a non-empty buffer.")
|
||||||
|
}
|
||||||
|
|
||||||
|
output := make([]byte, l)
|
||||||
|
|
||||||
|
overflow := byte(0)
|
||||||
|
for i := int(l - 1); i >= 0; i-- {
|
||||||
|
output[i] = b[i] << 1
|
||||||
|
output[i] |= overflow
|
||||||
|
overflow = (b[i] & 0x80) >> 7
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msb returns the most significant bit of the supplied data (which must be
|
||||||
|
// non-empty). This is the MSB(L) function of RFC 4493.
|
||||||
|
func Msb(buf []byte) uint8 {
|
||||||
|
if len(buf) == 0 {
|
||||||
|
panic("msb requires non-empty buffer.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf[0] >> 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// genPayloadMIC computes MIC given the payload and the key
|
||||||
|
func genPayloadMIC(payload []uint8, key [16]uint8) [4]uint8 {
|
||||||
|
var mic [4]uint8
|
||||||
|
hash, _ := NewCmac(key[:])
|
||||||
|
hash.Write(payload)
|
||||||
|
hb := hash.Sum([]byte{})
|
||||||
|
copy(mic[:], hb[0:4])
|
||||||
|
return mic
|
||||||
|
}
|
||||||
|
|
||||||
|
func calcMessageMIC(payload []uint8, key [16]uint8, dir uint8, addr []byte, fCnt uint32, lenMessage uint8) [4]uint8 {
|
||||||
|
var b0 []byte
|
||||||
|
b0 = append(b0, 0x49, 0x00, 0x00, 0x00, 0x00)
|
||||||
|
b0 = append(b0, dir)
|
||||||
|
b0 = append(b0, addr[:]...)
|
||||||
|
var b [4]byte
|
||||||
|
binary.LittleEndian.PutUint32(b[:], fCnt)
|
||||||
|
b0 = append(b0, b[:]...)
|
||||||
|
b0 = append(b0, 0x00)
|
||||||
|
b0 = append(b0, lenMessage)
|
||||||
|
|
||||||
|
var full []byte
|
||||||
|
full = append(full, b0...)
|
||||||
|
full = append(full, payload...)
|
||||||
|
|
||||||
|
var mic [4]uint8
|
||||||
|
hash, _ := NewCmac(key[:])
|
||||||
|
hash.Write(full)
|
||||||
|
hb := hash.Sum([]byte{})
|
||||||
|
copy(mic[:], hb[0:4])
|
||||||
|
return mic
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Otaa is used to store Over The Air Activation data of a LoRaWAN session
|
||||||
|
type Otaa struct {
|
||||||
|
DevEUI [8]uint8
|
||||||
|
AppEUI [8]uint8
|
||||||
|
AppKey [16]uint8
|
||||||
|
DevNonce [2]uint8
|
||||||
|
AppNonce [3]uint8
|
||||||
|
NetID [3]uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateJoinRequest Generates a LoraWAN Join request
|
||||||
|
func (o *Otaa) GenerateJoinRequest(buf []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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeJoinAccept Decodes a Lora Join Accept packet
|
||||||
|
func (o *Otaa) DecodeJoinAccept(phyPload []uint8, s *Session) error {
|
||||||
|
if len(phyPload) < 12 {
|
||||||
|
return errors.New("Bad packet")
|
||||||
|
}
|
||||||
|
data := phyPload[1:] // Remove trailing 0x20
|
||||||
|
|
||||||
|
// Prepare AES Cipher
|
||||||
|
block, err := aes.NewCipher(o.AppKey[:])
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Lora Cipher error 1")
|
||||||
|
}
|
||||||
|
buf := make([]byte, len(data))
|
||||||
|
for k := 0; k < len(data)/aes.BlockSize; k++ {
|
||||||
|
block.Encrypt(buf[k*aes.BlockSize:], data[k*aes.BlockSize:])
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(o.AppNonce[:], buf[0:3])
|
||||||
|
copy(o.NetID[:], buf[3:6])
|
||||||
|
copy(s.DevAddr[:], buf[6:10])
|
||||||
|
s.DLSettings = buf[10]
|
||||||
|
s.RXDelay = buf[11]
|
||||||
|
|
||||||
|
if len(buf) > 16 {
|
||||||
|
copy(s.CFList[:], buf[12:28])
|
||||||
|
}
|
||||||
|
rxMic := buf[len(buf)-4:]
|
||||||
|
|
||||||
|
dataMic := []byte{}
|
||||||
|
dataMic = append(dataMic, phyPload[0])
|
||||||
|
dataMic = append(dataMic, o.AppNonce[:]...)
|
||||||
|
dataMic = append(dataMic, o.NetID[:]...)
|
||||||
|
dataMic = append(dataMic, s.DevAddr[:]...)
|
||||||
|
dataMic = append(dataMic, s.DLSettings)
|
||||||
|
dataMic = append(dataMic, s.RXDelay)
|
||||||
|
dataMic = append(dataMic, s.CFList[:]...)
|
||||||
|
computedMic := genPayloadMIC(dataMic[:], o.AppKey)
|
||||||
|
if !bytes.Equal(computedMic[:], rxMic[:]) {
|
||||||
|
return errors.New("invalid Mic")
|
||||||
|
}
|
||||||
|
// Generate NwkSKey
|
||||||
|
// NwkSKey = aes128_encrypt(AppKey, 0x01|AppNonce|NetID|DevNonce|pad16)
|
||||||
|
sKey := []byte{}
|
||||||
|
sKey = append(sKey, 0x01)
|
||||||
|
sKey = append(sKey, o.AppNonce[:]...)
|
||||||
|
sKey = append(sKey, o.NetID[:]...)
|
||||||
|
sKey = append(sKey, o.DevNonce[:]...)
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
sKey = append(sKey, 0x00) // PAD to 16
|
||||||
|
}
|
||||||
|
block.Encrypt(buf, sKey)
|
||||||
|
copy(s.NwkSKey[:], buf[0:16])
|
||||||
|
|
||||||
|
// Generate AppSKey
|
||||||
|
// AppSKey = aes128_encrypt(AppKey, 0x02|AppNonce|NetID|DevNonce|pad16)
|
||||||
|
sKey[0] = 0x02
|
||||||
|
block.Encrypt(buf, sKey)
|
||||||
|
copy(s.AppSKey[:], buf[0:16])
|
||||||
|
|
||||||
|
// Reset counters
|
||||||
|
s.FCntDown = 0
|
||||||
|
s.FCntUp = 0
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Session is used to store session data of a LoRaWAN session
|
||||||
|
type Session struct {
|
||||||
|
NwkSKey [16]uint8
|
||||||
|
AppSKey [16]uint8
|
||||||
|
DevAddr [4]uint8
|
||||||
|
FCntDown uint32
|
||||||
|
FCntUp uint32
|
||||||
|
CFList [16]uint8
|
||||||
|
RXDelay uint8
|
||||||
|
DLSettings uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenMessage Forge an uplink message
|
||||||
|
func (s *Session) GenMessage(dir uint8, payload []uint8) ([]uint8, error) {
|
||||||
|
var buf []uint8
|
||||||
|
buf = append(buf, 0b01000000) // FHDR Unconfirmed up
|
||||||
|
buf = append(buf, s.DevAddr[:]...)
|
||||||
|
|
||||||
|
// FCtl : No ADR, No RFU, No ACK, No FPending, No FOpt
|
||||||
|
buf = append(buf, 0x00)
|
||||||
|
|
||||||
|
// FCnt Up
|
||||||
|
buf = append(buf, uint8(s.FCntUp&0xFF), uint8((s.FCntUp>>8)&0xFF))
|
||||||
|
|
||||||
|
// FPort=1
|
||||||
|
buf = append(buf, 0x01)
|
||||||
|
|
||||||
|
fCnt := uint32(0)
|
||||||
|
if dir == 0 {
|
||||||
|
fCnt = s.FCntUp
|
||||||
|
s.FCntUp++
|
||||||
|
} else {
|
||||||
|
fCnt = s.FCntDown
|
||||||
|
}
|
||||||
|
data, err := s.genFRMPayload(dir, fCnt, payload, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf = append(buf, data[:]...)
|
||||||
|
|
||||||
|
mic := calcMessageMIC(buf, s.NwkSKey, dir, s.DevAddr[:], fCnt, uint8(len(buf)))
|
||||||
|
buf = append(buf, mic[:]...)
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) genFRMPayload(dir uint8, fCnt uint32, payload []byte, isFOpts bool) ([]byte, error) {
|
||||||
|
k := len(payload) / aes.BlockSize
|
||||||
|
if len(payload)%aes.BlockSize != 0 {
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
if k > math.MaxUint8 {
|
||||||
|
return nil, errors.New("Payload too big !")
|
||||||
|
}
|
||||||
|
encrypted := make([]byte, 0, k*16)
|
||||||
|
cipher, err := aes.NewCipher(s.AppSKey[:])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var a [aes.BlockSize]byte
|
||||||
|
a[0] = 0x01
|
||||||
|
a[5] = dir
|
||||||
|
copy(a[6:10], s.DevAddr[:])
|
||||||
|
binary.LittleEndian.PutUint32(a[10:14], fCnt)
|
||||||
|
var ss [aes.BlockSize]byte
|
||||||
|
var b [aes.BlockSize]byte
|
||||||
|
for i := uint8(0); i < uint8(k); i++ {
|
||||||
|
copy(b[:], payload[i*aes.BlockSize:])
|
||||||
|
if !isFOpts {
|
||||||
|
a[15] = i + 1
|
||||||
|
}
|
||||||
|
cipher.Encrypt(ss[:], a[:])
|
||||||
|
for j := 0; j < aes.BlockSize; j++ {
|
||||||
|
b[j] = b[j] ^ ss[j]
|
||||||
|
}
|
||||||
|
encrypted = append(encrypted, b[:]...)
|
||||||
|
}
|
||||||
|
return encrypted[:len(payload)], nil
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package lora
|
||||||
|
|
||||||
|
type Radio interface {
|
||||||
|
Reset()
|
||||||
|
Tx(pkt []uint8, timeoutMs uint32) error
|
||||||
|
Rx(timeoutMs uint32) ([]uint8, error)
|
||||||
|
SetFrequency(freq uint32)
|
||||||
|
SetIqMode(mode uint8)
|
||||||
|
SetCodingRate(cr uint8)
|
||||||
|
SetBandwidth(bw uint8)
|
||||||
|
SetCrc(enable bool)
|
||||||
|
SetSpreadingFactor(sf uint8)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user