private static DownlinkMessage BuildDownstreamMessage(LoRaDevice loRaDevice, StationEui stationEUI, ILogger logger, ulong xTime, ReceiveWindow?rx1, ReceiveWindow rx2, RxDelay lnsRxDelay, LoRaPayloadData loRaMessage, LoRaDeviceClassType deviceClassType, uint?antennaPreference = null) { var messageBytes = loRaMessage.Serialize(loRaDevice.AppSKey.Value, loRaDevice.NwkSKey.Value); var downlinkMessage = new DownlinkMessage( messageBytes, xTime, rx1, rx2, loRaDevice.DevEUI, lnsRxDelay, deviceClassType, stationEUI, antennaPreference ); if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug($"{loRaMessage.MessageType} {JsonConvert.SerializeObject(downlinkMessage)}"); } return(downlinkMessage); }
public async Task Validate_Limits( uint payloadFcntUp, uint?deviceFcntUp, uint?deviceFcntDown, uint?startFcntUp, uint?startFcntDown, uint expectedFcntUp, uint expectedFcntDown, bool abpRelaxed, bool confirmed, bool supports32Bit = false, LoRaDeviceRequestFailedReason?failedReason = null) { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID, supports32BitFcnt: supports32Bit)); var devEui = simulatedDevice.LoRaDevice.DevEui; var devAddr = simulatedDevice.LoRaDevice.DevAddr.Value; LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)).ReturnsAsync(true); var initialTwin = SetupTwins(deviceFcntUp, deviceFcntDown, startFcntUp, startFcntDown, abpRelaxed, supports32Bit, simulatedDevice, devEui, devAddr); LoRaDeviceClient .Setup(x => x.GetTwinAsync(CancellationToken.None)).Returns(() => { return(Task.FromResult(initialTwin)); }); uint?fcntUpSavedInTwin = null; uint?fcntDownSavedInTwin = null; LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>())) .Returns <TwinCollection, CancellationToken>((t, _) => { fcntUpSavedInTwin = (uint)t[TwinProperty.FCntUp]; fcntDownSavedInTwin = (uint)t[TwinProperty.FCntDown]; return(Task.FromResult(true)); }); LoRaDeviceClient .Setup(x => x.ReceiveAsync(It.IsAny <TimeSpan>())) .ReturnsAsync((Message)null); var shouldReset = payloadFcntUp == 0 && abpRelaxed; if (shouldReset) { LoRaDeviceApi.Setup(x => x.ABPFcntCacheResetAsync(devEui, It.IsAny <uint>(), It.IsNotNull <string>())).ReturnsAsync(true); } LoRaDeviceApi.Setup(x => x.SearchByDevAddrAsync(devAddr)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "abc").AsList())); using var memoryCache = new MemoryCache(new MemoryCacheOptions()); using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache); // Send to message processor using var messageDispatcher = new MessageDispatcher( ServerConfiguration, deviceRegistry, FrameCounterUpdateStrategyProvider); var loRaPayloadData = confirmed ? simulatedDevice.CreateConfirmedDataUpMessage("1234", fcnt: payloadFcntUp, appSKey: simulatedDevice.AppSKey, nwkSKey: simulatedDevice.NwkSKey) : simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcntUp); using var req = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), loRaPayloadData); messageDispatcher.DispatchRequest(req); Assert.True(await req.WaitCompleteAsync(-1)); if (failedReason.HasValue) { Assert.Equal(failedReason.Value, req.ProcessingFailedReason); } else { Assert.True(req.ProcessingSucceeded); Assert.True(DeviceCache.TryGetByDevEui(devEui, out var loRaDevice)); if (confirmed) { Assert.NotNull(req.ResponseDownlink); Assert.True(req.ProcessingSucceeded); Assert.Single(DownstreamMessageSender.DownlinkMessages); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0]; var payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); payloadDataDown.Serialize(simulatedDevice.AppSKey.Value); Assert.Equal(expectedFcntDown, payloadDataDown.Fcnt); } Assert.Equal(expectedFcntUp, loRaDevice.FCntUp); } }
/// <summary> /// Creates downlink message with ack for confirmation or cloud to device message /// </summary> internal static DownlinkMessageBuilderResponse CreateDownlinkMessage( NetworkServerConfiguration configuration, LoRaDevice loRaDevice, LoRaRequest request, LoRaOperationTimeWatcher timeWatcher, IReceivedLoRaCloudToDeviceMessage cloudToDeviceMessage, bool fpending, uint fcntDown, LoRaADRResult loRaADRResult) { var fcntDownToSend = ValidateAndConvert16bitFCnt(fcntDown); var upstreamPayload = (LoRaPayloadData)request.Payload; var rxpk = request.Rxpk; var loRaRegion = request.Region; bool isMessageTooLong = false; // default fport byte fctrl = 0; if (upstreamPayload.LoRaMessageType == LoRaMessageType.ConfirmedDataUp) { // Confirm receiving message to device fctrl = (byte)FctrlEnum.Ack; } // Calculate receive window var receiveWindow = timeWatcher.ResolveReceiveWindowToUse(loRaDevice); if (receiveWindow == Constants.INVALID_RECEIVE_WINDOW) { // No valid receive window. Abandon the message isMessageTooLong = true; return(new DownlinkMessageBuilderResponse(null, isMessageTooLong)); } byte[] rndToken = new byte[2]; lock (RndLock) { RndDownlinkMessageBuilder.NextBytes(rndToken); } string datr; double freq; long tmst; if (receiveWindow == Constants.RECEIVE_WINDOW_2) { tmst = rxpk.Tmst + CalculateTime(timeWatcher.GetReceiveWindow2Delay(loRaDevice), loRaDevice.ReportedRXDelay); (freq, datr) = loRaRegion.GetDownstreamRX2DRAndFreq(loRaDevice.DevEUI, configuration.Rx2DataRate, configuration.Rx2DataFrequency, loRaDevice.ReportedRX2DataRate); } else { datr = loRaRegion.GetDownstreamDR(rxpk, (uint)loRaDevice.ReportedRX1DROffset); if (datr == null) { Logger.Log(loRaDevice.DevEUI, "there was a problem in setting the data rate in the downstream message packet forwarder settings", LogLevel.Error); return(new DownlinkMessageBuilderResponse(null, false)); } if (!loRaRegion.TryGetUpstreamChannelFrequency(rxpk, out freq)) { Logger.Log(loRaDevice.DevEUI, "there was a problem in setting the frequency in the downstream message packet forwarder settings", LogLevel.Error); return(new DownlinkMessageBuilderResponse(null, false)); } tmst = rxpk.Tmst + CalculateTime(timeWatcher.GetReceiveWindow1Delay(loRaDevice), loRaDevice.ReportedRXDelay); } // get max. payload size based on data rate from LoRaRegion var maxPayloadSize = loRaRegion.GetMaxPayloadSize(datr); // Deduct 8 bytes from max payload size. maxPayloadSize -= Constants.LORA_PROTOCOL_OVERHEAD_SIZE; var availablePayloadSize = maxPayloadSize; var macCommands = new List <MacCommand>(); byte?fport = null; var requiresDeviceAcknowlegement = false; var macCommandType = CidEnum.Zero; byte[] frmPayload = null; if (cloudToDeviceMessage != null) { // Get C2D Mac coomands var macCommandsC2d = PrepareMacCommandAnswer(loRaDevice.DevEUI, null, cloudToDeviceMessage?.MacCommands, rxpk, null); // Calculate total C2D payload size var totalC2dSize = cloudToDeviceMessage.GetPayload()?.Length ?? 0; totalC2dSize += macCommandsC2d?.Sum(x => x.Length) ?? 0; // Total C2D payload will fit if (availablePayloadSize >= totalC2dSize) { // Add frmPayload frmPayload = cloudToDeviceMessage.GetPayload(); // Add C2D Mac commands if (macCommandsC2d?.Count > 0) { foreach (MacCommand macCommand in macCommandsC2d) { macCommands.Add(macCommand); } } // Deduct frmPayload size from available payload size, continue processing and log availablePayloadSize -= (uint)totalC2dSize; if (cloudToDeviceMessage.Confirmed) { requiresDeviceAcknowlegement = true; loRaDevice.LastConfirmedC2DMessageID = cloudToDeviceMessage.MessageId ?? Constants.C2D_MSG_ID_PLACEHOLDER; } if (cloudToDeviceMessage.Fport > 0) { fport = cloudToDeviceMessage.Fport; } Logger.Log(loRaDevice.DevEUI, $"cloud to device message: {((frmPayload?.Length ?? 0) == 0 ? "empty" : ConversionHelper.ByteArrayToString(frmPayload))}, id: {cloudToDeviceMessage.MessageId ?? "undefined"}, fport: {fport ?? 0}, confirmed: {requiresDeviceAcknowlegement}, cidType: {macCommandType}, macCommand: {macCommands.Count > 0}", LogLevel.Information); Array.Reverse(frmPayload); } else { // Flag message to be abandoned and log Logger.Log(loRaDevice.DevEUI, $"cloud to device message: {((frmPayload?.Length ?? 0) == 0 ? "empty" : Encoding.UTF8.GetString(frmPayload))}, id: {cloudToDeviceMessage.MessageId ?? "undefined"}, fport: {fport ?? 0}, confirmed: {requiresDeviceAcknowlegement} too long for current receive window. Abandoning.", LogLevel.Debug); isMessageTooLong = true; } } // Get request Mac commands var macCommandsRequest = PrepareMacCommandAnswer(loRaDevice.DevEUI, upstreamPayload.MacCommands, null, rxpk, loRaADRResult); // Calculate request Mac commands size var macCommandsRequestSize = macCommandsRequest?.Sum(x => x.Length) ?? 0; // Try adding request Mac commands if (availablePayloadSize >= macCommandsRequestSize) { if (macCommandsRequest?.Count > 0) { foreach (MacCommand macCommand in macCommandsRequest) { macCommands.Add(macCommand); } } } if (fpending || isMessageTooLong) { fctrl |= (int)FctrlEnum.FpendingOrClassB; } if (upstreamPayload.IsAdrEnabled) { fctrl |= (byte)FctrlEnum.ADR; } var srcDevAddr = upstreamPayload.DevAddr.Span; var reversedDevAddr = new byte[srcDevAddr.Length]; for (int i = reversedDevAddr.Length - 1; i >= 0; --i) { reversedDevAddr[i] = srcDevAddr[srcDevAddr.Length - (1 + i)]; } var msgType = requiresDeviceAcknowlegement ? LoRaMessageType.ConfirmedDataDown : LoRaMessageType.UnconfirmedDataDown; var ackLoRaMessage = new LoRaPayloadData( msgType, reversedDevAddr, new byte[] { fctrl }, BitConverter.GetBytes(fcntDownToSend), macCommands, fport.HasValue ? new byte[] { fport.Value } : null, frmPayload, 1, loRaDevice.Supports32BitFCnt ? fcntDown : (uint?)null); // todo: check the device twin preference if using confirmed or unconfirmed down Logger.Log(loRaDevice.DevEUI, $"sending a downstream message with ID {ConversionHelper.ByteArrayToString(rndToken)}", LogLevel.Information); return(new DownlinkMessageBuilderResponse( ackLoRaMessage.Serialize(loRaDevice.AppSKey, loRaDevice.NwkSKey, datr, freq, tmst, loRaDevice.DevEUI), isMessageTooLong)); }
internal static DownlinkMessageBuilderResponse CreateDownlinkMessage( NetworkServerConfiguration configuration, LoRaDevice loRaDevice, Region loRaRegion, IReceivedLoRaCloudToDeviceMessage cloudToDeviceMessage, uint fcntDown) { var fcntDownToSend = ValidateAndConvert16bitFCnt(fcntDown); // default fport byte fctrl = 0; CidEnum macCommandType = CidEnum.Zero; byte[] rndToken = new byte[2]; lock (RndLock) { RndDownlinkMessageBuilder.NextBytes(rndToken); } bool isMessageTooLong = false; // Class C always uses RX2 string datr; double freq; var tmst = 0; // immediate mode // Class C always use RX2 (freq, datr) = loRaRegion.GetDownstreamRX2DRAndFreq(loRaDevice.DevEUI, configuration.Rx2DataRate, configuration.Rx2DataFrequency, loRaDevice.ReportedRX2DataRate); // get max. payload size based on data rate from LoRaRegion var maxPayloadSize = loRaRegion.GetMaxPayloadSize(datr); // Deduct 8 bytes from max payload size. maxPayloadSize -= Constants.LORA_PROTOCOL_OVERHEAD_SIZE; var availablePayloadSize = maxPayloadSize; var macCommands = PrepareMacCommandAnswer(loRaDevice.DevEUI, null, cloudToDeviceMessage.MacCommands, null, null); // Calculate total C2D payload size var totalC2dSize = cloudToDeviceMessage.GetPayload()?.Length ?? 0; totalC2dSize += macCommands?.Sum(x => x.Length) ?? 0; // Total C2D payload will NOT fit if (availablePayloadSize < totalC2dSize) { isMessageTooLong = true; return(new DownlinkMessageBuilderResponse(null, isMessageTooLong)); } if (macCommands?.Count > 0) { macCommandType = macCommands.First().Cid; } if (cloudToDeviceMessage.Confirmed) { loRaDevice.LastConfirmedC2DMessageID = cloudToDeviceMessage.MessageId ?? Constants.C2D_MSG_ID_PLACEHOLDER; } var frmPayload = cloudToDeviceMessage.GetPayload(); if (Logger.LoggerLevel >= LogLevel.Information) { Logger.Log(loRaDevice.DevEUI, $"cloud to device message: {ConversionHelper.ByteArrayToString(frmPayload)}, id: {cloudToDeviceMessage.MessageId ?? "undefined"}, fport: {cloudToDeviceMessage.Fport}, confirmed: {cloudToDeviceMessage.Confirmed}, cidType: {macCommandType}", LogLevel.Information); Logger.Log(loRaDevice.DevEUI, $"sending a downstream message with ID {ConversionHelper.ByteArrayToString(rndToken)}", LogLevel.Information); } Array.Reverse(frmPayload); var payloadDevAddr = ConversionHelper.StringToByteArray(loRaDevice.DevAddr); var reversedDevAddr = new byte[payloadDevAddr.Length]; for (int i = reversedDevAddr.Length - 1; i >= 0; --i) { reversedDevAddr[i] = payloadDevAddr[payloadDevAddr.Length - (1 + i)]; } var msgType = cloudToDeviceMessage.Confirmed ? LoRaMessageType.ConfirmedDataDown : LoRaMessageType.UnconfirmedDataDown; var ackLoRaMessage = new LoRaPayloadData( msgType, reversedDevAddr, new byte[] { fctrl }, BitConverter.GetBytes(fcntDownToSend), macCommands, new byte[] { cloudToDeviceMessage.Fport }, frmPayload, 1, loRaDevice.Supports32BitFCnt ? fcntDown : (uint?)null); return(new DownlinkMessageBuilderResponse( ackLoRaMessage.Serialize(loRaDevice.AppSKey, loRaDevice.NwkSKey, datr, freq, tmst, loRaDevice.DevEUI), isMessageTooLong)); }
public async Task Unconfirmed_Cloud_To_Device_From_Decoder_Should_Send_Downstream_Message(string c2dDevEUI) { const uint PayloadFcnt = 10; const uint InitialDeviceFcntUp = 9; const uint InitialDeviceFcntDown = 20; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp, frmCntDown: InitialDeviceFcntDown); var loraDevice = CreateLoRaDevice(simulatedDevice); LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); var decoderResult = new DecodePayloadResult("1") { CloudToDeviceMessage = new ReceivedLoRaCloudToDeviceMessage() { Fport = TestPort, MessageId = "123", Payload = "12", DevEUI = c2dDevEUI is { } someDevEui?DevEui.Parse(someDevEui) : null }, }; var payloadDecoder = new Mock <ILoRaPayloadDecoder>(MockBehavior.Strict); payloadDecoder.Setup(x => x.DecodeMessageAsync(simulatedDevice.DevEUI, It.IsNotNull <byte[]>(), TestPort, It.IsAny <string>())) .ReturnsAsync(decoderResult); PayloadDecoder.SetDecoder(payloadDecoder.Object); using var cache = EmptyMemoryCache(); using var loraDeviceCache = CreateDeviceCache(loraDevice); using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache); // Send to message processor using var messageProcessor = new MessageDispatcher( ServerConfiguration, deviceRegistry, FrameCounterUpdateStrategyProvider); var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: PayloadFcnt); using var request = CreateWaitableRequest(payload); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); // 2. Return is downstream message Assert.NotNull(request.ResponseDownlink); Assert.True(request.ProcessingSucceeded); Assert.Single(DownstreamMessageSender.DownlinkMessages); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0]; var payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); payloadDataDown.Serialize(loraDevice.AppSKey.Value); Assert.Equal(payloadDataDown.DevAddr, loraDevice.DevAddr); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(MacMessageType.UnconfirmedDataDown, payloadDataDown.MessageType); // 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.Fcnt); // 6. Frame count has pending changes Assert.True(loraDevice.HasFrameCountChanges); LoRaDeviceApi.VerifyAll(); LoRaDeviceClient.VerifyAll(); payloadDecoder.VerifyAll(); }
public async Task When_Device_Prefers_Second_Window_Should_Send_Downstream_In_Second_Window() { const uint PayloadFcnt = 10; const uint InitialDeviceFcntUp = 9; const uint InitialDeviceFcntDown = 20; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp, frmCntDown: InitialDeviceFcntDown); var devAddr = simulatedDevice.DevAddr.Value; var devEUI = simulatedDevice.DevEUI; // Will get twin to initialize LoRaDevice var deviceTwin = TestUtils.CreateABPTwin( simulatedDevice, desiredProperties: new Dictionary <string, object> { { TwinProperty.PreferredWindow, 2 } }); LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)).ReturnsAsync(deviceTwin); LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>())) .ReturnsAsync(true); LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); using var cloudToDeviceMessage = new ReceivedLoRaCloudToDeviceMessage() { Payload = "c2d", Fport = TestPort } .CreateMessage(); LoRaDeviceClient.SetupSequence(x => x.ReceiveAsync(It.IsAny <TimeSpan>())) .ReturnsAsync(cloudToDeviceMessage) .Returns(EmptyAdditionalMessageReceiveAsync); // 2nd cloud to device message does not return anything LoRaDeviceClient.Setup(x => x.CompleteAsync(cloudToDeviceMessage)) .ReturnsAsync(true); LoRaDeviceApi.Setup(x => x.SearchByDevAddrAsync(devAddr)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "adad").AsList())); using var cache = NewMemoryCache(); using var loRaDeviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache); // Send to message processor using var messageProcessor = new MessageDispatcher( ServerConfiguration, loRaDeviceRegistry, FrameCounterUpdateStrategyProvider); var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: PayloadFcnt); using var request = CreateWaitableRequest(payload); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); // 3. Return is downstream message Assert.True(request.ProcessingSucceeded); Assert.NotNull(request.ResponseDownlink); Assert.Single(DownstreamMessageSender.DownlinkMessages); var downlinkMessage = DownstreamMessageSender.DownlinkMessages.First(); TestUtils.CheckDRAndFrequencies(request, downlinkMessage, true); // Get the device from cache Assert.True(DeviceCache.TryGetForPayload(request.Payload, out var loRaDevice)); var payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); payloadDataDown.Serialize(loRaDevice.AppSKey.Value); Assert.Equal(payloadDataDown.DevAddr, loRaDevice.DevAddr); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(MacMessageType.UnconfirmedDataDown, payloadDataDown.MessageType); // 4. Frame counter up was updated Assert.Equal(PayloadFcnt, loRaDevice.FCntUp); // 5. Frame counter down is updated var expectedFcntDown = InitialDeviceFcntDown + Constants.MaxFcntUnsavedDelta - 1 + 1; // adding 9 as buffer when creating a new device instance Assert.Equal(expectedFcntDown, loRaDevice.FCntDown); Assert.Equal(expectedFcntDown, payloadDataDown.Fcnt); Assert.Equal(0U, loRaDevice.FCntDown - loRaDevice.LastSavedFCntDown); // 6. Frame count has no pending changes Assert.False(loRaDevice.HasFrameCountChanges); }
public async Task OTAA_Unconfirmed_With_Cloud_To_Device_Mac_Command_Returns_Downstream_Message(string msg) { const uint PayloadFcnt = 20; const uint InitialDeviceFcntUp = 9; const uint InitialDeviceFcntDown = 20; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp, frmCntDown: InitialDeviceFcntDown); var loraDevice = CreateLoRaDevice(simulatedDevice); LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>())) .ReturnsAsync(true); var c2d = new ReceivedLoRaCloudToDeviceMessage() { Payload = msg, MacCommands = { new DevStatusRequest() }, }; if (!string.IsNullOrEmpty(msg)) { c2d.Fport = TestPort; } using var cloudToDeviceMessage = c2d.CreateMessage(); 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); using var cache = EmptyMemoryCache(); using var loraDeviceCache = CreateDeviceCache(loraDevice); using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache); // Send to message processor using var messageProcessor = new MessageDispatcher( ServerConfiguration, deviceRegistry, FrameCounterUpdateStrategyProvider); var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: PayloadFcnt); using var request = CreateWaitableRequest(payload); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); // 2. Return is downstream message Assert.NotNull(request.ResponseDownlink); Assert.True(request.ProcessingSucceeded); Assert.Single(DownstreamMessageSender.DownlinkMessages); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0]; var payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); // in case no payload the mac is in the FRMPayload and is decrypted with NwkSKey if (string.IsNullOrEmpty(msg)) { payloadDataDown.Serialize(loraDevice.NwkSKey.Value); } else { payloadDataDown.Serialize(loraDevice.AppSKey.Value); } Assert.Equal(payloadDataDown.DevAddr, loraDevice.DevAddr); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(MacMessageType.UnconfirmedDataDown, payloadDataDown.MessageType); // 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.Fcnt); // 6. Frame count has no pending changes Assert.False(loraDevice.HasFrameCountChanges); // 7. Mac commands should be present in reply // mac command is in fopts if there is a c2d message if (string.IsNullOrEmpty(msg)) { Assert.Equal(FramePort.MacCommand, payloadDataDown.Fport); Assert.NotNull(payloadDataDown.Frmpayload.Span.ToArray()); Assert.Single(payloadDataDown.Frmpayload.Span.ToArray()); Assert.Equal((byte)LoRaTools.Cid.DevStatusCmd, payloadDataDown.Frmpayload.Span[0]); } else { Assert.NotNull(payloadDataDown.Fopts.Span.ToArray()); Assert.Single(payloadDataDown.Fopts.Span.ToArray()); Assert.Equal((byte)LoRaTools.Cid.DevStatusCmd, payloadDataDown.Fopts.Span[0]); } LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); }
public async Task Perform_NbRep_Adaptation_When_Needed() { uint deviceId = 31; var currentDR = LoRaDataRate.SF8BW125; var currentLsnr = -20; var messageCount = 20; uint payloadFcnt = 0; const uint InitialDeviceFcntUp = 1; const uint ExpectedDeviceFcntDown = 4; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(deviceId, gatewayID: ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp); var loraDevice = CreateLoRaDevice(simulatedDevice); LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); var reportedNbRep = 0; LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>())) .Callback <TwinCollection, CancellationToken>((t, _) => { if (t.Contains(TwinProperty.NbRep)) { reportedNbRep = (int)t[TwinProperty.NbRep]; } }) .ReturnsAsync(true); using var cache = EmptyMemoryCache(); using var loraDeviceCache = CreateDeviceCache(loraDevice); using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache); // Send to message processor using var messageProcessor = new MessageDispatcher( ServerConfiguration, deviceRegistry, FrameCounterUpdateStrategyProvider); payloadFcnt = await 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 (var i = 0; i < messageCount; i++) { payloadFcnt = await SendMessage(currentLsnr, currentDR, payloadFcnt + 3, simulatedDevice, messageProcessor, FrameControlFlags.Adr); } var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrlFlags: FrameControlFlags.AdrAckReq | FrameControlFlags.Adr); var radioMetadata = new RadioMetadata(TestUtils.TestRegion.GetDataRateIndex(currentDR), Hertz.Mega(868.1), new RadioMetadataUpInfo(0, 0, 0, 0, currentLsnr)); using var request = CreateWaitableRequest(radioMetadata, payload); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); Assert.True(request.ProcessingSucceeded); Assert.NotNull(request.ResponseDownlink); Assert.Equal(2, DownstreamMessageSender.DownlinkMessages.Count); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[1]; var payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); // We expect a mac command in the payload Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); var decryptedPayload = payloadDataDown.Serialize(simulatedDevice.NwkSKey.Value); Assert.Equal(FramePort.MacCommand, payloadDataDown.Fport); Assert.Equal((byte)Cid.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 (var i = 0; i < messageCount; i++) { payloadFcnt = await SendMessage(currentLsnr, currentDR, payloadFcnt, simulatedDevice, messageProcessor, FrameControlFlags.Adr); } payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrlFlags: FrameControlFlags.AdrAckReq | FrameControlFlags.Adr); using var secondRequest = CreateWaitableRequest(radioMetadata, payload); messageProcessor.DispatchRequest(secondRequest); Assert.True(await secondRequest.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); Assert.True(secondRequest.ProcessingSucceeded); Assert.NotNull(secondRequest.ResponseDownlink); Assert.Equal(3, DownstreamMessageSender.DownlinkMessages.Count); downlinkMessage = DownstreamMessageSender.DownlinkMessages[2]; payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); // We expect a mac command in the payload Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); decryptedPayload = payloadDataDown.Serialize(simulatedDevice.NwkSKey.Value); Assert.Equal(FramePort.MacCommand, payloadDataDown.Fport); Assert.Equal((byte)Cid.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, loraDevice.DevAddr); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(MacMessageType.UnconfirmedDataDown, payloadDataDown.MessageType); // 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 (var i = 0; i < messageCount; i++) { payloadFcnt = await SendMessage(currentLsnr, currentDR, payloadFcnt, simulatedDevice, messageProcessor, FrameControlFlags.Adr); } payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrlFlags: FrameControlFlags.AdrAckReq | FrameControlFlags.Adr); using var thirdRequest = CreateWaitableRequest(radioMetadata, payload); messageProcessor.DispatchRequest(thirdRequest); Assert.True(await thirdRequest.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); Assert.True(thirdRequest.ProcessingSucceeded); Assert.NotNull(thirdRequest.ResponseDownlink); Assert.Equal(4, DownstreamMessageSender.DownlinkMessages.Count); downlinkMessage = DownstreamMessageSender.DownlinkMessages[3]; payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); // We expect a mac command in the payload Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); decryptedPayload = payloadDataDown.Serialize(simulatedDevice.NwkSKey.Value); Assert.Equal(FramePort.MacCommand, payloadDataDown.Fport); Assert.Equal((byte)Cid.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.Fcnt); }
public async Task OTAA_Confirmed_With_Cloud_To_Device_Message_Returns_Downstream_Message(uint initialDeviceFcntUp, uint payloadFcnt) { const uint InitialDeviceFcntDown = 20; var needsToSaveFcnt = payloadFcnt - initialDeviceFcntUp >= Constants.MaxFcntUnsavedDelta; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID), frmCntUp: initialDeviceFcntUp, frmCntDown: InitialDeviceFcntDown); var loraDevice = CreateLoRaDevice(simulatedDevice); LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); if (needsToSaveFcnt) { LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>())) .ReturnsAsync(true); } using var cloudToDeviceMessage = new ReceivedLoRaCloudToDeviceMessage() { Payload = "c2d", Fport = TestPort } .CreateMessage(); 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); using var cache = EmptyMemoryCache(); using var loraDeviceCache = CreateDeviceCache(loraDevice); using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache); // Send to message processor using var messageProcessor = new MessageDispatcher( ServerConfiguration, deviceRegistry, FrameCounterUpdateStrategyProvider); var payload = simulatedDevice.CreateConfirmedDataUpMessage("1234", fcnt: payloadFcnt); using var request = CreateWaitableRequest(payload); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); // 2. Return is downstream message Assert.NotNull(request.ResponseDownlink); Assert.True(request.ProcessingSucceeded); Assert.Single(DownstreamMessageSender.DownlinkMessages); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0]; var payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); payloadDataDown.Serialize(loraDevice.AppSKey.Value); Assert.Equal(payloadDataDown.DevAddr, loraDevice.DevAddr); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(MacMessageType.UnconfirmedDataDown, payloadDataDown.MessageType); // 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.Fcnt); // 6. Frame count has pending changes? if (needsToSaveFcnt) { Assert.False(loraDevice.HasFrameCountChanges); } else { Assert.True(loraDevice.HasFrameCountChanges); } }
public async Task Perform_Rate_Adapatation_When_Possible(uint deviceId, int count, DataRateIndex expectedDR, int expectedTXPower, int expectedNbRep, uint initialDeviceFcntUp) { uint payloadFcnt = 0; const uint InitialDeviceFcntDown = 0; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(deviceId, gatewayID: ServerConfiguration.GatewayID), frmCntUp: initialDeviceFcntUp); var loraDevice = CreateLoRaDevice(simulatedDevice); LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>())) .ReturnsAsync(true); using var cache = EmptyMemoryCache(); using var loraDeviceCache = CreateDeviceCache(loraDevice); using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache); // Send to message processor using var messageProcessor = new MessageDispatcher( ServerConfiguration, deviceRegistry, FrameCounterUpdateStrategyProvider); // In this case we want to simulate a cache failure, so we don't initialize the cache. if (deviceId != 12) { payloadFcnt = await InitializeCacheToDefaultValuesAsync(payloadFcnt, simulatedDevice, messageProcessor); } else { var payloadInt = simulatedDevice.CreateConfirmedDataUpMessage("1234", fcnt: payloadFcnt); using var requestInt = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), payloadInt); messageProcessor.DispatchRequest(requestInt); Assert.True(await requestInt.WaitCompleteAsync(-1)); payloadFcnt++; } for (var i = 0; i < count; i++) { var payloadInt = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrlFlags: FrameControlFlags.Adr); using var requestInt = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), payloadInt); messageProcessor.DispatchRequest(requestInt); Assert.True(await requestInt.WaitCompleteAsync(-1)); payloadFcnt++; } var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrlFlags: FrameControlFlags.AdrAckReq | FrameControlFlags.Adr); using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), payload); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); Assert.NotNull(request.ResponseDownlink); Assert.True(request.ProcessingSucceeded); Assert.Equal(2, DownstreamMessageSender.DownlinkMessages.Count); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[1]; var payloadDataDown = new LoRaPayloadData(downlinkMessage.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.Serialize(simulatedDevice.NwkSKey.Value); Assert.Equal(FramePort.MacCommand, payloadDataDown.Fport); Assert.Equal((byte)Cid.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, loraDevice.DevAddr); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(MacMessageType.UnconfirmedDataDown, payloadDataDown.MessageType); // 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.Fcnt); }
public async Task Perform_TXPower_Adaptation_When_Needed() { uint deviceId = 44; var messageCount = 21; uint payloadFcnt = 0; const uint InitialDeviceFcntUp = 9; const uint InitialDeviceFcntDown = 0; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(deviceId, gatewayID: ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp); var loraDevice = CreateLoRaDevice(simulatedDevice); LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); var reportedDR = DR0; var reportedTxPower = 0; LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>())) .Callback <TwinCollection, CancellationToken>((t, _) => { if (t.Contains(TwinProperty.DataRate)) { reportedDR = (DataRateIndex)(int)(object)t[TwinProperty.DataRate].Value; } if (t.Contains(TwinProperty.TxPower)) { reportedTxPower = t[TwinProperty.TxPower].Value; } }) .ReturnsAsync(true); using var cache = EmptyMemoryCache(); using var loraDeviceCache = CreateDeviceCache(loraDevice); using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache); // Send to message processor using var messageProcessor = new MessageDispatcher( ServerConfiguration, deviceRegistry, FrameCounterUpdateStrategyProvider); payloadFcnt = await 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; var currentDR = LoRaDataRate.SF9BW125; // todo add case without buffer for (var i = 0; i < messageCount; i++) { payloadFcnt = await SendMessage(currentLsnr, currentDR, payloadFcnt, simulatedDevice, messageProcessor, FrameControlFlags.Adr); } var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrlFlags: FrameControlFlags.AdrAckReq | FrameControlFlags.Adr); var radioMetadata = new RadioMetadata(TestUtils.TestRegion.GetDataRateIndex(currentDR), Hertz.Mega(868.1), new RadioMetadataUpInfo(0, 0, 0, 0, currentLsnr)); using var request = CreateWaitableRequest(radioMetadata, payload); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); Assert.NotNull(request.ResponseDownlink); Assert.Equal(2, DownstreamMessageSender.DownlinkMessages.Count); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[1]; var payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); // We expect a mac command in the payload Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); var decryptedPayload = payloadDataDown.Serialize(simulatedDevice.NwkSKey.Value); Assert.Equal(FramePort.MacCommand, payloadDataDown.Fport); Assert.Equal((byte)Cid.LinkADRCmd, decryptedPayload[0]); var linkAdr = new LinkADRRequest(decryptedPayload); Assert.Equal(DR5, linkAdr.DataRate); Assert.Equal(DR5, loraDevice.DataRate); Assert.Equal(DR5, reportedDR); Assert.Equal(7, linkAdr.TxPower); Assert.Equal(7, loraDevice.TxPower); Assert.Equal(7, reportedTxPower); Assert.Equal(payloadDataDown.DevAddr, loraDevice.DevAddr); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(MacMessageType.UnconfirmedDataDown, payloadDataDown.MessageType); // 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.Fcnt); // **************************************************** // Second part reduce connectivity and verify the DR stay to 5 and power set to max // **************************************************** currentLsnr = -50; // DR5 currentDR = LoRaDataRate.SF7BW125; // todo add case without buffer for (var i = 0; i < messageCount; i++) { payloadFcnt = await SendMessage(currentLsnr, currentDR, payloadFcnt, simulatedDevice, messageProcessor, FrameControlFlags.Adr); } payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrlFlags: FrameControlFlags.AdrAckReq | FrameControlFlags.Adr); var radioMetadataWithDatr5 = new RadioMetadata(TestUtils.TestRegion.GetDataRateIndex(currentDR), Hertz.Mega(868.1), new RadioMetadataUpInfo(0, 0, 0, 0, currentLsnr)); using var secondRequest = CreateWaitableRequest(radioMetadataWithDatr5, payload); messageProcessor.DispatchRequest(secondRequest); Assert.True(await secondRequest.WaitCompleteAsync()); Assert.NotNull(secondRequest.ResponseDownlink); Assert.Equal(3, DownstreamMessageSender.DownlinkMessages.Count); downlinkMessage = DownstreamMessageSender.DownlinkMessages[2]; payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); // We expect a mac command in the payload Assert.Equal(5, payloadDataDown.Frmpayload.Span.Length); decryptedPayload = payloadDataDown.Serialize(simulatedDevice.NwkSKey.Value); Assert.Equal(FramePort.MacCommand, payloadDataDown.Fport); Assert.Equal((byte)Cid.LinkADRCmd, decryptedPayload[0]); linkAdr = new LinkADRRequest(decryptedPayload); Assert.Equal(DR5, linkAdr.DataRate); Assert.Equal(DR5, loraDevice.DataRate); Assert.Equal(DR5, reportedDR); Assert.Equal(0, linkAdr.TxPower); Assert.Equal(0, loraDevice.TxPower); Assert.Equal(0, reportedTxPower); Assert.Equal(payloadDataDown.DevAddr, loraDevice.DevAddr); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(MacMessageType.UnconfirmedDataDown, payloadDataDown.MessageType); // 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.Fcnt); // Expectations // 1. Message was sent to IoT Hub LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); Assert.True(secondRequest.ProcessingSucceeded); }
public async Task Perform_DR_Adaptation_When_Needed(uint deviceId, float currentLsnr, string currentDRString, DataRateIndex expectedDR, int expectedTxPower) { var currentDR = LoRaDataRate.Parse(currentDRString); var messageCount = 21; uint payloadFcnt = 0; const uint InitialDeviceFcntUp = 9; const uint ExpectedDeviceFcntDown = 0; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(deviceId, gatewayID: ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp); var loraDevice = CreateLoRaDevice(simulatedDevice); LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>())) .ReturnsAsync((Message)null); var twinDR = DR0; var twinTxPower = 0; LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>())) .Callback <TwinCollection, CancellationToken>((t, _) => { if (t.Contains(TwinProperty.DataRate)) { twinDR = t[TwinProperty.DataRate]; } if (t.Contains(TwinProperty.TxPower)) { twinTxPower = (int)t[TwinProperty.TxPower]; } }) .ReturnsAsync(true); using var cache = EmptyMemoryCache(); using var loraDeviceCache = CreateDeviceCache(loraDevice); using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache); // Send to message processor using var messageProcessor = new MessageDispatcher( ServerConfiguration, deviceRegistry, FrameCounterUpdateStrategyProvider); payloadFcnt = await InitializeCacheToDefaultValuesAsync(payloadFcnt, simulatedDevice, messageProcessor); for (var i = 0; i < messageCount; i++) { payloadFcnt = await SendMessage(currentLsnr, currentDR, payloadFcnt, simulatedDevice, messageProcessor, FrameControlFlags.Adr); } var payload = simulatedDevice.CreateUnconfirmedDataUpMessage("1234", fcnt: payloadFcnt, fctrlFlags: FrameControlFlags.AdrAckReq | FrameControlFlags.Adr); var radioMetadata = new RadioMetadata(TestUtils.TestRegion.GetDataRateIndex(currentDR), Hertz.Mega(868.1), new RadioMetadataUpInfo(0, 0, 0, 0, currentLsnr)); using var request = CreateWaitableRequest(radioMetadata, payload); messageProcessor.DispatchRequest(request); Assert.True(await request.WaitCompleteAsync()); // Expectations // 1. Message was sent to IoT Hub LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); Assert.True(request.ProcessingSucceeded); Assert.NotNull(request.ResponseDownlink); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[1]; var payloadDataDown = new LoRaPayloadData(downlinkMessage.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.Serialize(simulatedDevice.NwkSKey.Value); Assert.Equal(FramePort.MacCommand, payloadDataDown.Fport); Assert.Equal((byte)Cid.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, loraDevice.DevAddr); Assert.False(payloadDataDown.IsConfirmed); Assert.Equal(MacMessageType.UnconfirmedDataDown, payloadDataDown.MessageType); // 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.Fcnt); } }
public async Task Should_Reject( bool isConfirmed, bool hasMacInUpstream, bool hasMacInC2D, [CombinatorialValues("SF10BW125", "SF9BW125", "SF8BW125", "SF7BW125")] string datr) { const int InitialDeviceFcntUp = 9; const int InitialDeviceFcntDown = 20; var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(1, gatewayID: ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp, frmCntDown: InitialDeviceFcntDown); var loraDevice = CreateLoRaDevice(simulatedDevice); var(radioMetaData, loraPayload) = CreateUpstreamMessage(isConfirmed, hasMacInUpstream, LoRaDataRate.Parse(datr), simulatedDevice); if (!hasMacInUpstream) { LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); } var region = TestUtils.TestRegion; var c2dMessageMacCommand = new DevStatusRequest(); var c2dMessageMacCommandSize = hasMacInC2D ? c2dMessageMacCommand.Length : 0; var expectedDownlinkDatr = region.GetDataRateIndex(LoRaDataRate.Parse(datr)); var c2dPayloadSize = region.GetMaxPayloadSize(expectedDownlinkDatr) - c2dMessageMacCommandSize + 1 // make message too long on purpose - Constants.LoraProtocolOverheadSize; var c2dMessagePayload = TestUtils.GeneratePayload("123457890", (int)c2dPayloadSize); var c2dMessage = new ReceivedLoRaCloudToDeviceMessage() { Payload = c2dMessagePayload, Fport = FramePorts.App1, }; if (hasMacInC2D) { c2dMessage.MacCommands.Add(c2dMessageMacCommand); } using var cloudToDeviceMessage = c2dMessage.CreateMessage(); LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsAny <TimeSpan>())) .ReturnsAsync(cloudToDeviceMessage); LoRaDeviceClient.Setup(x => x.RejectAsync(cloudToDeviceMessage)) .ReturnsAsync(true); using var cache = EmptyMemoryCache(); using var loraDeviceCache = CreateDeviceCache(loraDevice); using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, loraDeviceCache); // Send to message processor using var messageProcessor = new MessageDispatcher( ServerConfiguration, deviceRegistry, FrameCounterUpdateStrategyProvider); using var request = CreateWaitableRequest(radioMetaData, loraPayload, constantElapsedTime: TimeSpan.Zero); messageProcessor.DispatchRequest(request); // Expectations // 1. Message was sent to IoT Hub Assert.True(await request.WaitCompleteAsync()); Assert.True(request.ProcessingSucceeded); var shouldHaveADownlink = isConfirmed || hasMacInUpstream; // 2. Return is downstream message ONLY // if is confirmed or had Mac commands in upstream message if (shouldHaveADownlink) { Assert.NotNull(request.ResponseDownlink); // TODO CHANGE THIS WHEN MOVING RXPK in #1086 // Assert.Equal(expectedDownlinkDatr, request.ResponseDownlink.Txpk.Datr); var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0]; var payloadDataDown = new LoRaPayloadData(downlinkMessage.Data); payloadDataDown.Serialize(loraDevice.AppSKey.Value); Assert.Equal(payloadDataDown.DevAddr, loraDevice.DevAddr); Assert.Equal(MacMessageType.UnconfirmedDataDown, payloadDataDown.MessageType); if (hasMacInUpstream) { Assert.Equal(new LinkCheckAnswer(1, 1).Length, payloadDataDown.Frmpayload.Length); Assert.Equal(FramePort.MacCommand, payloadDataDown.Fport); } } else { Assert.Null(request.ResponseDownlink); } LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); }
/// <summary> /// Creates downlink message with ack for confirmation or cloud to device message /// </summary> DownlinkPktFwdMessage CreateDownlinkMessage( Message cloudToDeviceMessage, LoRaDevice loRaDevice, Rxpk rxpk, LoRaPayloadData upstreamPayload, LoRaOperationTimeWatcher timeWatcher, ReadOnlyMemory <byte> payloadDevAddr, bool fpending, ushort fcntDown) { // default fport byte fctrl = 0; if (upstreamPayload.LoRaMessageType == LoRaMessageType.ConfirmedDataUp) { // Confirm receiving message to device fctrl = (byte)FctrlEnum.Ack; } byte?fport = null; var requiresDeviceAcknowlegement = false; byte[] macbytes = null; byte[] rndToken = new byte[2]; Random rnd = new Random(); rnd.NextBytes(rndToken); byte[] frmPayload = null; if (cloudToDeviceMessage != null) { if (cloudToDeviceMessage.Properties.TryGetValueCaseInsensitive("cidtype", out var cidTypeValue)) { Logger.Log(loRaDevice.DevEUI, "Cloud to device MAC command received", LogLevel.Information); MacCommandHolder macCommandHolder = new MacCommandHolder(Convert.ToByte(cidTypeValue)); macbytes = macCommandHolder.MacCommand[0].ToBytes(); } if (cloudToDeviceMessage.Properties.TryGetValueCaseInsensitive("confirmed", out var confirmedValue) && confirmedValue.Equals("true", StringComparison.OrdinalIgnoreCase)) { requiresDeviceAcknowlegement = true; loRaDevice.LastConfirmedC2DMessageID = cloudToDeviceMessage.MessageId ?? C2D_MSG_ID_PLACEHOLDER; } if (cloudToDeviceMessage.Properties.TryGetValueCaseInsensitive("fport", out var fPortValue)) { fport = byte.Parse(fPortValue); } Logger.Log(loRaDevice.DevEUI, $"Sending a downstream message with ID {ConversionHelper.ByteArrayToString(rndToken)}", LogLevel.Debug); frmPayload = cloudToDeviceMessage?.GetBytes(); Logger.Log(loRaDevice.DevEUI, $"C2D message: {Encoding.UTF8.GetString(frmPayload)}, id: {cloudToDeviceMessage.MessageId ?? "undefined"}, fport: {fport}, confirmed: {requiresDeviceAcknowlegement}, cidType: {cidTypeValue}", LogLevel.Information); // cut to the max payload of lora for any EU datarate if (frmPayload.Length > 51) { Array.Resize(ref frmPayload, 51); } Array.Reverse(frmPayload); } if (fpending) { fctrl |= (int)FctrlEnum.FpendingOrClassB; } // if (macbytes != null && linkCheckCmdResponse != null) // macbytes = macbytes.Concat(linkCheckCmdResponse).ToArray(); var reversedDevAddr = new byte[payloadDevAddr.Length]; var srcDevAddr = payloadDevAddr.Span; for (int i = reversedDevAddr.Length - 1; i >= 0; --i) { reversedDevAddr[i] = srcDevAddr[srcDevAddr.Length - (1 + i)]; } var msgType = requiresDeviceAcknowlegement ? LoRaMessageType.ConfirmedDataDown : LoRaMessageType.UnconfirmedDataDown; var ackLoRaMessage = new LoRaPayloadData( msgType, reversedDevAddr, new byte[] { fctrl }, BitConverter.GetBytes(fcntDown), macbytes, fport.HasValue ? new byte[] { fport.Value } : null, frmPayload, 1); // var firstWindowTime = timeWatcher.GetRemainingTimeToReceiveFirstWindow(loRaDevice); // if (firstWindowTime > TimeSpan.Zero) // System.Threading.Thread.Sleep(firstWindowTime); var receiveWindow = timeWatcher.ResolveReceiveWindowToUse(loRaDevice); if (receiveWindow == 0) { return(null); } string datr = null; double freq = 0; long tmst = 0; if (receiveWindow == 2) { tmst = rxpk.Tmst + timeWatcher.GetReceiveWindow2Delay(loRaDevice) * 1000000; if (string.IsNullOrEmpty(this.configuration.Rx2DataRate)) { Logger.Log(loRaDevice.DevEUI, "using standard second receive windows", LogLevel.Information); freq = this.loraRegion.RX2DefaultReceiveWindows.frequency; datr = this.loraRegion.DRtoConfiguration[this.loraRegion.RX2DefaultReceiveWindows.dr].configuration; } // if specific twins are set, specify second channel to be as specified else { freq = this.configuration.Rx2DataFrequency; datr = this.configuration.Rx2DataRate; Logger.Log(loRaDevice.DevEUI, $"using custom DR second receive windows freq : {freq}, datr:{datr}", LogLevel.Information); } } else { try { datr = this.loraRegion.GetDownstreamDR(rxpk); freq = this.loraRegion.GetDownstreamChannelFrequency(rxpk); tmst = rxpk.Tmst + this.loraRegion.Receive_delay1 * 1000000; } catch (RegionLimitException ex) { Logger.Log(loRaDevice.DevEUI, ex.Message, LogLevel.Error); return(null); } } // todo: check the device twin preference if using confirmed or unconfirmed down return(ackLoRaMessage.Serialize(loRaDevice.AppSKey, loRaDevice.NwkSKey, datr, freq, tmst, loRaDevice.DevEUI)); }