public async Task GetDeviceForJoinRequestAsync_When_Join_Handled_By_Other_Cache_Is_Updated(bool joinedDevice) { var devNonce = new DevNonce(1); var apiService = new Mock <LoRaDeviceAPIServiceBase>(); var otaaDevice = TestDeviceInfo.CreateOTAADevice(1); if (joinedDevice) { otaaDevice.AppSKey = new AppSessionKey(); } var simulatedDevice = new SimulatedDevice(otaaDevice); apiService.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, simulatedDevice.DevEUI, devNonce)) .ReturnsAsync(new SearchDevicesResult() { IsDevNonceAlreadyUsed = true }); DeviceCache.Register(CreateLoRaDevice(simulatedDevice)); using var target = new LoRaDeviceRegistry(ServerConfiguration, this.cache, apiService.Object, this.loraDeviceFactoryMock.Object, DeviceCache); Assert.Null(await target.GetDeviceForJoinRequestAsync(simulatedDevice.DevEUI, devNonce)); Assert.Equal(joinedDevice, !DeviceCache.TryGetByDevEui(simulatedDevice.DevEUI, out _)); }
public async Task ABP_From_Another_Gateway_Unconfirmed_Message_Should_Load_Device_Cache_And_Disconnect() { const string gateway1 = "gateway1"; const string gateway2 = "gateway2"; var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: gateway1)) { FrmCntUp = 9 }; LoRaDeviceApi.Setup(x => x.SearchByDevAddrAsync(simulatedDevice.DevAddr.Value)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(simulatedDevice.DevAddr, simulatedDevice.DevEUI, "1234") { GatewayId = gateway2 }.AsList())); 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 payload1 = simulatedDevice.CreateUnconfirmedDataUpMessage("1", fcnt: 10); using var request1 = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), payload1); messageProcessor.DispatchRequest(request1); Assert.True(await request1.WaitCompleteAsync()); Assert.True(request1.ProcessingFailed); Assert.Equal(LoRaDeviceRequestFailedReason.BelongsToAnotherGateway, request1.ProcessingFailedReason); // Let loading finish await Task.Delay(50); // device should be cached Assert.True(DeviceCache.TryGetByDevEui(simulatedDevice.DevEUI, out var cachedDevice)); var payload2 = simulatedDevice.CreateUnconfirmedDataUpMessage("2", fcnt: 11); using var request2 = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), payload2); messageProcessor.DispatchRequest(request2); Assert.True(await request2.WaitCompleteAsync()); Assert.True(request2.ProcessingFailed); Assert.Equal(LoRaDeviceRequestFailedReason.BelongsToAnotherGateway, request2.ProcessingFailedReason); // Expectations // 1. we never loaded the twins for the device not belonging to us LoRaDeviceClient.Verify(x => x.GetTwinAsync(It.IsAny <CancellationToken>()), Times.Never); LoRaDeviceClient.Verify(x => x.EnsureConnected(), Times.Never); LoRaDeviceApi.VerifyAll(); LoRaDeviceApi.Verify(x => x.SearchByDevAddrAsync(simulatedDevice.DevAddr.Value), Times.Once()); // 2. The device is registered Assert.Equal(1, DeviceCache.RegistrationCount(simulatedDevice.DevAddr.Value)); }
public void When_Cache_Clear_Is_Called_Should_Removed_Cached_Devices(string deviceGatewayID) { LoRaDeviceClient.Setup(ldc => ldc.Dispose()); const int deviceCount = 10; var deviceList = new HashSet <LoRaDevice>(); var apiService = new Mock <LoRaDeviceAPIServiceBase>(); var deviceFactory = new TestLoRaDeviceFactory(LoRaDeviceClient.Object, DeviceCache); using var target = new LoRaDeviceRegistry(ServerConfiguration, this.cache, apiService.Object, deviceFactory, DeviceCache); using var connectionManager = new SingleDeviceConnectionManager(LoRaDeviceClient.Object); for (var deviceID = 1; deviceID <= deviceCount; ++deviceID) { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice((uint)deviceID, gatewayID: deviceGatewayID)); #pragma warning disable CA2000 // Dispose objects before losing scope - transfer ownership var device = TestUtils.CreateFromSimulatedDevice(simulatedDevice, connectionManager); #pragma warning restore CA2000 // Dispose objects before losing scope DeviceCache.Register(device); deviceList.Add(device); } Assert.Equal(deviceCount, DeviceCache.CalculateStatistics().Count); // Device was searched by DevAddr apiService.VerifyAll(); // Device was created by factory this.loraDeviceFactoryMock.VerifyAll(); // ensure all devices are in cache Assert.Equal(deviceCount, deviceList.Count(x => DeviceCache.TryGetByDevEui(x.DevEUI, out _))); target.ResetDeviceCache(); Assert.False(deviceList.Any(x => DeviceCache.TryGetByDevEui(x.DevEUI, out _)), "Should not find devices again"); }
public async Task Validate_Limits( uint payloadFcntUp, uint?deviceFcntUp, uint?deviceFcntDown, uint?startFcntUp, uint?startFcntDown, uint expectedFcntUp, uint expectedFcntDown, bool abpRelaxed, bool confirmed, bool supports32Bit = false, LoRaDeviceRequestFailedReason?failedReason = null) { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID, supports32BitFcnt: supports32Bit)); var devEui = simulatedDevice.LoRaDevice.DevEui; var devAddr = simulatedDevice.LoRaDevice.DevAddr.Value; LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)).ReturnsAsync(true); var initialTwin = SetupTwins(deviceFcntUp, deviceFcntDown, startFcntUp, startFcntDown, abpRelaxed, supports32Bit, simulatedDevice, devEui, devAddr); LoRaDeviceClient .Setup(x => x.GetTwinAsync(CancellationToken.None)).Returns(() => { return(Task.FromResult(initialTwin)); }); uint?fcntUpSavedInTwin = null; uint?fcntDownSavedInTwin = null; LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>())) .Returns <TwinCollection, CancellationToken>((t, _) => { fcntUpSavedInTwin = (uint)t[TwinProperty.FCntUp]; fcntDownSavedInTwin = (uint)t[TwinProperty.FCntDown]; return(Task.FromResult(true)); }); LoRaDeviceClient .Setup(x => x.ReceiveAsync(It.IsAny <TimeSpan>())) .ReturnsAsync((Message)null); var shouldReset = payloadFcntUp == 0 && abpRelaxed; if (shouldReset) { LoRaDeviceApi.Setup(x => x.ABPFcntCacheResetAsync(devEui, It.IsAny <uint>(), It.IsNotNull <string>())).ReturnsAsync(true); } 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); var loRaPayloadData = confirmed ? simulatedDevice.CreateConfirmedDataUpMessage("1234", fcnt: payloadFcntUp, appSKey: simulatedDevice.AppSKey, nwkSKey: simulatedDevice.NwkSKey) : simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcntUp); using var req = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), loRaPayloadData); messageDispatcher.DispatchRequest(req); Assert.True(await req.WaitCompleteAsync(-1)); if (failedReason.HasValue) { Assert.Equal(failedReason.Value, req.ProcessingFailedReason); } else { Assert.True(req.ProcessingSucceeded); Assert.True(DeviceCache.TryGetByDevEui(devEui, out var loRaDevice)); if (confirmed) { Assert.NotNull(req.ResponseDownlink); Assert.True(req.ProcessingSucceeded); Assert.Single(DownstreamMessageSender.DownlinkMessages); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0]; var payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); payloadDataDown.Serialize(simulatedDevice.AppSKey.Value); Assert.Equal(expectedFcntDown, payloadDataDown.Fcnt); } Assert.Equal(expectedFcntUp, loRaDevice.FCntUp); } }
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 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(); }
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(); }
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(); }
public async Task When_Device_Is_Found_In_Api_Should_Update_Twin_And_Return() { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: ServerConfiguration.GatewayID)); simulatedDevice.LoRaDevice.NwkSKey = null; simulatedDevice.LoRaDevice.AppSKey = null; var joinRequest = simulatedDevice.CreateJoinRequest(); // this former join request is just to set the loradevice cache to another devnonce. _ = simulatedDevice.CreateJoinRequest(); var devEui = simulatedDevice.LoRaDevice.DevEui; LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(It.IsNotNull <string>(), devEui, joinRequest.DevNonce)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(null, devEui, "123") { GatewayId = ServerConfiguration.GatewayID }.AsList())); // Ensure that the device twin was updated LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.Is <TwinCollection>((t) => t.Contains(TwinProperty.DevAddr) && t.Contains(TwinProperty.FCntDown)), It.IsAny <CancellationToken>())) .ReturnsAsync(true); LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)) .ReturnsAsync(simulatedDevice.CreateOTAATwin()); 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); using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), joinRequest); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); Assert.NotNull(request.ResponseDownlink); Assert.Single(DownstreamMessageSender.DownlinkMessages); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0]; var joinAccept = new LoRaPayloadJoinAccept(downlinkMessage.Data, simulatedDevice.AppKey.Value); Assert.Equal(1, DeviceCache.RegistrationCount(joinAccept.DevAddr)); Assert.True(DeviceCache.TryGetByDevEui(devEui, out var loRaDevice)); Assert.Equal(joinAccept.DevAddr, loRaDevice.DevAddr); // Device properties were set with the computes values of the join operation Assert.Equal(joinAccept.AppNonce, loRaDevice.AppNonce); Assert.NotNull(loRaDevice.NwkSKey); Assert.NotNull(loRaDevice.AppSKey); Assert.True(loRaDevice.IsOurDevice); // Device frame counts were reset Assert.Equal(0U, loRaDevice.FCntDown); Assert.Equal(0U, loRaDevice.FCntUp); Assert.False(loRaDevice.HasFrameCountChanges); // Twin property were updated LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); }
public async Task When_Getting_Device_Information_From_Twin_Returns_JoinAccept(string deviceGatewayID) { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID)); var joinRequest = 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(); 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, joinRequest.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); // 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.NotNull(request.ResponseDownlink); Assert.Single(DownstreamMessageSender.DownlinkMessages); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0]; var joinAccept = new LoRaPayloadJoinAccept(downlinkMessage.Data, simulatedDevice.LoRaDevice.AppKey.Value); Assert.Equal(joinAccept.DevAddr.ToString(), afterJoinDevAddr); // check that the device is in cache Assert.True(DeviceCache.HasRegistrations(joinAccept.DevAddr)); Assert.True(DeviceCache.TryGetByDevEui(devEui, out var cachedDevice)); Assert.Equal(afterJoinAppSKey, cachedDevice.AppSKey); Assert.Equal(afterJoinNwkSKey, cachedDevice.NwkSKey); Assert.Equal(joinAccept.DevAddr, cachedDevice.DevAddr); Assert.True(cachedDevice.IsOurDevice); if (deviceGatewayID == null) { Assert.Null(cachedDevice.GatewayID); } else { Assert.Equal(deviceGatewayID, cachedDevice.GatewayID); } // fcnt is restarted Assert.Equal(0U, cachedDevice.FCntUp); Assert.Equal(0U, cachedDevice.FCntDown); Assert.False(cachedDevice.HasFrameCountChanges); }
public async Task When_Queueing_To_Multiple_Devices_With_Same_DevAddr_Should_Queue_To_Device_Matching_Mic(string deviceGatewayID) { var simulatedDevice1 = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: deviceGatewayID)); var payload = simulatedDevice1.CreateUnconfirmedDataUpMessage("1234"); var loRaDeviceClient1 = new Mock <ILoRaDeviceClient>(MockBehavior.Loose); loRaDeviceClient1.Setup(x => x.GetTwinAsync(CancellationToken.None)) .ReturnsAsync(simulatedDevice1.CreateABPTwin()); using var connectionManager1 = new SingleDeviceConnectionManager(loRaDeviceClient1.Object); using var loraDevice1 = TestUtils.CreateFromSimulatedDevice(simulatedDevice1, connectionManager1); var devAddr = loraDevice1.DevAddr.Value; var reqHandler1 = new Mock <ILoRaDataRequestHandler>(MockBehavior.Strict); reqHandler1.Setup(x => x.ProcessRequestAsync(It.IsNotNull <LoRaRequest>(), loraDevice1)) .ReturnsAsync(new LoRaDeviceRequestProcessResult(loraDevice1, null)); loraDevice1.SetRequestHandler(reqHandler1.Object); var simulatedDevice2 = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: deviceGatewayID)); simulatedDevice2.LoRaDevice.DeviceID = new DevEui(2).ToString(); simulatedDevice2.LoRaDevice.NwkSKey = TestKeys.CreateNetworkSessionKey(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF); var loRaDeviceClient2 = new Mock <ILoRaDeviceClient>(MockBehavior.Loose); loRaDeviceClient2.Setup(x => x.GetTwinAsync(CancellationToken.None)) .ReturnsAsync(simulatedDevice2.CreateABPTwin()); using var connectionManager2 = new SingleDeviceConnectionManager(loRaDeviceClient2.Object); using var loraDevice2 = TestUtils.CreateFromSimulatedDevice(simulatedDevice2, connectionManager2); // Api service: search devices async var iotHubDeviceInfo1 = new IoTHubDeviceInfo(devAddr, loraDevice1.DevEUI, string.Empty); var iotHubDeviceInfo2 = new IoTHubDeviceInfo(devAddr, loraDevice2.DevEUI, string.Empty); var apiService = new Mock <LoRaDeviceAPIServiceBase>(); apiService.Setup(x => x.SearchByDevAddrAsync(devAddr)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo[] { iotHubDeviceInfo2, iotHubDeviceInfo1, })); // Device factory: create 2 devices this.loraDeviceFactoryMock.Setup(x => x.CreateAndRegisterAsync(iotHubDeviceInfo1, It.IsAny <CancellationToken>())).ReturnsAsync(() => { DeviceCache.Register(loraDevice1); return(loraDevice1); }); this.loraDeviceFactoryMock.Setup(x => x.CreateAndRegisterAsync(iotHubDeviceInfo2, It.IsAny <CancellationToken>())).ReturnsAsync(() => { DeviceCache.Register(loraDevice2); return(loraDevice2); }); using var target = new LoRaDeviceRegistry(ServerConfiguration, this.cache, apiService.Object, this.loraDeviceFactoryMock.Object, DeviceCache); using var request = WaitableLoRaRequest.Create(payload); target.GetLoRaRequestQueue(request).Queue(request); Assert.True(await request.WaitCompleteAsync()); Assert.True(request.ProcessingSucceeded); // Device was searched by DevAddr apiService.VerifyAll(); // Device was created by factory this.loraDeviceFactoryMock.VerifyAll(); // Both devices are in cache Assert.Equal(2, DeviceCache.RegistrationCount(devAddr)); // 2 devices with same devAddr exist in cache // find device 1 Assert.True(DeviceCache.TryGetForPayload(request.Payload, out var actualCachedLoRaDevice1)); Assert.Same(loraDevice1, actualCachedLoRaDevice1); Assert.True(loraDevice1.IsOurDevice); // find device 2 Assert.True(DeviceCache.TryGetByDevEui(loraDevice2.DevEUI, out var actualCachedLoRaDevice2)); Assert.Same(loraDevice2, actualCachedLoRaDevice2); Assert.True(loraDevice2.IsOurDevice); reqHandler1.VerifyAll(); }