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 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(); }
private void EnsureDownlinkIsCorrect(DownlinkPktFwdMessage downlink, SimulatedDevice simDevice, ReceivedLoRaCloudToDeviceMessage sentMessage) { Assert.NotNull(downlink); Assert.NotNull(downlink.Txpk); Assert.True(downlink.Txpk.Imme); Assert.Equal(0, downlink.Txpk.Tmst); Assert.NotEmpty(downlink.Txpk.Data); byte[] downstreamPayloadBytes = Convert.FromBase64String(downlink.Txpk.Data); var downstreamPayload = new LoRaPayloadData(downstreamPayloadBytes); Assert.Equal(sentMessage.Fport, downstreamPayload.GetFPort()); Assert.Equal(downstreamPayload.DevAddr.ToArray(), ConversionHelper.StringToByteArray(simDevice.DevAddr)); var decryptedPayload = downstreamPayload.GetDecryptedPayload(simDevice.AppSKey); Assert.Equal(sentMessage.Payload, Encoding.UTF8.GetString(decryptedPayload)); }
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 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: this.ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp, frmCntDown: InitialDeviceFcntDown); var loraDevice = this.CreateLoRaDevice(simulatedDevice); Rxpk rxpk = this.CreateUpstreamRxpk(isConfirmed, hasMacInUpstream, datr, simulatedDevice); if (!hasMacInUpstream) { this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); } var euRegion = RegionManager.EU868; var c2dMessageMacCommand = new DevStatusRequest(); var c2dMessageMacCommandSize = hasMacInC2D ? c2dMessageMacCommand.Length : 0; var upstreamMessageMacCommandSize = 0; string expectedDownlinkDatr; if (hasMacInUpstream) { upstreamMessageMacCommandSize = new LinkCheckAnswer(1, 1).Length; } expectedDownlinkDatr = datr; var c2dPayloadSize = euRegion.GetMaxPayloadSize(expectedDownlinkDatr) - c2dMessageMacCommandSize + 1 // make message too long on purpose - Constants.LORA_PROTOCOL_OVERHEAD_SIZE; var c2dMessagePayload = TestUtils.GeneratePayload("123457890", (int)c2dPayloadSize); var c2dMessage = new ReceivedLoRaCloudToDeviceMessage() { Payload = c2dMessagePayload, Fport = 1, }; if (hasMacInC2D) { c2dMessage.MacCommands = new[] { c2dMessageMacCommand }; } var cloudToDeviceMessage = c2dMessage.CreateMessage(); this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsAny <TimeSpan>())) .ReturnsAsync(cloudToDeviceMessage); this.LoRaDeviceClient.Setup(x => x.RejectAsync(cloudToDeviceMessage)) .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); var request = this.CreateWaitableRequest(rxpk); 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); Assert.Equal(expectedDownlinkDatr, request.ResponseDownlink.Txpk.Datr); var downlinkMessage = this.PacketForwarder.DownlinkMessages[0]; 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.Equal(LoRaMessageType.UnconfirmedDataDown, payloadDataDown.LoRaMessageType); if (hasMacInUpstream) { Assert.Equal(new LinkCheckAnswer(1, 1).Length, payloadDataDown.Frmpayload.Length); Assert.Equal(0, payloadDataDown.GetFPort()); } } else { Assert.Null(request.ResponseDownlink); } this.LoRaDeviceClient.VerifyAll(); this.LoRaDeviceApi.VerifyAll(); }
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()); }
/// <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); } } }