コード例 #1
0
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
}
コード例 #2
0
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)
				}
			})
		})
	}
}