func validateAndCollectDataUpRXPacket(ctx Context, rxPacket models.RXPacket) error { // MACPayload must be of type *lorawan.MACPayload macPL, ok := rxPacket.PHYPayload.MACPayload.(*lorawan.MACPayload) if !ok { return fmt.Errorf("expected *lorawan.MACPayload, got: %T", rxPacket.PHYPayload.MACPayload) } // get the session data ns, err := storage.GetNodeSession(ctx.RedisPool, macPL.FHDR.DevAddr) if err != nil { return err } // validate and get the full int32 FCnt fullFCnt, ok := storage.ValidateAndGetFullFCntUp(ns, macPL.FHDR.FCnt) if !ok { log.WithFields(log.Fields{ "dev_addr": macPL.FHDR.DevAddr, "dev_eui": ns.DevEUI, "packet_fcnt": macPL.FHDR.FCnt, "server_fcnt": ns.FCntUp, }).Warning("invalid FCnt") return errors.New("invalid FCnt or too many dropped frames") } macPL.FHDR.FCnt = fullFCnt // validate MIC micOK, err := rxPacket.PHYPayload.ValidateMIC(ns.NwkSKey) if err != nil { return fmt.Errorf("validate MIC error: %s", err) } if !micOK { return errors.New("invalid MIC") } if macPL.FPort != nil { if *macPL.FPort == 0 { // decrypt FRMPayload with NwkSKey when FPort == 0 if err := rxPacket.PHYPayload.DecryptFRMPayload(ns.NwkSKey); err != nil { return fmt.Errorf("decrypt FRMPayload error: %s", err) } } else { if err := rxPacket.PHYPayload.DecryptFRMPayload(ns.AppSKey); err != nil { return fmt.Errorf("decrypt FRMPayload error: %s", err) } } } return collectAndCallOnce(ctx.RedisPool, rxPacket, func(rxPackets RXPackets) error { return handleCollectedDataUpPackets(ctx, rxPackets) }) }
// Get returns the node-session matching the given DevAddr. func (a *NodeSessionAPI) Get(ctx context.Context, req *pb.GetNodeSessionRequest) (*pb.GetNodeSessionResponse, error) { var devAddr lorawan.DevAddr if err := devAddr.UnmarshalText([]byte(req.DevAddr)); err != nil { return nil, err } ns, err := storage.GetNodeSession(a.ctx.RedisPool, devAddr) if err != nil { return nil, err } return a.nodeSessionToResponse(ns) }
func TestHandleJoinRequestPackets(t *testing.T) { conf := common.GetTestConfig() Convey("Given a dummy gateway and application backend and a clean Postgres and Redis database", t, func() { a := &testApplicationBackend{ rxPayloadChan: make(chan models.RXPayload, 1), notificationPayloadChan: make(chan interface{}, 10), } g := &testGatewayBackend{ rxPacketChan: make(chan models.RXPacket, 1), txPacketChan: make(chan models.TXPacket, 1), } p := storage.NewRedisPool(conf.RedisURL) common.MustFlushRedis(p) db, err := storage.OpenDatabase(conf.PostgresDSN) So(err, ShouldBeNil) common.MustResetDB(db) ctx := Context{ RedisPool: p, Gateway: g, Application: a, DB: db, } Convey("Given a node and application in the database", func() { app := models.Application{ AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, Name: "test app", } So(storage.CreateApplication(ctx.DB, app), ShouldBeNil) node := models.Node{ DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, AppEUI: app.AppEUI, AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, RXDelay: 3, RX1DROffset: 2, } So(storage.CreateNode(ctx.DB, node), ShouldBeNil) Convey("Given a JoinRequest with correct DevEUI but incorrect AppEUI", func() { phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.JoinRequestPayload{ AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 9}, DevEUI: node.DevEUI, DevNonce: [2]byte{1, 2}, }, } So(phy.SetMIC(node.AppKey), ShouldBeNil) rxPacket := models.RXPacket{ PHYPayload: phy, RXInfo: models.RXInfo{ Frequency: common.Band.UplinkChannels[0].Frequency, DataRate: common.Band.DataRates[common.Band.UplinkChannels[0].DataRates[0]], }, } Convey("then handleRXPacket returns an error", func() { So(handleRXPacket(ctx, rxPacket), ShouldResemble, errors.New("node 0807060504030201 belongs to application 0102030405060708, 0102030405060709 was given")) }) }) Convey("Given a JoinRequest packet", func() { phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.JoinRequestPayload{ AppEUI: app.AppEUI, DevEUI: node.DevEUI, DevNonce: [2]byte{1, 2}, }, } So(phy.SetMIC(node.AppKey), ShouldBeNil) rxPacket := models.RXPacket{ PHYPayload: phy, RXInfo: models.RXInfo{ Frequency: common.Band.UplinkChannels[0].Frequency, DataRate: common.Band.DataRates[common.Band.UplinkChannels[0].DataRates[0]], }, } Convey("When calling handleRXPacket", func() { So(handleRXPacket(ctx, rxPacket), ShouldBeNil) Convey("Then a JoinAccept was sent to the node", func() { txPacket := <-g.txPacketChan phy := txPacket.PHYPayload So(phy.DecryptJoinAcceptPayload(node.AppKey), ShouldBeNil) So(phy.MHDR.MType, ShouldEqual, lorawan.JoinAccept) Convey("Then it was sent after 5s", func() { So(txPacket.TXInfo.Timestamp, ShouldEqual, rxPacket.RXInfo.Timestamp+uint32(5*time.Second/time.Microsecond)) }) Convey("Then the RXDelay is set to 3s", func() { jaPL := phy.MACPayload.(*lorawan.JoinAcceptPayload) So(jaPL.RXDelay, ShouldEqual, 3) }) Convey("Then the DLSettings are set correctly", func() { jaPL := phy.MACPayload.(*lorawan.JoinAcceptPayload) So(jaPL.DLSettings.RX2DataRate, ShouldEqual, uint8(common.Band.RX2DataRate)) So(jaPL.DLSettings.RX1DROffset, ShouldEqual, node.RX1DROffset) }) Convey("Then a node-session was created", func() { jaPL := phy.MACPayload.(*lorawan.JoinAcceptPayload) _, err := storage.GetNodeSession(ctx.RedisPool, jaPL.DevAddr) So(err, ShouldBeNil) }) Convey("Then the dev-nonce was added to the used dev-nonces", func() { node, err := storage.GetNode(ctx.DB, node.DevEUI) So(err, ShouldBeNil) So([2]byte{1, 2}, ShouldBeIn, node.UsedDevNonces) }) Convey("Then a join notification was sent to the application", func() { notification := <-a.notificationPayloadChan join, ok := notification.(models.JoinNotification) So(ok, ShouldBeTrue) So(join.DevEUI, ShouldResemble, node.DevEUI) }) }) }) }) }) }) }
func handleCollectedDataUpPackets(ctx Context, rxPackets RXPackets) error { if len(rxPackets) == 0 { return errors.New("packet collector returned 0 packets") } rxPacket := rxPackets[0] var macs []string for _, p := range rxPackets { macs = append(macs, p.RXInfo.MAC.String()) } macPL, ok := rxPacket.PHYPayload.MACPayload.(*lorawan.MACPayload) if !ok { return fmt.Errorf("expected *lorawan.MACPayload, got: %T", rxPacket.PHYPayload.MACPayload) } ns, err := storage.GetNodeSession(ctx.RedisPool, macPL.FHDR.DevAddr) if err != nil { return err } log.WithFields(log.Fields{ "dev_eui": ns.DevEUI, "gw_count": len(rxPackets), "gw_macs": strings.Join(macs, ", "), "mtype": rxPackets[0].PHYPayload.MHDR.MType, }).Info("packet(s) collected") // send rx info notification to be used by the network-controller if err = sendRXInfoPayload(ctx, ns.AppEUI, ns.DevEUI, rxPackets); err != nil { return fmt.Errorf("send rx info notification error: %s", err) } // handle FOpts mac commands (if any) if len(macPL.FHDR.FOpts) > 0 { if err := handleUplinkMACCommands(ctx, ns.AppEUI, ns.DevEUI, false, macPL.FHDR.FOpts); err != nil { return fmt.Errorf("handle FOpts mac commands error: %s", err) } } if macPL.FPort != nil { if *macPL.FPort == 0 { if len(macPL.FRMPayload) == 0 { return errors.New("expected mac commands, but FRMPayload is empty (FPort=0)") } // since the PHYPayload has been marshaled / unmarshaled when // storing it into and retrieving it from the database, we need // to decode the MAC commands from the FRMPayload. if err = rxPacket.PHYPayload.DecodeFRMPayloadToMACCommands(); err != nil { return fmt.Errorf("decode FRMPayload field to MACCommand items error: %s", err) } var commands []lorawan.MACCommand for _, pl := range macPL.FRMPayload { cmd, ok := pl.(*lorawan.MACCommand) if !ok { return fmt.Errorf("expected MACPayload, but got %T", macPL.FRMPayload) } commands = append(commands, *cmd) } if err := handleUplinkMACCommands(ctx, ns.AppEUI, ns.DevEUI, true, commands); err != nil { return fmt.Errorf("handle FRMPayload mac commands error: %s", err) } } else { var data []byte // it is possible that the FRMPayload is empty, in this case only // the FPort will be send if len(macPL.FRMPayload) == 1 { dataPL, ok := macPL.FRMPayload[0].(*lorawan.DataPayload) if !ok { return errors.New("FRMPayload must be of type *lorawan.DataPayload") } data = dataPL.Bytes } err = ctx.Application.SendRXPayload(ns.AppEUI, ns.DevEUI, models.RXPayload{ DevEUI: ns.DevEUI, GatewayCount: len(rxPackets), FPort: *macPL.FPort, RSSI: rxPacket.RXInfo.RSSI, Data: data, }) if err != nil { return fmt.Errorf("send rx payload to application error: %s", err) } } } // sync counter with that of the device ns.FCntUp = macPL.FHDR.FCnt if err := storage.SaveNodeSession(ctx.RedisPool, ns); err != nil { return err } // handle uplink ACK if macPL.FHDR.FCtrl.ACK { if err := handleUplinkACK(ctx, ns); err != nil { return fmt.Errorf("handle uplink ack error: %s", err) } } // handle downlink (ACK) time.Sleep(CollectDataDownWait) if err := handleDataDownReply(ctx, rxPacket, ns); err != nil { return fmt.Errorf("handling downlink data for node %s failed: %s", ns.DevEUI, err) } return nil }