func handleDataDownReply(ctx Context, rxPacket models.RXPacket, ns models.NodeSession) error { macPL, ok := rxPacket.PHYPayload.MACPayload.(*lorawan.MACPayload) if !ok { return fmt.Errorf("expected *lorawan.MACPayload, got: %T", rxPacket.PHYPayload.MACPayload) } // get data down properies properties, err := getDataDownProperties(rxPacket.RXInfo, ns) if err != nil { return fmt.Errorf("get data down properties error: %s", err) } var frmMACCommands bool var macPayloads []models.MACPayload allMACPayloads, err := storage.ReadMACPayloadTXQueue(ctx.RedisPool, ns.DevAddr) if err != nil { return fmt.Errorf("read mac-payload tx queue error: %s", err) } if len(allMACPayloads) > 0 { if allMACPayloads[0].FRMPayload { // the first mac-commands must be sent as FRMPayload, filter the rest // of the MACPayload items with the same property, respecting the // max FRMPayload size for the data-rate. frmMACCommands = true macPayloads = storage.FilterMACPayloads(allMACPayloads, true, common.Band.MaxPayloadSize[properties.rx1DR].N) } else { // the first mac-command must be sent as FOpts, filter the rest of // the MACPayload items with the same property, respecting the // max FOpts size of 15. macPayloads = storage.FilterMACPayloads(allMACPayloads, false, 15) } } // if the MACCommands (if any) are not sent as FRMPayload, check if there // is a tx-payload in the queue and validate if the FOpts + FRMPayload // does not exceed the max payload size. var txPayload *models.TXPayload if !frmMACCommands { // check if there are payloads pending in the queue txPayload, err = getNextValidTXPayloadForDRFromQueue(ctx, ns, properties.rx1DR) if err != nil { return fmt.Errorf("get next valid tx-payload error: %s", err) } var macByteCount int for _, mac := range macPayloads { macByteCount += len(mac.MACCommand) } if txPayload != nil && len(txPayload.Data)+macByteCount > common.Band.MaxPayloadSize[properties.rx1DR].N { log.WithFields(log.Fields{ "data_rate": properties.rx1DR, "dev_eui": ns.DevEUI, "reference": txPayload.Reference, }).Info("scheduling tx-payload for next downlink, mac-commands + payload exceeds max size") txPayload = nil } } // convert the MACPayload items into MACCommand items var macCommmands []lorawan.MACCommand for _, pl := range macPayloads { var mac lorawan.MACCommand if err := mac.UnmarshalBinary(false, pl.MACCommand); err != nil { // in case the mac commands can't be unmarshaled, the payload // is ignored and an error sent to the network-controller errStr := fmt.Sprintf("unmarshal mac command error: %s", err) log.WithFields(log.Fields{ "dev_eui": ns.DevEUI, "reference": pl.Reference, }).Warning(errStr) err = ctx.Controller.SendErrorPayload(ns.AppEUI, ns.DevEUI, models.ErrorPayload{ Reference: pl.Reference, DevEUI: ns.DevEUI, Message: errStr, }) if err != nil { return fmt.Errorf("send error payload to network-controller error: %s", err) } continue } macCommmands = append(macCommmands, mac) } // uplink was unconfirmed and no downlink data in queue and no mac commands to send if txPayload == nil && rxPacket.PHYPayload.MHDR.MType == lorawan.UnconfirmedDataUp && len(macCommmands) == 0 { return nil } // get the queue size (the size includes the current payload) queueSize, err := storage.GetTXPayloadQueueSize(ctx.RedisPool, ns.DevEUI) if err != nil { return err } if txPayload != nil { queueSize-- // substract the current tx-payload from the queue-size } phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, } macPL = &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCtrl: lorawan.FCtrl{ ACK: rxPacket.PHYPayload.MHDR.MType == lorawan.ConfirmedDataUp, // set ACK when uplink packet was of type ConfirmedDataUp FPending: queueSize > 0 || len(allMACPayloads) != len(macPayloads), // items in the queue or not all mac commands being sent }, FCnt: ns.FCntDown, }, } phy.MACPayload = macPL if len(macCommmands) > 0 { if frmMACCommands { var fPort uint8 // 0 var frmPayload []lorawan.Payload for i := range macCommmands { frmPayload = append(frmPayload, &macCommmands[i]) } macPL.FPort = &fPort macPL.FRMPayload = frmPayload } else { macPL.FHDR.FOpts = macCommmands } } // add the payload to FRMPayload field // note that txPayload is by definition nil when there are mac commands // to send in the FRMPayload field. if txPayload != nil { if txPayload.Confirmed { phy.MHDR.MType = lorawan.ConfirmedDataDown } macPL.FPort = &txPayload.FPort macPL.FRMPayload = []lorawan.Payload{ &lorawan.DataPayload{Bytes: txPayload.Data}, } } // if there is no payload set, encrypt will just do nothing if len(macCommmands) > 0 && frmMACCommands { if err := phy.EncryptFRMPayload(ns.NwkSKey); err != nil { return fmt.Errorf("encrypt FRMPayload error: %s", err) } } else { if err := phy.EncryptFRMPayload(ns.AppSKey); err != nil { return fmt.Errorf("encrypt FRMPayload error: %s", err) } } if err := phy.SetMIC(ns.NwkSKey); err != nil { return fmt.Errorf("set MIC error: %s", err) } txPacket := models.TXPacket{ TXInfo: models.TXInfo{ MAC: rxPacket.RXInfo.MAC, Timestamp: rxPacket.RXInfo.Timestamp + uint32(properties.rxDelay/time.Microsecond), Frequency: properties.rx1Frequency, Power: common.Band.DefaultTXPower, DataRate: common.Band.DataRates[properties.rx1DR], CodeRate: rxPacket.RXInfo.CodeRate, }, PHYPayload: phy, } // window 1 if err := ctx.Gateway.SendTXPacket(txPacket); err != nil { return fmt.Errorf("send tx packet (rx window 1) to gateway error: %s", err) } // increment the FCntDown when MType != ConfirmedDataDown and clear // in-process queue. In case of ConfirmedDataDown we increment on ACK. if phy.MHDR.MType != lorawan.ConfirmedDataDown { ns.FCntDown++ if err = storage.SaveNodeSession(ctx.RedisPool, ns); err != nil { return err } if txPayload != nil { if _, err = storage.ClearInProcessTXPayload(ctx.RedisPool, ns.DevEUI); err != nil { return err } } } // remove the mac commands from the queue for _, pl := range macPayloads { if err = storage.DeleteMACPayloadFromTXQueue(ctx.RedisPool, ns.DevAddr, pl); err != nil { return fmt.Errorf("delete mac-payload from tx queue error: %s", err) } } return nil }
func runDataUpTests(ctx Context, devEUI lorawan.EUI64, devAddr lorawan.DevAddr, tests []dataUpTestCase) { for i, test := range tests { Convey(fmt.Sprintf("When testing: %s [%d]", test.Name, i), func() { ctx.Application.(*testApplicationBackend).err = test.ApplicationBackendError for _, pl := range test.TXPayloadQueue { So(storage.AddTXPayloadToQueue(ctx.RedisPool, pl), ShouldBeNil) } for _, mac := range test.TXMACPayloadQueue { So(storage.AddMACPayloadToTXQueue(ctx.RedisPool, mac), ShouldBeNil) } if test.TXMACPayloadInProcess != nil { So(storage.AddTXPayloadToQueue(ctx.RedisPool, *test.TXMACPayloadInProcess), ShouldBeNil) _, err := storage.GetTXPayloadFromQueue(ctx.RedisPool, devEUI) // getting an item from the queue will put it into in-process So(err, ShouldBeNil) } So(test.PHYPayload.EncryptFRMPayload(test.EncryptFRMPayloadKey), ShouldBeNil) So(test.PHYPayload.SetMIC(test.SetMICKey), ShouldBeNil) rxPacket := models.RXPacket{ PHYPayload: test.PHYPayload, RXInfo: test.RXInfo, } So(handleRXPacket(ctx, rxPacket), ShouldResemble, test.ExpectedHandleRXPacketError) Convey("Then the expected rx-info payloads are sent to the network-controller", func() { So(ctx.Controller.(*testControllerBackend).rxInfoPayloadChan, ShouldHaveLength, len(test.ExpectedControllerRXInfoPayloads)) for _, expPL := range test.ExpectedControllerRXInfoPayloads { pl := <-ctx.Controller.(*testControllerBackend).rxInfoPayloadChan So(pl, ShouldResemble, expPL) } }) Convey("Then the expected error payloads are sent to the network-controller", func() { So(ctx.Controller.(*testControllerBackend).errorPayloadChan, ShouldHaveLength, len(test.ExpectedControllerErrorPayloads)) for _, expPL := range test.ExpectedControllerErrorPayloads { pl := <-ctx.Controller.(*testControllerBackend).errorPayloadChan So(pl, ShouldResemble, expPL) } }) Convey("Then the expected mac-commands are received by the network-controller", func() { So(ctx.Controller.(*testControllerBackend).rxMACPayloadChan, ShouldHaveLength, len(test.ExpectedControllerRXMACPayloads)) for _, expPl := range test.ExpectedControllerRXMACPayloads { pl := <-ctx.Controller.(*testControllerBackend).rxMACPayloadChan So(pl, ShouldResemble, expPl) } }) Convey("Then the expected rx-payloads are received by the application-backend", func() { So(ctx.Application.(*testApplicationBackend).rxPayloadChan, ShouldHaveLength, len(test.ExpectedApplicationRXPayloads)) for _, expPL := range test.ExpectedApplicationRXPayloads { pl := <-ctx.Application.(*testApplicationBackend).rxPayloadChan So(pl, ShouldResemble, expPL) } }) Convey("Then the expected notifications are received by the application-backend", func() { So(ctx.Application.(*testApplicationBackend).notificationPayloadChan, ShouldHaveLength, len(test.ExpectedApplicationNotifications)) for _, expPL := range test.ExpectedApplicationNotifications { pl := <-ctx.Application.(*testApplicationBackend).notificationPayloadChan So(pl, ShouldResemble, expPL) } }) Convey("Then the expected tx-packets are received by the gateway (FRMPayload decrypted and MIC ignored)", func() { So(ctx.Gateway.(*testGatewayBackend).txPacketChan, ShouldHaveLength, len(test.ExpectedGatewayTXPackets)) for _, expPL := range test.ExpectedGatewayTXPackets { pl := <-ctx.Gateway.(*testGatewayBackend).txPacketChan So(pl.PHYPayload.DecryptFRMPayload(test.DecryptExpectedFRMPayloadKey), ShouldBeNil) expPL.PHYPayload.MIC = pl.PHYPayload.MIC So(pl, ShouldResemble, expPL) } }) Convey("Then the frame-counters are as expected", func() { ns, err := storage.GetNodeSessionByDevEUI(ctx.RedisPool, devEUI) So(err, ShouldBeNil) So(ns.FCntDown, ShouldEqual, test.ExpectedFCntDown) So(ns.FCntUp, ShouldEqual, test.ExpectedFCntUp) }) Convey("Then the MACPayload tx-queue is as expected", func() { macQueue, err := storage.ReadMACPayloadTXQueue(ctx.RedisPool, devAddr) So(err, ShouldBeNil) So(macQueue, ShouldResemble, test.ExpectedTXMACPayloadQueue) }) Convey("Then the next TXPayload is as expected", func() { txPL, err := storage.GetTXPayloadFromQueue(ctx.RedisPool, devEUI) if test.ExpectedGetTXPayloadFromQueue == nil { So(err, ShouldResemble, common.ErrEmptyQueue) } else { So(err, ShouldBeNil) So(txPL, ShouldResemble, *test.ExpectedGetTXPayloadFromQueue) } }) }) } }