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})
					})
				})
			})
		})

	})
}
Example #3
0
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)
						})
					})
				})
			})
		})
	})
}
Example #7
0
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)
					})
				}
			})

		})
	})
}