public async Task When_Api_Returns_DevAlreadyUsed_Should_Return_Null()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: null));
            var joinRequest     = simulatedDevice.CreateJoinRequest();

            var devEui = simulatedDevice.LoRaDevice.DevEui;

            // Lora device api will be search by devices with matching deveui,
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequest.DevNonce))
            .ReturnsAsync(new SearchDevicesResult()
            {
                IsDevNonceAlreadyUsed = true
            });

            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), joinRequest);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.Null(request.ResponseDownlink);

            LoRaDeviceApi.VerifyAll();
            LoRaDeviceClient.VerifyAll();
        }
示例#2
0
        public async Task After_Sending_Class_C_Downstream_Should_Disconnect_Client()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1,
                                                                                     gatewayID: ServerConfiguration.GatewayID,
                                                                                     deviceClassType: 'c'));
            var devEui = simulatedDevice.DevEUI;

            // will disconnected client
            using var disconnectedEvent = new SemaphoreSlim(0, 1);
            LoRaDeviceClient.Setup(x => x.Disconnect())
            .Callback(() =>
            {
                disconnectedEvent.Release();
            })
            .Returns(true);

            // will check client connection
            LoRaDeviceClient.Setup(x => x.EnsureConnected())
            .Returns(true);

            // will save twin
            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true);

            var c2dToDeviceMessage = new ReceivedLoRaCloudToDeviceMessage()
            {
                Payload   = "hello",
                DevEUI    = devEui,
                Fport     = FramePorts.App10,
                MessageId = Guid.NewGuid().ToString(),
            };

            var cachedDevice = CreateLoRaDevice(simulatedDevice);

            cachedDevice.KeepAliveTimeout = 3;
            cachedDevice.LoRaRegion       = LoRaRegionType.EU868;
            cachedDevice.InternalAcceptChanges();
            cachedDevice.SetFcntDown(cachedDevice.FCntDown + Constants.MaxFcntUnsavedDelta - 1);
            cachedDevice.SetLastProcessingStationEui(new StationEui(ulong.MaxValue));

            using var cache           = EmptyMemoryCache();
            using var loraDeviceCache = CreateDeviceCache(cachedDevice);
            using var deviceRegistry  = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache);

            var target = new DefaultClassCDevicesMessageSender(
                ServerConfiguration,
                deviceRegistry,
                DownstreamMessageSender,
                FrameCounterUpdateStrategyProvider,
                new TestOutputLogger <DefaultClassCDevicesMessageSender>(testOutputHelper),
                TestMeter.Instance);

            Assert.True(await target.SendAsync(c2dToDeviceMessage));
            Assert.Single(DownstreamMessageSender.DownlinkMessages);

            await EnsureDisconnectedAsync(disconnectedEvent);

            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
        public async Task When_No_Decoder_Is_Defined_Sends_Raw_Payload(string deviceGatewayID, string msgPayload, string sensorDecoder)
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: deviceGatewayID));
            var loRaDevice      = CreateLoRaDevice(simulatedDevice);

            loRaDevice.SensorDecoder = sensorDecoder;

            // message will be sent
            LoRaDeviceTelemetry loRaDeviceTelemetry = null;

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .Callback <LoRaDeviceTelemetry, Dictionary <string, string> >((t, _) => loRaDeviceTelemetry = t)
            .ReturnsAsync(true);

            // C2D message will be checked
            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            if (string.IsNullOrEmpty(deviceGatewayID))
            {
                // multi GW will reset
                LoRaDeviceApi.Setup(x => x.ABPFcntCacheResetAsync(It.IsNotNull <DevEui>(), It.IsAny <uint>(), It.IsNotNull <string>()))
                .ReturnsAsync(true);
            }

            using var cache           = EmptyMemoryCache();
            using var loraDeviceCache = CreateDeviceCache(loRaDevice);
            using var deviceRegistry  = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache);

            // Send to message processor
            using var messageDispatcher = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // sends unconfirmed message
            var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage(msgPayload, fcnt: 1);

            using var request = CreateWaitableRequest(unconfirmedMessagePayload);
            messageDispatcher.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.Null(request.ResponseDownlink);

            Assert.NotNull(loRaDeviceTelemetry);
            Assert.IsType <UndecodedPayload>(loRaDeviceTelemetry.Data);
            var undecodedPayload = (UndecodedPayload)loRaDeviceTelemetry.Data;
            var rawPayload       = Convert.ToBase64String(Encoding.UTF8.GetBytes(msgPayload));

            Assert.Equal(rawPayload, loRaDeviceTelemetry.Rawdata);
            Assert.Equal(rawPayload, undecodedPayload.Value);

            // Validate json
            var actualJsonTelemetry   = JsonConvert.SerializeObject(loRaDeviceTelemetry, Formatting.None);
            var expectedTelemetryJson = $"{{\"time\":100000,\"tmms\":100000,\"freq\":868.3,\"chan\":2,\"rfch\":1,\"modu\":\"LoRa\",\"datr\":\"SF10BW125\",\"rssi\":2.0,\"lsnr\":0.1,\"data\":{{\"value\":\"{rawPayload}\"}},\"port\":1,\"fcnt\":1,\"edgets\":{loRaDeviceTelemetry.Edgets},\"rawdata\":\"{rawPayload}\",\"eui\":\"0000000000000001\",\"gatewayid\":\"test-gateway\",\"stationeui\":\"0000000000000000\"}}";

            Assert.Equal(expectedTelemetryJson, actualJsonTelemetry);

            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
示例#4
0
        public async Task When_Device_Is_Loaded_Should_Disconnect_After_Sending_Data()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerGatewayID));

            // will search for the device by devAddr
            LoRaDeviceApi.Setup(x => x.SearchByDevAddrAsync(simulatedDevice.DevAddr.Value))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(simulatedDevice.DevAddr, simulatedDevice.DevEUI, "ada").AsList()));

            // will read the device twins
            var twin = simulatedDevice.CreateABPTwin(desiredProperties: new Dictionary <string, object>
            {
                { TwinProperty.KeepAliveTimeout, 3 }
            });

            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None))
            .ReturnsAsync(twin);

            // message will be sent
            LoRaDeviceTelemetry loRaDeviceTelemetry = null;

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .Callback <LoRaDeviceTelemetry, Dictionary <string, string> >((t, _) => loRaDeviceTelemetry = t)
            .ReturnsAsync(true);

            // C2D message will be checked
            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            // will check client connection
            LoRaDeviceClient.Setup(x => x.EnsureConnected())
            .Returns(true);

            // will disconnected client
            using var disconnectedEvent = new SemaphoreSlim(0, 1);
            LoRaDeviceClient.Setup(x => x.Disconnect())
            .Callback(() => disconnectedEvent.Release())
            .Returns(true);

            using var cache          = NewMemoryCache();
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            using var messageDispatcher = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // sends unconfirmed message
            var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("hello");

            using var request = CreateWaitableRequest(unconfirmedMessagePayload);
            messageDispatcher.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.True(request.ProcessingSucceeded);

            await EnsureDisconnectedAsync(disconnectedEvent, (int)TimeSpan.FromSeconds(Constants.MinKeepAliveTimeout * 2).TotalMilliseconds);

            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
示例#5
0
        public async Task When_Fcnt_Down_Fails_Should_Stop_And_Not_Update_Device_Twin(uint initialFcntDown, uint initialFcntUp, uint payloadFcnt)
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: null))
            {
                FrmCntDown = initialFcntDown,
                FrmCntUp   = initialFcntUp
            };

            var devEui  = simulatedDevice.LoRaDevice.DevEui;
            var devAddr = simulatedDevice.LoRaDevice.DevAddr;

            LoRaDeviceApi
            .Setup(x => x.ExecuteFunctionBundlerAsync(devEui, It.IsAny <FunctionBundlerRequest>()))
            .ReturnsAsync(() =>
                          new FunctionBundlerResult
            {
                AdrResult = new LoRaTools.ADR.LoRaADRResult {
                    CanConfirmToDevice = true, FCntDown = 0
                },
                NextFCntDown = 0
            });

            LoRaDeviceApi.Setup(x => x.ABPFcntCacheResetAsync(devEui, It.IsAny <uint>(), It.IsNotNull <string>()))
            .ReturnsAsync(true);

            using var memoryCache = new MemoryCache(new MemoryCacheOptions());
            var device = CreateLoRaDevice(simulatedDevice);

            DeviceCache.Register(device);
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageDispatcher = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // sends unconfirmed message
            var unconfirmedMessagePayload = simulatedDevice.CreateConfirmedDataUpMessage("hello", fcnt: payloadFcnt);

            using var request = CreateWaitableRequest(unconfirmedMessagePayload);
            messageDispatcher.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.Null(request.ResponseDownlink);

            // verify that the device in device registry has correct properties and frame counters
            Assert.True(DeviceCache.TryGetForPayload(request.Payload, out var loRaDevice));
            Assert.Equal(devAddr, loRaDevice.DevAddr);
            Assert.Equal(devEui, loRaDevice.DevEUI);
            Assert.True(loRaDevice.IsABP);
            Assert.Equal(initialFcntUp, loRaDevice.FCntUp);
            Assert.Equal(initialFcntDown, loRaDevice.FCntDown);
            Assert.False(loRaDevice.HasFrameCountChanges);

            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
        public async Task OTAA_Unconfirmed_Message_With_Fcnt_Change_Of_10_Should_Send_Data_To_IotHub_Update_FcntUp_And_Return_Null()
        {
            const uint PayloadFcnt           = 19;
            const uint InitialDeviceFcntUp   = 9;
            const uint InitialDeviceFcntDown = 20;

            var simulatedDevice = new SimulatedDevice(
                TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID),
                frmCntUp: InitialDeviceFcntUp,
                frmCntDown: InitialDeviceFcntDown);

            var loraDevice = CreateLoRaDevice(simulatedDevice);

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsAny <TimeSpan>()))
            .ReturnsAsync((Message)null);

            using var cache           = EmptyMemoryCache();
            using var loraDeviceCache = CreateDeviceCache(loraDevice);
            using var deviceRegistry  = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: PayloadFcnt);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), payload);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());

            // Expectations
            // 1. Message was sent to IoT Hub
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();

            // 2. Return nothing
            Assert.Null(request.ResponseDownlink);
            Assert.True(request.ProcessingSucceeded);

            // 4. Frame counter up was updated
            Assert.Equal(PayloadFcnt, loraDevice.FCntUp);

            // 5. Frame counter down is not changed
            Assert.Equal(InitialDeviceFcntDown, loraDevice.FCntDown);

            // 6. Frame count has no pending changes
            Assert.False(loraDevice.HasFrameCountChanges);
        }
示例#7
0
        public async Task After_ClassA_Sends_Data_Should_Disconnect()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1))
            {
                FrmCntUp = 10
            };

            // message will be sent
            LoRaDeviceTelemetry loRaDeviceTelemetry = null;

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .Callback <LoRaDeviceTelemetry, Dictionary <string, string> >((t, _) => loRaDeviceTelemetry = t)
            .ReturnsAsync(true);

            // C2D message will be checked
            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            // will check client connection
            LoRaDeviceClient.Setup(x => x.EnsureConnected())
            .Returns(true);

            // will disconnected client
            using var disconnectedEvent = new SemaphoreSlim(0, 1);
            LoRaDeviceClient.Setup(x => x.Disconnect())
            .Callback(() => disconnectedEvent.Release())
            .Returns(true);

            var cachedDevice = CreateLoRaDevice(simulatedDevice);

            cachedDevice.KeepAliveTimeout = 3;

            using var cache           = EmptyMemoryCache();
            using var loraDeviceCache = CreateDeviceCache(cachedDevice);
            using var deviceRegistry  = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache);

            using var messageDispatcher = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // sends unconfirmed message
            var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("hello");

            using var request = CreateWaitableRequest(unconfirmedMessagePayload);
            messageDispatcher.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.True(request.ProcessingSucceeded);

            await EnsureDisconnectedAsync(disconnectedEvent);

            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
示例#8
0
        public async Task OTAA_Unconfirmed_With_Cloud_To_Device_Mac_Command_Fails_Due_To_Wrong_Setup(string mac, int?fport)
        {
            const int PayloadFcnt           = 20;
            const int InitialDeviceFcntUp   = 9;
            const int InitialDeviceFcntDown = 20;

            var simulatedDevice = new SimulatedDevice(
                TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID),
                frmCntUp: InitialDeviceFcntUp,
                frmCntDown: InitialDeviceFcntDown);

            var loraDevice = CreateLoRaDevice(simulatedDevice);

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true);

            var c2dJson = $"{{\"fport\":{fport}, \"payload\":\"asd\", \"macCommands\": [ {{ \"cid\": \"{mac}\" }}] }}";

            using var cloudToDeviceMessage = new Message(Encoding.UTF8.GetBytes(c2dJson));

            LoRaDeviceClient.SetupSequence(x => x.ReceiveAsync(It.IsAny <TimeSpan>()))
            .ReturnsAsync(cloudToDeviceMessage)
            .ReturnsAsync((Message)null);     // 2nd cloud to device message does not return anything

            LoRaDeviceClient.Setup(x => x.RejectAsync(cloudToDeviceMessage))
            .ReturnsAsync(true);

            using var cache           = EmptyMemoryCache();
            using var loraDeviceCache = CreateDeviceCache(loraDevice);
            using var deviceRegistry  = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: PayloadFcnt);

            using var request = CreateWaitableRequest(payload, constantElapsedTime: TimeSpan.Zero);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());

            // Expectations
            // 1. Message was sent to IoT Hub
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();

            // 2. DownStream Message should be null as the processing should fail
            Assert.Null(request.ResponseDownlink);
        }
        public async Task OTAA_Confirmed_Message_Should_Send_Data_To_IotHub_Update_FcntUp_And_Return_DownstreamMessage()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID));
            var payload         = simulatedDevice.CreateConfirmedDataUpMessage("1234");

            var loraDevice = CreateLoRaDevice(simulatedDevice);

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true);

            using var cache           = EmptyMemoryCache();
            using var loraDeviceCache = CreateDeviceCache(loraDevice);
            using var deviceRegistry  = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), payload);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());

            // Expectations
            // 1. Message was sent to IoT Hub
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();

            // 3. Return is downstream message
            Assert.NotNull(request.ResponseDownlink);
            Assert.True(request.ProcessingSucceeded);
            Assert.Single(DownstreamMessageSender.DownlinkMessages);
            var downlinkMessage = DownstreamMessageSender.DownlinkMessages.First();
            var payloadDataDown = new LoRaPayloadData(downlinkMessage.Data);

            Assert.Equal(payloadDataDown.DevAddr, loraDevice.DevAddr);
            Assert.False(payloadDataDown.IsConfirmed);
            Assert.Equal(MacMessageType.UnconfirmedDataDown, payloadDataDown.MessageType);

            // 4. Frame counter up was updated
            Assert.Equal(1U, loraDevice.FCntUp);

            // 5. Frame counter down was incremented
            Assert.Equal(1U, loraDevice.FCntDown);
            Assert.Equal(1, payloadDataDown.Fcnt);
        }
        public async Task OTAA_Join_Should_Use_Rchf_0(string deviceGatewayID, uint rfch)
        {
            var simulatedDevice    = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequestPayload = simulatedDevice.CreateJoinRequest();

            var devAddr = (DevAddr?)null;
            var devEui  = simulatedDevice.LoRaDevice.DevEui;

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEui.ToString();
            twin.Properties.Desired[TwinProperty.AppEui]        = simulatedDevice.LoRaDevice.AppEui?.ToString();
            twin.Properties.Desired[TwinProperty.AppKey]        = simulatedDevice.LoRaDevice.AppKey?.ToString();
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)).ReturnsAsync(twin);

            // Device twin will be updated
            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui,
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            // using factory to create mock of

            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);
            var radio = TestUtils.GenerateTestRadioMetadata(antennaPreference: rfch);

            using var joinRequest = CreateWaitableRequest(radio, joinRequestPayload);
            messageProcessor.DispatchRequest(joinRequest);
            Assert.True(await joinRequest.WaitCompleteAsync());
            Assert.True(joinRequest.ProcessingSucceeded);
            Assert.NotNull(joinRequest.ResponseDownlink);
            Assert.Single(DownstreamMessageSender.DownlinkMessages);
            var downlinkJoinAcceptMessage = DownstreamMessageSender.DownlinkMessages[0];

            // validates txpk according to eu region
            Assert.True(RegionManager.EU868.TryGetDownstreamChannelFrequency(radio.Frequency, joinRequest.RadioMetadata.DataRate, deviceJoinInfo: null, downstreamFrequency: out var receivedFrequency));
            Assert.Equal(receivedFrequency, downlinkJoinAcceptMessage.Rx1?.Frequency);

            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
        public async Task When_New_ABP_Device_Instance_Is_Created_Should_Increment_FCntDown()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerGatewayID));

            var iotHubDeviceInfo = new IoTHubDeviceInfo(simulatedDevice.LoRaDevice.DevAddr, simulatedDevice.LoRaDevice.DevEui, "pk");

            LoRaDeviceApi.Setup(x => x.SearchByDevAddrAsync(It.IsAny <DevAddr>()))
            .ReturnsAsync(new SearchDevicesResult(iotHubDeviceInfo.AsList()));

            // device will:
            // - be initialized
            // - send event
            // - receive c2d
            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None))
            .ReturnsAsync(simulatedDevice.CreateABPTwin());
            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);
            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            using var cache          = NewMemoryCache();
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("2", fcnt: 2);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), payload, constantElapsedTime: TimeSpan.FromMilliseconds(300));
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.True(request.ProcessingSucceeded);

            // Wait until loader updates the device cache
            await Task.Delay(50);

            Assert.Equal(1, DeviceCache.RegistrationCount(simulatedDevice.DevAddr.Value));
            Assert.True(DeviceCache.TryGetForPayload(request.Payload, out var cachedDevice));
            Assert.True(cachedDevice.IsOurDevice);
            Assert.Equal(Constants.MaxFcntUnsavedDelta - 1U, cachedDevice.FCntDown);
            Assert.Equal(payload.Fcnt, (ushort)cachedDevice.FCntUp);

            // Device was searched by DevAddr
            LoRaDeviceApi.VerifyAll();

            // Device was created by factory
            LoRaDeviceClient.VerifyAll();
        }
        public async Task When_ABP_Device_With_Relaxed_FrameCounter_Has_FCntUP_Zero_Or_One_Should_Reset_Counter_And_Process_Message(uint payloadFCnt)
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID));

            // generate payload with frame count 0 or 1
            var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFCnt);

            simulatedDevice.FrmCntDown = 0;
            simulatedDevice.FrmCntUp   = 10;


            var loraDevice = CreateLoRaDevice(simulatedDevice);

            // Will send the event to IoT Hub
            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);

            // will try to get C2D message
            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsAny <TimeSpan>())).ReturnsAsync((Message)null);

            // Will save the fcnt up/down to zero
            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.Is <TwinCollection>((t) => IsTwinFcntZero(t)), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true);

            using var cache           = EmptyMemoryCache();
            using var loraDeviceCache = CreateDeviceCache(loraDevice);
            using var deviceRegistry  = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), payload);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());

            // Expectations
            // 1. Message was sent to IoT Hub
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();

            // 3. Return is null (there is nothing to send downstream)
            Assert.Null(request.ResponseDownlink);
            Assert.True(request.ProcessingSucceeded);

            // 4. Frame counter up was updated
            Assert.Equal(payloadFCnt, loraDevice.FCntUp);
        }
示例#13
0
        public async Task When_Device_Is_Assigned_To_Another_Gateway_Cache_Locally_And_Return_Null()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: "another-gateway"));

            var apiService       = new Mock <LoRaDeviceAPIServiceBase>(MockBehavior.Strict);
            var iotHubDeviceInfo = new IoTHubDeviceInfo(simulatedDevice.LoRaDevice.DevAddr, simulatedDevice.LoRaDevice.DevEui, "pk")
            {
                GatewayId = "another-gateway",
                NwkSKey   = simulatedDevice.NwkSKey.Value
            };

            apiService.Setup(x => x.SearchByDevAddrAsync(It.IsAny <DevAddr>()))
            .ReturnsAsync(new SearchDevicesResult(iotHubDeviceInfo.AsList()));

            var deviceFactory = new TestLoRaDeviceFactory(LoRaDeviceClient.Object, DeviceCache, ConnectionManager);

            using var target = new LoRaDeviceRegistry(ServerConfiguration, this.cache, apiService.Object, deviceFactory, DeviceCache);

            // request #1
            var payload1 = simulatedDevice.CreateUnconfirmedDataUpMessage("1", fcnt: 11);

            using var request1 = WaitableLoRaRequest.Create(payload1);
            target.GetLoRaRequestQueue(request1).Queue(request1);
            Assert.True(await request1.WaitCompleteAsync());
            Assert.True(request1.ProcessingFailed);
            Assert.Equal(LoRaDeviceRequestFailedReason.BelongsToAnotherGateway, request1.ProcessingFailedReason);

            // request #2
            var payload2 = simulatedDevice.CreateUnconfirmedDataUpMessage("2", fcnt: 12);

            using var request2 = WaitableLoRaRequest.Create(payload2);
            target.GetLoRaRequestQueue(request2).Queue(request2);
            Assert.True(await request2.WaitCompleteAsync());
            Assert.True(request2.ProcessingFailed);
            Assert.Equal(LoRaDeviceRequestFailedReason.BelongsToAnotherGateway, request2.ProcessingFailedReason);

            // Device was searched by DevAddr
            apiService.VerifyAll();
            apiService.Verify(x => x.SearchByDevAddrAsync(It.IsAny <DevAddr>()), Times.Once());

            // Device should not be connected
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceClient.Verify(x => x.GetTwinAsync(CancellationToken.None), Times.Never());
            LoRaDeviceClient.Verify(x => x.Disconnect(), Times.Never());

            // device is in cache
            Assert.True(DeviceCache.TryGetForPayload(request1.Payload, out var loRaDevice));
            Assert.False(loRaDevice.IsOurDevice);
        }
        public async Task Join_Device_Has_Mismatching_AppKey_Should_Return_Null(string deviceGatewayID)
        {
            var simulatedDevice    = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequestPayload = simulatedDevice.CreateJoinRequest();

            var devAddr = (DevAddr?)null;
            var devEui  = simulatedDevice.LoRaDevice.DevEui;

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEui.ToString();
            twin.Properties.Desired[TwinProperty.AppEui]        = simulatedDevice.LoRaDevice.AppEui?.ToString();
            twin.Properties.Desired[TwinProperty.AppKey]        = "012345678901234567890123456789FF";
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)).ReturnsAsync(twin);

            // Lora device api will be search by devices with matching deveui,
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            // using factory to create mock of
            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // join request should fail
            using var joinRequest = CreateWaitableRequest(joinRequestPayload);
            messageProcessor.DispatchRequest(joinRequest);
            Assert.True(await joinRequest.WaitCompleteAsync());
            Assert.True(joinRequest.ProcessingFailed);
            Assert.Null(joinRequest.ResponseDownlink);

            LoRaDeviceClient.Verify(x => x.UpdateReportedPropertiesAsync(It.IsAny <TwinCollection>(), It.IsAny <CancellationToken>()), Times.Never);
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
        public async Task When_Device_AppEUI_Does_Not_Match_Should_Return_Null()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: ServerConfiguration.GatewayID));
            var joinRequest     = simulatedDevice.CreateJoinRequest();

            var devEui = simulatedDevice.LoRaDevice.DevEui;

            simulatedDevice.LoRaDevice.AppEui = new JoinEui(0xFFFFFFFFFFFFFFFF);

            using var connectionManager = new SingleDeviceConnectionManager(LoRaDeviceClient.Object);
            using var loRaDevice        = TestUtils.CreateFromSimulatedDevice(simulatedDevice, connectionManager);
            loRaDevice.SetFcntDown(10);
            loRaDevice.SetFcntUp(20);

            var loRaDeviceRegistryMock = new Mock <ILoRaDeviceRegistry>(MockBehavior.Strict);

            loRaDeviceRegistryMock.Setup(x => x.RegisterDeviceInitializer(It.IsNotNull <ILoRaDeviceInitializer>()));
            loRaDeviceRegistryMock.Setup(x => x.GetDeviceForJoinRequestAsync(devEui, joinRequest.DevNonce))
            .ReturnsAsync(() => loRaDevice);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      loRaDeviceRegistryMock.Object,
                      FrameCounterUpdateStrategyProvider);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), joinRequest);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.Null(request.ResponseDownlink);

            // Device frame counts did not changed
            Assert.Equal(10U, loRaDevice.FCntDown);
            Assert.Equal(20U, loRaDevice.FCntUp);

            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
            loRaDeviceRegistryMock.VerifyAll();

            loRaDeviceRegistryMock.Setup(dr => dr.Dispose());
            LoRaDeviceClient.Setup(ldc => ldc.Dispose());
        }
        public async Task When_Mic_Check_Fails_Join_Process_Should_Fail()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: ServerConfiguration.GatewayID));
            var wrongAppKey     = TestKeys.CreateAppKey(0x3000000, 0x30000);

            var joinRequest = simulatedDevice.CreateJoinRequest(wrongAppKey);

            using var loRaDevice = CreateLoRaDevice(simulatedDevice);
            loRaDevice.SetFcntDown(10);
            loRaDevice.SetFcntUp(20);

            var devEui = simulatedDevice.LoRaDevice.DevEui;

            var loRaDeviceRegistryMock = new Mock <ILoRaDeviceRegistry>(MockBehavior.Strict);

            loRaDeviceRegistryMock.Setup(x => x.RegisterDeviceInitializer(It.IsNotNull <ILoRaDeviceInitializer>()));
            loRaDeviceRegistryMock.Setup(x => x.GetDeviceForJoinRequestAsync(devEui, joinRequest.DevNonce))
            .ReturnsAsync(() => loRaDevice);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      loRaDeviceRegistryMock.Object,
                      FrameCounterUpdateStrategyProvider);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), joinRequest);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.Null(request.ResponseDownlink);

            // Device frame counts were not modified
            Assert.Equal(10U, loRaDevice.FCntDown);
            Assert.Equal(20U, loRaDevice.FCntUp);

            // Twin property were updated
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
            loRaDeviceRegistryMock.VerifyAll();

            loRaDeviceRegistryMock.Setup(dr => dr.Dispose());
            LoRaDeviceClient.Setup(ldc => ldc.Dispose());
        }
示例#17
0
        public async Task When_Device_With_Downlink_Disabled_Received_Unconfirmed_Data_Should_Not_Check_For_C2D(string deviceGatewayID)
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: deviceGatewayID));

            // message will be sent
            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);
            if (string.IsNullOrEmpty(deviceGatewayID))
            {
                LoRaDeviceApi.Setup(x => x.ABPFcntCacheResetAsync(It.IsNotNull <DevEui>(), It.IsAny <uint>(), It.IsNotNull <string>()))
                .ReturnsAsync(true);
            }

            using var memoryCache = new MemoryCache(new MemoryCacheOptions());
            var cachedDevice = CreateLoRaDevice(simulatedDevice);

            cachedDevice.DownlinkEnabled = false;

            DeviceCache.Register(cachedDevice);

            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // sends unconfirmed message
            var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234");

            using var request = CreateWaitableRequest(unconfirmedMessagePayload);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.Null(request.ResponseDownlink);
            Assert.True(request.ProcessingSucceeded);

            LoRaDeviceClient.Verify(x => x.ReceiveAsync(It.IsAny <TimeSpan>()), Times.Never());

            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
        public async Task When_Api_Takes_Too_Long_Should_Return_Null()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: ServerConfiguration.GatewayID));
            var joinRequest     = simulatedDevice.CreateJoinRequest();

            using var loRaDevice = CreateLoRaDevice(simulatedDevice);
            loRaDevice.SetFcntDown(10);
            loRaDevice.SetFcntUp(20);

            var devEui = simulatedDevice.LoRaDevice.DevEui;

            var loRaDeviceRegistryMock = new Mock <ILoRaDeviceRegistry>(MockBehavior.Loose);

            loRaDeviceRegistryMock.Setup(x => x.RegisterDeviceInitializer(It.IsNotNull <ILoRaDeviceInitializer>()));
            loRaDeviceRegistryMock.Setup(x => x.GetDeviceForJoinRequestAsync(devEui, joinRequest.DevNonce))
            .ReturnsAsync(loRaDevice);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      loRaDeviceRegistryMock.Object,
                      FrameCounterUpdateStrategyProvider);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), joinRequest, constantElapsedTime: TimeSpan.FromSeconds(7));
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.Null(request.ResponseDownlink);
            Assert.Empty(DownstreamMessageSender.DownlinkMessages);
            Assert.Equal(LoRaDeviceRequestFailedReason.ReceiveWindowMissed, request.ProcessingFailedReason);

            // Device frame counts were not modified
            Assert.Equal(10U, loRaDevice.FCntDown);
            Assert.Equal(20U, loRaDevice.FCntUp);

            // Twin property were updated
            LoRaDeviceClient.VerifyAll();
            loRaDeviceRegistryMock.VerifyAll();
            LoRaDeviceApi.VerifyAll();

            LoRaDeviceClient.Setup(ldc => ldc.Dispose());
        }
        public async Task ABP_Unconfirmed_Message_Should_Send_Data_To_IotHub_Update_FcntUp_And_Return_Null()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID));
            var payload         = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: 10);

            simulatedDevice.FrmCntUp = 9;

            var loraDevice = CreateLoRaDevice(simulatedDevice);

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            using var cache           = EmptyMemoryCache();
            using var loraDeviceCache = CreateDeviceCache(loraDevice);
            using var deviceRegistry  = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), payload);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());

            // Expectations
            // 1. Message was sent to IoT Hub
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
            Assert.True(request.ProcessingSucceeded);

            // 2. Return is null (there is nothing to send downstream)
            Assert.Null(request.ResponseDownlink);

            // 3. Frame counter up was updated
            Assert.Equal(10U, loraDevice.FCntUp);
        }
        public async Task When_Processing_Data_Request_Should_Compute_Preferred_Gateway_And_Region(
            [CombinatorialValues(null, ServerGatewayID)] string deviceGatewayID,
            [CombinatorialValues(null, ServerGatewayID, "another-gateway")] string initialPreferredGatewayID,
            [CombinatorialValues(ServerGatewayID, "another-gateway")] string preferredGatewayID,
            [CombinatorialValues(null, LoRaRegionType.EU868, LoRaRegionType.US915)] LoRaRegionType?initialLoRaRegion)
        {
            const uint PayloadFcnt           = 10;
            const uint InitialDeviceFcntUp   = 9;
            const uint InitialDeviceFcntDown = 20;

            var simulatedDevice = new SimulatedDevice(
                TestDeviceInfo.CreateABPDevice(1, gatewayID: deviceGatewayID, deviceClassType: 'c'),
                frmCntUp: InitialDeviceFcntUp,
                frmCntDown: InitialDeviceFcntDown);

            var loraDevice = CreateLoRaDevice(simulatedDevice);

            loraDevice.UpdatePreferredGatewayID(initialPreferredGatewayID, acceptChanges: true);
            if (initialLoRaRegion.HasValue)
            {
                loraDevice.UpdateRegion(initialLoRaRegion.Value, acceptChanges: true);
            }

            var shouldSavePreferredGateway = string.IsNullOrEmpty(deviceGatewayID) && initialPreferredGatewayID != preferredGatewayID && preferredGatewayID == ServerGatewayID;
            var shouldSaveRegion           = (!initialLoRaRegion.HasValue || initialLoRaRegion.Value != LoRaRegionType.EU868) && (preferredGatewayID == ServerGatewayID || deviceGatewayID != null);

            var bundlerResult = new FunctionBundlerResult()
            {
                PreferredGatewayResult = new PreferredGatewayResult()
                {
                    DevEUI             = simulatedDevice.DevEUI,
                    PreferredGatewayID = preferredGatewayID,
                    CurrentFcntUp      = PayloadFcnt,
                    RequestFcntUp      = PayloadFcnt,
                }
            };

            if (string.IsNullOrEmpty(deviceGatewayID))
            {
                LoRaDeviceApi.Setup(x =>
                                    x.ExecuteFunctionBundlerAsync(simulatedDevice.DevEUI,
                                                                  It.Is((FunctionBundlerRequest r) => r.ClientFCntUp == PayloadFcnt &&
                                                                        r.GatewayId == ServerGatewayID &&
                                                                        r.FunctionItems == FunctionBundlerItemType.PreferredGateway)))
                .ReturnsAsync(bundlerResult);
            }

            TwinCollection actualSavedTwin = null;

            if (shouldSavePreferredGateway || shouldSaveRegion)
            {
                LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
                .ReturnsAsync(true)
                .Callback((TwinCollection t, CancellationToken _) => actualSavedTwin = t);
            }

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsAny <TimeSpan>()))
            .ReturnsAsync((Message)null);

            using var cache           = EmptyMemoryCache();
            using var loraDeviceCache = CreateDeviceCache(loraDevice);
            using var deviceRegistry  = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: PayloadFcnt);

            using var request = CreateWaitableRequest(payload, constantElapsedTime: TimeSpan.Zero);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());

            // Expectations
            // 1. Message was sent to IoT Hub
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();

            // 2. No downstream message for the current device is sent
            Assert.Null(request.ResponseDownlink);
            Assert.True(request.ProcessingSucceeded);

            if (!string.IsNullOrEmpty(deviceGatewayID))
            {
                Assert.Equal(initialPreferredGatewayID, loraDevice.PreferredGatewayID);
            }
            else
            {
                Assert.Equal(preferredGatewayID, loraDevice.PreferredGatewayID);
            }

            Assert.Equal(LoRaRegionType.EU868, loraDevice.LoRaRegion);

            if (shouldSavePreferredGateway || shouldSaveRegion)
            {
                if (shouldSavePreferredGateway)
                {
                    Assert.Equal(ServerGatewayID, actualSavedTwin[TwinProperty.PreferredGatewayID].Value as string);
                }
                else
                {
                    Assert.False(actualSavedTwin.Contains(TwinProperty.PreferredGatewayID));
                }

                if (shouldSaveRegion)
                {
                    Assert.Equal(LoRaRegionType.EU868.ToString(), actualSavedTwin[TwinProperty.Region].Value as string);
                }
                else
                {
                    Assert.False(actualSavedTwin.Contains(TwinProperty.Region));
                }
            }
        }
        public async Task When_Joining_Should_Save_Region_And_Preferred_Gateway(
            [CombinatorialValues(null, ServerGatewayID)] string deviceGatewayID,
            [CombinatorialValues(null, ServerGatewayID, "another-gateway")] string initialPreferredGatewayID,
            [CombinatorialValues(null, LoRaRegionType.EU868, LoRaRegionType.US915)] LoRaRegionType?initialLoRaRegion)
        {
            var simDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, deviceClassType: 'c', gatewayID: deviceGatewayID));

            var customReportedProperties = new Dictionary <string, object>();

            // reported: { 'PreferredGateway': '' } -> if device is for multiple gateways and one initial was defined
            if (string.IsNullOrEmpty(deviceGatewayID) && !string.IsNullOrEmpty(initialPreferredGatewayID))
            {
                customReportedProperties[TwinProperty.PreferredGatewayID] = initialPreferredGatewayID;
            }

            if (initialLoRaRegion.HasValue)
            {
                customReportedProperties[TwinProperty.Region] = initialLoRaRegion.Value.ToString();
            }

            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None))
            .ReturnsAsync(simDevice.CreateOTAATwin(reportedProperties: customReportedProperties));

            var shouldSavePreferredGateway = string.IsNullOrEmpty(deviceGatewayID) && initialPreferredGatewayID != ServerGatewayID;
            var shouldSaveRegion           = !initialLoRaRegion.HasValue || initialLoRaRegion.Value != LoRaRegionType.EU868;

            var            savedAppSKey         = string.Empty;
            var            savedNwkSKey         = string.Empty;
            var            savedDevAddr         = string.Empty;
            TwinCollection actualTwinCollection = null;

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true)
            .Callback <TwinCollection, CancellationToken>((t, _) =>
            {
                savedAppSKey         = t[TwinProperty.AppSKey];
                savedNwkSKey         = t[TwinProperty.NwkSKey];
                savedDevAddr         = t[TwinProperty.DevAddr];
                actualTwinCollection = t;
            });

            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, simDevice.DevEUI, It.IsAny <DevNonce>()))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(simDevice.DevAddr, simDevice.DevEUI, "123").AsList()));

            using var cache          = NewMemoryCache();
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            using var messageDispatcher = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            var joinPayload = simDevice.CreateJoinRequest();

            using var joinRequest = CreateWaitableRequest(joinPayload);
            messageDispatcher.DispatchRequest(joinRequest);
            Assert.True(await joinRequest.WaitCompleteAsync());
            Assert.True(joinRequest.ProcessingSucceeded);

            Assert.NotEmpty(savedAppSKey);
            Assert.NotEmpty(savedNwkSKey);
            Assert.NotEmpty(savedDevAddr);
            if (shouldSaveRegion)
            {
                Assert.Equal(LoRaRegionType.EU868.ToString(), actualTwinCollection[TwinProperty.Region].Value as string);
            }
            else
            {
                Assert.False(actualTwinCollection.Contains(TwinProperty.Region));
            }

            // Only save preferred gateway if device does not have one assigned
            if (shouldSavePreferredGateway)
            {
                Assert.Equal(ServerConfiguration.GatewayID, actualTwinCollection[TwinProperty.PreferredGatewayID].Value as string);
            }
            else
            {
                Assert.False(actualTwinCollection.Contains(TwinProperty.PreferredGatewayID));
            }

            Assert.True(DeviceCache.TryGetByDevEui(simDevice.DevEUI, out var loRaDevice));

            Assert.Equal(LoRaDeviceClassType.C, loRaDevice.ClassType);
            if (string.IsNullOrEmpty(simDevice.LoRaDevice.GatewayID))
            {
                Assert.Equal(ServerConfiguration.GatewayID, loRaDevice.PreferredGatewayID);
            }
            else
            {
                Assert.Empty(loRaDevice.PreferredGatewayID);
            }

            Assert.Equal(LoRaRegionType.EU868, loRaDevice.LoRaRegion);

            LoRaDeviceApi.VerifyAll();
            LoRaDeviceClient.VerifyAll();
        }
        public async Task Unconfirmed_Cloud_To_Device_From_Decoder_Should_Call_ClassC_Message_Sender()
        {
            const uint PayloadFcnt           = 10;
            const uint InitialDeviceFcntUp   = 9;
            const uint InitialDeviceFcntDown = 20;
            var        devEui = new DevEui(2);

            var simulatedDevice = new SimulatedDevice(
                TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID),
                frmCntUp: InitialDeviceFcntUp,
                frmCntDown: InitialDeviceFcntDown);

            var loraDevice = CreateLoRaDevice(simulatedDevice);

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsAny <TimeSpan>()))
            .ReturnsAsync((Message)null);

            var decoderResult = new DecodePayloadResult("1")
            {
                CloudToDeviceMessage = new ReceivedLoRaCloudToDeviceMessage()
                {
                    Fport     = FramePorts.App1,
                    MessageId = "123",
                    Payload   = "12",
                    DevEUI    = devEui,
                },
            };

            var payloadDecoder = new Mock <ILoRaPayloadDecoder>(MockBehavior.Strict);

            payloadDecoder.Setup(x => x.DecodeMessageAsync(simulatedDevice.DevEUI, It.IsNotNull <byte[]>(), FramePorts.App1, It.IsAny <string>()))
            .ReturnsAsync(decoderResult);
            PayloadDecoder.SetDecoder(payloadDecoder.Object);

            using var cache           = EmptyMemoryCache();
            using var loraDeviceCache = CreateDeviceCache(loraDevice);
            using var deviceRegistry  = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache);

            using var c2dMessageSent = new SemaphoreSlim(0);
            var classCMessageSender = new Mock <IClassCDeviceMessageSender>(MockBehavior.Strict);

            classCMessageSender.Setup(x => x.SendAsync(It.Is <IReceivedLoRaCloudToDeviceMessage>(m => !m.Confirmed && m.DevEUI == devEui), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true)
            .Callback <IReceivedLoRaCloudToDeviceMessage, CancellationToken>((m, _) => c2dMessageSent.Release());
            RequestHandlerImplementation.SetClassCMessageSender(classCMessageSender.Object);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: PayloadFcnt);

            using var request = CreateWaitableRequest(payload);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());

            // Expectations
            // 1. Message was sent to IoT Hub
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();

            // 2. No downstream message for the current device is sent
            Assert.Null(request.ResponseDownlink);
            Assert.True(request.ProcessingSucceeded);

            // 4. Frame counter up was updated
            Assert.Equal(PayloadFcnt, loraDevice.FCntUp);

            // 5. Frame counter down is unchanged
            Assert.Equal(InitialDeviceFcntDown, loraDevice.FCntDown);

            // 6. Frame count has pending changes
            Assert.True(loraDevice.HasFrameCountChanges);

            // Ensure the message was sent
            Assert.True(await c2dMessageSent.WaitAsync(10 * 1000));

            payloadDecoder.VerifyAll();
            classCMessageSender.VerifyAll();
        }
        public async Task When_OTAA_Join_Then_Sends_Upstream_DirectMethod_Should_Send_Downstream(string deviceGatewayID)
        {
            var simDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, deviceClassType: 'c', gatewayID: deviceGatewayID));

            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None))
            .ReturnsAsync(simDevice.CreateOTAATwin());

            AppSessionKey?    savedAppSKey = null;
            NetworkSessionKey?savedNwkSKey = null;
            var savedDevAddr = string.Empty;

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true)
            .Callback <TwinCollection, CancellationToken>((t, _) =>
            {
                savedAppSKey = AppSessionKey.Parse(t[TwinProperty.AppSKey].Value);
                savedNwkSKey = NetworkSessionKey.Parse(t[TwinProperty.NwkSKey].Value);
                savedDevAddr = t[TwinProperty.DevAddr];
            });

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            if (deviceGatewayID == null)
            {
                LoRaDeviceApi.Setup(x => x.ExecuteFunctionBundlerAsync(simDevice.DevEUI, It.IsNotNull <FunctionBundlerRequest>()))
                .ReturnsAsync(new FunctionBundlerResult());
            }

            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, simDevice.DevEUI, It.IsAny <DevNonce>()))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(simDevice.DevAddr, simDevice.DevEUI, "123").AsList()));

            using var cache          = NewMemoryCache();
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            using var messageDispatcher = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            var payloadata = simDevice.CreateJoinRequest();

            using var joinRequest = CreateWaitableRequest(payloadata);
            joinRequest.SetStationEui(new StationEui(ulong.MaxValue));
            messageDispatcher.DispatchRequest(joinRequest);
            Assert.True(await joinRequest.WaitCompleteAsync());
            Assert.True(joinRequest.ProcessingSucceeded);

            Assert.NotNull(savedAppSKey);
            Assert.NotNull(savedNwkSKey);
            Assert.NotEmpty(savedDevAddr);
            simDevice.SetupJoin(savedAppSKey.Value, savedNwkSKey.Value, DevAddr.Parse(savedDevAddr));
            using var request = CreateWaitableRequest(simDevice.CreateUnconfirmedDataUpMessage("1"));
            request.SetStationEui(new StationEui(ulong.MaxValue));
            messageDispatcher.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.True(request.ProcessingSucceeded);

            var classCSender = new DefaultClassCDevicesMessageSender(
                ServerConfiguration,
                deviceRegistry,
                DownstreamMessageSender,
                FrameCounterUpdateStrategyProvider,
                new TestOutputLogger <DefaultClassCDevicesMessageSender>(this.testOutputHelper),
                TestMeter.Instance);

            var c2d = new ReceivedLoRaCloudToDeviceMessage()
            {
                DevEUI    = simDevice.DevEUI,
                MessageId = Guid.NewGuid().ToString(),
                Payload   = "aaaa",
                Fport     = FramePorts.App14,
            };

            if (string.IsNullOrEmpty(deviceGatewayID))
            {
                LoRaDeviceApi.Setup(x => x.NextFCntDownAsync(simDevice.DevEUI, simDevice.FrmCntDown, 0, ServerConfiguration.GatewayID))
                .ReturnsAsync((ushort)(simDevice.FrmCntDown + 1));
            }

            Assert.True(await classCSender.SendAsync(c2d));
            Assert.Equal(2, DownstreamMessageSender.DownlinkMessages.Count);
            var downstreamMsg = DownstreamMessageSender.DownlinkMessages[1];

            TestLogger.Log($"appSKey: {simDevice.AppSKey}, nwkSKey: {simDevice.NwkSKey}");

            var downstreamPayloadBytes = downstreamMsg.Data;
            var downstreamPayload      = new LoRaPayloadData(downstreamPayloadBytes);

            Assert.Equal(1, downstreamPayload.Fcnt);
            Assert.Equal(c2d.Fport, downstreamPayload.Fport);
            Assert.Equal(downstreamPayload.DevAddr, DevAddr.Parse(savedDevAddr));
            var decryptedPayload = downstreamPayload.GetDecryptedPayload(simDevice.AppSKey.Value);

            Assert.Equal(c2d.Payload, Encoding.UTF8.GetString(decryptedPayload));

            LoRaDeviceApi.VerifyAll();
            LoRaDeviceClient.VerifyAll();
        }
        public async Task When_First_Join_Fails_Due_To_Slow_Twin_Update_Retry_Second_Attempt_Should_Succeed(string deviceGatewayID)
        {
            var simulatedDevice     = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequestPayload1 = simulatedDevice.CreateJoinRequest();
            var joinRequestPayload2 = simulatedDevice.CreateJoinRequest();

            var devAddr = (DevAddr?)null;
            var devEui  = simulatedDevice.LoRaDevice.DevEui;

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEui.ToString();
            twin.Properties.Desired[TwinProperty.AppEui]        = simulatedDevice.LoRaDevice.AppEui?.ToString();
            twin.Properties.Desired[TwinProperty.AppKey]        = simulatedDevice.LoRaDevice.AppKey?.ToString();
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None))
            .ReturnsAsync(twin);

            // Device twin will be updated
            AppSessionKey?    afterJoin2AppSKey = null;
            NetworkSessionKey?afterJoin2NwkSKey = null;
            DevAddr?          afterJoin2DevAddr = null;

            var mockSequence = new MockSequence();

            LoRaDeviceClient.InSequence(mockSequence)
            .Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .Returns <TwinCollection, CancellationToken>(async(_, token) =>
            {
                try
                {
                    await Task.Delay(TimeSpan.FromSeconds(20), token);
                    Assert.True(false, "Token timeout expected");
                }
                catch (OperationCanceledException) { }
                return(false);
            });
            LoRaDeviceClient.InSequence(mockSequence)
            .Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .Returns <TwinCollection, CancellationToken>((updatedTwin, token) =>
            {
                afterJoin2AppSKey = updatedTwin.SafeRead <AppSessionKey>(TwinProperty.AppSKey);
                afterJoin2NwkSKey = updatedTwin.SafeRead <NetworkSessionKey>(TwinProperty.NwkSKey);
                afterJoin2DevAddr = updatedTwin.SafeRead <DevAddr>(TwinProperty.DevAddr);
                return(Task.FromResult(true));
            });

            // Lora device api will be search by devices with matching deveui,
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload1.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload2.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            using var cache          = NewMemoryCache();
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            using var joinRequest1 = CreateWaitableRequest(joinRequestPayload1, useRealTimer: true);
            messageProcessor.DispatchRequest(joinRequest1);
            await Task.Delay(TimeSpan.FromSeconds(7));

            using var joinRequest2 = CreateWaitableRequest(joinRequestPayload2, useRealTimer: true);
            messageProcessor.DispatchRequest(joinRequest2);

            await Task.WhenAll(joinRequest1.WaitCompleteAsync(), joinRequest2.WaitCompleteAsync());

            Assert.True(joinRequest1.ProcessingFailed);
            Assert.Null(joinRequest1.ResponseDownlink);
            Assert.Equal(LoRaDeviceRequestFailedReason.IoTHubProblem, joinRequest1.ProcessingFailedReason);
            Assert.True(joinRequest2.ProcessingSucceeded);
            Assert.NotNull(joinRequest2.ResponseDownlink);
            Assert.Single(DownstreamMessageSender.DownlinkMessages);

            Assert.True(DeviceCache.TryGetByDevEui(devEui, out var loRaDevice));
            Assert.True(loRaDevice.IsOurDevice);
            Assert.Equal(afterJoin2DevAddr, loRaDevice.DevAddr);
            Assert.Equal(afterJoin2NwkSKey, loRaDevice.NwkSKey);
            Assert.Equal(afterJoin2AppSKey, loRaDevice.AppSKey);

            // get twin should happen only once
            LoRaDeviceClient.Verify(x => x.GetTwinAsync(CancellationToken.None), Times.Once());
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
示例#25
0
        public async Task When_Join_Fails_Due_To_Timeout_Second_Try_Should_Reuse_Cached_Device_Twin(string deviceGatewayID)
        {
            var simulatedDevice     = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequestPayload1 = simulatedDevice.CreateJoinRequest();

            var devAddr = (DevAddr?)null;
            var devEui  = simulatedDevice.LoRaDevice.DevEui;

            // Device twin will be queried twice, 1st time will take 7 seconds, 2nd time 0.1 second
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI] = devEui.ToString();
            twin.Properties.Desired[TwinProperty.AppEui] = simulatedDevice.LoRaDevice.AppEui?.ToString();
            twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey?.ToString();
            if (deviceGatewayID != null)
            {
                twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID;
            }
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None))
            .ReturnsAsync(twin);

            // Device twin will be updated
            AppSessionKey?    afterJoinAppSKey = null;
            NetworkSessionKey?afterJoinNwkSKey = null;
            string            afterJoinDevAddr = null;

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .Callback <TwinCollection, CancellationToken>((updatedTwin, _) =>
            {
                afterJoinAppSKey = AppSessionKey.Parse(updatedTwin[TwinProperty.AppSKey].Value);
                afterJoinNwkSKey = NetworkSessionKey.Parse(updatedTwin[TwinProperty.NwkSKey].Value);
                afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr];
            })
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui,
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload1.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // 1st join request
            // Should fail
            using var joinRequest1 = CreateWaitableRequest(joinRequestPayload1, constantElapsedTime: TimeSpan.FromSeconds(7));
            messageProcessor.DispatchRequest(joinRequest1);
            Assert.True(await joinRequest1.WaitCompleteAsync());
            Assert.True(joinRequest1.ProcessingFailed);
            Assert.Null(joinRequest1.ResponseDownlink);
            Assert.Equal(LoRaDeviceRequestFailedReason.ReceiveWindowMissed, joinRequest1.ProcessingFailedReason);

            // 2nd attempt
            var joinRequestPayload2 = simulatedDevice.CreateJoinRequest();

            // setup response to this device search
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload2.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            using var joinRequest2 = CreateWaitableRequest(joinRequestPayload2);
            messageProcessor.DispatchRequest(joinRequest2);
            Assert.True(await joinRequest2.WaitCompleteAsync());
            Assert.True(joinRequest2.ProcessingSucceeded);
            Assert.NotNull(joinRequest2.ResponseDownlink);
            Assert.Single(DownstreamMessageSender.DownlinkMessages);
            var joinRequestDownlinkMessage = DownstreamMessageSender.DownlinkMessages[0];
            var joinAccept = new LoRaPayloadJoinAccept(joinRequestDownlinkMessage.Data, simulatedDevice.LoRaDevice.AppKey.Value);

            Assert.Equal(joinAccept.DevAddr.ToString(), afterJoinDevAddr);

            Assert.True(DeviceCache.TryGetByDevEui(devEui, out var loRaDevice));

            Assert.Equal(simulatedDevice.AppKey, loRaDevice.AppKey);
            Assert.Equal(simulatedDevice.AppEui, loRaDevice.AppEui);
            Assert.Equal(afterJoinAppSKey, loRaDevice.AppSKey);
            Assert.Equal(afterJoinNwkSKey, loRaDevice.NwkSKey);
            Assert.Equal(joinAccept.DevAddr, loRaDevice.DevAddr);
            if (deviceGatewayID == null)
            {
                Assert.Null(loRaDevice.GatewayID);
            }
            else
            {
                Assert.Equal(deviceGatewayID, loRaDevice.GatewayID);
            }

            // fcnt is restarted
            Assert.Equal(0U, loRaDevice.FCntUp);
            Assert.Equal(0U, loRaDevice.FCntDown);
            Assert.False(loRaDevice.HasFrameCountChanges);

            // searching the device should happen twice
            LoRaDeviceApi.Verify(x => x.SearchAndLockForJoinAsync(ServerGatewayID, devEui, It.IsAny <DevNonce>()), Times.Exactly(2));

            // getting the device twin should happens once
            LoRaDeviceClient.Verify(x => x.GetTwinAsync(CancellationToken.None), Times.Once());

            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
        public async Task When_Multiple_Joins_Are_Received_Should_Get_Twins_Once(string deviceGatewayID)
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));

            var devAddr = (DevAddr?)null;
            var devEui  = simulatedDevice.LoRaDevice.DevEui;

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEui.ToString();
            twin.Properties.Desired[TwinProperty.AppEui]        = simulatedDevice.LoRaDevice.AppEui?.ToString();
            twin.Properties.Desired[TwinProperty.AppKey]        = simulatedDevice.LoRaDevice.AppKey?.ToString();
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None))
            .ReturnsAsync(twin);

            // Device twin will be updated
            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, It.IsAny <DevNonce>()))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            // using factory to create mock of
            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // 1st join request
            using var joinRequest1 =
                      CreateWaitableRequest(simulatedDevice.CreateJoinRequest());
            messageProcessor.DispatchRequest(joinRequest1);

            await Task.Delay(100);

            // 2nd join request
            using var joinRequest2 =
                      CreateWaitableRequest(simulatedDevice.CreateJoinRequest());
            messageProcessor.DispatchRequest(joinRequest2);

            await Task.WhenAll(joinRequest1.WaitCompleteAsync(), joinRequest2.WaitCompleteAsync());

            Assert.Equal(2, DownstreamMessageSender.DownlinkMessages.Count);
            Assert.NotNull(joinRequest1.ResponseDownlink);
            Assert.NotNull(joinRequest2.ResponseDownlink);

            // get twin only once called
            LoRaDeviceClient.Verify(x => x.GetTwinAsync(CancellationToken.None), Times.Once());

            // get device for join called x2
            LoRaDeviceApi.Verify(x => x.SearchAndLockForJoinAsync(ServerGatewayID, devEui, It.IsAny <DevNonce>()), Times.Exactly(2));

            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
示例#27
0
        public async Task ABP_New_Loaded_Device_With_Fcnt_1_Or_0_Should_Reset_Fcnt_And_Send_To_IotHub(
            string twinGatewayID,
            uint payloadFcntUp,
            uint?deviceTwinFcntUp,
            uint?deviceTwinFcntDown)
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: null));

            var devEui  = simulatedDevice.LoRaDevice.DevEui;
            var devAddr = simulatedDevice.LoRaDevice.DevAddr.Value;

            // message will be sent
            LoRaDeviceTelemetry loRaDeviceTelemetry = null;

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .Callback <LoRaDeviceTelemetry, Dictionary <string, string> >((t, _) => loRaDeviceTelemetry = t)
            .ReturnsAsync(true);

            // C2D message will be checked
            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            // twin will be loaded
            var initialTwin = new Twin();

            initialTwin.Properties.Desired[TwinProperty.DevEUI]  = devEui.ToString();
            initialTwin.Properties.Desired[TwinProperty.AppEui]  = simulatedDevice.LoRaDevice.AppEui?.ToString();
            initialTwin.Properties.Desired[TwinProperty.AppKey]  = simulatedDevice.LoRaDevice.AppKey?.ToString();
            initialTwin.Properties.Desired[TwinProperty.NwkSKey] = simulatedDevice.LoRaDevice.NwkSKey?.ToString();
            initialTwin.Properties.Desired[TwinProperty.AppSKey] = simulatedDevice.LoRaDevice.AppSKey?.ToString();
            initialTwin.Properties.Desired[TwinProperty.DevAddr] = devAddr.ToString();
            if (twinGatewayID != null)
            {
                initialTwin.Properties.Desired[TwinProperty.GatewayID] = twinGatewayID;
            }
            initialTwin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            if (deviceTwinFcntDown.HasValue)
            {
                initialTwin.Properties.Reported[TwinProperty.FCntDown] = deviceTwinFcntDown.Value;
            }
            if (deviceTwinFcntUp.HasValue)
            {
                initialTwin.Properties.Reported[TwinProperty.FCntUp] = deviceTwinFcntUp.Value;
            }

            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)).ReturnsAsync(initialTwin);

            // twin will be updated with new fcnt
            int?fcntUpSavedInTwin   = null;
            int?fcntDownSavedInTwin = null;

            var shouldSaveTwin = (deviceTwinFcntDown ?? 0) != 0 || (deviceTwinFcntUp ?? 0) != 0;

            if (shouldSaveTwin)
            {
                LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
                .Callback <TwinCollection, CancellationToken>((t, _) =>
                {
                    fcntUpSavedInTwin   = (int)t[TwinProperty.FCntUp];
                    fcntDownSavedInTwin = (int)t[TwinProperty.FCntDown];
                })
                .ReturnsAsync(true);
            }

            LoRaDeviceApi.Setup(x => x.ABPFcntCacheResetAsync(devEui, It.IsAny <uint>(), It.IsNotNull <string>()))
            .ReturnsAsync(true);

            // device api will be searched for payload
            LoRaDeviceApi.Setup(x => x.SearchByDevAddrAsync(devAddr))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "abc").AsList()));

            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageDispatcher = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // sends unconfirmed message
            var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("hello", fcnt: payloadFcntUp);

            using var request = CreateWaitableRequest(unconfirmedMessagePayload);
            messageDispatcher.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.Null(request.ResponseDownlink);

            // Ensure that a telemetry was sent
            Assert.NotNull(loRaDeviceTelemetry);

            // Ensure that the device twins were saved
            if (shouldSaveTwin)
            {
                Assert.NotNull(fcntDownSavedInTwin);
                Assert.NotNull(fcntUpSavedInTwin);
                Assert.Equal(0, fcntDownSavedInTwin.Value);
                Assert.Equal(0, fcntUpSavedInTwin.Value);
            }

            // verify that the device in device registry has correct properties and frame counters
            Assert.True(DeviceCache.TryGetForPayload(request.Payload, out var loRaDevice));
            Assert.Equal(devAddr, loRaDevice.DevAddr);
            Assert.Equal(devEui, loRaDevice.DevEUI);
            Assert.True(loRaDevice.IsABP);
            Assert.Equal(payloadFcntUp, loRaDevice.FCntUp);
            Assert.Equal(0U, loRaDevice.FCntDown);
            if (payloadFcntUp == 0)
            {
                Assert.False(loRaDevice.HasFrameCountChanges); // no changes
            }
            else
            {
                Assert.True(loRaDevice.HasFrameCountChanges); // should have changes!
            }
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
        public async Task When_Updating_PreferredGateway_And_FcntUp_Should_Save_Twin_Once()
        {
            const uint PayloadFcnt           = 10;
            const uint InitialDeviceFcntUp   = 9;
            const uint InitialDeviceFcntDown = 20;

            var simulatedDevice = new SimulatedDevice(
                TestDeviceInfo.CreateABPDevice(1, deviceClassType: 'c'),
                frmCntUp: InitialDeviceFcntUp,
                frmCntDown: InitialDeviceFcntDown);

            var loraDevice = CreateLoRaDevice(simulatedDevice);

            loraDevice.UpdatePreferredGatewayID("another-gateway", acceptChanges: true);

            var bundlerResult = new FunctionBundlerResult()
            {
                PreferredGatewayResult = new PreferredGatewayResult()
                {
                    DevEUI             = simulatedDevice.DevEUI,
                    PreferredGatewayID = ServerGatewayID,
                    CurrentFcntUp      = PayloadFcnt,
                    RequestFcntUp      = PayloadFcnt,
                }
            };

            LoRaDeviceApi
            .Setup(x => x.ExecuteFunctionBundlerAsync(simulatedDevice.DevEUI, It.Is((FunctionBundlerRequest r) => PayloadFcnt == r.ClientFCntUp &&
                                                                                    ServerGatewayID == r.GatewayId &&
                                                                                    FunctionBundlerItemType.PreferredGateway == r.FunctionItems)))
            .ReturnsAsync(bundlerResult);

            TwinCollection actualSavedTwin = null;

            LoRaDeviceClient
            .Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .Callback <TwinCollection, CancellationToken>((savedTwin, _) => actualSavedTwin = savedTwin)
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsAny <TimeSpan>()))
            .ReturnsAsync((Message)null);

            using var cache           = EmptyMemoryCache();
            using var loraDeviceCache = CreateDeviceCache(loraDevice);
            using var deviceRegistry  = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: PayloadFcnt);

            using var request = CreateWaitableRequest(payload);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());

            // Expectations
            // 1. Message was sent to IoT Hub
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();

            // 2. No downstream message for the current device is sent
            Assert.Null(request.ResponseDownlink);
            Assert.True(request.ProcessingSucceeded);

            Assert.Equal(ServerGatewayID, loraDevice.PreferredGatewayID);
            Assert.Equal(LoRaRegionType.EU868, loraDevice.LoRaRegion);
            Assert.Equal(PayloadFcnt, loraDevice.FCntUp);

            LoRaDeviceClient.Verify(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()), Times.Once());

            Assert.Equal(ServerGatewayID, actualSavedTwin[TwinProperty.PreferredGatewayID].Value as string);
            Assert.Equal(LoRaRegionType.EU868.ToString(), actualSavedTwin[TwinProperty.Region].Value as string);
            Assert.Equal(PayloadFcnt, (uint)actualSavedTwin[TwinProperty.FCntUp].Value);
        }
        public async Task When_ABP_Sends_Upstream_Followed_By_DirectMethod_Should_Send_Upstream_And_Downstream(string deviceGatewayID, uint fcntDownFromTwin, uint fcntDelta, Region region)
        {
            const uint payloadFcnt = 2; // to avoid relax mode reset

            var simDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: deviceGatewayID, deviceClassType: 'c'), frmCntDown: fcntDownFromTwin);

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true);

            var twin = simDevice.CreateABPTwin(reportedProperties: new Dictionary <string, object>
            {
                { TwinProperty.Region, region.LoRaRegion.ToString() }
            });

            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None))
            .ReturnsAsync(twin);

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            LoRaDeviceApi.Setup(x => x.SearchByDevAddrAsync(simDevice.DevAddr.Value))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(simDevice.DevAddr, simDevice.DevEUI, "123").AsList()));

            if (deviceGatewayID == null)
            {
                LoRaDeviceApi.Setup(x => x.ExecuteFunctionBundlerAsync(simDevice.DevEUI, It.IsNotNull <FunctionBundlerRequest>()))
                .ReturnsAsync(new FunctionBundlerResult());
            }

            using var cache          = NewMemoryCache();
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            using var messageDispatcher = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);
            var payloadData = simDevice.CreateUnconfirmedDataUpMessage("1", fcnt: payloadFcnt);

            using var request = CreateWaitableRequest(payloadData, region: region);
            request.SetStationEui(new StationEui(ulong.MaxValue));
            messageDispatcher.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.True(request.ProcessingSucceeded);

            // wait until cache has been updated
            await Task.Delay(50);

            // Adds fcntdown to device, simulating multiple downstream calls
            Assert.True(DeviceCache.TryGetForPayload(request.Payload, out var loRaDevice));
            loRaDevice.SetFcntDown(fcntDelta + loRaDevice.FCntDown);

            var classCSender = new DefaultClassCDevicesMessageSender(
                ServerConfiguration,
                deviceRegistry,
                DownstreamMessageSender,
                FrameCounterUpdateStrategyProvider,
                new TestOutputLogger <DefaultClassCDevicesMessageSender>(this.testOutputHelper),
                TestMeter.Instance);

            var c2d = new ReceivedLoRaCloudToDeviceMessage()
            {
                DevEUI    = simDevice.DevEUI,
                MessageId = Guid.NewGuid().ToString(),
                Payload   = "aaaa",
                Fport     = FramePorts.App18,
            };

            var expectedFcntDown = fcntDownFromTwin + Constants.MaxFcntUnsavedDelta + fcntDelta;

            if (string.IsNullOrEmpty(deviceGatewayID))
            {
                LoRaDeviceApi.Setup(x => x.NextFCntDownAsync(simDevice.DevEUI, fcntDownFromTwin + fcntDelta, 0, ServerConfiguration.GatewayID))
                .ReturnsAsync((ushort)expectedFcntDown);
            }

            Assert.True(await classCSender.SendAsync(c2d));
            Assert.Single(DownstreamMessageSender.DownlinkMessages);
            var downstreamMsg = DownstreamMessageSender.DownlinkMessages[0];

            var downstreamPayloadBytes = downstreamMsg.Data;
            var downstreamPayload      = new LoRaPayloadData(downstreamPayloadBytes);

            Assert.Equal(expectedFcntDown, downstreamPayload.Fcnt);
            Assert.Equal(c2d.Fport, downstreamPayload.Fport);
            Assert.Equal(downstreamPayload.DevAddr, simDevice.DevAddr);
            var decryptedPayload = downstreamPayload.GetDecryptedPayload(simDevice.AppSKey.Value);

            Assert.Equal(c2d.Payload, Encoding.UTF8.GetString(decryptedPayload));

            Assert.Equal(expectedFcntDown, loRaDevice.FCntDown);
            Assert.Equal(payloadFcnt, loRaDevice.FCntUp);

            LoRaDeviceApi.VerifyAll();
            LoRaDeviceClient.VerifyAll();
        }
        private async Task Join_With_Subsequent_Unconfirmed_And_Confirmed_Messages(string deviceGatewayID, uint initialFcntUp, uint initialFcntDown, uint startingPayloadFcnt, int netId, Region region)
        {
            var simulatedDevice    = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequestPayload = simulatedDevice.CreateJoinRequest();

            var devAddr = (DevAddr?)null;
            var devEui  = simulatedDevice.LoRaDevice.DevEui;

            ServerConfiguration.NetId = new NetId(netId);
            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI] = devEui.ToString();
            twin.Properties.Desired[TwinProperty.AppEui] = simulatedDevice.LoRaDevice.AppEui?.ToString();
            twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey?.ToString();
            if (deviceGatewayID != null)
            {
                twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID;
            }
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            twin.Properties.Reported[TwinProperty.FCntUp]       = initialFcntUp;
            twin.Properties.Reported[TwinProperty.FCntDown]     = initialFcntDown;
            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)).ReturnsAsync(twin);

            // Device twin will be updated
            AppSessionKey?    afterJoinAppSKey = null;
            NetworkSessionKey?afterJoinNwkSKey = null;
            string            afterJoinDevAddr = null;
            uint afterJoinFcntDown             = 0;
            uint afterJoinFcntUp = 0;

            TwinCollection actualSavedTwin = null;

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .Callback <TwinCollection, CancellationToken>((updatedTwin, _) =>
            {
                if (updatedTwin.Contains(TwinProperty.AppSKey))
                {
                    afterJoinAppSKey = AppSessionKey.Parse(updatedTwin[TwinProperty.AppSKey].Value);
                }
                if (updatedTwin.Contains(TwinProperty.NwkSKey))
                {
                    afterJoinNwkSKey = NetworkSessionKey.Parse(updatedTwin[TwinProperty.NwkSKey].Value);
                }
                if (updatedTwin.Contains(TwinProperty.DevAddr))
                {
                    afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr];
                }
                afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown];
                afterJoinFcntUp   = updatedTwin[TwinProperty.FCntUp];
                actualSavedTwin   = updatedTwin;
            })
            .ReturnsAsync(true);

            // message will be sent
            var sentTelemetry = new List <LoRaDeviceTelemetry>();

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .Callback <LoRaDeviceTelemetry, Dictionary <string, string> >((t, _) => sentTelemetry.Add(t))
            .ReturnsAsync(true);

            // C2D message will be checked
            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            // Lora device api will be search by devices with matching deveui,
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            // multi gateway will request a next frame count down from the lora device api, prepare it
            if (string.IsNullOrEmpty(deviceGatewayID))
            {
                LoRaDeviceApi.Setup(x => x.NextFCntDownAsync(devEui, 0, startingPayloadFcnt + 1, ServerConfiguration.GatewayID))
                .ReturnsAsync((ushort)1);
                LoRaDeviceApi
                .Setup(x => x.ExecuteFunctionBundlerAsync(devEui, It.IsAny <FunctionBundlerRequest>()))
                .ReturnsAsync(() => new FunctionBundlerResult
                {
                    AdrResult = new LoRaTools.ADR.LoRaADRResult
                    {
                        CanConfirmToDevice = false,
                        FCntDown           = 1,
                        NbRepetition       = 1,
                        TxPower            = 0
                    },
                    NextFCntDown = 1
                });
            }

            // using factory to create mock of
            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // Create a join request and join with the device.
            using var joinRequest =
                      CreateWaitableRequest(joinRequestPayload, constantElapsedTime: TimeSpan.FromMilliseconds(300), region: region);
            messageProcessor.DispatchRequest(joinRequest);
            Assert.True(await joinRequest.WaitCompleteAsync());
            Assert.True(joinRequest.ProcessingSucceeded, $"Failed due to '{joinRequest.ProcessingFailedReason}'.");
            Assert.NotNull(joinRequest.ResponseDownlink);
            Assert.Single(DownstreamMessageSender.DownlinkMessages);
            var downlinkJoinAcceptMessage = DownstreamMessageSender.DownlinkMessages[0];
            var joinAccept = new LoRaPayloadJoinAccept(downlinkJoinAcceptMessage.Data, simulatedDevice.LoRaDevice.AppKey.Value);

            Assert.Equal(joinAccept.DevAddr.ToString(), afterJoinDevAddr);

            // check that the device is in cache
            Assert.True(DeviceCache.TryGetByDevEui(devEui, out var loRaDevice));
            Assert.Equal(afterJoinAppSKey, loRaDevice.AppSKey);
            Assert.Equal(afterJoinNwkSKey, loRaDevice.NwkSKey);
            Assert.Equal(afterJoinDevAddr, loRaDevice.DevAddr.ToString());
            var netIdBytes = BitConverter.GetBytes(netId);

            Assert.Equal(netIdBytes[0] & 0b01111111, DevAddr.Parse(afterJoinDevAddr).NetworkId);
            if (deviceGatewayID == null)
            {
                Assert.Null(loRaDevice.GatewayID);
            }
            else
            {
                Assert.Equal(deviceGatewayID, loRaDevice.GatewayID);
            }

            // Assert that after a join the fcnt is restarted
            Assert.Equal(0U, afterJoinFcntDown);
            Assert.Equal(0U, afterJoinFcntUp);
            Assert.Equal(0U, loRaDevice.FCntUp);
            Assert.Equal(0U, loRaDevice.FCntDown);
            Assert.False(loRaDevice.HasFrameCountChanges);

            simulatedDevice.LoRaDevice.AppSKey = afterJoinAppSKey;
            simulatedDevice.LoRaDevice.NwkSKey = afterJoinNwkSKey;
            simulatedDevice.LoRaDevice.DevAddr = DevAddr.Parse(afterJoinDevAddr);

            // sends unconfirmed message with a given starting frame counter
            var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("100", fcnt: startingPayloadFcnt);
            var radioMetadata             = TestUtils.GenerateTestRadioMetadata();

            using var unconfirmedRequest =
                      CreateWaitableRequest(radioMetadata, unconfirmedMessagePayload,
                                            constantElapsedTime: TimeSpan.FromMilliseconds(300));
            messageProcessor.DispatchRequest(unconfirmedRequest);
            Assert.True(await unconfirmedRequest.WaitCompleteAsync());
            Assert.Null(unconfirmedRequest.ResponseDownlink);
            Assert.True(unconfirmedRequest.ProcessingSucceeded);

            // fcnt up was updated
            Assert.Equal(startingPayloadFcnt, loRaDevice.FCntUp);
            Assert.Equal(0U, loRaDevice.FCntDown);

            // If the starting payload was not 0, it is expected that it updates the framecounter char
            // The device will perform the frame counter update and at this point in time it will have the same frame counter as the desired
            // Therefore savechangesasync will set the hasframcounter change to false
            // if (startingPayloadFcnt != 0)
            // {
            //    // Frame change flag will be set, only saving every 10 messages
            //    Assert.True(loRaDevice.HasFrameCountChanges);
            // }

            Assert.Single(sentTelemetry);

            // sends confirmed message
            var confirmedMessagePayload = simulatedDevice.CreateConfirmedDataUpMessage("200", fcnt: startingPayloadFcnt + 1);

            using var confirmedRequest = CreateWaitableRequest(confirmedMessagePayload, constantElapsedTime: TimeSpan.FromMilliseconds(300), region: region);
            messageProcessor.DispatchRequest(confirmedRequest);
            Assert.True(await confirmedRequest.WaitCompleteAsync());
            Assert.True(confirmedRequest.ProcessingSucceeded);
            Assert.NotNull(confirmedRequest.ResponseDownlink);
            Assert.Equal(2, DownstreamMessageSender.DownlinkMessages.Count);
            Assert.Equal(2, sentTelemetry.Count);
            var downstreamMessage = DownstreamMessageSender.DownlinkMessages[1];

            // validates txpk according to region
            DeviceJoinInfo deviceJoinInfo = null;

            if (region is RegionCN470RP2 cnRegion && cnRegion.TryGetJoinChannelIndex(confirmedRequest.RadioMetadata.Frequency, out var channelIndex))
            {
                deviceJoinInfo = new DeviceJoinInfo(channelIndex);
            }
            Assert.True(region.TryGetDownstreamChannelFrequency(confirmedRequest.RadioMetadata.Frequency, confirmedRequest.RadioMetadata.DataRate, deviceJoinInfo, downstreamFrequency: out var frequency));
            Assert.Equal(frequency, downstreamMessage.Rx1?.Frequency);

            var rx2Freq = region.GetDownstreamRX2Freq(null, deviceJoinInfo, NullLogger.Instance);

            Assert.Equal(rx2Freq, downstreamMessage.Rx2.Frequency);

            var rx2DataRate = region.GetDownstreamRX2DataRate(null, null, deviceJoinInfo, NullLogger.Instance);

            Assert.Equal(rx2DataRate, downstreamMessage.Rx2.DataRate);

            // fcnt up was updated
            Assert.Equal(startingPayloadFcnt + 1, loRaDevice.FCntUp);
            Assert.Equal(1U, loRaDevice.FCntDown);

            // Frame change flag will be set, only saving every 10 messages
            Assert.True(loRaDevice.HasFrameCountChanges);

            // C2D message will be checked twice (for AS923 only once, since we use the first C2D message to send the dwell time MAC command)
            var numberOfC2DMessageChecks = region is RegionAS923 ? 1 : 2;

            LoRaDeviceClient.Verify(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()), Times.Exactly(numberOfC2DMessageChecks));

            // has telemetry with both fcnt
            Assert.Single(sentTelemetry, (t) => t.Fcnt == startingPayloadFcnt);
            Assert.Single(sentTelemetry, (t) => t.Fcnt == (startingPayloadFcnt + 1));

            // should not save class C device properties
            Assert.False(actualSavedTwin.Contains(TwinProperty.Region));
            Assert.False(actualSavedTwin.Contains(TwinProperty.PreferredGatewayID));

            LoRaDeviceClient.VerifyAll();
        }