Example #1
0
        public void When_Creating_Join_Request_Recreating_Should_Pass_Mic_Check(
            string appEUIText,
            string devEUIText,
            string appKeyText,
            string devNonceText)
        {
            var wrongAppKeyText = "00000000000000000000000000003333";

            // create a join request
            var devNonce = ConversionHelper.StringToByteArray(devNonceText);

            Array.Reverse(devNonce);

            var join = new LoRaPayloadJoinRequest(appEUIText, devEUIText, devNonce);

            Assert.Equal(appEUIText, join.GetAppEUIAsString());
            Assert.Equal(devEUIText, join.GetDevEUIAsString());
            var uplinkMessage = join.SerializeUplink(appKeyText);

            Assert.False(join.CheckMic(wrongAppKeyText), "Mic check with wrong appKey should not pass");
            Assert.True(join.CheckMic(appKeyText), "Mic check should work after setting it");

            var rxpk = uplinkMessage.Rxpk[0];

            Assert.True(LoRaPayload.TryCreateLoRaPayload(rxpk, out LoRaPayload parsedLoRaPayload));
            Assert.IsType <LoRaPayloadJoinRequest>(parsedLoRaPayload);
            var parsedLoRaJoinRequest = (LoRaPayloadJoinRequest)parsedLoRaPayload;

            Assert.True(parsedLoRaPayload.CheckMic(appKeyText), "Parsed join request should pass mic check with correct appKey");
            Assert.False(parsedLoRaJoinRequest.CheckMic(wrongAppKeyText), "Parsed join request should not pass mic check with wrong appKey");
        }
        /// <summary>
        /// Process a raw message
        /// </summary>
        /// <param name="rxpk"><see cref="Rxpk"/> representing incoming message</param>
        /// <param name="startTimeProcessing">Starting time counting from the moment the message was received</param>
        /// <returns>A <see cref="DownlinkPktFwdMessage"/> if a message has to be sent back to device</returns>
        public async Task <DownlinkPktFwdMessage> ProcessMessageAsync(Rxpk rxpk, DateTime startTimeProcessing)
        {
            if (!LoRaPayload.TryCreateLoRaPayload(rxpk, out LoRaPayload loRaPayload))
            {
                Logger.Log("There was a problem in decoding the Rxpk", LogLevel.Error);
                return(null);
            }

            if (this.loraRegion == null)
            {
                if (!RegionFactory.TryResolveRegion(rxpk))
                {
                    // log is generated in Region factory
                    // move here once V2 goes GA
                    return(null);
                }

                this.loraRegion = RegionFactory.CurrentRegion;
            }

            if (loRaPayload.LoRaMessageType == LoRaMessageType.JoinRequest)
            {
                return(await this.ProcessJoinRequestAsync(rxpk, (LoRaPayloadJoinRequest)loRaPayload, startTimeProcessing));
            }
            else if (loRaPayload.LoRaMessageType == LoRaMessageType.UnconfirmedDataUp || loRaPayload.LoRaMessageType == LoRaMessageType.ConfirmedDataUp)
            {
                return(await this.ProcessDataMessageAsync(rxpk, (LoRaPayloadData)loRaPayload, startTimeProcessing));
            }

            Logger.Log("Unknwon message type in rxpk, message ignored", LogLevel.Error);
            return(null);
        }
        private static WaitableLoRaRequest Create(RadioMetadata radioMetadata,
                                                  IEnumerable <TimeSpan> elapsedTimes,
                                                  IDownstreamMessageSender downstreamMessageSender = null,
                                                  TimeSpan?startTimeOffset = null,
                                                  bool useRealTimer        = false,
                                                  LoRaPayload loRaPayload  = null,
                                                  Region region            = null)
        {
            var request = new WaitableLoRaRequest(radioMetadata,
                                                  downstreamMessageSender ?? new TestDownstreamMessageSender(),
                                                  DateTime.UtcNow.Subtract(startTimeOffset ?? TimeSpan.Zero));
            var effectiveRegion = region ?? TestUtils.TestRegion;

            request.SetRegion(effectiveRegion);
            if (loRaPayload is not null)
            {
                request.SetPayload(loRaPayload);
            }
            if (!useRealTimer)
            {
                var timeWatcher = new TestLoRaOperationTimeWatcher(effectiveRegion, elapsedTimes);
                request.UseTimeWatcher(timeWatcher);
            }

            return(request);
        }
 /// <summary>
 /// Creates a WaitableLoRaRequest that uses a deterministic time handler.
 /// </summary>
 /// <param name="rxpk">Rxpk instance.</param>
 /// <param name="downstreamMessageSender">DownstreamMessageSender instance.</param>
 /// <param name="startTimeOffset">Is subtracted from the current time to determine the start time for the deterministic time watcher. Default is TimeSpan.Zero.</param>
 /// <param name="constantElapsedTime">Controls how much time is elapsed when querying the time watcher. Default is TimeSpan.Zero.</param>
 /// <param name="useRealTimer">Allows you to opt-in to use a real, non-deterministic time watcher.</param>
 public static WaitableLoRaRequest Create(RadioMetadata radioMetadata,
                                          LoRaPayload loRaPayload,
                                          IDownstreamMessageSender downstreamMessageSender = null,
                                          TimeSpan?startTimeOffset     = null,
                                          TimeSpan?constantElapsedTime = null,
                                          bool useRealTimer            = false,
                                          Region region = null) =>
 Create(radioMetadata, LoRaEnumerable.RepeatInfinite(constantElapsedTime ?? TimeSpan.Zero), downstreamMessageSender, startTimeOffset, useRealTimer, loRaPayload, region: region);
Example #5
0
        public void TestUnconfirmedUplink()
        {
            string jsonUplinkUnconfirmedDataUp = @"{ ""rxpk"":[
               {
               ""time"":""2013-03-31T16:21:17.528002Z"",
                ""tmst"":3512348611,
                ""chan"":2,
                ""rfch"":0,
                ""freq"":866.349812,
                ""stat"":1,
                ""modu"":""LORA"",
                ""datr"":""SF7BW125"",
                ""codr"":""4/6"",
                ""rssi"":-35,
                ""lsnr"":5.1,
                ""size"":32,
                ""data"":""QAQDAgGAAQABppRkJhXWw7WC""
                 }]}";

            byte[] physicalUpstreamPyld = new byte[12];
            physicalUpstreamPyld[0] = 2;

            var         jsonUplinkUnconfirmedDataUpBytes = Encoding.Default.GetBytes(jsonUplinkUnconfirmedDataUp);
            List <Rxpk> rxpk = Rxpk.CreateRxpk(physicalUpstreamPyld.Concat(jsonUplinkUnconfirmedDataUpBytes).ToArray());

            Assert.True(LoRaPayload.TryCreateLoRaPayload(rxpk[0], out LoRaPayload loRaPayload));

            Assert.Equal(LoRaMessageType.UnconfirmedDataUp, loRaPayload.LoRaMessageType);

            LoRaPayloadData loRaPayloadUplinkObj = (LoRaPayloadData)loRaPayload;

            Assert.True(loRaPayloadUplinkObj.Fcnt.Span.SequenceEqual(new byte[2] {
                1, 0
            }));

            Assert.True(loRaPayloadUplinkObj.DevAddr.Span.SequenceEqual(new byte[4] {
                1, 2, 3, 4
            }));
            byte[] loRaPayloadUplinkNwkKey = new byte[16] {
                2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
            };

            Assert.True(loRaPayloadUplinkObj.CheckMic(ConversionHelper.ByteArrayToString(loRaPayloadUplinkNwkKey)));

            byte[] loRaPayloadUplinkAppKey = new byte[16] {
                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
            };
            var key = ConversionHelper.ByteArrayToString(loRaPayloadUplinkAppKey);

            Assert.Equal("hello", Encoding.ASCII.GetString(loRaPayloadUplinkObj.PerformEncryption(key)));
        }
        /// <summary>
        /// Dispatches a request
        /// </summary>
        public void DispatchRequest(LoRaRequest request)
        {
            if (!LoRaPayload.TryCreateLoRaPayload(request.Rxpk, out LoRaPayload loRaPayload))
            {
                Logger.Log("There was a problem in decoding the Rxpk", LogLevel.Error);
                request.NotifyFailed(LoRaDeviceRequestFailedReason.InvalidRxpk);
                return;
            }

            if (this.loraRegion == null)
            {
                if (!RegionManager.TryResolveRegion(request.Rxpk, out var currentRegion))
                {
                    // log is generated in Region factory
                    // move here once V2 goes GA
                    request.NotifyFailed(LoRaDeviceRequestFailedReason.InvalidRegion);
                    return;
                }

                this.loraRegion = currentRegion;
            }

            request.SetPayload(loRaPayload);
            request.SetRegion(this.loraRegion);

            var loggingRequest = new LoggingLoRaRequest(request);

            if (loRaPayload.LoRaMessageType == LoRaMessageType.JoinRequest)
            {
                this.DispatchLoRaJoinRequest(loggingRequest);
            }
            else if (loRaPayload.LoRaMessageType == LoRaMessageType.UnconfirmedDataUp || loRaPayload.LoRaMessageType == LoRaMessageType.ConfirmedDataUp)
            {
                this.DispatchLoRaDataMessage(loggingRequest);
            }
            else
            {
                Logger.Log("Unknwon message type in rxpk, message ignored", LogLevel.Error);
            }
        }
Example #7
0
        private static void TestRxpk(Rxpk rxpk)
        {
            Assert.True(LoRaPayload.TryCreateLoRaPayload(rxpk, out LoRaPayload loRaPayload));
            Assert.Equal(LoRaMessageType.JoinRequest, loRaPayload.LoRaMessageType);
            LoRaPayloadJoinRequest joinRequestMessage = (LoRaPayloadJoinRequest)loRaPayload;

            byte[] joinRequestAppKey = new byte[16]
            {
                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
            };
            var joinRequestBool = joinRequestMessage.CheckMic(ConversionHelper.ByteArrayToString(joinRequestAppKey));

            if (!joinRequestBool)
            {
                Console.WriteLine("Join Request type was not computed correclty");
            }

            byte[] joinRequestAppEui = new byte[8]
            {
                1, 2, 3, 4, 1, 2, 3, 4
            };

            byte[] joinRequestDevEUI = new byte[8]
            {
                2, 3, 4, 5, 2, 3, 4, 5
            };
            byte[] joinRequestDevNonce = new byte[2]
            {
                16, 45
            };

            Array.Reverse(joinRequestAppEui);
            Array.Reverse(joinRequestDevEUI);
            Array.Reverse(joinRequestDevNonce);
            Assert.True(joinRequestMessage.AppEUI.ToArray().SequenceEqual(joinRequestAppEui));
            Assert.True(joinRequestMessage.DevEUI.ToArray().SequenceEqual(joinRequestDevEUI));
            Assert.True(joinRequestMessage.DevNonce.ToArray().SequenceEqual(joinRequestDevNonce));
        }
        async Task RunUdpListener()
        {
            while (true)
            {
                UdpReceiveResult receivedResults = await udpClient.ReceiveAsync();

                // Logger.LogAlways($"UDP message received ({receivedResults.Buffer.Length} bytes) from port: {receivedResults.RemoteEndPoint.Port}");

                // If 4, it may mean we received a confirmation
                if (receivedResults.Buffer.Length >= 4)
                {
                    // get the token
                    byte[] token = new byte[2];
                    token[0] = receivedResults.Buffer[1];
                    token[1] = receivedResults.Buffer[2];

                    // identifier
                    var identifier = (PhysicalIdentifier)receivedResults.Buffer[3];

                    // Find the device
                    try
                    {
                        foreach (var dev in this.listDevices)
                        {
                            if (dev.LastPayload != null)
                            {
                                if ((dev.LastPayload.Token[0] == token[0]) && (dev.LastPayload.Token[1] == token[1]))
                                {
                                    string device = dev.LoRaDevice.DevEUI;

                                    // check last operation and answer
                                    // Is is a simple push data?
                                    if (identifier == PhysicalIdentifier.PUSH_ACK)
                                    {
                                        if (dev.LastPayload.Identifier == PhysicalIdentifier.PUSH_DATA)
                                        {
                                            Logger.Log(device, $"PUSH_DATA confirmation receiveced from NetworkServer", LogLevel.Information);
                                        }
                                        else
                                        {
                                            Logger.Log(device, $"PUSH_ACK confirmation receiveced from ", LogLevel.Information);
                                        }
                                    }
                                    else if (identifier == PhysicalIdentifier.PULL_RESP)
                                    {
                                        // we asked something, we get an answer
                                        var txpk = Txpk.CreateTxpk(receivedResults.Buffer, dev.LoRaDevice.AppKey);
                                        LoRaPayload.TryCreateLoRaPayloadForSimulator(txpk, dev.LoRaDevice.AppKey, out LoRaPayload loraMessage);

                                        // Check if the device is not joined, then it is maybe the answer
                                        if ((loraMessage.LoRaMessageType == LoRaMessageType.JoinAccept) && (dev.LoRaDevice.DevAddr == string.Empty))
                                        {
                                            Logger.Log(device, $"Received join accept", LogLevel.Information);

                                            var payload = (LoRaPayloadJoinAccept)loraMessage;

                                            // TODO Need to check if the time is not passed

                                            // Calculate the keys
                                            var netid = payload.NetID.ToArray();
                                            Array.Reverse(netid);
                                            var appNonce = payload.AppNonce.ToArray();
                                            Array.Reverse(appNonce);
                                            var devNonce = dev.LoRaDevice.GetDevNonce();
                                            Array.Reverse(devNonce);
                                            var appSKey = payload.CalculateKey(LoRaPayloadKeyType.AppSKey, appNonce, netid, devNonce, dev.LoRaDevice.GetAppKey());
                                            dev.LoRaDevice.AppSKey = BitConverter.ToString(appSKey).Replace("-", "");
                                            var nwkSKey = payload.CalculateKey(LoRaPayloadKeyType.NwkSkey, appNonce, netid, devNonce, dev.LoRaDevice.GetAppKey());
                                            dev.LoRaDevice.NwkSKey  = BitConverter.ToString(nwkSKey).Replace("-", "");
                                            dev.LoRaDevice.NetId    = BitConverter.ToString(netid).Replace("-", "");
                                            dev.LoRaDevice.AppNonce = BitConverter.ToString(appNonce).Replace("-", "");
                                            var devAdd = payload.DevAddr;

                                            // Array.Reverse(devAdd);
                                            dev.LoRaDevice.DevAddr = BitConverter.ToString(devAdd.ToArray()).Replace("-", "");
                                        }
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.Log($"Something when wrong: {ex.Message}", LogLevel.Error);
                    }

                    // if (receivedResults.Buffer[3] == (byte)PhysicalIdentifier.PUSH_ACK)
                    // {
                    //    //Bingo
                    //    Logger.LogAlways($"Confirmation receiveced");
                    // }
                }

                // try
                // {
                //    // TODO: process the message not really implemented yet
                //    MessageProcessor messageProcessor = new MessageProcessor();
                //    _ = messageProcessor.processMessage(receivedResults.Buffer);
                // }
                // catch (Exception ex)
                // {
                //    Logger.Log($"Error processing the message {ex.Message}", LogLevel.Error);
                // }
            }
        }
        public async Task <LoRaDeviceRequestProcessResult> ProcessRequestAsync(LoRaRequest request, LoRaDevice loRaDevice)
        {
            var timeWatcher = request.GetTimeWatcher();

            using (var deviceConnectionActivity = loRaDevice.BeginDeviceClientConnectionActivity())
            {
                if (deviceConnectionActivity == null)
                {
                    return(new LoRaDeviceRequestProcessResult(loRaDevice, request, LoRaDeviceRequestFailedReason.DeviceClientConnectionFailed));
                }

                var loraPayload = (LoRaPayloadData)request.Payload;

                var payloadFcnt = loraPayload.GetFcnt();

                uint payloadFcntAdjusted = LoRaPayload.InferUpper32BitsForClientFcnt(payloadFcnt, loRaDevice.FCntUp);
                Logger.Log(loRaDevice.DevEUI, $"converted 16bit FCnt {payloadFcnt} to 32bit FCnt {payloadFcntAdjusted}", LogLevel.Debug);

                var payloadPort          = loraPayload.GetFPort();
                var requiresConfirmation = loraPayload.IsConfirmed || loraPayload.IsMacAnswerRequired;

                LoRaADRResult loRaADRResult = null;

                var frameCounterStrategy = this.frameCounterUpdateStrategyProvider.GetStrategy(loRaDevice.GatewayID);
                if (frameCounterStrategy == null)
                {
                    Logger.Log(loRaDevice.DevEUI, $"failed to resolve frame count update strategy, device gateway: {loRaDevice.GatewayID}, message ignored", LogLevel.Error);
                    return(new LoRaDeviceRequestProcessResult(loRaDevice, request, LoRaDeviceRequestFailedReason.ApplicationError));
                }

                // Contains the Cloud to message we need to send
                IReceivedLoRaCloudToDeviceMessage cloudToDeviceMessage = null;

                // 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
                bool isFrameCounterFromNewlyStartedDevice = await this.DetermineIfFramecounterIsFromNewlyStartedDeviceAsync(loRaDevice, payloadFcntAdjusted, frameCounterStrategy);

                // 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)
                if (!ValidateRequest(request, isFrameCounterFromNewlyStartedDevice, payloadFcntAdjusted, loRaDevice, requiresConfirmation, out bool isConfirmedResubmit, out LoRaDeviceRequestProcessResult result))
                {
                    return(result);
                }

                var useMultipleGateways = string.IsNullOrEmpty(loRaDevice.GatewayID);

                try
                {
                    var bundlerResult = await this.TryUseBundler(request, loRaDevice, loraPayload, useMultipleGateways);

                    loRaADRResult = bundlerResult?.AdrResult;

                    if (bundlerResult?.PreferredGatewayResult != null)
                    {
                        this.HandlePreferredGatewayChanges(request, loRaDevice, bundlerResult);
                    }

                    if (loraPayload.IsAdrReq)
                    {
                        Logger.Log(loRaDevice.DevEUI, $"ADR ack request received", LogLevel.Debug);
                    }

                    // ADR should be performed before the deduplication
                    // as we still want to collect the signal info, even if we drop
                    // it in the next step
                    if (loRaADRResult == null && loraPayload.IsAdrEnabled)
                    {
                        loRaADRResult = await this.PerformADR(request, loRaDevice, loraPayload, payloadFcntAdjusted, loRaADRResult, frameCounterStrategy);
                    }

                    if (loRaADRResult?.CanConfirmToDevice == true || loraPayload.IsAdrReq)
                    {
                        // if we got an ADR result or request, we have to send the update to the device
                        requiresConfirmation = true;
                    }

                    if (useMultipleGateways)
                    {
                        // applying the correct deduplication
                        if (bundlerResult?.DeduplicationResult != null && !bundlerResult.DeduplicationResult.CanProcess)
                        {
                            // duplication strategy is indicating that we do not need to continue processing this message
                            Logger.Log(loRaDevice.DevEUI, $"duplication strategy indicated to not process message: {payloadFcnt}", LogLevel.Debug);
                            return(new LoRaDeviceRequestProcessResult(loRaDevice, request, LoRaDeviceRequestFailedReason.DeduplicationDrop));
                        }
                    }
                    else
                    {
                        // we must save class C devices regions in order to send c2d messages
                        if (loRaDevice.ClassType == LoRaDeviceClassType.C && request.Region.LoRaRegion != loRaDevice.LoRaRegion)
                        {
                            loRaDevice.UpdateRegion(request.Region.LoRaRegion, acceptChanges: false);
                        }
                    }

                    // if deduplication already processed the next framecounter down, use that
                    uint?fcntDown = loRaADRResult?.FCntDown != null ? loRaADRResult.FCntDown : bundlerResult?.NextFCntDown;

                    if (fcntDown.HasValue)
                    {
                        LogFrameCounterDownState(loRaDevice, fcntDown.Value);
                    }

                    // 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 this.EnsureHasFcntDownAsync(loRaDevice, fcntDown, payloadFcntAdjusted, frameCounterStrategy);

                        // 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)
                        {
                            return(new LoRaDeviceRequestProcessResult(loRaDevice, request, LoRaDeviceRequestFailedReason.HandledByAnotherGateway));
                        }
                    }

                    var validFcntUp = isFrameCounterFromNewlyStartedDevice || (payloadFcntAdjusted > loRaDevice.FCntUp);
                    if (validFcntUp || isConfirmedResubmit)
                    {
                        if (!isConfirmedResubmit)
                        {
                            Logger.Log(loRaDevice.DevEUI, $"valid frame counter, msg: {payloadFcntAdjusted} server: {loRaDevice.FCntUp}", LogLevel.Debug);
                        }

                        object payloadData          = null;
                        byte[] decryptedPayloadData = null;

                        if (loraPayload.Frmpayload.Length > 0)
                        {
                            try
                            {
                                decryptedPayloadData = loraPayload.GetDecryptedPayload(loRaDevice.AppSKey);
                            }
                            catch (Exception ex)
                            {
                                Logger.Log(loRaDevice.DevEUI, $"failed to decrypt message: {ex.Message}", LogLevel.Error);
                            }
                        }

                        if (payloadPort == LoRaFPort.MacCommand)
                        {
                            if (decryptedPayloadData?.Length > 0)
                            {
                                loraPayload.MacCommands = MacCommand.CreateMacCommandFromBytes(loRaDevice.DevEUI, decryptedPayloadData);
                            }

                            if (loraPayload.IsMacAnswerRequired)
                            {
                                fcntDown = await this.EnsureHasFcntDownAsync(loRaDevice, fcntDown, payloadFcntAdjusted, frameCounterStrategy);

                                if (!fcntDown.HasValue || fcntDown <= 0)
                                {
                                    return(new LoRaDeviceRequestProcessResult(loRaDevice, request, LoRaDeviceRequestFailedReason.HandledByAnotherGateway));
                                }

                                requiresConfirmation = true;
                            }
                        }
                        else
                        {
                            if (string.IsNullOrEmpty(loRaDevice.SensorDecoder))
                            {
                                Logger.Log(loRaDevice.DevEUI, $"no decoder set in device twin. port: {payloadPort}", LogLevel.Debug);
                                payloadData = new UndecodedPayload(decryptedPayloadData);
                            }
                            else
                            {
                                Logger.Log(loRaDevice.DevEUI, $"decoding with: {loRaDevice.SensorDecoder} port: {payloadPort}", LogLevel.Debug);
                                var decodePayloadResult = await this.payloadDecoder.DecodeMessageAsync(loRaDevice.DevEUI, decryptedPayloadData, payloadPort, loRaDevice.SensorDecoder);

                                payloadData = decodePayloadResult.GetDecodedPayload();

                                if (decodePayloadResult.CloudToDeviceMessage != null)
                                {
                                    if (string.IsNullOrEmpty(decodePayloadResult.CloudToDeviceMessage.DevEUI) || string.Equals(loRaDevice.DevEUI, decodePayloadResult.CloudToDeviceMessage.DevEUI, StringComparison.InvariantCultureIgnoreCase))
                                    {
                                        // sending c2d to same device
                                        cloudToDeviceMessage = decodePayloadResult.CloudToDeviceMessage;
                                        fcntDown             = await this.EnsureHasFcntDownAsync(loRaDevice, fcntDown, payloadFcntAdjusted, frameCounterStrategy);

                                        if (!fcntDown.HasValue || fcntDown <= 0)
                                        {
                                            // We did not get a valid frame count down, therefore we should not process the message
                                            _ = cloudToDeviceMessage.AbandonAsync();

                                            cloudToDeviceMessage = null;
                                        }
                                        else
                                        {
                                            requiresConfirmation = true;
                                        }
                                    }
                                    else
                                    {
                                        this.SendClassCDeviceMessage(decodePayloadResult.CloudToDeviceMessage);
                                    }
                                }
                            }
                        }

                        if (!isConfirmedResubmit)
                        {
                            // In case it is a Mac Command only we don't want to send it to the IoT Hub
                            if (payloadPort != LoRaFPort.MacCommand)
                            {
                                if (!await this.SendDeviceEventAsync(request, loRaDevice, timeWatcher, payloadData, bundlerResult?.DeduplicationResult, decryptedPayloadData))
                                {
                                    // failed to send event to IoT Hub, stop now
                                    return(new LoRaDeviceRequestProcessResult(loRaDevice, request, LoRaDeviceRequestFailedReason.IoTHubProblem));
                                }
                            }

                            loRaDevice.SetFcntUp(payloadFcntAdjusted);
                        }
                    }

                    // 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()})", LogLevel.Information);
                        }

                        return(new LoRaDeviceRequestProcessResult(loRaDevice, request));
                    }

                    // 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))
                    {
                        var downlinkMessageBuilderResp = DownlinkMessageBuilder.CreateDownlinkMessage(
                            this.configuration,
                            loRaDevice,
                            request,
                            timeWatcher,
                            cloudToDeviceMessage,
                            false, // fpending
                            fcntDown.GetValueOrDefault(),
                            loRaADRResult);

                        if (downlinkMessageBuilderResp.DownlinkPktFwdMessage != null)
                        {
                            _ = request.PacketForwarder.SendDownstreamAsync(downlinkMessageBuilderResp.DownlinkPktFwdMessage);

                            if (cloudToDeviceMessage != null)
                            {
                                if (downlinkMessageBuilderResp.IsMessageTooLong)
                                {
                                    await cloudToDeviceMessage.AbandonAsync();
                                }
                                else
                                {
                                    await cloudToDeviceMessage.CompleteAsync();
                                }
                            }
                        }

                        return(new LoRaDeviceRequestProcessResult(loRaDevice, request, downlinkMessageBuilderResp.DownlinkPktFwdMessage));
                    }

                    // Flag indicating if there is another C2D message waiting
                    var fpending = false;

                    // If downlink is enabled and we did not get a cloud to device message from decoder
                    // try to get one from IoT Hub C2D
                    if (loRaDevice.DownlinkEnabled && cloudToDeviceMessage == null)
                    {
                        // 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 this.ReceiveCloudToDeviceAsync(loRaDevice, timeAvailableToCheckCloudToDeviceMessages);

                            if (cloudToDeviceMessage != null && !this.ValidateCloudToDeviceMessage(loRaDevice, request, cloudToDeviceMessage))
                            {
                                // Reject cloud to device message based on result from ValidateCloudToDeviceMessage
                                _ = cloudToDeviceMessage.RejectAsync();
                                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 this.EnsureHasFcntDownAsync(loRaDevice, fcntDown, payloadFcntAdjusted, frameCounterStrategy);

                                    if (!fcntDown.HasValue || fcntDown <= 0)
                                    {
                                        // We did not get a valid frame count down, therefore we should not process the message
                                        _ = cloudToDeviceMessage.AbandonAsync();

                                        cloudToDeviceMessage = null;
                                    }
                                    else
                                    {
                                        requiresConfirmation = true;
                                    }
                                }

                                // 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 this.ReceiveCloudToDeviceAsync(loRaDevice, LoRaOperationTimeWatcher.MinimumAvailableTimeToCheckForCloudMessage);

                                        if (additionalMsg != null)
                                        {
                                            fpending = true;
                                            Logger.Log(loRaDevice.DevEUI, $"found cloud to device message, setting fpending flag, message id: {additionalMsg.MessageId ?? "undefined"}", LogLevel.Information);
                                            _ = additionalMsg.AbandonAsync();
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // No C2D message and request was not confirmed, return nothing
                    if (!requiresConfirmation)
                    {
                        return(new LoRaDeviceRequestProcessResult(loRaDevice, request));
                    }

                    var confirmDownlinkMessageBuilderResp = DownlinkMessageBuilder.CreateDownlinkMessage(
                        this.configuration,
                        loRaDevice,
                        request,
                        timeWatcher,
                        cloudToDeviceMessage,
                        fpending,
                        fcntDown.GetValueOrDefault(),
                        loRaADRResult);

                    if (cloudToDeviceMessage != null)
                    {
                        if (confirmDownlinkMessageBuilderResp.DownlinkPktFwdMessage == null)
                        {
                            Logger.Log(loRaDevice.DevEUI, $"out of time for downstream message, will abandon cloud to device message id: {cloudToDeviceMessage.MessageId ?? "undefined"}", LogLevel.Information);
                            _ = cloudToDeviceMessage.AbandonAsync();
                        }
                        else if (confirmDownlinkMessageBuilderResp.IsMessageTooLong)
                        {
                            Logger.Log(loRaDevice.DevEUI, $"payload will not fit in current receive window, will abandon cloud to device message id: {cloudToDeviceMessage.MessageId ?? "undefined"}", LogLevel.Error);
                            _ = cloudToDeviceMessage.AbandonAsync();
                        }
                        else
                        {
                            _ = cloudToDeviceMessage.CompleteAsync();
                        }
                    }

                    if (confirmDownlinkMessageBuilderResp.DownlinkPktFwdMessage != null)
                    {
                        _ = request.PacketForwarder.SendDownstreamAsync(confirmDownlinkMessageBuilderResp.DownlinkPktFwdMessage);
                    }

                    return(new LoRaDeviceRequestProcessResult(loRaDevice, request, confirmDownlinkMessageBuilderResp.DownlinkPktFwdMessage));
                }
                finally
                {
                    try
                    {
                        await loRaDevice.SaveChangesAsync();
                    }
                    catch (Exception saveChangesException)
                    {
                        Logger.Log(loRaDevice.DevEUI, $"error updating reported properties. {saveChangesException.Message}", LogLevel.Error);
                    }
                }
            }
        }
Example #10
0
        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());
        }
Example #11
0
        public void TestKeys()
        {
            // create random message
            string jsonUplink       = @"{ ""rxpk"":[
                {
		            ""time"":""2013-03-31T16:21:17.528002Z"",
                    ""tmst"":3512348611,
                    ""chan"":2,
                    ""rfch"":0,
                    ""freq"":866.349812,
                    ""stat"":1,
                    ""modu"":""LORA"",
                    ""datr"":""SF7BW125"",
                    ""codr"":""4/6"",
                    ""rssi"":-35,
                    ""lsnr"":5.1,
                    ""size"":32,
                    ""data"":""AAQDAgEEAwIBBQQDAgUEAwItEGqZDhI=""
                }]}";
            var    joinRequestInput = Encoding.Default.GetBytes(jsonUplink);

            byte[] physicalUpstreamPyld = new byte[12];
            physicalUpstreamPyld[0] = 2;
            List <Rxpk> rxpk = Rxpk.CreateRxpk(physicalUpstreamPyld.Concat(joinRequestInput).ToArray());

            Assert.True(LoRaPayload.TryCreateLoRaPayload(rxpk[0], out LoRaPayload loRaPayload));
            Assert.Equal(LoRaMessageType.JoinRequest, loRaPayload.LoRaMessageType);
            var joinReq = (LoRaPayloadJoinRequest)loRaPayload;

            joinReq.DevAddr = new byte[4]
            {
                4, 3, 2, 1,
            };
            joinReq.DevEUI = new byte[8]
            {
                8, 7, 6, 5, 4, 3, 2, 1,
            };
            joinReq.DevNonce = new byte[2]
            {
                2, 1,
            };
            joinReq.AppEUI = new byte[8]
            {
                1, 2, 3, 4, 5, 6, 7, 8,
            };

            byte[] appNonce = new byte[3]
            {
                0, 0, 1,
            };
            byte[] netId = new byte[3]
            {
                3, 2, 1,
            };
            byte[] appKey = new byte[16]
            {
                1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
            };
            var key = joinReq.CalculateKey(LoRaPayloadKeyType.NwkSkey, appNonce, netId, joinReq.DevNonce.ToArray(), appKey);

            Assert.Equal(
                key,
                new byte[16] {
                223, 83, 195, 95, 48, 52, 204, 206, 208, 255, 53, 76, 112, 222, 4, 223,
            });
        }
 /// <summary>
 /// Creates a WaitableLoRaRequest using a real time watcher.
 /// </summary>
 public static WaitableLoRaRequest Create(LoRaPayload payload) =>
 new WaitableLoRaRequest(payload);
Example #13
0
 protected override bool ValidateMic(LoRaDevice loRaDevice, LoRaPayload loRaPayload)
 {
     return(this.validateMic == null || this.validateMic(loRaDevice, loRaPayload));
 }
 public static WaitableLoRaRequest CreateWaitableRequest(LoRaPayload loRaPayload,
                                                         IDownstreamMessageSender downstreamMessageSender = null) =>
 Create(TestUtils.GenerateTestRadioMetadata(),
        loRaPayload,
        downstreamMessageSender);
 protected LoRaRequest(LoRaPayload payload)
 {
     Payload = payload;
 }
 internal void SetPayload(LoRaPayload loRaPayload) => Payload = loRaPayload;
 private WaitableLoRaRequest(LoRaPayload payload)
     : base(payload)
 {
 }