Hi! I asked this some time ago but I think it got lost on a topic that was about simulating devices (my bad). Anyway, I’m basically stuck at trying to process a successful join response for LoRaWAN 1.1 (it works with 1.0) using latest version of loraserver and lora-app-server. My Join function looks like this:
//Join sends a join request for a given device (OTAA) and rxInfo.
func (d *Device) Join(client MQTT.Client, gwMac string, rxInfo RxInfo) error {
d.Joined = false
devNonceKey := fmt.Sprintf("dev-nonce-%s", d.DevEUI[:])
var devNonce uint16
sdn, err := redisClient.Get(devNonceKey).Result()
if err == nil {
dn, err := strconv.Atoi(sdn)
if err == nil {
devNonce = uint16(dn + 1)
}
}
redisClient.Set(devNonceKey, devNonce, 0)
d.DevNonce = lorawan.DevNonce(devNonce)
joinPhy := lorawan.PHYPayload{
MHDR: lorawan.MHDR{
MType: lorawan.JoinRequest,
Major: lorawan.LoRaWANR1,
},
MACPayload: &lorawan.JoinRequestPayload{
JoinEUI: d.JoinEUI,
DevEUI: d.DevEUI,
DevNonce: d.DevNonce,
},
}
if err := joinPhy.SetUplinkJoinMIC(d.NwkKey); err != nil {
return err
}
joinStr, err := joinPhy.MarshalText()
if err != nil {
return err
}
message := &Message{
PhyPayload: string(joinStr),
RxInfo: &rxInfo,
}
pErr := publish(client, "gateway/"+rxInfo.Mac+"/rx", message)
return pErr
}
My function to process the join response downlink looks like this:
func (d *Device) processJoinResponse(phy lorawan.PHYPayload, payload []byte, mv lorawan.MACVersion) (string, error) {
log.Infoln("processing join response")
err := phy.DecryptJoinAcceptPayload(d.NwkKey)
if err != nil {
log.Errorf("can't decrypt join accept: %s", err)
}
ok, err := phy.ValidateDownlinkJoinMIC(lorawan.JoinRequestType, d.DevEUI, d.DevNonce, d.NwkKey)
if err != nil {
log.Error("failed at join mic function")
return "", err
}
if !ok {
return "", errors.New("join invalid mic")
}
if err := phy.DecryptFOpts(d.NwkKey); err != nil {
log.Error("failed at opts decryption")
return "", err
}
phyJSON, err := phy.MarshalJSON()
if err != nil {
log.Error("failed at json marshal")
return "", err
}
jap := phy.MACPayload.(*lorawan.JoinAcceptPayload)
log.Debugf("join accept payload: %+v", jap)
//Check that JoinNonce is greater than the one already stored.
joinNonceKey := fmt.Sprintf("join-nonce-%s", d.DevEUI[:])
var joinNonce lorawan.JoinNonce
sjn, err := redisClient.Get(joinNonceKey).Result()
if err == nil {
jn, err := strconv.Atoi(sjn)
if err == nil {
joinNonce = lorawan.JoinNonce(jn + 1)
}
}
if jap.JoinNonce <= joinNonce {
return "", errors.New("got lower or equal JoinNonce from server")
}
redisClient.Set(joinNonceKey, joinNonce, 0)
d.FNwkSIntKey, err = getFNwkSIntKey(jap.DLSettings.OptNeg, d.NwkKey, jap.HomeNetID, d.DevEUI, jap.JoinNonce, d.DevNonce)
if d.MACVersion == 0 {
d.NwkSEncKey = d.FNwkSIntKey
d.SNwkSIntKey = d.FNwkSIntKey
} else {
d.NwkSEncKey, err = getNwkSEncKey(jap.DLSettings.OptNeg, d.NwkKey, jap.HomeNetID, d.DevEUI, jap.JoinNonce, d.DevNonce)
d.SNwkSIntKey, err = getSNwkSIntKey(jap.DLSettings.OptNeg, d.NwkKey, jap.HomeNetID, d.DevEUI, jap.JoinNonce, d.DevNonce)
}
if jap.DLSettings.OptNeg {
d.AppSKey, err = getAppSKey(jap.DLSettings.OptNeg, d.AppKey, jap.HomeNetID, d.DevEUI, jap.JoinNonce, d.DevNonce)
} else {
d.AppSKey, err = getAppSKey(jap.DLSettings.OptNeg, d.NwkKey, jap.HomeNetID, d.DevEUI, jap.JoinNonce, d.DevNonce)
}
d.DevAddr = jap.DevAddr
d.Joined = true
d.UlFcnt = 0
d.DlFcnt = 0
//Set frame counters to 0.
ulFcntKey := fmt.Sprintf("ul-fcnt-%s", d.DevEUI[:])
dlFcntKey := fmt.Sprintf("dl-fcnt-%s", d.DevEUI[:])
redisClient.Set(ulFcntKey, d.UlFcnt, 0)
redisClient.Set(dlFcntKey, d.DlFcnt, 0)
return string(phyJSON), nil
}
func (d *Device) processDownlink(phy lorawan.PHYPayload, payload []byte, mv lorawan.MACVersion) (string, error) {
//Get downlink frame counter and icnrease it by 1.
dlFcntKey := fmt.Sprintf("dl-fcnt-%s", d.DevEUI[:])
df, err := redisClient.Get(dlFcntKey).Result()
if err == nil {
dfn, err := strconv.Atoi(df)
if err == nil {
d.DlFcnt = uint32(dfn)
}
}
ok, err := phy.ValidateDownlinkDataMIC(mv, 0, d.NwkSEncKey)
if err != nil {
log.Error("failed at downlink mic function")
return "", err
}
if !ok {
return "", errors.New("downlink error: invalid mic")
}
if err := phy.DecryptFOpts(d.NwkSEncKey); err != nil {
log.Error("failed at downlink opts decryption")
return "", err
}
if err := phy.DecryptFRMPayload(d.AppSKey); err != nil {
log.Error("failed at downlink frm payload decryption")
return "", err
}
/*if err := phy.DecodeFOptsToMACCommands(); err != nil {
log.Error("failed at downlink opts to mac commands decoding")
return "", err
}*/
phyJSON, err := phy.MarshalJSON()
if err != nil {
log.Error("failed at downlink json marshal")
return "", err
}
macPayload := phy.MACPayload.(*lorawan.MACPayload)
log.Infof("mac payload: %+v", macPayload)
for _, fOpt := range macPayload.FHDR.FOpts {
opt := fOpt.(*lorawan.MACCommand)
log.Infof("fOpt: %+v", opt.Payload)
}
log.Infof("fctrl: %+v", macPayload.FHDR.FCtrl)
for _, frmPayload := range macPayload.FRMPayload {
log.Infof("data payload: %+v", frmPayload.(*lorawan.DataPayload))
}
log.Infof("dlFcnt: %d / received Fcnt: %d", d.DlFcnt, macPayload.FHDR.FCnt)
//Set downlink frame counter.
d.DlFcnt++
redisClient.Set(dlFcntKey, d.DlFcnt, 0)
return string(phyJSON), nil
}
When sending a JoinRequest, everything goes OK at the network’s side and I can see a successful JoinAccept being generated and pushed down:
{
"downlinkMetaData": {
"txInfo": {
"gatewayId": "b827ebfffe9448d0",
"immediately": false,
"timeSinceGpsEpoch": null,
"timestamp": 1558184016,
"frequency": 923300000,
"power": 27,
"modulation": "LORA",
"loraModulationInfo": {
"bandwidth": 500,
"spreadingFactor": 10,
"codeRate": "4/5",
"polarizationInversion": true
},
"board": 0,
"antenna": 0
}
},
"phyPayload": {
"mhdr": {
"mType": "JoinAccept",
"major": "LoRaWANR1"
},
"macPayload": {
"bytes": "xcykrGR0bye6NUmaZHWl10fR7F/NWKJ3BiPl1A=="
},
"mic": "51b6018d"
}
},
{
"uplinkMetaData": {
"rxInfo": [
{
"gatewayId": "b827ebfffe9448d0",
"time": "2019-03-21T16:00:16Z",
"timeSinceGpsEpoch": null,
"timestamp": 1553184016,
"rssi": -57,
"loraSnr": 7,
"channel": 0,
"rfChain": 1,
"board": 0,
"antenna": 0,
"location": {
"latitude": -33.4348397,
"longitude": -70.6161374,
"altitude": 0,
"source": "UNKNOWN",
"accuracy": 0
},
"fineTimestampType": "NONE"
}
],
"txInfo": {
"frequency": 916800000,
"modulation": "LORA",
"loRaModulationInfo": {
"bandwidth": 125,
"spreadingFactor": 10,
"codeRate": "4/5",
"polarizationInversion": false
}
}
},
"phyPayload": {
"mhdr": {
"mType": "JoinRequest",
"major": "LoRaWANR1"
},
"macPayload": {
"joinEUI": "0000000000000003",
"devEUI": "0000000000000003",
"devNonce": 12
},
"mic": "026ad8c4"
}
}
But, at processing the response it’ll fail with downlink error: invalid mic
on the MIC validation bits.
Does anyone have any idea what’s wrong with my processing function (maybe some step is out of order, maybe I’m using wrong keys, etc.)?
You can check the whole code at https://github.com/iegomez/lds.