public LoRaDeviceTelemetry(Rxpk rxpk, LoRaPayloadData upstreamPayload, object payloadData, byte[] decryptedPayloadData) { if (rxpk.ExtraData != null) { this.ExtraData = new Dictionary <string, object>(rxpk.ExtraData); } this.Chan = rxpk.Chan; this.Codr = rxpk.Codr; this.Data = payloadData; this.Rawdata = decryptedPayloadData?.Length > 0 ? Convert.ToBase64String(decryptedPayloadData) : string.Empty; this.Datr = rxpk.Datr; this.Freq = rxpk.Freq; this.Lsnr = rxpk.Lsnr; this.Modu = rxpk.Modu; this.Rfch = rxpk.Rfch; this.Rssi = rxpk.Rssi; this.Size = rxpk.Size; this.Stat = rxpk.Stat; this.Time = rxpk.Time; this.Tmms = rxpk.Tmms; this.Tmst = rxpk.Tmst; this.Fcnt = upstreamPayload.GetFcnt(); this.Port = upstreamPayload.GetFPort(); }
public LoRaDeviceTelemetry(Rxpk rxpk, LoRaPayloadData loRaPayloadData, object payloadData) { if (rxpk.ExtraData != null) { this.ExtraData = new Dictionary <string, object>(rxpk.ExtraData); } this.Chan = rxpk.Chan; this.Codr = rxpk.Codr; this.Data = payloadData; this.Rawdata = rxpk.Data; this.Datr = rxpk.Datr; this.Freq = rxpk.Freq; this.Lsnr = rxpk.Lsnr; this.Modu = rxpk.Modu; this.Rfch = rxpk.Rfch; this.Rssi = rxpk.Rssi; this.Size = rxpk.Size; this.Stat = rxpk.Stat; this.Time = rxpk.Time; this.Tmms = rxpk.Tmms; this.Tmst = rxpk.Tmst; this.Fcnt = loRaPayloadData.GetFcnt(); this.Port = loRaPayloadData.GetFPort(); }
public FunctionBundler CreateIfRequired( string gatewayId, LoRaPayloadData loRaPayload, LoRaDevice loRaDevice, IDeduplicationStrategyFactory deduplicationFactory, LoRaRequest request) { if (!string.IsNullOrEmpty(loRaDevice.GatewayID)) { // single gateway mode return(null); } var context = new FunctionBundlerExecutionContext { DeduplicationFactory = deduplicationFactory, FCntDown = loRaDevice.FCntDown, FCntUp = loRaPayload.GetFcnt(), GatewayId = gatewayId, LoRaDevice = loRaDevice, LoRaPayload = loRaPayload, Request = request }; var qualifyingExecutionItems = new List <IFunctionBundlerExecutionItem>(functionItems.Count); for (var i = 0; i < functionItems.Count; i++) { var itm = functionItems[i]; if (itm.RequiresExecution(context)) { qualifyingExecutionItems.Add(itm); } } if (qualifyingExecutionItems.Count == 0) { return(null); } var bundlerRequest = new FunctionBundlerRequest { ClientFCntDown = context.FCntDown, ClientFCntUp = context.FCntUp, GatewayId = gatewayId, Rssi = context.Request.Rxpk.Rssi, }; for (var i = 0; i < qualifyingExecutionItems.Count; i++) { qualifyingExecutionItems[i].Prepare(context, bundlerRequest); } Logger.Log(loRaDevice.DevEUI, "FunctionBundler request: ", bundlerRequest, LogLevel.Debug); return(new FunctionBundler(loRaDevice.DevEUI, this.deviceApi, bundlerRequest, qualifyingExecutionItems, context)); }
public async Task When_ABP_Sends_Upstream_Followed_By_DirectMethod_Should_Send_Upstream_And_Downstream(string deviceGatewayID, uint fcntDownFromTwin, uint fcntDelta) { const uint payloadFcnt = 2; // to avoid relax mode reset var simDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: deviceGatewayID, deviceClassType: 'c'), frmCntDown: fcntDownFromTwin); this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .ReturnsAsync(true); var twin = simDevice.CreateABPTwin(reportedProperties: new Dictionary <string, object> { { TwinProperty.Region, LoRaRegionType.EU868.ToString() } }); this.LoRaDeviceClient.Setup(x => x.GetTwinAsync()) .ReturnsAsync(twin); this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); this.LoRaDeviceApi.Setup(x => x.SearchByDevAddrAsync(simDevice.DevAddr)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(simDevice.DevAddr, simDevice.DevEUI, "123").AsList())); if (deviceGatewayID == null) { this.LoRaDeviceApi.Setup(x => x.ExecuteFunctionBundlerAsync(simDevice.DevEUI, It.IsNotNull <FunctionBundlerRequest>())) .ReturnsAsync(new FunctionBundlerResult()); } var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, this.NewMemoryCache(), this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); var messageDispatcher = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); var request = this.CreateWaitableRequest(simDevice.CreateUnconfirmedMessageUplink("1", fcnt: payloadFcnt).Rxpk[0]); messageDispatcher.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); Assert.True(request.ProcessingSucceeded); // wait until cache has been updated await Task.Delay(50); // Adds fcntdown to device, simulating multiple downstream calls Assert.True(deviceRegistry.InternalGetCachedDevicesForDevAddr(simDevice.DevAddr).TryGetValue(simDevice.DevEUI, out var loRaDevice)); loRaDevice.SetFcntDown(fcntDelta + loRaDevice.FCntDown); var classCSender = new DefaultClassCDevicesMessageSender( this.ServerConfiguration, deviceRegistry, this.PacketForwarder, this.FrameCounterUpdateStrategyProvider); var c2d = new ReceivedLoRaCloudToDeviceMessage() { DevEUI = simDevice.DevEUI, MessageId = Guid.NewGuid().ToString(), Payload = "aaaa", Fport = 18, }; var expectedFcntDown = fcntDownFromTwin + Constants.MAX_FCNT_UNSAVED_DELTA + fcntDelta; if (string.IsNullOrEmpty(deviceGatewayID)) { this.LoRaDeviceApi.Setup(x => x.NextFCntDownAsync(simDevice.DevEUI, fcntDownFromTwin + fcntDelta, 0, this.ServerConfiguration.GatewayID)) .ReturnsAsync((ushort)expectedFcntDown); } Assert.True(await classCSender.SendAsync(c2d)); Assert.Single(this.PacketForwarder.DownlinkMessages); var downstreamMsg = this.PacketForwarder.DownlinkMessages[0]; byte[] downstreamPayloadBytes = Convert.FromBase64String(downstreamMsg.Txpk.Data); var downstreamPayload = new LoRaPayloadData(downstreamPayloadBytes); Assert.Equal(expectedFcntDown, downstreamPayload.GetFcnt()); Assert.Equal(c2d.Fport, downstreamPayload.GetFPort()); Assert.Equal(downstreamPayload.DevAddr.ToArray(), ConversionHelper.StringToByteArray(simDevice.DevAddr)); var decryptedPayload = downstreamPayload.GetDecryptedPayload(simDevice.AppSKey); Assert.Equal(c2d.Payload, Encoding.UTF8.GetString(decryptedPayload)); Assert.Equal(expectedFcntDown, loRaDevice.FCntDown); Assert.Equal(payloadFcnt, loRaDevice.FCntUp); Assert.False(loRaDevice.HasFrameCountChanges); this.LoRaDeviceApi.VerifyAll(); this.LoRaDeviceClient.VerifyAll(); }
public async Task When_OTAA_Join_Then_Sends_Upstream_DirectMethod_Should_Send_Downstream(string deviceGatewayID) { var simDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, deviceClassType: 'c', gatewayID: deviceGatewayID)); this.LoRaDeviceClient.Setup(x => x.GetTwinAsync()) .ReturnsAsync(simDevice.CreateOTAATwin()); var savedAppSKey = string.Empty; var savedNwkSKey = string.Empty; var savedDevAddr = string.Empty; this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .ReturnsAsync(true) .Callback <TwinCollection>((t) => { savedAppSKey = t[TwinProperty.AppSKey]; savedNwkSKey = t[TwinProperty.NwkSKey]; savedDevAddr = t[TwinProperty.DevAddr]; Assert.NotEmpty(savedAppSKey); Assert.NotEmpty(savedNwkSKey); Assert.NotEmpty(savedDevAddr); }); this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); if (deviceGatewayID == null) { this.LoRaDeviceApi.Setup(x => x.ExecuteFunctionBundlerAsync(simDevice.DevEUI, It.IsNotNull <FunctionBundlerRequest>())) .ReturnsAsync(new FunctionBundlerResult()); } this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, simDevice.DevEUI, simDevice.AppEUI, It.IsNotNull <string>())) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(simDevice.DevAddr, simDevice.DevEUI, "123").AsList())); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, this.NewMemoryCache(), this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); var messageDispatcher = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); var joinRxpk = simDevice.CreateJoinRequest().SerializeUplink(simDevice.AppKey).Rxpk[0]; var joinRequest = this.CreateWaitableRequest(joinRxpk); messageDispatcher.DispatchRequest(joinRequest); Assert.True(await joinRequest.WaitCompleteAsync()); Assert.True(joinRequest.ProcessingSucceeded); simDevice.SetupJoin(savedAppSKey, savedNwkSKey, savedDevAddr); var request = this.CreateWaitableRequest(simDevice.CreateUnconfirmedMessageUplink("1").Rxpk[0]); messageDispatcher.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); Assert.True(request.ProcessingSucceeded); var classCSender = new DefaultClassCDevicesMessageSender( this.ServerConfiguration, deviceRegistry, this.PacketForwarder, this.FrameCounterUpdateStrategyProvider); var c2d = new ReceivedLoRaCloudToDeviceMessage() { DevEUI = simDevice.DevEUI, MessageId = Guid.NewGuid().ToString(), Payload = "aaaa", Fport = 14, }; if (string.IsNullOrEmpty(deviceGatewayID)) { this.LoRaDeviceApi.Setup(x => x.NextFCntDownAsync(simDevice.DevEUI, simDevice.FrmCntDown, 0, this.ServerConfiguration.GatewayID)) .ReturnsAsync((ushort)(simDevice.FrmCntDown + 1)); } Assert.True(await classCSender.SendAsync(c2d)); Assert.Equal(2, this.PacketForwarder.DownlinkMessages.Count); var downstreamMsg = this.PacketForwarder.DownlinkMessages[1]; TestLogger.Log($"appSKey: {simDevice.AppSKey}, nwkSKey: {simDevice.NwkSKey}"); byte[] downstreamPayloadBytes = Convert.FromBase64String(downstreamMsg.Txpk.Data); var downstreamPayload = new LoRaPayloadData(downstreamPayloadBytes); Assert.Equal(1, downstreamPayload.GetFcnt()); Assert.Equal(c2d.Fport, downstreamPayload.GetFPort()); Assert.Equal(downstreamPayload.DevAddr.ToArray(), ConversionHelper.StringToByteArray(savedDevAddr)); var decryptedPayload = downstreamPayload.GetDecryptedPayload(simDevice.AppSKey); Assert.Equal(c2d.Payload, Encoding.UTF8.GetString(decryptedPayload)); this.LoRaDeviceApi.VerifyAll(); this.LoRaDeviceClient.VerifyAll(); }
public async Task Validate_Limits( uint payloadFcntUp, uint?deviceFcntUp, uint?deviceFcntDown, uint?startFcntUp, uint?startFcntDown, uint expectedFcntUp, uint expectedFcntDown, bool abpRelaxed, bool confirmed, bool supports32Bit = false, LoRaDeviceRequestFailedReason?failedReason = null) { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: this.ServerConfiguration.GatewayID, supports32BitFcnt: supports32Bit)); var devEUI = simulatedDevice.LoRaDevice.DeviceID; var devAddr = simulatedDevice.LoRaDevice.DevAddr; this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)).ReturnsAsync(true); var initialTwin = SetupTwins(deviceFcntUp, deviceFcntDown, startFcntUp, startFcntDown, abpRelaxed, supports32Bit, simulatedDevice, devEUI, devAddr); this.LoRaDeviceClient .Setup(x => x.GetTwinAsync()).Returns(() => { return(Task.FromResult(initialTwin)); }); uint?fcntUpSavedInTwin = null; uint?fcntDownSavedInTwin = null; this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .Returns <TwinCollection>((t) => { fcntUpSavedInTwin = (uint)t[TwinProperty.FCntUp]; fcntDownSavedInTwin = (uint)t[TwinProperty.FCntDown]; return(Task.FromResult(true)); }); this.LoRaDeviceClient .Setup(x => x.ReceiveAsync(It.IsAny <TimeSpan>())) .ReturnsAsync((Message)null); var shouldReset = payloadFcntUp == 0 && abpRelaxed; if (shouldReset) { this.LoRaDeviceApi.Setup(x => x.ABPFcntCacheResetAsync(devEUI, It.IsAny <uint>(), It.IsNotNull <string>())).ReturnsAsync(true); } this.LoRaDeviceApi.Setup(x => x.SearchByDevAddrAsync(devAddr)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "abc").AsList())); var memoryCache = new MemoryCache(new MemoryCacheOptions()); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); // Send to message processor var messageDispatcher = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); WaitableLoRaRequest req = null; if (confirmed) { var payload = simulatedDevice.CreateConfirmedDataUpMessage("1234", fcnt: (uint)payloadFcntUp); var rxpk = payload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0]; req = this.CreateWaitableRequest(rxpk); } else { var rxpk = simulatedDevice.CreateUnconfirmedMessageUplink("1234", fcnt: (uint)payloadFcntUp).Rxpk[0]; req = new WaitableLoRaRequest(rxpk, this.PacketForwarder); } messageDispatcher.DispatchRequest(req); Assert.True(await req.WaitCompleteAsync(-1)); if (failedReason.HasValue) { Assert.Equal(failedReason.Value, req.ProcessingFailedReason); } else { Assert.True(req.ProcessingSucceeded); Assert.True(this.LoRaDeviceFactory.TryGetLoRaDevice(devEUI, out var loRaDevice)); if (confirmed) { Assert.NotNull(req.ResponseDownlink); Assert.True(req.ProcessingSucceeded); Assert.Single(this.PacketForwarder.DownlinkMessages); var downlinkMessage = this.PacketForwarder.DownlinkMessages[0]; var payloadDataDown = new LoRaPayloadData(Convert.FromBase64String(downlinkMessage.Txpk.Data)); payloadDataDown.PerformEncryption(simulatedDevice.AppSKey); Assert.Equal(expectedFcntDown, payloadDataDown.GetFcnt()); } Assert.Equal(expectedFcntUp, loRaDevice.FCntUp); } }
public void When_Creating_Rxpk_Recreating_Payload_Should_Match_Source_Values(LoRaMessageType loRaMessageType, string data) { var devAddrText = "00000060"; var appSKeyText = "00000060000000600000006000000060"; var nwkSKeyText = "00000060000000600000006000000060"; ushort fcnt = 12; byte[] devAddr = ConversionHelper.StringToByteArray(devAddrText); Array.Reverse(devAddr); byte[] fCtrl = new byte[] { 0x80 }; var fcntBytes = BitConverter.GetBytes(fcnt); var fopts = new List <MacCommand>(); byte[] fPort = new byte[] { 1 }; byte[] payload = Encoding.UTF8.GetBytes(data); Array.Reverse(payload); // 0 = uplink, 1 = downlink int direction = 0; var devicePayloadData = new LoRaPayloadData(loRaMessageType, devAddr, fCtrl, fcntBytes, fopts, fPort, payload, direction); Assert.Equal(12, devicePayloadData.GetFcnt()); Assert.Equal(0, devicePayloadData.Direction); Assert.Equal(1, devicePayloadData.GetFPort()); var datr = "SF10BW125"; var freq = 868.3; var uplinkMsg = devicePayloadData.SerializeUplink(appSKeyText, nwkSKeyText, datr, freq, 0); // Now try to recreate LoRaPayloadData from rxpk Assert.True(LoRaPayload.TryCreateLoRaPayload(uplinkMsg.Rxpk[0], out LoRaPayload parsedLoRaPayload)); Assert.Equal(loRaMessageType, parsedLoRaPayload.LoRaMessageType); Assert.IsType <LoRaPayloadData>(parsedLoRaPayload); var parsedLoRaPayloadData = (LoRaPayloadData)parsedLoRaPayload; Assert.Equal(12, parsedLoRaPayloadData.GetFcnt()); Assert.Equal(0, parsedLoRaPayloadData.Direction); Assert.Equal(1, parsedLoRaPayloadData.GetFPort()); // Ensure that mic check and getting payload back works Assert.True(parsedLoRaPayloadData.CheckMic(nwkSKeyText)); // does not matter where the check mic happen, should always work! var parsedPayloadBytes = parsedLoRaPayloadData.GetDecryptedPayload(appSKeyText); Assert.Equal(data, Encoding.UTF8.GetString(parsedPayloadBytes)); // checking mic and getting payload should not change the payload properties Assert.Equal(12, parsedLoRaPayloadData.GetFcnt()); Assert.Equal(0, parsedLoRaPayloadData.Direction); Assert.Equal(1, parsedLoRaPayloadData.GetFPort()); // checking mic should not break getting the payload Assert.True(parsedLoRaPayloadData.CheckMic(nwkSKeyText)); // does not matter where the check mic happen, should always work! var parsedPayloadBytes2 = parsedLoRaPayloadData.GetDecryptedPayload(appSKeyText); Assert.Equal(data, Encoding.UTF8.GetString(parsedPayloadBytes2)); // checking mic and getting payload should not change the payload properties Assert.Equal(12, parsedLoRaPayloadData.GetFcnt()); Assert.Equal(0, parsedLoRaPayloadData.Direction); Assert.Equal(1, parsedLoRaPayloadData.GetFPort()); }
public async Task When_Device_Prefers_Second_Window_Should_Send_Downstream_In_Second_Window() { const int PayloadFcnt = 10; const int InitialDeviceFcntUp = 9; const int InitialDeviceFcntDown = 20; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(1, gatewayID: this.ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp, frmCntDown: InitialDeviceFcntDown); var devAddr = simulatedDevice.DevAddr; var devEUI = simulatedDevice.DevEUI; var loraDeviceClient = new Mock <ILoRaDeviceClient>(MockBehavior.Strict); // Will get twin to initialize LoRaDevice var deviceTwin = TestUtils.CreateABPTwin( simulatedDevice, desiredProperties: new Dictionary <string, object> { { TwinProperty.PreferredWindow, 2 } }); loraDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(deviceTwin); loraDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); loraDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .ReturnsAsync(true); var cloudToDeviceMessage = new Message(Encoding.UTF8.GetBytes("c2d")); cloudToDeviceMessage.Properties[MessageProcessor.FPORT_MSG_PROPERTY_KEY] = "1"; loraDeviceClient.SetupSequence(x => x.ReceiveAsync(It.IsAny <TimeSpan>())) .ReturnsAsync(cloudToDeviceMessage) .Returns(this.EmptyAdditionalMessageReceiveAsync); // 2nd cloud to device message does not return anything loraDeviceClient.Setup(x => x.CompleteAsync(cloudToDeviceMessage)) .ReturnsAsync(true); var payloadDecoder = new Mock <ILoRaPayloadDecoder>(); var loRaDeviceAPI = new Mock <LoRaDeviceAPIServiceBase>(); loRaDeviceAPI.Setup(x => x.SearchByDevAddrAsync(devAddr)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "adad").AsList())); var loRaDeviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, new MemoryCache(new MemoryCacheOptions()), loRaDeviceAPI.Object, new TestLoRaDeviceFactory(loraDeviceClient.Object)); // Send to message processor var messageProcessor = new MessageProcessor( this.ServerConfiguration, loRaDeviceRegistry, new LoRaDeviceFrameCounterUpdateStrategyFactory(this.ServerConfiguration.GatewayID, loRaDeviceAPI.Object), new LoRaPayloadDecoder()); var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: PayloadFcnt); var rxpk = payload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0]; var actual = await messageProcessor.ProcessMessageAsync(rxpk); // Expectations // 1. Message was sent to IoT Hub loraDeviceClient.VerifyAll(); // 2. Single gateway frame counter strategy was used this.FrameCounterUpdateStrategyFactory.VerifyAll(); // 3. Return is downstream message Assert.NotNull(actual); Assert.IsType <DownlinkPktFwdMessage>(actual); var downlinkMessage = (DownlinkPktFwdMessage)actual; var euRegion = RegionFactory.CreateEU868Region(); // Ensure we are using second window frequency Assert.Equal(euRegion.RX2DefaultReceiveWindows.frequency, actual.Txpk.Freq); // Ensure we are using second window datr Assert.Equal(euRegion.DRtoConfiguration[euRegion.RX2DefaultReceiveWindows.dr].configuration, actual.Txpk.Datr); // Ensure tmst was computed to 2 seconds (2 windows in Europe) Assert.Equal(2000000, actual.Txpk.Tmst); // Get the device from registry var deviceDictionary = loRaDeviceRegistry.InternalGetCachedDevicesForDevAddr(devAddr); Assert.True(deviceDictionary.TryGetValue(simulatedDevice.DevEUI, out var loRaDevice)); var payloadDataDown = new LoRaPayloadData(Convert.FromBase64String(downlinkMessage.Txpk.Data)); payloadDataDown.PerformEncryption(loRaDevice.AppSKey); Assert.Equal(payloadDataDown.DevAddr.ToArray(), ConversionHelper.StringToByteArray(loRaDevice.DevAddr)); Assert.False(payloadDataDown.IsConfirmed()); Assert.Equal(LoRaMessageType.UnconfirmedDataDown, payloadDataDown.LoRaMessageType); // 4. Frame counter up was updated Assert.Equal(PayloadFcnt, loRaDevice.FCntUp); // 5. Frame counter down is updated var expectedFcntDown = InitialDeviceFcntDown + 10 + 1; // adding 10 as buffer when creating a new device instance Assert.Equal(expectedFcntDown, loRaDevice.FCntDown); Assert.Equal(expectedFcntDown, payloadDataDown.GetFcnt()); // 6. Frame count has no pending changes Assert.False(loRaDevice.HasFrameCountChanges); }
public async Task OTAA_Confirmed_With_Cloud_To_Device_Message_Returns_Downstream_Message() { const int PayloadFcnt = 10; const int InitialDeviceFcntUp = 9; const int InitialDeviceFcntDown = 20; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(1, gatewayID: this.ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp, frmCntDown: InitialDeviceFcntDown); var loraDeviceClient = new Mock <ILoRaDeviceClient>(MockBehavior.Strict); var loraDevice = TestUtils.CreateFromSimulatedDevice(simulatedDevice, loraDeviceClient.Object); loraDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); loraDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .ReturnsAsync(true); var cloudToDeviceMessage = new Message(Encoding.UTF8.GetBytes("c2d")); cloudToDeviceMessage.Properties[MessageProcessor.FPORT_MSG_PROPERTY_KEY] = "1"; loraDeviceClient.SetupSequence(x => x.ReceiveAsync(It.IsAny <TimeSpan>())) .ReturnsAsync(cloudToDeviceMessage) .ReturnsAsync((Message)null); // 2nd cloud to device message does not return anything loraDeviceClient.Setup(x => x.CompleteAsync(cloudToDeviceMessage)) .ReturnsAsync(true); var payloadDecoder = new Mock <ILoRaPayloadDecoder>(); this.LoRaDeviceRegistry.Setup(x => x.GetDeviceForPayloadAsync(It.IsAny <LoRaTools.LoRaMessage.LoRaPayloadData>())) .ReturnsAsync(loraDevice); // Setup frame counter strategy this.FrameCounterUpdateStrategyFactory.Setup(x => x.GetSingleGatewayStrategy()) .Returns(new SingleGatewayFrameCounterUpdateStrategy()); // Send to message processor var messageProcessor = new MessageProcessor( this.ServerConfiguration, this.LoRaDeviceRegistry.Object, this.FrameCounterUpdateStrategyFactory.Object, payloadDecoder.Object); var payload = simulatedDevice.CreateConfirmedDataUpMessage("1234", fcnt: PayloadFcnt); var rxpk = payload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0]; var actual = await messageProcessor.ProcessMessageAsync(rxpk); // Expectations // 1. Message was sent to IoT Hub loraDeviceClient.VerifyAll(); // 2. Single gateway frame counter strategy was used this.FrameCounterUpdateStrategyFactory.VerifyAll(); // 3. Return is downstream message Assert.NotNull(actual); Assert.IsType <DownlinkPktFwdMessage>(actual); var downlinkMessage = (DownlinkPktFwdMessage)actual; var payloadDataDown = new LoRaPayloadData(Convert.FromBase64String(downlinkMessage.Txpk.Data)); payloadDataDown.PerformEncryption(loraDevice.AppSKey); Assert.Equal(payloadDataDown.DevAddr.ToArray(), LoRaTools.Utils.ConversionHelper.StringToByteArray(loraDevice.DevAddr)); Assert.False(payloadDataDown.IsConfirmed()); Assert.Equal(LoRaMessageType.UnconfirmedDataDown, payloadDataDown.LoRaMessageType); // 4. Frame counter up was updated Assert.Equal(PayloadFcnt, loraDevice.FCntUp); // 5. Frame counter down is updated Assert.Equal(InitialDeviceFcntDown + 1, loraDevice.FCntDown); Assert.Equal(InitialDeviceFcntDown + 1, payloadDataDown.GetFcnt()); // 6. Frame count has no pending changes Assert.False(loraDevice.HasFrameCountChanges); }
/// <summary> /// Process LoRa message where the payload is of type LoRaPayloadData /// </summary> async Task <DownlinkPktFwdMessage> ProcessDataMessageAsync(LoRaTools.LoRaPhysical.Rxpk rxpk, LoRaPayloadData loraPayload, DateTime startTime) { var devAddr = loraPayload.DevAddr; var timeWatcher = new LoRaOperationTimeWatcher(this.loraRegion, startTime); using (var processLogger = new ProcessLogger(timeWatcher, devAddr)) { if (!this.IsValidNetId(loraPayload.GetDevAddrNetID(), this.configuration.NetId)) { Logger.Log(ConversionHelper.ByteArrayToString(devAddr), "device is using another network id, ignoring this message", LogLevel.Debug); processLogger.LogLevel = LogLevel.Debug; return(null); } // Find device that matches: // - devAddr // - mic check (requires: loraDeviceInfo.NwkSKey or loraDeviceInfo.AppKey, rxpk.LoraPayload.Mic) // - gateway id var loRaDevice = await this.deviceRegistry.GetDeviceForPayloadAsync(loraPayload); if (loRaDevice == null) { Logger.Log(ConversionHelper.ByteArrayToString(devAddr), $"device is not from our network, ignoring message", LogLevel.Information); return(null); } // Add context to logger processLogger.SetDevEUI(loRaDevice.DevEUI); var isMultiGateway = !string.Equals(loRaDevice.GatewayID, this.configuration.GatewayID, StringComparison.InvariantCultureIgnoreCase); var frameCounterStrategy = isMultiGateway ? this.frameCounterUpdateStrategyFactory.GetMultiGatewayStrategy() : this.frameCounterUpdateStrategyFactory.GetSingleGatewayStrategy(); var payloadFcnt = loraPayload.GetFcnt(); var requiresConfirmation = loraPayload.IsConfirmed(); using (new LoRaDeviceFrameCounterSession(loRaDevice, frameCounterStrategy)) { // Leaf devices that restart lose the counter. In relax mode we accept the incoming frame counter // ABP device does not reset the Fcnt so in relax mode we should reset for 0 (LMIC based) or 1 var isFrameCounterFromNewlyStartedDevice = false; if (payloadFcnt <= 1) { if (loRaDevice.IsABP) { if (loRaDevice.IsABPRelaxedFrameCounter && loRaDevice.FCntUp >= 0 && payloadFcnt <= 1) { // known problem when device restarts, starts fcnt from zero _ = frameCounterStrategy.ResetAsync(loRaDevice); isFrameCounterFromNewlyStartedDevice = true; } } else if (loRaDevice.FCntUp == payloadFcnt && payloadFcnt == 0) { // Some devices start with frame count 0 isFrameCounterFromNewlyStartedDevice = true; } } // Reply attack or confirmed reply // Confirmed resubmit: A confirmed message that was received previously but we did not answer in time // Device will send it again and we just need to return an ack (but also check for C2D to send it over) var isConfirmedResubmit = false; if (!isFrameCounterFromNewlyStartedDevice && payloadFcnt <= loRaDevice.FCntUp) { // if it is confirmed most probably we did not ack in time before or device lost the ack packet so we should continue but not send the msg to iothub if (requiresConfirmation && payloadFcnt == loRaDevice.FCntUp) { if (!loRaDevice.ValidateConfirmResubmit(payloadFcnt)) { Logger.Log(loRaDevice.DevEUI, $"resubmit from confirmed message exceeds threshold of {LoRaDevice.MaxConfirmationResubmitCount}, message ignored, msg: {payloadFcnt} server: {loRaDevice.FCntUp}", LogLevel.Debug); processLogger.LogLevel = LogLevel.Debug; return(null); } isConfirmedResubmit = true; Logger.Log(loRaDevice.DevEUI, $"resubmit from confirmed message detected, msg: {payloadFcnt} server: {loRaDevice.FCntUp}", LogLevel.Information); } else { Logger.Log(loRaDevice.DevEUI, $"invalid frame counter, message ignored, msg: {payloadFcnt} server: {loRaDevice.FCntUp}", LogLevel.Information); return(null); } } var fcntDown = 0; // If it is confirmed it require us to update the frame counter down // Multiple gateways: in redis, otherwise in device twin if (requiresConfirmation) { fcntDown = await frameCounterStrategy.NextFcntDown(loRaDevice, payloadFcnt); // Failed to update the fcnt down // In multi gateway scenarios it means the another gateway was faster than using, can stop now if (fcntDown <= 0) { // update our fcntup anyway? // loRaDevice.SetFcntUp(payloadFcnt); Logger.Log(loRaDevice.DevEUI, "another gateway has already sent ack or downlink msg", LogLevel.Information); return(null); } Logger.Log(loRaDevice.DevEUI, $"down frame counter: {loRaDevice.FCntDown}", LogLevel.Information); } if (!isConfirmedResubmit) { var validFcntUp = isFrameCounterFromNewlyStartedDevice || (payloadFcnt > loRaDevice.FCntUp); if (validFcntUp) { Logger.Log(loRaDevice.DevEUI, $"valid frame counter, msg: {payloadFcnt} server: {loRaDevice.FCntUp}", LogLevel.Information); object payloadData = null; // if it is an upward acknowledgement from the device it does not have a payload // This is confirmation from leaf device that he received a C2D confirmed // if a message payload is null we don't try to decrypt it. if (loraPayload.Frmpayload.Length != 0) { byte[] decryptedPayloadData = null; try { decryptedPayloadData = loraPayload.GetDecryptedPayload(loRaDevice.AppSKey); } catch (Exception ex) { Logger.Log(loRaDevice.DevEUI, $"failed to decrypt message: {ex.Message}", LogLevel.Error); } var fportUp = loraPayload.GetFPort(); if (string.IsNullOrEmpty(loRaDevice.SensorDecoder)) { Logger.Log(loRaDevice.DevEUI, $"no decoder set in device twin. port: {fportUp}", LogLevel.Debug); payloadData = Convert.ToBase64String(decryptedPayloadData); } else { Logger.Log(loRaDevice.DevEUI, $"decoding with: {loRaDevice.SensorDecoder} port: {fportUp}", LogLevel.Debug); payloadData = await this.payloadDecoder.DecodeMessageAsync(decryptedPayloadData, fportUp, loRaDevice.SensorDecoder); } } if (!await this.SendDeviceEventAsync(loRaDevice, rxpk, payloadData, loraPayload, timeWatcher)) { // failed to send event to IoT Hub, stop now return(null); } loRaDevice.SetFcntUp(payloadFcnt); } else { Logger.Log(loRaDevice.DevEUI, $"invalid frame counter, msg: {payloadFcnt} server: {loRaDevice.FCntUp}", LogLevel.Information); } } // We check if we have time to futher progress or not // C2D checks are quite expensive so if we are really late we just stop here var timeToSecondWindow = timeWatcher.GetRemainingTimeToReceiveSecondWindow(loRaDevice); if (timeToSecondWindow < LoRaOperationTimeWatcher.ExpectedTimeToPackageAndSendMessage) { if (requiresConfirmation) { Logger.Log(loRaDevice.DevEUI, $"too late for down message ({timeWatcher.GetElapsedTime()}), sending only ACK to gateway", LogLevel.Information); } return(null); } // If it is confirmed and // - Downlink is disabled for the device or // - we don't have time to check c2d and send to device we return now if (requiresConfirmation && (!loRaDevice.DownlinkEnabled || timeToSecondWindow.Subtract(LoRaOperationTimeWatcher.ExpectedTimeToPackageAndSendMessage) <= LoRaOperationTimeWatcher.MinimumAvailableTimeToCheckForCloudMessage)) { return(this.CreateDownlinkMessage( null, loRaDevice, rxpk, loraPayload, timeWatcher, devAddr, false, // fpending (ushort)fcntDown)); } // Flag indicating if there is another C2D message waiting var fpending = false; // Contains the Cloud to message we need to send Message cloudToDeviceMessage = null; if (loRaDevice.DownlinkEnabled) { // ReceiveAsync has a longer timeout // But we wait less that the timeout (available time before 2nd window) // if message is received after timeout, keep it in loraDeviceInfo and return the next call var timeAvailableToCheckCloudToDeviceMessages = timeWatcher.GetAvailableTimeToCheckCloudToDeviceMessage(loRaDevice); if (timeAvailableToCheckCloudToDeviceMessages >= LoRaOperationTimeWatcher.MinimumAvailableTimeToCheckForCloudMessage) { cloudToDeviceMessage = await loRaDevice.ReceiveCloudToDeviceAsync(timeAvailableToCheckCloudToDeviceMessages); if (cloudToDeviceMessage != null && !this.ValidateCloudToDeviceMessage(loRaDevice, cloudToDeviceMessage)) { _ = loRaDevice.CompleteCloudToDeviceMessageAsync(cloudToDeviceMessage); cloudToDeviceMessage = null; } if (cloudToDeviceMessage != null) { if (!requiresConfirmation) { // The message coming from the device was not confirmed, therefore we did not computed the frame count down // Now we need to increment because there is a C2D message to be sent fcntDown = await frameCounterStrategy.NextFcntDown(loRaDevice, payloadFcnt); if (fcntDown == 0) { // We did not get a valid frame count down, therefore we should not process the message _ = loRaDevice.AbandonCloudToDeviceMessageAsync(cloudToDeviceMessage); cloudToDeviceMessage = null; } else { requiresConfirmation = true; } Logger.Log(loRaDevice.DevEUI, $"down frame counter: {loRaDevice.FCntDown}", LogLevel.Information); } // Checking again if cloudToDeviceMessage is valid because the fcntDown resolution could have failed, // causing us to drop the message if (cloudToDeviceMessage != null) { var remainingTimeForFPendingCheck = timeWatcher.GetRemainingTimeToReceiveSecondWindow(loRaDevice) - (LoRaOperationTimeWatcher.CheckForCloudMessageCallEstimatedOverhead + LoRaOperationTimeWatcher.MinimumAvailableTimeToCheckForCloudMessage); if (remainingTimeForFPendingCheck >= LoRaOperationTimeWatcher.MinimumAvailableTimeToCheckForCloudMessage) { var additionalMsg = await loRaDevice.ReceiveCloudToDeviceAsync(LoRaOperationTimeWatcher.MinimumAvailableTimeToCheckForCloudMessage); if (additionalMsg != null) { fpending = true; _ = loRaDevice.AbandonCloudToDeviceMessageAsync(additionalMsg); Logger.Log(loRaDevice.DevEUI, $"found fpending c2d message id: {additionalMsg.MessageId ?? "undefined"}", LogLevel.Information); } } } } } } // No C2D message and request was not confirmed, return nothing if (!requiresConfirmation) { return(null); } var confirmDownstream = this.CreateDownlinkMessage( cloudToDeviceMessage, loRaDevice, rxpk, loraPayload, timeWatcher, devAddr, fpending, (ushort)fcntDown); if (cloudToDeviceMessage != null) { if (confirmDownstream == null) { Logger.Log(loRaDevice.DevEUI, $"out of time for downstream message, will abandon c2d message id: {cloudToDeviceMessage.MessageId ?? "undefined"}", LogLevel.Information); _ = loRaDevice.AbandonCloudToDeviceMessageAsync(cloudToDeviceMessage); } else { _ = loRaDevice.CompleteCloudToDeviceMessageAsync(cloudToDeviceMessage); } } return(confirmDownstream); } } }
public async Task Perform_NbRep_Adaptation_When_Needed() { uint deviceId = 31; string currentDR = "SF8BW125"; int currentLsnr = -20; int messageCount = 20; uint payloadFcnt = 0; const uint InitialDeviceFcntUp = 1; const uint ExpectedDeviceFcntDown = 4; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(deviceId, gatewayID: this.ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp); var loraDevice = this.CreateLoRaDevice(simulatedDevice); this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); int reportedNbRep = 0; this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .Callback <TwinCollection>((t) => { if (t.Contains(TwinProperty.NbRep)) { reportedNbRep = (int)t[TwinProperty.NbRep]; } }) .ReturnsAsync(true); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, this.NewNonEmptyCache(loraDevice), this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); // Send to message processor var messageProcessor = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); payloadFcnt = await this.InitializeCacheToDefaultValuesAsync(payloadFcnt, simulatedDevice, messageProcessor); // **************************************************** // First part send messages with spaces in fcnt to simulate wrong network connectivity // **************************************************** // send a message with a fcnt every 4. for (int i = 0; i < messageCount; i++) { payloadFcnt = await this.SendMessage(currentLsnr, currentDR, payloadFcnt + 3, simulatedDevice, messageProcessor, (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); } var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrl: (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADRAckReq + (int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); var rxpk = payload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey, lsnr: currentLsnr, datr: currentDR).Rxpk[0]; var request = this.CreateWaitableRequest(rxpk); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub this.LoRaDeviceClient.VerifyAll(); this.LoRaDeviceApi.VerifyAll(); Assert.True(request.ProcessingSucceeded); Assert.NotNull(request.ResponseDownlink); Assert.Equal(2, this.PacketForwarder.DownlinkMessages.Count); var downlinkMessage = this.PacketForwarder.DownlinkMessages[1]; var payloadDataDown = new LoRaPayloadData(Convert.FromBase64String(downlinkMessage.Txpk.Data)); // We expect a mac command in the payload Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); var decryptedPayload = payloadDataDown.PerformEncryption(simulatedDevice.NwkSKey); Array.Reverse(decryptedPayload); Assert.Equal(0, payloadDataDown.Fport.Span[0]); Assert.Equal((byte)CidEnum.LinkADRCmd, decryptedPayload[0]); var linkAdr = new LinkADRRequest(decryptedPayload); Assert.Equal(3, reportedNbRep); Assert.Equal(3, linkAdr.NbRep); Assert.Equal(3, loraDevice.NbRep); // **************************************************** // Second part send normal messages to decrease NbRep // **************************************************** // send a message with a fcnt every 1 for (int i = 0; i < messageCount; i++) { payloadFcnt = await this.SendMessage(currentLsnr, currentDR, payloadFcnt, simulatedDevice, messageProcessor, (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); } payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrl: (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADRAckReq + (int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); rxpk = payload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey, lsnr: currentLsnr, datr: currentDR).Rxpk[0]; request = this.CreateWaitableRequest(rxpk); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub this.LoRaDeviceClient.VerifyAll(); this.LoRaDeviceApi.VerifyAll(); Assert.True(request.ProcessingSucceeded); Assert.NotNull(request.ResponseDownlink); Assert.Equal(3, this.PacketForwarder.DownlinkMessages.Count); downlinkMessage = this.PacketForwarder.DownlinkMessages[2]; payloadDataDown = new LoRaPayloadData(Convert.FromBase64String(downlinkMessage.Txpk.Data)); // We expect a mac command in the payload Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); decryptedPayload = payloadDataDown.PerformEncryption(simulatedDevice.NwkSKey); Assert.Equal(0, payloadDataDown.Fport.Span[0]); Assert.Equal((byte)CidEnum.LinkADRCmd, decryptedPayload[0]); linkAdr = new LinkADRRequest(decryptedPayload); Assert.Equal(2, reportedNbRep); Assert.Equal(2, linkAdr.NbRep); Assert.Equal(2, loraDevice.NbRep); // in case no payload the mac is in the FRMPayload and is decrypted with NwkSKey Assert.Equal(payloadDataDown.DevAddr.ToArray(), LoRaTools.Utils.ConversionHelper.StringToByteArray(loraDevice.DevAddr)); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(LoRaMessageType.UnconfirmedDataDown, payloadDataDown.LoRaMessageType); // 4. Frame counter up was updated Assert.Equal(payloadFcnt, loraDevice.FCntUp); // **************************************************** // Third part send normal messages to decrease NbRep to 1 // **************************************************** // send a message with a fcnt every 1 for (int i = 0; i < messageCount; i++) { payloadFcnt = await this.SendMessage(currentLsnr, currentDR, payloadFcnt, simulatedDevice, messageProcessor, (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); } payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrl: (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADRAckReq + (int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); rxpk = payload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey, lsnr: currentLsnr, datr: currentDR).Rxpk[0]; request = this.CreateWaitableRequest(rxpk); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub this.LoRaDeviceClient.VerifyAll(); this.LoRaDeviceApi.VerifyAll(); Assert.True(request.ProcessingSucceeded); Assert.NotNull(request.ResponseDownlink); Assert.Equal(4, this.PacketForwarder.DownlinkMessages.Count); downlinkMessage = this.PacketForwarder.DownlinkMessages[3]; payloadDataDown = new LoRaPayloadData(Convert.FromBase64String(downlinkMessage.Txpk.Data)); // We expect a mac command in the payload Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); decryptedPayload = payloadDataDown.PerformEncryption(simulatedDevice.NwkSKey); Assert.Equal(0, payloadDataDown.Fport.Span[0]); Assert.Equal((byte)CidEnum.LinkADRCmd, decryptedPayload[0]); linkAdr = new LinkADRRequest(decryptedPayload); Assert.Equal(1, reportedNbRep); Assert.Equal(1, linkAdr.NbRep); Assert.Equal(1, loraDevice.NbRep); // 5. Frame counter down is updated Assert.Equal(ExpectedDeviceFcntDown, loraDevice.FCntDown); Assert.Equal(ExpectedDeviceFcntDown, payloadDataDown.GetFcnt()); }
public async Task Perform_Rate_Adapatation_When_Possible(uint deviceId, int count, int expectedDR, int expectedTXPower, int expectedNbRep, uint initialDeviceFcntUp) { uint payloadFcnt = 0; const uint InitialDeviceFcntDown = 0; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(deviceId, gatewayID: this.ServerConfiguration.GatewayID), frmCntUp: initialDeviceFcntUp); var loraDevice = this.CreateLoRaDevice(simulatedDevice); this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .ReturnsAsync(true); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, this.NewNonEmptyCache(loraDevice), this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); // Send to message processor var messageProcessor = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); // In this case we want to simulate a cache failure, so we don't initialize the cache. if (deviceId != 12) { payloadFcnt = await this.InitializeCacheToDefaultValuesAsync(payloadFcnt, simulatedDevice, messageProcessor); } else { var payloadInt = simulatedDevice.CreateConfirmedDataUpMessage("1234", fcnt: payloadFcnt); var rxpkInt = payloadInt.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0]; var requestInt = this.CreateWaitableRequest(rxpkInt); messageProcessor.DispatchRequest(requestInt); Assert.True(await requestInt.WaitCompleteAsync(-1)); payloadFcnt++; } for (int i = 0; i < count; i++) { var payloadInt = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrl: (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); var rxpkInt = payloadInt.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0]; var requestInt = this.CreateWaitableRequest(rxpkInt); messageProcessor.DispatchRequest(requestInt); Assert.True(await requestInt.WaitCompleteAsync(-1)); payloadFcnt++; } var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrl: (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADRAckReq + (int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); var rxpk = payload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0]; var request = this.CreateWaitableRequest(rxpk); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub this.LoRaDeviceClient.VerifyAll(); this.LoRaDeviceApi.VerifyAll(); Assert.NotNull(request.ResponseDownlink); Assert.True(request.ProcessingSucceeded); Assert.Equal(2, this.PacketForwarder.DownlinkMessages.Count); var downlinkMessage = this.PacketForwarder.DownlinkMessages[1]; var payloadDataDown = new LoRaPayloadData(Convert.FromBase64String(downlinkMessage.Txpk.Data)); // in this case we expect a null payload if (deviceId == 11) { Assert.Equal(0, payloadDataDown.Frmpayload.Span.Length); } else { // We expect a mac command in the payload Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); var decryptedPayload = payloadDataDown.PerformEncryption(simulatedDevice.NwkSKey); Assert.Equal(0, payloadDataDown.Fport.Span[0]); Assert.Equal((byte)CidEnum.LinkADRCmd, decryptedPayload[0]); var linkAdr = new LinkADRRequest(decryptedPayload); Assert.Equal(expectedDR, linkAdr.DataRate); Assert.Equal(expectedDR, loraDevice.DataRate); Assert.Equal(expectedTXPower, linkAdr.TxPower); Assert.Equal(expectedTXPower, loraDevice.TxPower); Assert.Equal(expectedNbRep, linkAdr.NbRep); Assert.Equal(expectedNbRep, loraDevice.NbRep); } // in case no payload the mac is in the FRMPayload and is decrypted with NwkSKey Assert.Equal(payloadDataDown.DevAddr.ToArray(), LoRaTools.Utils.ConversionHelper.StringToByteArray(loraDevice.DevAddr)); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(LoRaMessageType.UnconfirmedDataDown, payloadDataDown.LoRaMessageType); // 4. Frame counter up was updated Assert.Equal(payloadFcnt, loraDevice.FCntUp); // 5. Frame counter down is updated Assert.Equal(InitialDeviceFcntDown + 1, loraDevice.FCntDown); Assert.Equal(InitialDeviceFcntDown + 1, payloadDataDown.GetFcnt()); }
public async Task Perform_TXPower_Adaptation_When_Needed() { uint deviceId = 44; int messageCount = 21; uint payloadFcnt = 0; const uint InitialDeviceFcntUp = 9; const uint InitialDeviceFcntDown = 0; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(deviceId, gatewayID: this.ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp); var loraDevice = this.CreateLoRaDevice(simulatedDevice); this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); int reportedDR = 0; int reportedTxPower = 0; this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .Callback <TwinCollection>((t) => { if (t.Contains(TwinProperty.DataRate)) { reportedDR = t[TwinProperty.DataRate].Value; } if (t.Contains(TwinProperty.TxPower)) { reportedTxPower = t[TwinProperty.TxPower].Value; } }) .ReturnsAsync(true); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, this.NewNonEmptyCache(loraDevice), this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); // Send to message processor var messageProcessor = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); payloadFcnt = await this.InitializeCacheToDefaultValuesAsync(payloadFcnt, simulatedDevice, messageProcessor); // **************************************************** // First part gives very good connectivity and ensure we set power to minimum and DR increase to 5 // **************************************************** // set to very good lsnr and DR3 float currentLsnr = 20; string currentDR = "SF9BW125"; // todo add case without buffer for (int i = 0; i < messageCount; i++) { payloadFcnt = await this.SendMessage(currentLsnr, currentDR, payloadFcnt, simulatedDevice, messageProcessor, (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); } var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrl: (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADRAckReq + (int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); var rxpk = payload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey, lsnr: currentLsnr, datr: currentDR).Rxpk[0]; var request = this.CreateWaitableRequest(rxpk); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); Assert.NotNull(request.ResponseDownlink); Assert.Equal(2, this.PacketForwarder.DownlinkMessages.Count); var downlinkMessage = this.PacketForwarder.DownlinkMessages[1]; var payloadDataDown = new LoRaPayloadData(Convert.FromBase64String(downlinkMessage.Txpk.Data)); // We expect a mac command in the payload Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); var decryptedPayload = payloadDataDown.PerformEncryption(simulatedDevice.NwkSKey); Assert.Equal(0, payloadDataDown.Fport.Span[0]); Assert.Equal((byte)CidEnum.LinkADRCmd, decryptedPayload[0]); var linkAdr = new LinkADRRequest(decryptedPayload); Assert.Equal(5, linkAdr.DataRate); Assert.Equal(5, loraDevice.DataRate); Assert.Equal(5, reportedDR); Assert.Equal(7, linkAdr.TxPower); Assert.Equal(7, loraDevice.TxPower); Assert.Equal(7, reportedTxPower); Assert.Equal(payloadDataDown.DevAddr.ToArray(), LoRaTools.Utils.ConversionHelper.StringToByteArray(loraDevice.DevAddr)); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(LoRaMessageType.UnconfirmedDataDown, payloadDataDown.LoRaMessageType); // 4. Frame counter up was updated Assert.Equal(payloadFcnt, loraDevice.FCntUp); // 5. Frame counter down is updated Assert.Equal(InitialDeviceFcntDown + 1, loraDevice.FCntDown); Assert.Equal(InitialDeviceFcntDown + 1, payloadDataDown.GetFcnt()); // **************************************************** // Second part reduce connectivity and verify the DR stay to 5 and power set to max // **************************************************** currentLsnr = -50; // DR5 currentDR = "SF7BW125"; // todo add case without buffer for (int i = 0; i < messageCount; i++) { payloadFcnt = await this.SendMessage(currentLsnr, currentDR, payloadFcnt, simulatedDevice, messageProcessor, (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); } payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrl: (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADRAckReq + (int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); rxpk = payload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey, lsnr: currentLsnr, datr: currentDR).Rxpk[0]; request = this.CreateWaitableRequest(rxpk); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); Assert.NotNull(request.ResponseDownlink); Assert.Equal(3, this.PacketForwarder.DownlinkMessages.Count); downlinkMessage = this.PacketForwarder.DownlinkMessages[2]; payloadDataDown = new LoRaPayloadData(Convert.FromBase64String(downlinkMessage.Txpk.Data)); // We expect a mac command in the payload Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); decryptedPayload = payloadDataDown.PerformEncryption(simulatedDevice.NwkSKey); Assert.Equal(0, payloadDataDown.Fport.Span[0]); Assert.Equal((byte)CidEnum.LinkADRCmd, decryptedPayload[0]); linkAdr = new LinkADRRequest(decryptedPayload); Assert.Equal(5, linkAdr.DataRate); Assert.Equal(5, loraDevice.DataRate); Assert.Equal(5, reportedDR); Assert.Equal(0, linkAdr.TxPower); Assert.Equal(0, loraDevice.TxPower); Assert.Equal(0, reportedTxPower); Assert.Equal(payloadDataDown.DevAddr.ToArray(), LoRaTools.Utils.ConversionHelper.StringToByteArray(loraDevice.DevAddr)); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(LoRaMessageType.UnconfirmedDataDown, payloadDataDown.LoRaMessageType); // 4. Frame counter up was updated Assert.Equal(payloadFcnt, loraDevice.FCntUp); // 5. Frame counter down is updated Assert.Equal(InitialDeviceFcntDown + 2, loraDevice.FCntDown); Assert.Equal(InitialDeviceFcntDown + 2, payloadDataDown.GetFcnt()); // Expectations // 1. Message was sent to IoT Hub this.LoRaDeviceClient.VerifyAll(); this.LoRaDeviceApi.VerifyAll(); Assert.True(request.ProcessingSucceeded); }
public async Task Perform_DR_Adaptation_When_Needed(uint deviceId, float currentLsnr, string currentDR, int expectedDR, int expectedTxPower) { int messageCount = 21; uint payloadFcnt = 0; const uint InitialDeviceFcntUp = 9; const uint ExpectedDeviceFcntDown = 0; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(deviceId, gatewayID: this.ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp); var loraDevice = this.CreateLoRaDevice(simulatedDevice); this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); int twinDR = 0; int twinTxPower = 0; this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>())) .Callback <TwinCollection>((t) => { if (t.Contains(TwinProperty.DataRate)) { twinDR = t[TwinProperty.DataRate]; } if (t.Contains(TwinProperty.TxPower)) { twinTxPower = (int)t[TwinProperty.TxPower]; } }) .ReturnsAsync(true); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, this.NewNonEmptyCache(loraDevice), this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); // Send to message processor var messageProcessor = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); payloadFcnt = await this.InitializeCacheToDefaultValuesAsync(payloadFcnt, simulatedDevice, messageProcessor); for (int i = 0; i < messageCount; i++) { payloadFcnt = await this.SendMessage(currentLsnr, currentDR, payloadFcnt, simulatedDevice, messageProcessor, (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); } var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrl: (byte)((int)LoRaTools.LoRaMessage.FctrlEnum.ADRAckReq + (int)LoRaTools.LoRaMessage.FctrlEnum.ADR)); var rxpk = payload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey, lsnr: currentLsnr, datr: currentDR).Rxpk[0]; var request = this.CreateWaitableRequest(rxpk); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub this.LoRaDeviceClient.VerifyAll(); this.LoRaDeviceApi.VerifyAll(); Assert.True(request.ProcessingSucceeded); Assert.NotNull(request.ResponseDownlink); var downlinkMessage = this.PacketForwarder.DownlinkMessages[1]; var payloadDataDown = new LoRaPayloadData(Convert.FromBase64String(downlinkMessage.Txpk.Data)); // We expect a mac command in the payload if (deviceId == 221) { // In this case, no ADR adaptation is performed, so the message should be empty Assert.Equal(0, payloadDataDown.Frmpayload.Span.Length); Assert.Equal(0, payloadDataDown.Fopts.Span.Length); } else { Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); var decryptedPayload = payloadDataDown.PerformEncryption(simulatedDevice.NwkSKey); Assert.Equal(0, payloadDataDown.Fport.Span[0]); Assert.Equal((byte)CidEnum.LinkADRCmd, decryptedPayload[0]); var linkAdr = new LinkADRRequest(decryptedPayload); Assert.Equal(expectedDR, linkAdr.DataRate); Assert.Equal(expectedDR, loraDevice.DataRate); Assert.Equal(expectedDR, twinDR); Assert.Equal(expectedTxPower, linkAdr.TxPower); Assert.Equal(expectedTxPower, loraDevice.TxPower); Assert.Equal(expectedTxPower, twinTxPower); // in case no payload the mac is in the FRMPayload and is decrypted with NwkSKey Assert.Equal(payloadDataDown.DevAddr.ToArray(), LoRaTools.Utils.ConversionHelper.StringToByteArray(loraDevice.DevAddr)); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(LoRaMessageType.UnconfirmedDataDown, payloadDataDown.LoRaMessageType); // 4. Frame counter up was updated Assert.Equal(payloadFcnt, loraDevice.FCntUp); // 5. Frame counter down is updated Assert.Equal(ExpectedDeviceFcntDown + 1, loraDevice.FCntDown); Assert.Equal(ExpectedDeviceFcntDown + 1, payloadDataDown.GetFcnt()); } }