func TestgetRandomDevAddr(t *testing.T) { conf := common.GetTestConfig() Convey("Given a Redis database and NetID 010203", t, func() { p := NewRedisPool(conf.RedisURL) common.MustFlushRedis(p) netID := lorawan.NetID{1, 2, 3} Convey("When calling getRandomDevAddr many times, it should always return an unique DevAddr", func() { log := make(map[lorawan.DevAddr]struct{}) for i := 0; i < 1000; i++ { devAddr, err := GetRandomDevAddr(p, netID) if err != nil { t.Fatal(err) } if devAddr.NwkID() != netID.NwkID() { t.Fatalf("%b must equal %b", devAddr.NwkID(), netID.NwkID()) } if len(log) != i { t.Fatalf("%d must equal %d", len(log), i) } log[devAddr] = struct{}{} } }) }) }
func TestMACPayloadTXQueue(t *testing.T) { conf := common.GetTestConfig() Convey("Given a clean Redis database", t, func() { p := NewRedisPool(conf.RedisURL) common.MustFlushRedis(p) Convey("Given a node-session", func() { ns := models.NodeSession{ DevAddr: [4]byte{1, 2, 3, 4}, DevEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, } So(CreateNodeSession(p, ns), ShouldBeNil) Convey("When adding MACPayload a and b to the queue", func() { a := models.MACPayload{ Reference: "a", DevEUI: ns.DevEUI, } b := models.MACPayload{ Reference: "b", DevEUI: ns.DevEUI, } So(AddMACPayloadToTXQueue(p, a), ShouldBeNil) So(AddMACPayloadToTXQueue(p, b), ShouldBeNil) Convey("Then readMACPayloadTXQueue returns both MACPayload in the correct order", func() { payloads, err := ReadMACPayloadTXQueue(p, ns.DevAddr) So(err, ShouldBeNil) So(payloads, ShouldResemble, []models.MACPayload{a, b}) }) Convey("When deleting MACPayload a", func() { So(DeleteMACPayloadFromTXQueue(p, ns.DevAddr, a), ShouldBeNil) Convey("Then only MACPayload b is in the queue", func() { payloads, err := ReadMACPayloadTXQueue(p, ns.DevAddr) So(err, ShouldBeNil) So(payloads, ShouldResemble, []models.MACPayload{b}) }) }) }) }) }) }
func TestTXPayloadQueue(t *testing.T) { conf := common.GetTestConfig() Convey("Given a clean Redis database", t, func() { p := NewRedisPool(conf.RedisURL) common.MustFlushRedis(p) Convey("Given two TXPayload structs (a and b) for the same DevEUI", func() { devEUI := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} a := models.TXPayload{ DevEUI: devEUI, Data: []byte("hello!"), } b := models.TXPayload{ DevEUI: devEUI, Data: []byte("world"), } Convey("When getting an item from the non-existing queue", func() { _, err := GetTXPayloadFromQueue(p, devEUI) Convey("Then errEmptyQueue error is returned", func() { So(err, ShouldResemble, common.ErrEmptyQueue) }) }) Convey("Given the NodePayloadQueueTTL is 100 ms", func() { common.NodeTXPayloadQueueTTL = 100 * time.Millisecond Convey("Given struct a and b are pushed to the queue", func() { So(AddTXPayloadToQueue(p, a), ShouldBeNil) So(AddTXPayloadToQueue(p, b), ShouldBeNil) Convey("Then the queue size is 2", func() { count, err := GetTXPayloadQueueSize(p, devEUI) So(err, ShouldBeNil) So(count, ShouldEqual, 2) }) Convey("Then after 150 ms the queue has expired", func() { time.Sleep(150 * time.Millisecond) _, err := GetTXPayloadFromQueue(p, devEUI) So(err, ShouldResemble, common.ErrEmptyQueue) }) Convey("When consuming an item from the queue", func() { pl, err := GetTXPayloadFromQueue(p, devEUI) So(err, ShouldBeNil) Convey("Then the queue size is still 2", func() { count, err := GetTXPayloadQueueSize(p, devEUI) So(err, ShouldBeNil) So(count, ShouldEqual, 2) }) Convey("Then it equals to struct a", func() { So(pl, ShouldResemble, a) }) Convey("When consuming an item from the queue again", func() { pl, err := GetTXPayloadFromQueue(p, devEUI) So(err, ShouldBeNil) Convey("Then it equals to struct a (again)", func() { So(pl, ShouldResemble, a) }) }) Convey("When flushing the queue", func() { So(FlushTXPayloadQueue(p, devEUI), ShouldBeNil) Convey("Then the queue size is 0", func() { count, err := GetTXPayloadQueueSize(p, devEUI) So(err, ShouldBeNil) So(count, ShouldEqual, 0) }) }) Convey("Then after 150 ms the queue has expired (both in-process and the queue)", func() { time.Sleep(100 * time.Millisecond) _, err := GetTXPayloadFromQueue(p, devEUI) So(err, ShouldResemble, common.ErrEmptyQueue) }) Convey("After clearing the in-process payload", func() { _, err := ClearInProcessTXPayload(p, devEUI) So(err, ShouldBeNil) Convey("Then the queue size is 1", func() { count, err := GetTXPayloadQueueSize(p, devEUI) So(err, ShouldBeNil) So(count, ShouldEqual, 1) }) Convey("When consuming an item from the queue", func() { pl, err := GetTXPayloadFromQueue(p, devEUI) So(err, ShouldBeNil) Convey("Then it equals to struct b", func() { So(pl, ShouldResemble, b) }) }) }) }) }) }) }) }) }
func TestNodeSessionAPI(t *testing.T) { conf := common.GetTestConfig() Convey("Given a clean database and api instance", t, func() { db, err := storage.OpenDatabase(conf.PostgresDSN) So(err, ShouldBeNil) common.MustResetDB(db) p := storage.NewRedisPool(conf.RedisURL) common.MustFlushRedis(p) lsCtx := loraserver.Context{DB: db, RedisPool: p, NetID: [3]byte{1, 2, 3}} ctx := context.Background() api := NewNodeSessionAPI(lsCtx) Convey("Given an application and node are created (fk constraints)", func() { app := models.Application{ AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, Name: "test app", } So(storage.CreateApplication(db, app), ShouldBeNil) node := models.Node{ DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, UsedDevNonces: [][2]byte{}, } So(storage.CreateNode(db, node), ShouldBeNil) Convey("When creating a node-session", func() { _, err := api.Create(ctx, &pb.CreateNodeSessionRequest{ DevAddr: "06020304", DevEUI: node.DevEUI.String(), AppEUI: node.AppEUI.String(), AppSKey: node.AppKey.String(), NwkSKey: node.AppKey.String(), FCntUp: 10, FCntDown: 11, RxDelay: 1, Rx1DROffset: 2, CFList: []uint32{ 868700000, }, }) So(err, ShouldBeNil) Convey("Then it can be retrieved by DevAddr", func() { resp, err := api.Get(ctx, &pb.GetNodeSessionRequest{DevAddr: "06020304"}) So(err, ShouldBeNil) So(resp, ShouldResemble, &pb.GetNodeSessionResponse{ DevAddr: "06020304", DevEUI: node.DevEUI.String(), AppEUI: node.AppEUI.String(), AppSKey: node.AppKey.String(), NwkSKey: node.AppKey.String(), FCntUp: 10, FCntDown: 11, RxDelay: 1, Rx1DROffset: 2, CFList: []uint32{ 868700000, 0, 0, 0, 0, }, }) }) Convey("Then it can be retrieved by DevEUI", func() { resp, err := api.GetByDevEUI(ctx, &pb.GetNodeSessionByDevEUIRequest{DevEUI: node.DevEUI.String()}) So(err, ShouldBeNil) So(resp, ShouldResemble, &pb.GetNodeSessionResponse{ DevAddr: "06020304", DevEUI: node.DevEUI.String(), AppEUI: node.AppEUI.String(), AppSKey: node.AppKey.String(), NwkSKey: node.AppKey.String(), FCntUp: 10, FCntDown: 11, RxDelay: 1, Rx1DROffset: 2, CFList: []uint32{ 868700000, 0, 0, 0, 0, }, }) }) Convey("When updating the node-session", func() { _, err := api.Update(ctx, &pb.UpdateNodeSessionRequest{ DevAddr: "06020304", DevEUI: node.DevEUI.String(), AppEUI: node.AppEUI.String(), AppSKey: node.AppKey.String(), NwkSKey: node.AppKey.String(), FCntUp: 20, FCntDown: 22, RxDelay: 10, Rx1DROffset: 20, CFList: []uint32{ 868700000, 868800000, }, }) So(err, ShouldBeNil) Convey("Then the node-session has been updated", func() { resp, err := api.Get(ctx, &pb.GetNodeSessionRequest{DevAddr: "06020304"}) So(err, ShouldBeNil) So(resp, ShouldResemble, &pb.GetNodeSessionResponse{ DevAddr: "06020304", DevEUI: node.DevEUI.String(), AppEUI: node.AppEUI.String(), AppSKey: node.AppKey.String(), NwkSKey: node.AppKey.String(), FCntUp: 20, FCntDown: 22, RxDelay: 10, Rx1DROffset: 20, CFList: []uint32{ 868700000, 868800000, 0, 0, 0, }, }) }) }) Convey("When deleting the node-session", func() { _, err := api.Delete(ctx, &pb.DeleteNodeSessionRequest{DevAddr: "06020304"}) So(err, ShouldBeNil) Convey("Then the node-session has been deleted", func() { _, err := api.Get(ctx, &pb.GetNodeSessionRequest{DevAddr: "06020304"}) So(err, ShouldNotBeNil) }) }) }) }) }) }
func TestHandleDataUpScenarios(t *testing.T) { conf := common.GetTestConfig() Convey("Given a clean state, test backends and a node-session", t, func() { app := &testApplicationBackend{ rxPayloadChan: make(chan models.RXPayload, 1), txPayloadChan: make(chan models.TXPayload, 1), notificationPayloadChan: make(chan interface{}, 10), } gw := &testGatewayBackend{ rxPacketChan: make(chan models.RXPacket, 1), txPacketChan: make(chan models.TXPacket, 1), } ctrl := &testControllerBackend{ rxMACPayloadChan: make(chan models.MACPayload, 2), txMACPayloadChan: make(chan models.MACPayload, 1), rxInfoPayloadChan: make(chan models.RXInfoPayload, 1), errorPayloadChan: make(chan models.ErrorPayload, 1), } p := storage.NewRedisPool(conf.RedisURL) common.MustFlushRedis(p) ctx := Context{ RedisPool: p, Gateway: gw, Application: app, Controller: ctrl, } ns := models.NodeSession{ DevAddr: [4]byte{1, 2, 3, 4}, DevEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, AppSKey: [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, FCntUp: 8, FCntDown: 5, AppEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, } So(storage.CreateNodeSession(ctx.RedisPool, ns), ShouldBeNil) rxInfo := models.RXInfo{ Frequency: common.Band.UplinkChannels[0].Frequency, DataRate: common.Band.DataRates[common.Band.UplinkChannels[0].DataRates[0]], } var fPortZero uint8 var fPortOne uint8 = 1 Convey("Given a set of test-scenarios for basic flows (nothing in queue)", func() { tests := []dataUpTestCase{ // errors { Name: "the application backend returns an error", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, ApplicationBackendError: errors.New("BOOM!"), PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedFCntUp: 8, ExpectedFCntDown: 5, ExpectedHandleRXPacketError: errors.New("send rx payload to application error: BOOM!"), }, { Name: "the frame-counter is invalid", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 7, }, FPort: &fPortOne, }, }, ExpectedFCntUp: 8, ExpectedFCntDown: 5, ExpectedHandleRXPacketError: errors.New("invalid FCnt or too many dropped frames"), }, { Name: "the mic is invalid", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, SetMICKey: ns.AppSKey, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, }, }, ExpectedFCntUp: 8, ExpectedFCntDown: 5, ExpectedHandleRXPacketError: errors.New("invalid MIC"), }, // basic flows { Name: "unconfirmed uplink data with payload", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, FRMPayload: []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}}, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1, Data: []byte{1, 2, 3, 4}}, }, ExpectedFCntUp: 10, ExpectedFCntDown: 5, }, { Name: "unconfirmed uplink data without payload (just a FPort)", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1}, }, ExpectedFCntUp: 10, ExpectedFCntDown: 5, }, { Name: "confirmed uplink data with payload", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.ConfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, FRMPayload: []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}}, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1, Data: []byte{1, 2, 3, 4}}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, FCtrl: lorawan.FCtrl{ ACK: true, }, }, }, }, }, }, ExpectedFCntUp: 10, ExpectedFCntDown: 6, }, { Name: "confirmed uplink data without payload", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.ConfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, FCtrl: lorawan.FCtrl{ ACK: true, }, }, }, }, }, }, ExpectedFCntUp: 10, ExpectedFCntDown: 6, }, { Name: "two uplink mac commands (FOpts)", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.NwkSKey, SetMICKey: ns.NwkSKey, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, FOpts: []lorawan.MACCommand{ {CID: lorawan.LinkCheckReq}, {CID: lorawan.LinkADRAns, Payload: &lorawan.LinkADRAnsPayload{ChannelMaskACK: true}}, }, }, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedControllerRXMACPayloads: []models.MACPayload{ {DevEUI: ns.DevEUI, MACCommand: []byte{2}}, {DevEUI: ns.DevEUI, MACCommand: []byte{3, 1}}, }, ExpectedFCntUp: 10, ExpectedFCntDown: 5, }, { Name: "two uplink mac commands (FRMPayload)", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.NwkSKey, SetMICKey: ns.NwkSKey, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortZero, FRMPayload: []lorawan.Payload{ &lorawan.MACCommand{CID: lorawan.LinkCheckReq}, &lorawan.MACCommand{CID: lorawan.LinkADRAns, Payload: &lorawan.LinkADRAnsPayload{ChannelMaskACK: true}}, }, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedControllerRXMACPayloads: []models.MACPayload{ {DevEUI: ns.DevEUI, FRMPayload: true, MACCommand: []byte{2}}, {DevEUI: ns.DevEUI, FRMPayload: true, MACCommand: []byte{3, 1}}, }, ExpectedFCntUp: 10, ExpectedFCntDown: 5, }, } runDataUpTests(ctx, ns.DevEUI, ns.DevAddr, tests) }) Convey("Given a set of test-scenarios for mac-command queue", func() { var fPortThree uint8 = 3 tests := []dataUpTestCase{ { Name: "unconfirmed uplink data + two downlink mac commands in queue (FOpts)", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, TXMACPayloadQueue: []models.MACPayload{ {DevEUI: ns.DevEUI, MACCommand: []byte{6}}, {DevEUI: ns.DevEUI, MACCommand: []byte{8, 3}}, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, FRMPayload: []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}}, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1, Data: []byte{1, 2, 3, 4}}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, FOpts: []lorawan.MACCommand{ {CID: lorawan.CID(6)}, {CID: lorawan.CID(8), Payload: &lorawan.RXTimingSetupReqPayload{Delay: 3}}, }, }, }, }, }, }, ExpectedFCntUp: 10, ExpectedFCntDown: 6, }, { Name: "unconfirmed uplink data + two downlink mac commands in queue (FOpts) + unconfirmed tx-payload in queue", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, DecryptExpectedFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, TXPayloadQueue: []models.TXPayload{ {DevEUI: ns.DevEUI, FPort: 3, Data: []byte{4, 5, 6}}, }, TXMACPayloadQueue: []models.MACPayload{ {DevEUI: ns.DevEUI, MACCommand: []byte{6}}, {DevEUI: ns.DevEUI, MACCommand: []byte{8, 3}}, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, FRMPayload: []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}}, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1, Data: []byte{1, 2, 3, 4}}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, FOpts: []lorawan.MACCommand{ {CID: lorawan.CID(6)}, {CID: lorawan.CID(8), Payload: &lorawan.RXTimingSetupReqPayload{Delay: 3}}, }, }, FPort: &fPortThree, FRMPayload: []lorawan.Payload{ &lorawan.DataPayload{Bytes: []byte{4, 5, 6}}, }, }, }, }, }, ExpectedFCntUp: 10, ExpectedFCntDown: 6, }, { Name: "unconfirmed uplink data + two downlink mac commands in queue (FRMPayload)", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, DecryptExpectedFRMPayloadKey: ns.NwkSKey, SetMICKey: ns.NwkSKey, TXMACPayloadQueue: []models.MACPayload{ {DevEUI: ns.DevEUI, FRMPayload: true, MACCommand: []byte{6}}, {DevEUI: ns.DevEUI, FRMPayload: true, MACCommand: []byte{8, 3}}, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, FRMPayload: []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}}, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1, Data: []byte{1, 2, 3, 4}}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, }, FPort: &fPortZero, FRMPayload: []lorawan.Payload{ &lorawan.MACCommand{CID: lorawan.CID(6)}, &lorawan.MACCommand{CID: lorawan.CID(8), Payload: &lorawan.RXTimingSetupReqPayload{Delay: 3}}, }, }, }, }, }, ExpectedFCntUp: 10, ExpectedFCntDown: 6, }, { Name: "unconfirmed uplink data + two downlink mac commands in queue (FRMPayload) + unconfirmed tx-payload in queue", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, DecryptExpectedFRMPayloadKey: ns.NwkSKey, SetMICKey: ns.NwkSKey, TXPayloadQueue: []models.TXPayload{ {DevEUI: ns.DevEUI, FPort: 3, Data: []byte{4, 5, 6}}, }, TXMACPayloadQueue: []models.MACPayload{ {DevEUI: ns.DevEUI, FRMPayload: true, MACCommand: []byte{6}}, {DevEUI: ns.DevEUI, FRMPayload: true, MACCommand: []byte{8, 3}}, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, FRMPayload: []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}}, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1, Data: []byte{1, 2, 3, 4}}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, FCtrl: lorawan.FCtrl{ FPending: true, }, }, FPort: &fPortZero, FRMPayload: []lorawan.Payload{ &lorawan.MACCommand{CID: lorawan.CID(6)}, &lorawan.MACCommand{CID: lorawan.CID(8), Payload: &lorawan.RXTimingSetupReqPayload{Delay: 3}}, }, }, }, }, }, ExpectedGetTXPayloadFromQueue: &models.TXPayload{DevEUI: ns.DevEUI, FPort: 3, Data: []byte{4, 5, 6}}, ExpectedFCntUp: 10, ExpectedFCntDown: 6, }, { Name: "unconfirmed uplink data + 18 bytes of MAC commands (FOpts) of which one is invalid", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, TXMACPayloadQueue: []models.MACPayload{ {Reference: "a", DevEUI: ns.DevEUI, MACCommand: []byte{2, 10, 5}}, {Reference: "b", DevEUI: ns.DevEUI, MACCommand: []byte{6}}, {Reference: "c", DevEUI: ns.DevEUI, MACCommand: []byte{4, 15}}, {Reference: "d", DevEUI: ns.DevEUI, MACCommand: []byte{4, 15, 16}}, // invalid payload, should be discarded + error notification {Reference: "e", DevEUI: ns.DevEUI, MACCommand: []byte{2, 10, 5}}, {Reference: "f", DevEUI: ns.DevEUI, MACCommand: []byte{2, 10, 5}}, {Reference: "g", DevEUI: ns.DevEUI, MACCommand: []byte{2, 10, 6}}, // this payload should stay in the queue }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, FRMPayload: []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}}, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedControllerErrorPayloads: []models.ErrorPayload{ {Reference: "d", DevEUI: ns.DevEUI, Message: "unmarshal mac command error: lorawan: 1 byte of data is expected"}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1, Data: []byte{1, 2, 3, 4}}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, FCtrl: lorawan.FCtrl{ FPending: true, }, FOpts: []lorawan.MACCommand{ {CID: lorawan.LinkCheckAns, Payload: &lorawan.LinkCheckAnsPayload{Margin: 10, GwCnt: 5}}, {CID: lorawan.DevStatusReq}, {CID: lorawan.DutyCycleReq, Payload: &lorawan.DutyCycleReqPayload{MaxDCCycle: 15}}, {CID: lorawan.LinkCheckAns, Payload: &lorawan.LinkCheckAnsPayload{Margin: 10, GwCnt: 5}}, {CID: lorawan.LinkCheckAns, Payload: &lorawan.LinkCheckAnsPayload{Margin: 10, GwCnt: 5}}, }, }, }, }, }, }, ExpectedTXMACPayloadQueue: []models.MACPayload{ {Reference: "g", DevEUI: ns.DevEUI, MACCommand: []byte{2, 10, 6}}, }, ExpectedFCntUp: 10, ExpectedFCntDown: 6, }, } runDataUpTests(ctx, ns.DevEUI, ns.DevAddr, tests) }) Convey("Given a set of test-scenarios for tx-payload queue", func() { var fPortTen uint8 = 10 tests := []dataUpTestCase{ { Name: "unconfirmed uplink data + one unconfirmed downlink payload in queue", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, DecryptExpectedFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, TXPayloadQueue: []models.TXPayload{ {Reference: "a", DevEUI: ns.DevEUI, FPort: 10, Data: []byte{1, 2, 3, 4}}, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, }, FPort: &fPortTen, FRMPayload: []lorawan.Payload{ &lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}, }, }, }, }, }, ExpectedFCntUp: 10, ExpectedFCntDown: 6, }, { Name: "unconfirmed uplink data + two unconfirmed downlink payloads in queue", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, DecryptExpectedFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, TXPayloadQueue: []models.TXPayload{ {Reference: "a", DevEUI: ns.DevEUI, FPort: 10, Data: []byte{1, 2, 3, 4}}, {Reference: "b", DevEUI: ns.DevEUI, FPort: 10, Data: []byte{4, 3, 2, 1}}, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, FCtrl: lorawan.FCtrl{ FPending: true, }, }, FPort: &fPortTen, FRMPayload: []lorawan.Payload{ &lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}, }, }, }, }, }, ExpectedFCntUp: 10, ExpectedFCntDown: 6, ExpectedGetTXPayloadFromQueue: &models.TXPayload{Reference: "b", DevEUI: ns.DevEUI, FPort: 10, Data: []byte{4, 3, 2, 1}}, }, { Name: "unconfirmed uplink data + one confirmed downlink payload in queue", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, DecryptExpectedFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, TXPayloadQueue: []models.TXPayload{ {Reference: "a", Confirmed: true, DevEUI: ns.DevEUI, FPort: 10, Data: []byte{1, 2, 3, 4}}, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.ConfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, }, FPort: &fPortTen, FRMPayload: []lorawan.Payload{ &lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}, }, }, }, }, }, ExpectedFCntUp: 10, ExpectedFCntDown: 5, // will be incremented after the node ACKs the frame ExpectedGetTXPayloadFromQueue: &models.TXPayload{Reference: "a", Confirmed: true, DevEUI: ns.DevEUI, FPort: 10, Data: []byte{1, 2, 3, 4}}, }, { Name: "unconfirmed uplink data with ACK + one confirmed downlink payload in in-process queue", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, DecryptExpectedFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, TXMACPayloadInProcess: &models.TXPayload{Reference: "a", Confirmed: true, DevEUI: ns.DevEUI, FPort: 10, Data: []byte{1, 2, 3, 4}}, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, FCtrl: lorawan.FCtrl{ ACK: true, }, }, FPort: &fPortOne, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1}, }, ExpectedApplicationNotifications: []interface{}{ models.ACKNotification{Reference: "a", DevEUI: ns.DevEUI}, }, ExpectedFCntUp: 10, ExpectedFCntDown: 6, // has been updated because of the ACK }, { Name: "unconfirmed uplink data + two unconfirmed downlink payload in queue of which the first exceeds the max payload size (for dr 0)", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, DecryptExpectedFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, TXPayloadQueue: []models.TXPayload{ {Reference: "a", DevEUI: ns.DevEUI, FPort: 10, Data: make([]byte, 52)}, {Reference: "b", DevEUI: ns.DevEUI, FPort: 10, Data: []byte{1, 2, 3, 4}}, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1}, }, ExpectedApplicationNotifications: []interface{}{ models.ErrorPayload{Reference: "a", DevEUI: ns.DevEUI, Message: "downlink payload max size exceeded (dr: 0, allowed: 51, got: 52)"}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, }, FPort: &fPortTen, FRMPayload: []lorawan.Payload{ &lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}, }, }, }, }, }, ExpectedFCntUp: 10, ExpectedFCntDown: 6, }, { Name: "unconfirmed uplink data + one unconfirmed downlink payload in queue (exactly max size for dr 0) + one mac command", RXInfo: rxInfo, EncryptFRMPayloadKey: ns.AppSKey, DecryptExpectedFRMPayloadKey: ns.AppSKey, SetMICKey: ns.NwkSKey, TXPayloadQueue: []models.TXPayload{ {Reference: "a", DevEUI: ns.DevEUI, FPort: 10, Data: make([]byte, 51)}, }, TXMACPayloadQueue: []models.MACPayload{ {DevEUI: ns.DevEUI, MACCommand: []byte{6}}, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 10, }, FPort: &fPortOne, }, }, ExpectedControllerRXInfoPayloads: []models.RXInfoPayload{ {DevEUI: ns.DevEUI, FCnt: 10, RXInfo: []models.RXInfo{rxInfo}}, }, ExpectedApplicationRXPayloads: []models.RXPayload{ {DevEUI: ns.DevEUI, FPort: 1, GatewayCount: 1}, }, ExpectedGatewayTXPackets: []models.TXPacket{ { TXInfo: models.TXInfo{ Timestamp: rxInfo.Timestamp + 1000000, Frequency: rxInfo.Frequency, Power: 14, DataRate: rxInfo.DataRate, }, PHYPayload: lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.MACPayload{ FHDR: lorawan.FHDR{ DevAddr: ns.DevAddr, FCnt: 5, FCtrl: lorawan.FCtrl{ FPending: true, }, FOpts: []lorawan.MACCommand{ {CID: lorawan.CID(6)}, }, }, }, }, }, }, ExpectedFCntUp: 10, ExpectedFCntDown: 6, ExpectedGetTXPayloadFromQueue: &models.TXPayload{Reference: "a", DevEUI: ns.DevEUI, FPort: 10, Data: make([]byte, 51)}, }, } runDataUpTests(ctx, ns.DevEUI, ns.DevAddr, tests) }) }) }
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 TestNodeAPI(t *testing.T) { conf := common.GetTestConfig() Convey("Given a clean database with an application and api instance", t, func() { db, err := storage.OpenDatabase(conf.PostgresDSN) So(err, ShouldBeNil) common.MustResetDB(db) p := storage.NewRedisPool(conf.RedisURL) common.MustFlushRedis(p) ctx := context.Background() lsCtx := loraserver.Context{DB: db, RedisPool: p} api := NewNodeAPI(lsCtx) app := models.Application{ AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, Name: "test app", } So(storage.CreateApplication(db, app), ShouldBeNil) Convey("When creating a node", func() { _, err := api.Create(ctx, &pb.CreateNodeRequest{ DevEUI: "0807060504030201", AppEUI: "0102030405060708", AppKey: "01020304050607080102030405060708", RxDelay: 1, Rx1DROffset: 3, }) So(err, ShouldBeNil) Convey("The node has been created", func() { node, err := api.Get(ctx, &pb.GetNodeRequest{DevEUI: "0807060504030201"}) So(err, ShouldBeNil) So(node, ShouldResemble, &pb.GetNodeResponse{ DevEUI: "0807060504030201", AppEUI: "0102030405060708", AppKey: "01020304050607080102030405060708", RxDelay: 1, Rx1DROffset: 3, }) }) Convey("Then listing the nodes returns a single items", func() { nodes, err := api.List(ctx, &pb.ListNodeRequest{ Limit: 10, }) So(err, ShouldBeNil) So(nodes.Result, ShouldHaveLength, 1) So(nodes.TotalCount, ShouldEqual, 1) So(nodes.Result[0], ShouldResemble, &pb.GetNodeResponse{ DevEUI: "0807060504030201", AppEUI: "0102030405060708", AppKey: "01020304050607080102030405060708", RxDelay: 1, Rx1DROffset: 3, }) }) Convey("Then listing the nodes for a given AppEUI returns a single item", func() { nodes, err := api.ListByAppEUI(ctx, &pb.ListNodeByAppEUIRequest{ Limit: 10, AppEUI: "0102030405060708", }) So(err, ShouldBeNil) So(nodes.Result, ShouldHaveLength, 1) So(nodes.TotalCount, ShouldEqual, 1) So(nodes.Result[0], ShouldResemble, &pb.GetNodeResponse{ DevEUI: "0807060504030201", AppEUI: "0102030405060708", AppKey: "01020304050607080102030405060708", RxDelay: 1, Rx1DROffset: 3, }) }) Convey("When updating the node", func() { _, err := api.Update(ctx, &pb.UpdateNodeRequest{ DevEUI: "0807060504030201", AppEUI: "0102030405060708", AppKey: "08070605040302010807060504030201", RxDelay: 3, Rx1DROffset: 1, }) So(err, ShouldBeNil) Convey("Then the node has been updated", func() { node, err := api.Get(ctx, &pb.GetNodeRequest{DevEUI: "0807060504030201"}) So(err, ShouldBeNil) So(node, ShouldResemble, &pb.GetNodeResponse{ DevEUI: "0807060504030201", AppEUI: "0102030405060708", AppKey: "08070605040302010807060504030201", RxDelay: 3, Rx1DROffset: 1, }) }) }) Convey("After deleting the node", func() { _, err := api.Delete(ctx, &pb.DeleteNodeRequest{DevEUI: "0807060504030201"}) So(err, ShouldBeNil) Convey("Then listing the nodes returns zero nodes", func() { nodes, err := api.List(ctx, &pb.ListNodeRequest{Limit: 10}) So(err, ShouldBeNil) So(nodes.TotalCount, ShouldEqual, 0) So(nodes.Result, ShouldHaveLength, 0) }) }) Convey("Given a tx payload in the the queue", func() { So(storage.AddTXPayloadToQueue(p, models.TXPayload{ DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, Data: []byte("hello!"), }), ShouldBeNil) count, err := storage.GetTXPayloadQueueSize(p, [8]byte{8, 7, 6, 5, 4, 3, 2, 1}) So(err, ShouldBeNil) So(count, ShouldEqual, 1) Convey("When flushing the tx-payload queue", func() { _, err := api.FlushTXPayloadQueue(ctx, &pb.FlushTXPayloadQueueRequest{DevEUI: "0807060504030201"}) So(err, ShouldBeNil) Convey("Then the queue is empty", func() { count, err := storage.GetTXPayloadQueueSize(p, [8]byte{8, 7, 6, 5, 4, 3, 2, 1}) So(err, ShouldBeNil) So(count, ShouldEqual, 0) }) }) }) }) }) }
func TestNodeSession(t *testing.T) { conf := common.GetTestConfig() Convey("Given a clean Redis database", t, func() { p := NewRedisPool(conf.RedisURL) common.MustFlushRedis(p) Convey("Given a NodeSession", func() { ns := models.NodeSession{ DevAddr: [4]byte{1, 2, 3, 4}, DevEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, } Convey("When getting a non-existing NodeSession", func() { _, err := GetNodeSession(p, ns.DevAddr) Convey("Then an error is returned", func() { So(err, ShouldResemble, errors.New("get node-session for DevAddr 01020304 error: redigo: nil returned")) }) }) Convey("When saving the NodeSession", func() { So(SaveNodeSession(p, ns), ShouldBeNil) Convey("Then when getting the NodeSession, the same data is returned", func() { ns2, err := GetNodeSession(p, ns.DevAddr) So(err, ShouldBeNil) So(ns2, ShouldResemble, ns) }) Convey("Then the session can be retrieved by it's DevEUI", func() { ns2, err := GetNodeSessionByDevEUI(p, ns.DevEUI) So(err, ShouldBeNil) So(ns2, ShouldResemble, ns) }) }) Convey("When calling validateAndGetFullFCntUp", func() { testTable := []struct { ServerFCnt uint32 NodeFCnt uint32 FullFCnt uint32 Valid bool }{ {0, 1, 1, true}, // ideal case counter was incremented {1, 1, 1, true}, // re-transmission {2, 1, 0, false}, // old packet received {0, common.Band.MaxFCntGap, 0, false}, // gap should be less than MaxFCntGap {0, common.Band.MaxFCntGap - 1, common.Band.MaxFCntGap - 1, true}, // gap is exactly within the allowed MaxFCntGap {65536, common.Band.MaxFCntGap - 1, common.Band.MaxFCntGap - 1 + 65536, true}, // roll-over happened, gap ix exactly within allowed MaxFCntGap {65535, common.Band.MaxFCntGap, 0, false}, // roll-over happened, but too many lost frames {65535, 0, 65536, true}, // roll-over happened {65536, 0, 65536, true}, // re-transmission {4294967295, 0, 0, true}, // 32 bit roll-over happened, counter started at 0 again } for _, test := range testTable { Convey(fmt.Sprintf("Then when FCntUp=%d, ValidateAndGetFullFCntUp(%d) should return (%d, %t)", test.ServerFCnt, test.NodeFCnt, test.FullFCnt, test.Valid), func() { ns.FCntUp = test.ServerFCnt fullFCntUp, ok := ValidateAndGetFullFCntUp(ns, test.NodeFCnt) So(ok, ShouldEqual, test.Valid) So(fullFCntUp, ShouldEqual, test.FullFCnt) }) } }) }) }) }