public void TestJoinAccept() { byte[] AppNonce = new byte[3] { 87, 11, 199 }; byte[] NetId = new byte[3] { 34, 17, 1 }; byte[] DevAddr = new byte[4] { 2, 3, 25, 128 }; var netId = ConversionHelper.ByteArrayToString(NetId); LoRaPayloadJoinAccept joinAccept = new LoRaPayloadJoinAccept(netId, "00112233445566778899AABBCCDDEEFF", DevAddr, AppNonce, new byte[] { 0 }, new byte[] { 0 }, null); Console.WriteLine(BitConverter.ToString(joinAccept.GetByteMessage())); LoRaMessageWrapper joinAcceptMessage = new LoRaMessageWrapper(joinAccept, LoRaMessageType.JoinAccept, new byte[] { 0x01 }); byte[] joinAcceptMic = new byte[4] { 67, 72, 91, 188 }; Assert.True((((LoRaPayloadJoinAccept)joinAcceptMessage.LoRaPayloadMessage).Mic.ToArray().SequenceEqual(joinAcceptMic))); var msg = ConversionHelper.ByteArrayToString(joinAcceptMessage.LoRaPayloadMessage.GetByteMessage()); Assert.Equal("20493EEB51FBA2116F810EDB3742975142", msg); }
public void TestJoinAccept() { byte[] AppNonce = new byte[3] { 87, 11, 199 }; byte[] NetId = new byte[3] { 34, 17, 1 }; byte[] DevAddr = new byte[4] { 2, 3, 25, 128 }; var netId = BitConverter.ToString(NetId).Replace("-", ""); LoRaPayloadJoinAccept joinAccept = new LoRaPayloadJoinAccept(netId, "00112233445566778899AABBCCDDEEFF", DevAddr, AppNonce); Console.WriteLine(BitConverter.ToString(joinAccept.ToMessage())); LoRaMessage joinAcceptMessage = new LoRaMessage(joinAccept, LoRaMessageType.JoinAccept, new byte[] { 0x01 }); byte[] joinAcceptMic = new byte[4] { 67, 72, 91, 188 }; Assert.True((((LoRaPayloadJoinAccept)joinAcceptMessage.payloadMessage).mic.ToArray().SequenceEqual(joinAcceptMic))); var msg = BitConverter.ToString(((LoRaPayloadJoinAccept)joinAcceptMessage.payloadMessage).ToMessage()).Replace("-", String.Empty); Assert.Equal("20493EEB51FBA2116F810EDB3742975142", msg); }
public void TestJoinAccept() { byte[] appNonce = new byte[3] { 87, 11, 199, }; byte[] netId1 = new byte[3] { 34, 17, 1, }; byte[] devAddr = new byte[4] { 2, 3, 25, 128, }; var netId = ConversionHelper.ByteArrayToString(netId1); var appkey = "00112233445566778899AABBCCDDEEFF"; LoRaPayloadJoinAccept joinAccept = new LoRaPayloadJoinAccept(netId, devAddr, appNonce, new byte[] { 0 }, 0, null); var joinacceptbyte = joinAccept.Serialize(appkey, "SF10BW125", 866.349812, 10000, "test"); var decodedJoinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(joinacceptbyte.Txpk.Data), appkey); byte[] joinAcceptMic = new byte[4] { 67, 72, 91, 188, }; Assert.True(decodedJoinAccept.Mic.ToArray().SequenceEqual(joinAcceptMic)); var msg = ConversionHelper.ByteArrayToString(Convert.FromBase64String(joinacceptbyte.Txpk.Data)); Assert.Equal("20493EEB51FBA2116F810EDB3742975142", msg); }
public void TestJoinAccept() { byte[] appNonce = new byte[3] { 87, 11, 199, }; byte[] netId1 = new byte[3] { 34, 17, 1, }; byte[] devAddr = new byte[4] { 2, 3, 25, 128, }; var netId = ConversionHelper.ByteArrayToString(netId1); LoRaPayloadJoinAccept joinAccept = new LoRaPayloadJoinAccept(netId, "00112233445566778899AABBCCDDEEFF", devAddr, appNonce, new byte[] { 0 }, new byte[] { 0 }, null); Console.WriteLine(BitConverter.ToString(joinAccept.GetByteMessage())); byte[] joinAcceptMic = new byte[4] { 67, 72, 91, 188, }; Assert.True(joinAccept.Mic.ToArray().SequenceEqual(joinAcceptMic)); var msg = ConversionHelper.ByteArrayToString(joinAccept.GetByteMessage()); Assert.Equal("20493EEB51FBA2116F810EDB3742975142", msg); }
public async Task When_Getting_DLSettings_From_Twin_Returns_JoinAccept_With_Correct_Settings(int rx1DROffset, int rx2datarate) { string deviceGatewayID = ServerGatewayID; var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID)); var joinRequest = simulatedDevice.CreateJoinRequest(); // Create Rxpk var rxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce); var devAddr = string.Empty; var devEUI = simulatedDevice.LoRaDevice.DeviceID; var appEUI = simulatedDevice.LoRaDevice.AppEUI; // Device twin will be queried var twin = new Twin(); twin.Properties.Desired[TwinProperty.DevEUI] = devEUI; twin.Properties.Desired[TwinProperty.AppEUI] = simulatedDevice.LoRaDevice.AppEUI; twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey; twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID; twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder; twin.Properties.Desired[TwinProperty.RX1DROffset] = rx1DROffset; twin.Properties.Desired[TwinProperty.RX2DataRate] = rx2datarate; this.LoRaDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(twin); this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .ReturnsAsync(true); // Lora device api will be search by devices with matching deveui, this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, devNonce)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList())); var loRaDeviceFactory = new TestLoRaDeviceFactory(this.LoRaDeviceClient.Object); var memoryCache = new MemoryCache(new MemoryCacheOptions()); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); // Send to message processor var messageProcessor = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); var request = this.CreateWaitableRequest(rxpk); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); Assert.NotNull(request.ResponseDownlink); Assert.Single(this.PacketForwarder.DownlinkMessages); var downlinkMessage = this.PacketForwarder.DownlinkMessages[0]; var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey); joinAccept.DlSettings.Span.Reverse(); Assert.Equal(rx1DROffset, joinAccept.Rx1DrOffset); Assert.Equal(rx2datarate, joinAccept.Rx2Dr); }
public async Task When_Getting_DLSettings_From_Twin_Returns_JoinAccept_With_Correct_Settings(int rx1DROffset, DataRateIndex rx2datarate) { var deviceGatewayID = ServerGatewayID; 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(); twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID; twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder; twin.Properties.Desired[TwinProperty.RX1DROffset] = rx1DROffset; twin.Properties.Desired[TwinProperty.RX2DataRate] = rx2datarate; LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)).ReturnsAsync(twin); 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, 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(rx1DROffset, joinAccept.Rx1DrOffset); Assert.Equal(rx2datarate, joinAccept.Rx2Dr); }
// Performs join public async Task <bool> JoinAsync(SimulatedPacketForwarder packetForwarder, int timeoutInMs = 30 * 1000) { if (this.IsJoined) { return(true); } var token = await RandomTokenGenerator.GetTokenAsync(); this.LastPayload = new PhysicalPayload(token, PhysicalIdentifier.PUSH_DATA, null); var header = this.LastPayload.GetSyncHeader(packetForwarder.MacAddress); var joinRequest = this.CreateJoinRequest(); var joinCompleted = new SemaphoreSlim(0); var joinRequestUplinkMessage = joinRequest.SerializeUplink(this.AppKey); packetForwarder.SubscribeOnce((response) => { // handle join var txpk = Txpk.CreateTxpk(response, this.LoRaDevice.AppKey); byte[] convertedInputMessage = Convert.FromBase64String(txpk.Data); var joinAccept = new LoRaPayloadJoinAccept(convertedInputMessage, this.AppKey); var result = this.HandleJoinAccept(joinAccept); // may need to return bool and only release if true. joinCompleted.Release(); return(result); }); await packetForwarder.SendAsync(header, joinRequest.GetByteMessage()); TestLogger.Log($"[{this.LoRaDevice.DeviceID}] Join request: {BitConverter.ToString(header).Replace("-", string.Empty)}"); #if DEBUG if (System.Diagnostics.Debugger.IsAttached) { timeoutInMs = 60 * 1000; } #endif return(await joinCompleted.WaitAsync(timeoutInMs)); }
bool HandleJoinAccept(LoRaPayloadJoinAccept payload) { try { // Calculate the keys var netid = payload.NetID.ToArray(); Array.Reverse(netid); var appNonce = payload.AppNonce.ToArray(); Array.Reverse(appNonce); var devNonce = ConversionHelper.StringToByteArray(this.DevNonce); Array.Reverse(devNonce); var deviceAppKey = ConversionHelper.StringToByteArray(this.LoRaDevice.AppKey); var appSKey = payload.CalculateKey(LoRaPayloadKeyType.AppSKey, appNonce, netid, devNonce, deviceAppKey); var nwkSKey = payload.CalculateKey(LoRaPayloadKeyType.NwkSkey, appNonce, netid, devNonce, deviceAppKey); var devAddr = payload.DevAddr; // if mic check failed, return false /* * if (!payload.CheckMic(BitConverter.ToString(nwkSKey).Replace("-", ""))) * { * return false; * } */ this.LoRaDevice.AppSKey = BitConverter.ToString(appSKey).Replace("-", string.Empty); this.LoRaDevice.NwkSKey = BitConverter.ToString(nwkSKey).Replace("-", string.Empty); this.NetId = BitConverter.ToString(netid).Replace("-", string.Empty); this.AppNonce = BitConverter.ToString(appNonce).Replace("-", string.Empty); this.LoRaDevice.DevAddr = BitConverter.ToString(devAddr.ToArray()).Replace("-", string.Empty); return(true); } catch { } return(false); }
public async Task When_Join_Fails_Due_To_GetTwin_Error_Second_Try_Should_Reload_Device_Twin(string deviceGatewayID) { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID)); var joinRequestPayload1 = simulatedDevice.CreateJoinRequest(); // Create Rxpk var joinRequestRxpk1 = joinRequestPayload1.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var joinRequestDevNonce1 = ConversionHelper.ByteArrayToString(joinRequestPayload1.DevNonce); var devAddr = string.Empty; var devEUI = simulatedDevice.LoRaDevice.DeviceID; var appEUI = simulatedDevice.LoRaDevice.AppEUI; // Device twin will be queried var twin = new Twin(); twin.Properties.Desired[TwinProperty.DevEUI] = devEUI; twin.Properties.Desired[TwinProperty.AppEUI] = simulatedDevice.LoRaDevice.AppEUI; twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey; twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID; twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder; this.LoRaDeviceClient.SetupSequence(x => x.GetTwinAsync()) .ReturnsAsync((Twin)null) .ReturnsAsync(twin); // Device twin will be updated string afterJoinAppSKey = null; string afterJoinNwkSKey = null; string afterJoinDevAddr = null; this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .Callback <TwinCollection>((updatedTwin) => { afterJoinAppSKey = updatedTwin[TwinProperty.AppSKey]; afterJoinNwkSKey = updatedTwin[TwinProperty.NwkSKey]; afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr]; }) .ReturnsAsync(true); // Lora device api will be search by devices with matching deveui, this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, joinRequestDevNonce1)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList())); // using factory to create mock of var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, this.NewMemoryCache(), this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); var messageProcessor = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); // 1st join request // Should fail var joinRequest1 = this.CreateWaitableRequest(joinRequestRxpk1); messageProcessor.DispatchRequest(joinRequest1); Assert.True(await joinRequest1.WaitCompleteAsync()); Assert.True(joinRequest1.ProcessingFailed); Assert.Null(joinRequest1.ResponseDownlink); // 2nd attempt var joinRequestPayload2 = simulatedDevice.CreateJoinRequest(); // will reload the device matched by deveui this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, ConversionHelper.ByteArrayToString(joinRequestPayload2.DevNonce))) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList())); var joinRequestRxpk2 = joinRequestPayload2.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var joinRequestDevNonce2 = LoRaTools.Utils.ConversionHelper.ByteArrayToString(joinRequestPayload2.DevNonce); var joinRequest2 = this.CreateWaitableRequest(joinRequestRxpk2); messageProcessor.DispatchRequest(joinRequest2); Assert.True(await joinRequest2.WaitCompleteAsync()); Assert.NotNull(joinRequest2.ResponseDownlink); Assert.True(joinRequest2.ProcessingSucceeded); Assert.Single(this.PacketForwarder.DownlinkMessages); var joinRequestDownlinkMessage2 = this.PacketForwarder.DownlinkMessages[0]; var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(joinRequestDownlinkMessage2.Txpk.Data), simulatedDevice.LoRaDevice.AppKey); Assert.Equal(joinAccept.DevAddr.ToArray(), ConversionHelper.StringToByteArray(afterJoinDevAddr)); var devicesForDevAddr = deviceRegistry.InternalGetCachedDevicesForDevAddr(afterJoinDevAddr); Assert.Single(devicesForDevAddr); // should have the single device Assert.True(devicesForDevAddr.TryGetValue(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(afterJoinDevAddr, 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); // should get twin 2x (1st failed) this.LoRaDeviceClient.Verify(x => x.GetTwinAsync(), Times.Exactly(2)); // should get device for join 2x this.LoRaDeviceApi.Verify(x => x.SearchAndLockForJoinAsync(ServerGatewayID, devEUI, appEUI, It.IsAny <string>())); this.LoRaDeviceClient.VerifyAll(); this.LoRaDeviceApi.VerifyAll(); }
/// <summary> /// Process OTAA join request /// </summary> async Task ProcessJoinRequestAsync(LoRaRequest request) { LoRaDevice loRaDevice = null; string devEUI = null; var loraRegion = request.Region; try { var timeWatcher = new LoRaOperationTimeWatcher(loraRegion, request.StartTime); var joinReq = (LoRaPayloadJoinRequest)request.Payload; byte[] udpMsgForPktForwarder = new byte[0]; devEUI = joinReq.GetDevEUIAsString(); var appEUI = joinReq.GetAppEUIAsString(); var devNonce = joinReq.GetDevNonceAsString(); Logger.Log(devEUI, $"join request received", LogLevel.Information); loRaDevice = await this.deviceRegistry.GetDeviceForJoinRequestAsync(devEUI, appEUI, devNonce); if (loRaDevice == null) { request.NotifyFailed(devEUI, LoRaDeviceRequestFailedReason.UnknownDevice); // we do not log here as we assume that the deviceRegistry does a more informed logging if returning null return; } if (string.IsNullOrEmpty(loRaDevice.AppKey)) { Logger.Log(loRaDevice.DevEUI, "join refused: missing AppKey for OTAA device", LogLevel.Error); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidJoinRequest); return; } if (loRaDevice.AppEUI != appEUI) { Logger.Log(devEUI, "join refused: AppEUI for OTAA does not match device", LogLevel.Error); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidJoinRequest); return; } if (!joinReq.CheckMic(loRaDevice.AppKey)) { Logger.Log(devEUI, "join refused: invalid MIC", LogLevel.Error); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.JoinMicCheckFailed); return; } // Make sure that is a new request and not a replay if (!string.IsNullOrEmpty(loRaDevice.DevNonce) && loRaDevice.DevNonce == devNonce) { if (string.IsNullOrEmpty(loRaDevice.GatewayID)) { Logger.Log(devEUI, "join refused: join already processed by another gateway", LogLevel.Information); } else { Logger.Log(devEUI, "join refused: DevNonce already used by this device", LogLevel.Error); } loRaDevice.IsOurDevice = false; request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.JoinDevNonceAlreadyUsed); return; } // Check that the device is joining through the linked gateway and not another if (!string.IsNullOrEmpty(loRaDevice.GatewayID) && !string.Equals(loRaDevice.GatewayID, this.configuration.GatewayID, StringComparison.InvariantCultureIgnoreCase)) { Logger.Log(devEUI, $"join refused: trying to join not through its linked gateway, ignoring join request", LogLevel.Information); loRaDevice.IsOurDevice = false; request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.HandledByAnotherGateway); return; } var netIdBytes = BitConverter.GetBytes(this.configuration.NetId); var netId = new byte[3] { netIdBytes[0], netIdBytes[1], netIdBytes[2] }; var appNonce = OTAAKeysGenerator.GetAppNonce(); var appNonceBytes = LoRaTools.Utils.ConversionHelper.StringToByteArray(appNonce); var appKeyBytes = LoRaTools.Utils.ConversionHelper.StringToByteArray(loRaDevice.AppKey); var appSKey = OTAAKeysGenerator.CalculateKey(new byte[1] { 0x02 }, appNonceBytes, netId, joinReq.DevNonce, appKeyBytes); var nwkSKey = OTAAKeysGenerator.CalculateKey(new byte[1] { 0x01 }, appNonceBytes, netId, joinReq.DevNonce, appKeyBytes); var devAddr = OTAAKeysGenerator.GetNwkId(netId); var oldDevAddr = loRaDevice.DevAddr; if (!timeWatcher.InTimeForJoinAccept()) { // in this case it's too late, we need to break and avoid saving twins Logger.Log(devEUI, $"join refused: processing of the join request took too long, sending no message", LogLevel.Information); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.ReceiveWindowMissed); return; } var updatedProperties = new LoRaDeviceJoinUpdateProperties { DevAddr = devAddr, NwkSKey = nwkSKey, AppSKey = appSKey, AppNonce = appNonce, DevNonce = devNonce, NetID = ConversionHelper.ByteArrayToString(netId), Region = request.Region.LoRaRegion, PreferredGatewayID = this.configuration.GatewayID, }; if (loRaDevice.ClassType == LoRaDeviceClassType.C) { updatedProperties.SavePreferredGateway = true; updatedProperties.SaveRegion = true; } var deviceUpdateSucceeded = await loRaDevice.UpdateAfterJoinAsync(updatedProperties); if (!deviceUpdateSucceeded) { Logger.Log(devEUI, $"join refused: join request could not save twin", LogLevel.Error); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.ApplicationError); return; } var windowToUse = timeWatcher.ResolveJoinAcceptWindowToUse(loRaDevice); if (windowToUse == Constants.INVALID_RECEIVE_WINDOW) { Logger.Log(devEUI, $"join refused: processing of the join request took too long, sending no message", LogLevel.Information); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.ReceiveWindowMissed); return; } double freq = 0; string datr = null; uint tmst = 0; if (windowToUse == Constants.RECEIVE_WINDOW_1) { datr = loraRegion.GetDownstreamDR(request.Rxpk); if (!loraRegion.TryGetUpstreamChannelFrequency(request.Rxpk, out freq) || datr == null) { Logger.Log(loRaDevice.DevEUI, "could not resolve DR and/or frequency for downstream", LogLevel.Error); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidRxpk); return; } // set tmst for the normal case tmst = request.Rxpk.Tmst + loraRegion.Join_accept_delay1 * 1000000; } else { Logger.Log(devEUI, $"processing of the join request took too long, using second join accept receive window", LogLevel.Debug); tmst = request.Rxpk.Tmst + loraRegion.Join_accept_delay2 * 1000000; (freq, datr) = loraRegion.GetDownstreamRX2DRAndFreq(devEUI, this.configuration.Rx2DataRate, this.configuration.Rx2DataFrequency, null); } loRaDevice.IsOurDevice = true; this.deviceRegistry.UpdateDeviceAfterJoin(loRaDevice, oldDevAddr); // Build join accept downlink message Array.Reverse(netId); Array.Reverse(appNonceBytes); // Build the DlSettings fields that is a superposition of RX2DR and RX1DROffset field byte[] dlSettings = new byte[1]; if (request.Region.DRtoConfiguration.ContainsKey(loRaDevice.DesiredRX2DataRate)) { dlSettings[0] = (byte)(loRaDevice.DesiredRX2DataRate & 0b00001111); } else { Logger.Log(devEUI, $"twin RX2 DR value is not within acceptable values", LogLevel.Error); } if (request.Region.IsValidRX1DROffset(loRaDevice.DesiredRX1DROffset)) { var rx1droffset = (byte)(loRaDevice.DesiredRX1DROffset << 4); dlSettings[0] = (byte)(dlSettings[0] + rx1droffset); } else { Logger.Log(devEUI, $"twin RX1 offset DR value is not within acceptable values", LogLevel.Error); } ushort rxDelay = 0; if (request.Region.IsValidRXDelay(loRaDevice.DesiredRXDelay)) { rxDelay = loRaDevice.DesiredRXDelay; } else { Logger.Log(devEUI, $"twin RX delay value is not within acceptable values", LogLevel.Error); } var loRaPayloadJoinAccept = new LoRaPayloadJoinAccept( LoRaTools.Utils.ConversionHelper.ByteArrayToString(netId), // NETID 0 / 1 is default test ConversionHelper.StringToByteArray(devAddr), // todo add device address management appNonceBytes, dlSettings, rxDelay, null); var joinAccept = loRaPayloadJoinAccept.Serialize(loRaDevice.AppKey, datr, freq, tmst, devEUI); if (joinAccept != null) { _ = request.PacketForwarder.SendDownstreamAsync(joinAccept); request.NotifySucceeded(loRaDevice, joinAccept); if (Logger.LoggerLevel <= LogLevel.Debug) { var jsonMsg = JsonConvert.SerializeObject(joinAccept); Logger.Log(devEUI, $"{LoRaMessageType.JoinAccept.ToString()} {jsonMsg}", LogLevel.Debug); } else if (Logger.LoggerLevel == LogLevel.Information) { Logger.Log(devEUI, "join accepted", LogLevel.Information); } } } catch (Exception ex) { var deviceId = devEUI ?? ConversionHelper.ByteArrayToString(request.Payload.DevAddr); Logger.Log(deviceId, $"failed to handle join request. {ex.Message}", LogLevel.Error); request.NotifyFailed(loRaDevice, ex); } }
private async Task <byte[]> ProcessJoinRequest(LoRaMessageWrapper loraMessage) { byte[] udpMsgForPktForwarder = new Byte[0]; LoraDeviceInfo joinLoraDeviceInfo; var joinReq = (LoRaPayloadJoinRequest)loraMessage.LoRaPayloadMessage; joinReq.DevEUI.Span.Reverse(); joinReq.AppEUI.Span.Reverse(); string devEui = ConversionHelper.ByteArrayToString(joinReq.DevEUI.ToArray()); string devNonce = ConversionHelper.ByteArrayToString(joinReq.DevNonce.ToArray()); Logger.Log(devEui, $"join request received", Logger.LoggingLevel.Info); //checking if this devnonce was already processed or the deveui was already refused Cache.TryGetValue(devEui, out joinLoraDeviceInfo); //check if join request is valid. //we have a join request in the cache if (joinLoraDeviceInfo != null) { //we query every time in case the device is turned one while not yet in the registry //it is not our device so ingore the join //if (!joinLoraDeviceInfo.IsOurDevice) //{ // Logger.Log(devEui, $"join request refused the device is not ours", Logger.LoggingLevel.Info); // return null; //} //is our device but the join was not valid if (!joinLoraDeviceInfo.IsJoinValid) { //if the devNonce is equal to the current it is a potential replay attck if (joinLoraDeviceInfo.DevNonce == devNonce) { Logger.Log(devEui, $"join request refused devNonce already used", Logger.LoggingLevel.Info); return(null); } //Check if the device is trying to join through the wrong gateway if (!String.IsNullOrEmpty(joinLoraDeviceInfo.GatewayID) && joinLoraDeviceInfo.GatewayID.ToUpper() != GatewayID.ToUpper()) { Logger.Log(devEui, $"trying to join not through its linked gateway, ignoring join request", Logger.LoggingLevel.Info); return(null); } } } joinLoraDeviceInfo = await LoraDeviceInfoManager.PerformOTAAAsync(GatewayID, devEui, ConversionHelper.ByteArrayToString(joinReq.AppEUI.ToArray()), devNonce, joinLoraDeviceInfo); if (joinLoraDeviceInfo != null && joinLoraDeviceInfo.IsJoinValid) { //TODO to be fixed by Mik //if (!loraMessage.LoRaPayloadMessage.CheckMic(joinLoraDeviceInfo.AppKey)) //{ // Logger.Log(devEui, $"join request MIC invalid", Logger.LoggingLevel.Info); // return null; //} //join request resets the frame counters joinLoraDeviceInfo.FCntUp = 0; joinLoraDeviceInfo.FCntDown = 0; //in this case it's too late, we need to break and awoid saving twins if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.join_accept_delay2 * 1000)) { Logger.Log(devEui, $"processing of the join request took too long, sending no message", Logger.LoggingLevel.Info); var physicalResponse = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null); return(physicalResponse.GetMessage()); } //update reported properties and frame Counter await joinLoraDeviceInfo.HubSender.UpdateReportedPropertiesOTAAasync(joinLoraDeviceInfo); byte[] appNonce = ConversionHelper.StringToByteArray(joinLoraDeviceInfo.AppNonce); byte[] netId = ConversionHelper.StringToByteArray(joinLoraDeviceInfo.NetId); byte[] devAddr = ConversionHelper.StringToByteArray(joinLoraDeviceInfo.DevAddr); string appKey = joinLoraDeviceInfo.AppKey; Array.Reverse(netId); Array.Reverse(appNonce); LoRaPayloadJoinAccept loRaPayloadJoinAccept = new LoRaPayloadJoinAccept( //NETID 0 / 1 is default test ConversionHelper.ByteArrayToString(netId), //todo add app key management appKey, //todo add device address management devAddr, appNonce, new byte[] { 0 }, new byte[] { 0 }, null ); var datr = RegionFactory.CurrentRegion.GetDownstreamDR(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]); uint rfch = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].rfch; double freq = RegionFactory.CurrentRegion.GetDownstreamChannel(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]); //set tmst for the normal case long tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.join_accept_delay1 * 1000000; ////in this case it's too late, we need to break //if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.join_accept_delay2 * 1000)) //{ // Logger.Log(devEui, $"processing of the join request took too long, sending no message", Logger.LoggingLevel.Info); // var physicalResponse = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PULL_RESP, null); // Logger.Log(devEui, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info); // return physicalResponse.GetMessage(); //} //in this case the second join windows must be used if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.join_accept_delay1 * 1000 - 100)) { Logger.Log(devEui, $"processing of the join request took too long, using second join accept receive window", Logger.LoggingLevel.Info); tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.join_accept_delay2 * 1000000; if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RX2_DATR"))) { Logger.Log(devEui, $"using standard second receive windows for join request", Logger.LoggingLevel.Info); //using EU fix DR for RX2 freq = RegionFactory.CurrentRegion.RX2DefaultReceiveWindows.frequency; datr = RegionFactory.CurrentRegion.DRtoConfiguration[RegionFactory.CurrentRegion.RX2DefaultReceiveWindows.dr].configuration; } //if specific twins are set, specify second channel to be as specified else { Logger.Log(devEui, $"using custom second receive windows for join request", Logger.LoggingLevel.Info); freq = double.Parse(Environment.GetEnvironmentVariable("RX2_FREQ")); datr = Environment.GetEnvironmentVariable("RX2_DATR"); } } LoRaMessageWrapper joinAcceptMessage = new LoRaMessageWrapper(loRaPayloadJoinAccept, LoRaMessageType.JoinAccept, loraMessage.PhysicalPayload.token, datr, 0, freq, tmst); udpMsgForPktForwarder = joinAcceptMessage.PhysicalPayload.GetMessage(); //add to cache for processing normal messages. This awoids one additional call to the server. Cache.AddToCache(joinLoraDeviceInfo.DevAddr, joinLoraDeviceInfo); Logger.Log(devEui, $"join accept sent", Logger.LoggingLevel.Info); } else { Logger.Log(devEui, $"join request refused", Logger.LoggingLevel.Info); } Logger.Log(devEui, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info); return(udpMsgForPktForwarder); }
private async Task <byte[]> ProcessJoinRequest(LoRaMessage loraMessage) { byte[] udpMsgForPktForwarder = new Byte[0]; LoraDeviceInfo joinLoraDeviceInfo; var joinReq = (LoRaPayloadJoinRequest)loraMessage.payloadMessage; Array.Reverse(joinReq.devEUI); Array.Reverse(joinReq.appEUI); string devEui = BitConverter.ToString(joinReq.devEUI).Replace("-", ""); string devNonce = BitConverter.ToString(joinReq.devNonce).Replace("-", ""); Logger.Log(devEui, $"join request received", Logger.LoggingLevel.Info); //checking if this devnonce was already processed or the deveui was already refused Cache.TryGetValue(devEui, out joinLoraDeviceInfo); //we have a join request in the cache if (joinLoraDeviceInfo != null) { //it is not our device so ingore the join if (!joinLoraDeviceInfo.IsOurDevice) { Logger.Log(devEui, $"join request refused the device is not ours", Logger.LoggingLevel.Info); return(null); } //is our device but the join was not valid else if (!joinLoraDeviceInfo.IsJoinValid) { //if the devNonce is equal to the current it is a potential replay attck if (joinLoraDeviceInfo.DevNonce == devNonce) { Logger.Log(devEui, $"join request refused devNonce already used", Logger.LoggingLevel.Info); return(null); } //Check if the device is trying to join through the wrong gateway if (!String.IsNullOrEmpty(joinLoraDeviceInfo.GatewayID) && joinLoraDeviceInfo.GatewayID.ToUpper() != GatewayID.ToUpper()) { Logger.Log(devEui, $"trying to join not through its linked gateway, ignoring join request", Logger.LoggingLevel.Info); return(null); } } } joinLoraDeviceInfo = await LoraDeviceInfoManager.PerformOTAAAsync(GatewayID, devEui, BitConverter.ToString(joinReq.appEUI).Replace("-", ""), devNonce); if (joinLoraDeviceInfo.IsJoinValid) { byte[] appNonce = StringToByteArray(joinLoraDeviceInfo.AppNonce); byte[] netId = StringToByteArray(joinLoraDeviceInfo.NetId); byte[] devAddr = StringToByteArray(joinLoraDeviceInfo.DevAddr); string appKey = joinLoraDeviceInfo.AppKey; Array.Reverse(netId); Array.Reverse(appNonce); LoRaPayloadJoinAccept loRaPayloadJoinAccept = new LoRaPayloadJoinAccept( //NETID 0 / 1 is default test BitConverter.ToString(netId).Replace("-", ""), //todo add app key management appKey, //todo add device address management devAddr, appNonce ); var _datr = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].datr; uint _rfch = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].rfch; double _freq = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].freq; //set tmst for the normal case long _tmst = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].tmst + 5000000; //uncomment to force second windows usage //Thread.Sleep(4600-(int)(DateTime.Now - startTimeProcessing).TotalMilliseconds); //in this case it's too late, we need to break if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(6000)) { Logger.Log(devEui, $"processing of the join request took too long, sending no message", Logger.LoggingLevel.Info); var physicalResponse = new PhysicalPayload(loraMessage.physicalPayload.token, PhysicalIdentifier.PULL_RESP, null); return(physicalResponse.GetMessage()); } //in this case the second join windows must be used else if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(4500)) { Logger.Log(devEui, $"processing of the join request took too long, using second join accept receive window", Logger.LoggingLevel.Info); _tmst = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].tmst + 6000000; if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RX2_DATR"))) { Logger.Log(devEui, $"using standard second receive windows for join request", Logger.LoggingLevel.Info); //using EU fix DR for RX2 _freq = 869.525; _datr = "SF12BW125"; } //if specific twins are set, specify second channel to be as specified else { Logger.Log(devEui, $"using custom DR second receive windows for join request", Logger.LoggingLevel.Info); _freq = double.Parse(Environment.GetEnvironmentVariable("RX2_FREQ")); _datr = Environment.GetEnvironmentVariable("RX2_DATR"); } } LoRaMessage joinAcceptMessage = new LoRaMessage(loRaPayloadJoinAccept, LoRaMessageType.JoinAccept, loraMessage.physicalPayload.token, _datr, 0, _freq, _tmst); udpMsgForPktForwarder = joinAcceptMessage.physicalPayload.GetMessage(); joinLoraDeviceInfo.HubSender = new IoTHubSender(joinLoraDeviceInfo.DevEUI, joinLoraDeviceInfo.PrimaryKey); //open the connection to iot hub without waiting for perf optimization in case of the device start sending a msg just after join request _ = joinLoraDeviceInfo.HubSender.OpenAsync(); //join request resets the frame counters joinLoraDeviceInfo.FCntUp = 0; joinLoraDeviceInfo.FCntDown = 0; //update the frame counter on the server _ = joinLoraDeviceInfo.HubSender.UpdateFcntAsync(joinLoraDeviceInfo.FCntUp, joinLoraDeviceInfo.FCntDown); //add to cache for processing normal messages. This awoids one additional call to the server. Cache.AddToCache(joinLoraDeviceInfo.DevAddr, joinLoraDeviceInfo); Logger.Log(devEui, $"join accept sent", Logger.LoggingLevel.Info); } //add to cache to avoid replay attack, btw server side does the check too. Cache.AddToCache(devEui, joinLoraDeviceInfo); return(udpMsgForPktForwarder); }
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_Device_Is_Found_In_Api_Should_Update_Twin_And_Return() { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: this.ServerConfiguration.GatewayID)); simulatedDevice.LoRaDevice.NwkSKey = string.Empty; simulatedDevice.LoRaDevice.AppSKey = string.Empty; var joinRequest = simulatedDevice.CreateJoinRequest(); // Create Rxpk var rxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce); var devEUI = simulatedDevice.LoRaDevice.DeviceID; var appEUI = simulatedDevice.LoRaDevice.AppEUI; this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(It.IsNotNull <string>(), devEUI, appEUI, devNonce)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(string.Empty, devEUI, "123").AsList())); // Ensure that the device twin was updated this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.Is <TwinCollection>((t) => t.Contains(TwinProperty.DevAddr) && t.Contains(TwinProperty.FCntDown)))) .ReturnsAsync(true); this.LoRaDeviceClient.Setup(x => x.GetTwinAsync()) .ReturnsAsync(simulatedDevice.CreateOTAATwin()); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, this.NewMemoryCache(), this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); // Send to message processor var messageProcessor = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); var request = this.CreateWaitableRequest(rxpk); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); Assert.NotNull(request.ResponseDownlink); Assert.Single(this.PacketForwarder.DownlinkMessages); var pktFwdMessage = this.PacketForwarder.DownlinkMessages[0]; Assert.NotNull(pktFwdMessage.Txpk); var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(pktFwdMessage.Txpk.Data), simulatedDevice.AppKey); var joinedDeviceDevAddr = ConversionHelper.ByteArrayToString(joinAccept.DevAddr); var cachedDevices = deviceRegistry.InternalGetCachedDevicesForDevAddr(joinedDeviceDevAddr); Assert.Single(cachedDevices); Assert.True(cachedDevices.TryGetValue(devEUI, out var loRaDevice)); Assert.Equal(joinAccept.DevAddr.ToArray(), this.ByteArray(loRaDevice.DevAddr).ToArray()); // Device properties were set with the computes values of the join operation Assert.Equal(joinAccept.AppNonce.ToArray(), this.ReversedByteArray(loRaDevice.AppNonce).ToArray()); Assert.NotEmpty(loRaDevice.NwkSKey); Assert.NotEmpty(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 this.LoRaDeviceClient.VerifyAll(); this.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(); // Create Rxpk var rxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var payloadDecoder = new Mock <ILoRaPayloadDecoder>(); var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce); var devAddr = string.Empty; var devEUI = simulatedDevice.LoRaDevice.DeviceID; var appEUI = simulatedDevice.LoRaDevice.AppEUI; var loRaDeviceClient = new Mock <ILoRaDeviceClient>(MockBehavior.Strict); // Device twin will be queried var twin = new Twin(); twin.Properties.Desired[TwinProperty.DevEUI] = devEUI; twin.Properties.Desired[TwinProperty.AppEUI] = simulatedDevice.LoRaDevice.AppEUI; twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey; twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID; twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder; loRaDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(twin); // Device twin will be updated string afterJoinAppSKey = null; string afterJoinNwkSKey = null; string afterJoinDevAddr = null; loRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .Callback <TwinCollection>((updatedTwin) => { afterJoinAppSKey = updatedTwin[TwinProperty.AppSKey]; afterJoinNwkSKey = updatedTwin[TwinProperty.NwkSKey]; afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr]; }) .ReturnsAsync(true); // Lora device api will be search by devices with matching deveui, var loRaDeviceApi = new Mock <LoRaDeviceAPIServiceBase>(MockBehavior.Strict); loRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, devNonce)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList())); var loRaDeviceFactory = new TestLoRaDeviceFactory(loRaDeviceClient.Object); var memoryCache = new MemoryCache(new MemoryCacheOptions()); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, loRaDeviceApi.Object, loRaDeviceFactory); // Setup frame counter strategy for FrameCounterLoRaDeviceInitializer if (deviceGatewayID == null) { this.FrameCounterUpdateStrategyFactory.Setup(x => x.GetMultiGatewayStrategy()) .Returns(this.FrameCounterUpdateStrategy.Object); } else { this.FrameCounterUpdateStrategyFactory.Setup(x => x.GetSingleGatewayStrategy()) .Returns(this.FrameCounterUpdateStrategy.Object); } // Send to message processor var messageProcessor = new MessageProcessor( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyFactory.Object, payloadDecoder.Object); var downlinkMessage = await messageProcessor.ProcessMessageAsync(rxpk); Assert.NotNull(downlinkMessage); var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey); Assert.Equal(joinAccept.DevAddr.ToArray(), ConversionHelper.StringToByteArray(afterJoinDevAddr)); // check that the device is in cache Assert.True(memoryCache.TryGetValue <DevEUIToLoRaDeviceDictionary>(afterJoinDevAddr, out var cachedDevices)); Assert.True(cachedDevices.TryGetValue(devEUI, out var cachedDevice)); Assert.Equal(afterJoinAppSKey, cachedDevice.AppSKey); Assert.Equal(afterJoinNwkSKey, cachedDevice.NwkSKey); Assert.Equal(afterJoinDevAddr, cachedDevice.DevAddr); Assert.True(cachedDevice.IsOurDevice); if (deviceGatewayID == null) { Assert.Null(cachedDevice.GatewayID); } else { Assert.Equal(deviceGatewayID, cachedDevice.GatewayID); } // fcnt is restarted Assert.Equal(0, cachedDevice.FCntUp); Assert.Equal(0, cachedDevice.FCntDown); Assert.False(cachedDevice.HasFrameCountChanges); }
public async Task Join_And_Send_Unconfirmed_And_Confirmed_Messages(string deviceGatewayID, int initialFcntUp, int initialFcntDown, int startingPayloadFcnt, uint netId) { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID)); var joinRequest = simulatedDevice.CreateJoinRequest(); // Create Rxpk var joinRxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var devNonce = LoRaTools.Utils.ConversionHelper.ByteArrayToString(joinRequest.DevNonce); var devAddr = string.Empty; var devEUI = simulatedDevice.LoRaDevice.DeviceID; var appEUI = simulatedDevice.LoRaDevice.AppEUI; var loRaDeviceClient = new Mock <ILoRaDeviceClient>(MockBehavior.Strict); this.ServerConfiguration.NetId = netId; // Device twin will be queried var twin = new Twin(); twin.Properties.Desired[TwinProperty.DevEUI] = devEUI; twin.Properties.Desired[TwinProperty.AppEUI] = simulatedDevice.LoRaDevice.AppEUI; twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey; 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()).ReturnsAsync(twin); // Device twin will be updated string afterJoinAppSKey = null; string afterJoinNwkSKey = null; string afterJoinDevAddr = null; int afterJoinFcntDown = -1; int afterJoinFcntUp = -1; loRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .Callback <TwinCollection>((updatedTwin) => { afterJoinAppSKey = updatedTwin[TwinProperty.AppSKey]; afterJoinNwkSKey = updatedTwin[TwinProperty.NwkSKey]; afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr]; afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown]; afterJoinFcntUp = updatedTwin[TwinProperty.FCntUp]; }) .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, var loRaDeviceApi = new Mock <LoRaDeviceAPIServiceBase>(MockBehavior.Strict); loRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, 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, this.ServerConfiguration.GatewayID)) .ReturnsAsync((ushort)1); } // using factory to create mock of var loRaDeviceFactory = new TestLoRaDeviceFactory(loRaDeviceClient.Object); var memoryCache = new MemoryCache(new MemoryCacheOptions()); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, loRaDeviceApi.Object, loRaDeviceFactory); var frameCounterUpdateStrategyFactory = new LoRaDeviceFrameCounterUpdateStrategyFactory(this.ServerConfiguration.GatewayID, loRaDeviceApi.Object); // Send to message processor var messageProcessor = new MessageProcessor( this.ServerConfiguration, deviceRegistry, frameCounterUpdateStrategyFactory, new LoRaPayloadDecoder()); var downlinkJoinAcceptMessage = await messageProcessor.ProcessMessageAsync(joinRxpk); Assert.NotNull(downlinkJoinAcceptMessage); var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkJoinAcceptMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey); Assert.Equal(joinAccept.DevAddr.ToArray(), ConversionHelper.StringToByteArray(afterJoinDevAddr)); // check that the device is in cache var devicesForDevAddr = deviceRegistry.InternalGetCachedDevicesForDevAddr(afterJoinDevAddr); Assert.Single(devicesForDevAddr); // should have the single device Assert.True(devicesForDevAddr.TryGetValue(devEUI, out var loRaDevice)); Assert.Equal(afterJoinAppSKey, loRaDevice.AppSKey); Assert.Equal(afterJoinNwkSKey, loRaDevice.NwkSKey); Assert.Equal(afterJoinDevAddr, loRaDevice.DevAddr); var netIdBytes = BitConverter.GetBytes(netId); Assert.Equal((uint)(netIdBytes[0] & 0b01111111), NetIdHelper.GetNwkIdPart(afterJoinDevAddr)); if (deviceGatewayID == null) { Assert.Null(loRaDevice.GatewayID); } else { Assert.Equal(deviceGatewayID, loRaDevice.GatewayID); } // fcnt is restarted Assert.Equal(0, afterJoinFcntDown); Assert.Equal(0, afterJoinFcntUp); Assert.Equal(0, loRaDevice.FCntUp); Assert.Equal(0, loRaDevice.FCntDown); Assert.False(loRaDevice.HasFrameCountChanges); simulatedDevice.LoRaDevice.AppSKey = afterJoinAppSKey; simulatedDevice.LoRaDevice.NwkSKey = afterJoinNwkSKey; simulatedDevice.LoRaDevice.DevAddr = afterJoinDevAddr; // sends unconfirmed message var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("100", fcnt: startingPayloadFcnt); var unconfirmedMessageResult = await messageProcessor.ProcessMessageAsync(unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0]); Assert.Null(unconfirmedMessageResult); // fcnt up was updated Assert.Equal(startingPayloadFcnt, loRaDevice.FCntUp); Assert.Equal(0, loRaDevice.FCntDown); 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); var confirmedMessageRxpk = confirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0]; var confirmedMessage = await messageProcessor.ProcessMessageAsync(confirmedMessageRxpk); Assert.NotNull(confirmedMessage); Assert.NotNull(confirmedMessage.Txpk); Assert.Equal(2, sentTelemetry.Count); // validates txpk according to eu region Assert.Equal(RegionFactory.CreateEU868Region().GetDownstreamChannelFrequency(confirmedMessageRxpk), confirmedMessage.Txpk.Freq); Assert.Equal("4/5", confirmedMessage.Txpk.Codr); Assert.False(confirmedMessage.Txpk.Imme); Assert.True(confirmedMessage.Txpk.Ipol); Assert.Equal("LORA", confirmedMessage.Txpk.Modu); // fcnt up was updated Assert.Equal(startingPayloadFcnt + 1, loRaDevice.FCntUp); Assert.Equal(1, loRaDevice.FCntDown); // Frame change flag will be set, only saving every 10 messages Assert.True(loRaDevice.HasFrameCountChanges); // C2D message will be checked twice loRaDeviceClient.Verify(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()), Times.Exactly(2)); // has telemetry with both fcnt Assert.Single(sentTelemetry, (t) => t.Fcnt == startingPayloadFcnt); Assert.Single(sentTelemetry, (t) => t.Fcnt == (startingPayloadFcnt + 1)); loRaDeviceClient.VerifyAll(); loRaDeviceApi.VerifyAll(); }
public async Task When_Join_With_Custom_Join_Update_Old_Desired_Properties() { var beforeJoinValues = 2; var afterJoinValues = 3; int reportedBeforeJoinRx1DROffsetValue = 0; int reportedBeforeJoinRx2DRValue = 0; int reportedBeforeJoinRxDelayValue = 0; string deviceGatewayID = ServerGatewayID; var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID)); string afterJoinAppSKey = null; string afterJoinNwkSKey = null; string afterJoinDevAddr = null; int afterJoinFcntDown = -1; int afterJoinFcntUp = -1; var devAddr = string.Empty; var devEUI = simulatedDevice.LoRaDevice.DeviceID; var appEUI = simulatedDevice.LoRaDevice.AppEUI; // message will be sent var sentTelemetry = new List <LoRaDeviceTelemetry>(); this.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 this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); // Device twin will be queried var twin = new Twin(); twin.Properties.Desired[TwinProperty.DevEUI] = devEUI; twin.Properties.Desired[TwinProperty.AppEUI] = simulatedDevice.LoRaDevice.AppEUI; twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey; twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID; twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder; twin.Properties.Desired[TwinProperty.RX2DataRate] = afterJoinValues; twin.Properties.Desired[TwinProperty.RX1DROffset] = afterJoinValues; twin.Properties.Desired[TwinProperty.RXDelay] = afterJoinValues; this.LoRaDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(twin); this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .Callback <TwinCollection>((updatedTwin) => { if (updatedTwin.Contains(TwinProperty.AppSKey)) { afterJoinAppSKey = updatedTwin[TwinProperty.AppSKey].Value; afterJoinNwkSKey = updatedTwin[TwinProperty.NwkSKey].Value; afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr].Value; afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown].Value; afterJoinFcntUp = updatedTwin[TwinProperty.FCntUp].Value; } else { reportedBeforeJoinRx1DROffsetValue = updatedTwin[TwinProperty.RX1DROffset].Value; reportedBeforeJoinRx2DRValue = updatedTwin[TwinProperty.RX2DataRate].Value; reportedBeforeJoinRxDelayValue = updatedTwin[TwinProperty.RXDelay].Value; } }) .ReturnsAsync(true); // create a state before the join var startingTwin = new TwinCollection(); startingTwin[TwinProperty.RX2DataRate] = beforeJoinValues; startingTwin[TwinProperty.RX1DROffset] = beforeJoinValues; startingTwin[TwinProperty.RXDelay] = beforeJoinValues; await this.LoRaDeviceClient.Object.UpdateReportedPropertiesAsync(startingTwin); var memoryCache = new MemoryCache(new MemoryCacheOptions()); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); var loRaDeviceFactory = new TestLoRaDeviceFactory(this.LoRaDeviceClient.Object); // Send to message processor var messageProcessor = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); var joinRequest = simulatedDevice.CreateJoinRequest(); // Create Rxpk var rxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce); // Lora device api will be search by devices with matching deveui, this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, devNonce)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList())); var request = this.CreateWaitableRequest(rxpk); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); Assert.NotNull(request.ResponseDownlink); twin.Properties.Desired[TwinProperty.RX2DataRate] = 3; await Task.Delay(TimeSpan.FromMilliseconds(10)); var downlinkMessage = this.PacketForwarder.DownlinkMessages[0]; var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey); joinAccept.DlSettings.Span.Reverse(); Assert.Equal(afterJoinValues, joinAccept.Rx2Dr); Assert.Equal(afterJoinValues, joinAccept.Rx1DrOffset); Assert.Equal(beforeJoinValues, reportedBeforeJoinRx1DROffsetValue); Assert.Equal(beforeJoinValues, reportedBeforeJoinRx2DRValue); Assert.Equal(afterJoinValues, (int)joinAccept.RxDelay.Span[0]); Assert.Equal(beforeJoinValues, reportedBeforeJoinRxDelayValue); }
public async Task When_Getting_Custom_RX2_DR_From_Twin_Returns_JoinAccept_With_Correct_Settings_And_Behaves_Correctly(DataRateIndex rx2datarate) { var deviceGatewayID = ServerGatewayID; var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID)); var joinRequest = simulatedDevice.CreateJoinRequest(); AppSessionKey? afterJoinAppSKey = null; NetworkSessionKey?afterJoinNwkSKey = null; string afterJoinDevAddr = null; var afterJoinFcntDown = -1; var afterJoinFcntUp = -1; uint startingPayloadFcnt = 0; var devAddr = (DevAddr?)null; var devEui = simulatedDevice.LoRaDevice.DevEui; // 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); // 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; twin.Properties.Desired[TwinProperty.RX2DataRate] = rx2datarate; twin.Properties.Desired[TwinProperty.PreferredWindow] = 2; LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)).ReturnsAsync(twin); 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].Value; afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown].Value; afterJoinFcntUp = updatedTwin[TwinProperty.FCntUp].Value; }) .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); if (rx2datarate is > DR0 and < DR8) { Assert.Equal(rx2datarate, joinAccept.Rx2Dr); }
public async Task When_Getting_RXDelay_Offset_From_Twin_Returns_JoinAccept_With_Correct_Settings_And_Behaves_Correctly(int rxDelay, uint expectedDelay) { string deviceGatewayID = ServerGatewayID; var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID)); var joinRequest = simulatedDevice.CreateJoinRequest(); string afterJoinAppSKey = null; string afterJoinNwkSKey = null; string afterJoinDevAddr = null; int afterJoinFcntDown = -1; int afterJoinFcntUp = -1; uint startingPayloadFcnt = 0; // Create Rxpk var rxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce); var devAddr = string.Empty; var devEUI = simulatedDevice.LoRaDevice.DeviceID; var appEUI = simulatedDevice.LoRaDevice.AppEUI; // message will be sent var sentTelemetry = new List <LoRaDeviceTelemetry>(); this.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 this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); // Device twin will be queried var twin = new Twin(); twin.Properties.Desired[TwinProperty.DevEUI] = devEUI; twin.Properties.Desired[TwinProperty.AppEUI] = simulatedDevice.LoRaDevice.AppEUI; twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey; twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID; twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder; twin.Properties.Desired[TwinProperty.RXDelay] = rxDelay; twin.Properties.Desired[TwinProperty.PreferredWindow] = 1; this.LoRaDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(twin); this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .Callback <TwinCollection>((updatedTwin) => { afterJoinAppSKey = updatedTwin[TwinProperty.AppSKey].Value; afterJoinNwkSKey = updatedTwin[TwinProperty.NwkSKey].Value; afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr].Value; afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown].Value; afterJoinFcntUp = updatedTwin[TwinProperty.FCntUp].Value; }) .ReturnsAsync(true); // Lora device api will be search by devices with matching deveui, this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, devNonce)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList())); var loRaDeviceFactory = new TestLoRaDeviceFactory(this.LoRaDeviceClient.Object); var memoryCache = new MemoryCache(new MemoryCacheOptions()); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); // Send to message processor var messageProcessor = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); var request = this.CreateWaitableRequest(rxpk); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); Assert.NotNull(request.ResponseDownlink); Assert.Single(this.PacketForwarder.DownlinkMessages); var downlinkMessage = this.PacketForwarder.DownlinkMessages[0]; var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey); joinAccept.RxDelay.Span.Reverse(); if (rxDelay > 0 && rxDelay < 16) { Assert.Equal((int)expectedDelay, (int)joinAccept.RxDelay.Span[0]); } else { Assert.Equal(0, (int)joinAccept.RxDelay.Span[0]); } // Send a message simulatedDevice.LoRaDevice.AppSKey = afterJoinAppSKey; simulatedDevice.LoRaDevice.NwkSKey = afterJoinNwkSKey; simulatedDevice.LoRaDevice.DevAddr = afterJoinDevAddr; // sends confirmed message var confirmedMessagePayload = simulatedDevice.CreateConfirmedDataUpMessage("200", fcnt: startingPayloadFcnt + 1); var confirmedMessageRxpk = confirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0]; var confirmedRequest = this.CreateWaitableRequest(confirmedMessageRxpk); messageProcessor.DispatchRequest(confirmedRequest); Assert.True(await confirmedRequest.WaitCompleteAsync()); Assert.True(confirmedRequest.ProcessingSucceeded); Assert.NotNull(confirmedRequest.ResponseDownlink); Assert.NotNull(confirmedRequest.ResponseDownlink.Txpk); Assert.Equal(2, this.PacketForwarder.DownlinkMessages.Count); var downstreamMessage = this.PacketForwarder.DownlinkMessages[1]; // Message was sent on RX1 with correct delay and with a correct datarate offset if (rxDelay > 0 && rxDelay < 16) { Assert.Equal(expectedDelay * 1000000, downstreamMessage.Txpk.Tmst - confirmedMessageRxpk.Tmst); } else { Assert.Equal(expectedDelay * 1000000, downstreamMessage.Txpk.Tmst - confirmedMessageRxpk.Tmst); } }
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 Join_And_Send_Unconfirmed_And_Confirmed_Messages(string deviceGatewayID, uint initialFcntUp, uint initialFcntDown, uint startingPayloadFcnt, uint netId) { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID)); var joinRequestPayload = simulatedDevice.CreateJoinRequest(); // Create Rxpk var joinRxpk = joinRequestPayload.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var devNonce = LoRaTools.Utils.ConversionHelper.ByteArrayToString(joinRequestPayload.DevNonce); var devAddr = string.Empty; var devEUI = simulatedDevice.LoRaDevice.DeviceID; var appEUI = simulatedDevice.LoRaDevice.AppEUI; this.ServerConfiguration.NetId = netId; // Device twin will be queried var twin = new Twin(); twin.Properties.Desired[TwinProperty.DevEUI] = devEUI; twin.Properties.Desired[TwinProperty.AppEUI] = simulatedDevice.LoRaDevice.AppEUI; twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey; 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; this.LoRaDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(twin); // Device twin will be updated string afterJoinAppSKey = null; string afterJoinNwkSKey = null; string afterJoinDevAddr = null; uint afterJoinFcntDown = 0; uint afterJoinFcntUp = 0; this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .Callback <TwinCollection>((updatedTwin) => { afterJoinAppSKey = updatedTwin[TwinProperty.AppSKey]; afterJoinNwkSKey = updatedTwin[TwinProperty.NwkSKey]; afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr]; afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown]; afterJoinFcntUp = updatedTwin[TwinProperty.FCntUp]; // should not save class C device properties Assert.False(updatedTwin.Contains(TwinProperty.Region)); Assert.False(updatedTwin.Contains(TwinProperty.PreferredGatewayID)); }) .ReturnsAsync(true); // message will be sent var sentTelemetry = new List <LoRaDeviceTelemetry>(); this.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 this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); // Lora device api will be search by devices with matching deveui, this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, 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)) { this.LoRaDeviceApi.Setup(x => x.NextFCntDownAsync(devEUI, 0, startingPayloadFcnt + 1, this.ServerConfiguration.GatewayID)) .ReturnsAsync((ushort)1); this.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 var memoryCache = new MemoryCache(new MemoryCacheOptions()); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); // Send to message processor var messageProcessor = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); var joinRequest = this.CreateWaitableRequest(joinRxpk, constantElapsedTime: TimeSpan.FromMilliseconds(300)); messageProcessor.DispatchRequest(joinRequest); Assert.True(await joinRequest.WaitCompleteAsync()); Assert.True(joinRequest.ProcessingSucceeded); Assert.NotNull(joinRequest.ResponseDownlink); Assert.Single(this.PacketForwarder.DownlinkMessages); var downlinkJoinAcceptMessage = this.PacketForwarder.DownlinkMessages[0]; var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkJoinAcceptMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey); Assert.Equal(joinAccept.DevAddr.ToArray(), ConversionHelper.StringToByteArray(afterJoinDevAddr)); // check that the device is in cache var devicesForDevAddr = deviceRegistry.InternalGetCachedDevicesForDevAddr(afterJoinDevAddr); Assert.Single(devicesForDevAddr); // should have the single device Assert.True(devicesForDevAddr.TryGetValue(devEUI, out var loRaDevice)); Assert.Equal(afterJoinAppSKey, loRaDevice.AppSKey); Assert.Equal(afterJoinNwkSKey, loRaDevice.NwkSKey); Assert.Equal(afterJoinDevAddr, loRaDevice.DevAddr); var netIdBytes = BitConverter.GetBytes(netId); Assert.Equal((uint)(netIdBytes[0] & 0b01111111), NetIdHelper.GetNwkIdPart(afterJoinDevAddr)); if (deviceGatewayID == null) { Assert.Null(loRaDevice.GatewayID); } else { Assert.Equal(deviceGatewayID, loRaDevice.GatewayID); } // 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 = afterJoinDevAddr; // sends unconfirmed message var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("100", fcnt: startingPayloadFcnt); var unconfirmedRequest = this.CreateWaitableRequest(unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0], 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 (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); var confirmedMessageRxpk = confirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0]; var confirmedRequest = this.CreateWaitableRequest(confirmedMessageRxpk, constantElapsedTime: TimeSpan.FromMilliseconds(300)); messageProcessor.DispatchRequest(confirmedRequest); Assert.True(await confirmedRequest.WaitCompleteAsync()); Assert.True(confirmedRequest.ProcessingSucceeded); Assert.NotNull(confirmedRequest.ResponseDownlink); Assert.NotNull(confirmedRequest.ResponseDownlink.Txpk); Assert.Equal(2, this.PacketForwarder.DownlinkMessages.Count); Assert.Equal(2, sentTelemetry.Count); var downstreamMessage = this.PacketForwarder.DownlinkMessages[1]; // validates txpk according to eu region Assert.True(RegionManager.EU868.TryGetDownstreamChannelFrequency(confirmedMessageRxpk, out double frequency)); Assert.Equal(frequency, downstreamMessage.Txpk.Freq); Assert.Equal("4/5", downstreamMessage.Txpk.Codr); Assert.False(downstreamMessage.Txpk.Imme); Assert.True(downstreamMessage.Txpk.Ipol); Assert.Equal("LORA", downstreamMessage.Txpk.Modu); // 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 this.LoRaDeviceClient.Verify(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()), Times.Exactly(2)); // has telemetry with both fcnt Assert.Single(sentTelemetry, (t) => t.Fcnt == startingPayloadFcnt); Assert.Single(sentTelemetry, (t) => t.Fcnt == (startingPayloadFcnt + 1)); this.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(); }
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_Device_Is_Found_In_Api_Should_Update_Twin_And_Return() { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: this.ServerConfiguration.GatewayID)); simulatedDevice.LoRaDevice.NwkSKey = string.Empty; simulatedDevice.LoRaDevice.AppSKey = string.Empty; var joinRequest = simulatedDevice.CreateJoinRequest(); var loRaDeviceClient = new Mock <ILoRaDeviceClient>(); var loRaDevice = TestUtils.CreateFromSimulatedDevice(simulatedDevice, loRaDeviceClient.Object); loRaDevice.SetFcntDown(10); loRaDevice.SetFcntUp(20); // Create Rxpk var rxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var payloadDecoder = new Mock <ILoRaPayloadDecoder>(); var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce); var devEUI = simulatedDevice.LoRaDevice.DeviceID; var appEUI = simulatedDevice.LoRaDevice.AppEUI; this.LoRaDeviceRegistry.Setup(x => x.GetDeviceForJoinRequestAsync(devEUI, appEUI, devNonce)) .ReturnsAsync(() => loRaDevice); this.LoRaDeviceRegistry.Setup(x => x.UpdateDeviceAfterJoin(loRaDevice)); // Ensure that the device twin was updated loRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.Is <TwinCollection>((t) => t.Contains(TwinProperty.DevAddr) && t.Contains(TwinProperty.FCntDown)))) .ReturnsAsync(true); // Send to message processor var messageProcessor = new MessageProcessor( this.ServerConfiguration, this.LoRaDeviceRegistry.Object, this.FrameCounterUpdateStrategyFactory.Object, payloadDecoder.Object); var actual = await messageProcessor.ProcessMessageAsync(rxpk); Assert.NotNull(actual); var pktFwdMessage = actual.GetPktFwdMessage(); Assert.NotNull(pktFwdMessage.Txpk); var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(pktFwdMessage.Txpk.Data), loRaDevice.AppKey); Assert.Equal(joinAccept.DevAddr.ToArray(), this.ByteArray(loRaDevice.DevAddr).ToArray()); // Device properties were set with the computes values of the join operation Assert.Equal(joinAccept.AppNonce.ToArray(), this.ReversedByteArray(loRaDevice.AppNonce).ToArray()); Assert.NotEmpty(loRaDevice.NwkSKey); Assert.NotEmpty(loRaDevice.AppSKey); Assert.True(loRaDevice.IsOurDevice); // Device frame counts were reset Assert.Equal(0, loRaDevice.FCntDown); Assert.Equal(0, loRaDevice.FCntUp); Assert.False(loRaDevice.HasFrameCountChanges); // Twin property were updated loRaDeviceClient.VerifyAll(); }
internal async Task ProcessJoinRequestAsync(LoRaRequest request) { LoRaDevice loRaDevice = null; var loraRegion = request.Region; try { var timeWatcher = request.GetTimeWatcher(); var processingTimeout = timeWatcher.GetRemainingTimeToJoinAcceptSecondWindow() - TimeSpan.FromMilliseconds(100); using var joinAcceptCancellationToken = new CancellationTokenSource(processingTimeout > TimeSpan.Zero ? processingTimeout : TimeSpan.Zero); var joinReq = (LoRaPayloadJoinRequest)request.Payload; var devEui = joinReq.DevEUI; using var scope = this.logger.BeginDeviceScope(devEui); this.logger.LogInformation("join request received"); if (this.concentratorDeduplication.CheckDuplicateJoin(request) is ConcentratorDeduplicationResult.Duplicate) { request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.DeduplicationDrop); // we do not log here as the concentratorDeduplication service already does more detailed logging return; } loRaDevice = await this.deviceRegistry.GetDeviceForJoinRequestAsync(devEui, joinReq.DevNonce); if (loRaDevice == null) { request.NotifyFailed(devEui.ToString(), LoRaDeviceRequestFailedReason.UnknownDevice); // we do not log here as we assume that the deviceRegistry does a more informed logging if returning null return; } if (loRaDevice.AppKey is null) { this.logger.LogError("join refused: missing AppKey for OTAA device"); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidJoinRequest); return; } var appKey = loRaDevice.AppKey.Value; this.joinRequestCounter?.Add(1); if (loRaDevice.AppEui != joinReq.AppEui) { this.logger.LogError("join refused: AppEUI for OTAA does not match device"); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidJoinRequest); return; } if (!joinReq.CheckMic(appKey)) { this.logger.LogError("join refused: invalid MIC"); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.JoinMicCheckFailed); return; } // Make sure that is a new request and not a replay if (loRaDevice.DevNonce is { } devNonce&& devNonce == joinReq.DevNonce) { if (string.IsNullOrEmpty(loRaDevice.GatewayID)) { this.logger.LogInformation("join refused: join already processed by another gateway"); } else { this.logger.LogError("join refused: DevNonce already used by this device"); } request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.JoinDevNonceAlreadyUsed); return; } // Check that the device is joining through the linked gateway and not another if (!loRaDevice.IsOurDevice) { this.logger.LogInformation("join refused: trying to join not through its linked gateway, ignoring join request"); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.HandledByAnotherGateway); return; } var netId = this.configuration.NetId; var appNonce = new AppNonce(RandomNumberGenerator.GetInt32(toExclusive: AppNonce.MaxValue + 1)); var appSKey = OTAAKeysGenerator.CalculateAppSessionKey(appNonce, netId, joinReq.DevNonce, appKey); var nwkSKey = OTAAKeysGenerator.CalculateNetworkSessionKey(appNonce, netId, joinReq.DevNonce, appKey); var address = RandomNumberGenerator.GetInt32(toExclusive: DevAddr.MaxNetworkAddress + 1); // The 7 LBS of the NetID become the NwkID of a DevAddr: var devAddr = new DevAddr(unchecked ((byte)netId.NetworkId), address); var oldDevAddr = loRaDevice.DevAddr; if (!timeWatcher.InTimeForJoinAccept()) { this.receiveWindowMisses?.Add(1); // in this case it's too late, we need to break and avoid saving twins this.logger.LogInformation("join refused: processing of the join request took too long, sending no message"); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.ReceiveWindowMissed); return; } var updatedProperties = new LoRaDeviceJoinUpdateProperties { DevAddr = devAddr, NwkSKey = nwkSKey, AppSKey = appSKey, AppNonce = appNonce, DevNonce = joinReq.DevNonce, NetId = netId, Region = request.Region.LoRaRegion, PreferredGatewayID = this.configuration.GatewayID, }; if (loRaDevice.ClassType == LoRaDeviceClassType.C) { updatedProperties.SavePreferredGateway = true; updatedProperties.SaveRegion = true; updatedProperties.StationEui = request.StationEui; } DeviceJoinInfo deviceJoinInfo = null; if (request.Region.LoRaRegion == LoRaRegionType.CN470RP2) { if (request.Region.TryGetJoinChannelIndex(request.RadioMetadata.Frequency, out var channelIndex)) { updatedProperties.CN470JoinChannel = channelIndex; deviceJoinInfo = new DeviceJoinInfo(channelIndex); } else { this.logger.LogError("failed to retrieve the join channel index for device"); } } var deviceUpdateSucceeded = await loRaDevice.UpdateAfterJoinAsync(updatedProperties, joinAcceptCancellationToken.Token); if (!deviceUpdateSucceeded) { this.logger.LogError("join refused: join request could not save twin"); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.IoTHubProblem); return; } var windowToUse = timeWatcher.ResolveJoinAcceptWindowToUse(); if (windowToUse is null) { this.receiveWindowMisses?.Add(1); this.logger.LogInformation("join refused: processing of the join request took too long, sending no message"); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.ReceiveWindowMissed); return; } this.deviceRegistry.UpdateDeviceAfterJoin(loRaDevice, oldDevAddr); // Build join accept downlink message // Build the DlSettings fields that is a superposition of RX2DR and RX1DROffset field var dlSettings = new byte[1]; if (loRaDevice.DesiredRX2DataRate.HasValue) { if (request.Region.DRtoConfiguration.ContainsKey(loRaDevice.DesiredRX2DataRate.Value)) { dlSettings[0] = (byte)((byte)loRaDevice.DesiredRX2DataRate & 0b00001111); } else { this.logger.LogError("twin RX2 DR value is not within acceptable values"); } } if (request.Region.IsValidRX1DROffset(loRaDevice.DesiredRX1DROffset)) { var rx1droffset = (byte)(loRaDevice.DesiredRX1DROffset << 4); dlSettings[0] = (byte)(dlSettings[0] + rx1droffset); } else { this.logger.LogError("twin RX1 offset DR value is not within acceptable values"); } // The following DesiredRxDelay is different than the RxDelay to be passed to Serialize function // This one is a delay between TX and RX for any message to be processed by joining device // The field accepted by Serialize method is an indication of the delay (compared to receive time of join request) // of when the message Join Accept message should be sent var loraSpecDesiredRxDelay = RxDelay.RxDelay0; if (Enum.IsDefined(loRaDevice.DesiredRXDelay)) { loraSpecDesiredRxDelay = loRaDevice.DesiredRXDelay; } else { this.logger.LogError("twin RX delay value is not within acceptable values"); } var loRaPayloadJoinAccept = new LoRaPayloadJoinAccept( netId, // NETID 0 / 1 is default test devAddr, // todo add device address management appNonce, dlSettings, loraSpecDesiredRxDelay, null); if (!loraRegion.TryGetDownstreamChannelFrequency(request.RadioMetadata.Frequency, upstreamDataRate: request.RadioMetadata.DataRate, deviceJoinInfo: deviceJoinInfo, downstreamFrequency: out var freq)) { this.logger.LogError("could not resolve DR and/or frequency for downstream"); request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidUpstreamMessage); return; } var joinAcceptBytes = loRaPayloadJoinAccept.Serialize(appKey); var rx1 = windowToUse is not ReceiveWindow2 ? new ReceiveWindow(loraRegion.GetDownstreamDataRate(request.RadioMetadata.DataRate, loRaDevice.ReportedRX1DROffset), freq) : (ReceiveWindow?)null; var rx2 = new ReceiveWindow(loraRegion.GetDownstreamRX2DataRate(this.configuration.Rx2DataRate, null, deviceJoinInfo, this.logger), loraRegion.GetDownstreamRX2Freq(this.configuration.Rx2Frequency, deviceJoinInfo, this.logger)); var downlinkMessage = new DownlinkMessage(joinAcceptBytes, request.RadioMetadata.UpInfo.Xtime, rx1, rx2, loRaDevice.DevEUI, loraRegion.JoinAcceptDelay1, loRaDevice.ClassType, request.StationEui, request.RadioMetadata.UpInfo.AntennaPreference); this.receiveWindowHits?.Add(1, KeyValuePair.Create(MetricRegistry.ReceiveWindowTagName, (object)windowToUse)); _ = request.DownstreamMessageSender.SendDownstreamAsync(downlinkMessage); request.NotifySucceeded(loRaDevice, downlinkMessage); if (this.logger.IsEnabled(LogLevel.Debug)) { var jsonMsg = JsonConvert.SerializeObject(downlinkMessage); this.logger.LogDebug($"{MacMessageType.JoinAccept} {jsonMsg}"); } else { this.logger.LogInformation("join accepted"); } } catch (Exception ex) when (ExceptionFilterUtility.True(() => this.logger.LogError(ex, $"failed to handle join request. {ex.Message}", LogLevel.Error), () => this.unhandledExceptionCount?.Add(1))) { request.NotifyFailed(loRaDevice, ex); throw; } }
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 joinRequest1 = simulatedDevice.CreateJoinRequest(); // Create Rxpk var joinRequestRxpk1 = joinRequest1.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var joinRequestDevNonce1 = ConversionHelper.ByteArrayToString(joinRequest1.DevNonce); var devAddr = string.Empty; var devEUI = simulatedDevice.LoRaDevice.DeviceID; var appEUI = simulatedDevice.LoRaDevice.AppEUI; var loRaDeviceClient = new Mock <ILoRaDeviceClient>(MockBehavior.Strict); // 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; twin.Properties.Desired[TwinProperty.AppEUI] = simulatedDevice.LoRaDevice.AppEUI; twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey; twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID; twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder; loRaDeviceClient.Setup(x => x.GetTwinAsync()) .Returns(async() => { await Task.Delay(TimeSpan.FromSeconds(7)); return(twin); }); // Device twin will be updated string afterJoinAppSKey = null; string afterJoinNwkSKey = null; string afterJoinDevAddr = null; loRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .Callback <TwinCollection>((updatedTwin) => { afterJoinAppSKey = updatedTwin[TwinProperty.AppSKey]; afterJoinNwkSKey = updatedTwin[TwinProperty.NwkSKey]; afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr]; }) .ReturnsAsync(true); // Lora device api will be search by devices with matching deveui, var loRaDeviceApi = new Mock <LoRaDeviceAPIServiceBase>(MockBehavior.Strict); loRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, joinRequestDevNonce1)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList())); // using factory to create mock of var loRaDeviceFactory = new TestLoRaDeviceFactory(loRaDeviceClient.Object); var memoryCache = new MemoryCache(new MemoryCacheOptions()); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, loRaDeviceApi.Object, loRaDeviceFactory); var frameCounterUpdateStrategyFactory = new LoRaDeviceFrameCounterUpdateStrategyFactory(this.ServerConfiguration.GatewayID, loRaDeviceApi.Object); var messageProcessor = new MessageProcessor( this.ServerConfiguration, deviceRegistry, frameCounterUpdateStrategyFactory, new LoRaPayloadDecoder()); // 1st join request // Should fail var joinRequestDownlinkMessage1 = await messageProcessor.ProcessMessageAsync(joinRequestRxpk1); Assert.Null(joinRequestDownlinkMessage1); // 2nd attempt var joinRequest2 = simulatedDevice.CreateJoinRequest(); var joinRequestRxpk2 = joinRequest2.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0]; var joinRequestDevNonce2 = LoRaTools.Utils.ConversionHelper.ByteArrayToString(joinRequest2.DevNonce); // setup response to this device search loRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, joinRequestDevNonce2)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList())); var joinRequestDownlinkMessage2 = await messageProcessor.ProcessMessageAsync(joinRequestRxpk2); Assert.NotNull(joinRequestDownlinkMessage2); var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(joinRequestDownlinkMessage2.Txpk.Data), simulatedDevice.LoRaDevice.AppKey); Assert.Equal(joinAccept.DevAddr.ToArray(), ConversionHelper.StringToByteArray(afterJoinDevAddr)); var devicesForDevAddr = deviceRegistry.InternalGetCachedDevicesForDevAddr(afterJoinDevAddr); Assert.Single(devicesForDevAddr); // should have the single device Assert.True(devicesForDevAddr.TryGetValue(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(afterJoinDevAddr, loRaDevice.DevAddr); if (deviceGatewayID == null) { Assert.Null(loRaDevice.GatewayID); } else { Assert.Equal(deviceGatewayID, loRaDevice.GatewayID); } // fcnt is restarted Assert.Equal(0, loRaDevice.FCntUp); Assert.Equal(0, loRaDevice.FCntDown); Assert.False(loRaDevice.HasFrameCountChanges); // searching the device should happen twice loRaDeviceApi.Verify(x => x.SearchAndLockForJoinAsync(ServerGatewayID, devEUI, appEUI, It.IsNotNull <string>()), Times.Exactly(2)); // getting the device twin should happens once loRaDeviceClient.Verify(x => x.GetTwinAsync(), Times.Once()); loRaDeviceClient.VerifyAll(); loRaDeviceApi.VerifyAll(); }