/// <summary>
        /// Method to update reported properties at OTAA time
        /// </summary>
        /// <param name="loraDeviceInfo"> the LoRa info to report</param>
        public async Task <bool> UpdateReportedPropertiesOTAAasync(LoraDeviceInfo loraDeviceInfo)
        {
            try
            {
                CreateDeviceClient();

                deviceClient.OperationTimeoutInMilliseconds = 4000;

                setRetry(true);

                Logger.Log(DevEUI, $"saving join properties twins", Logger.LoggingLevel.Full);

                TwinCollection reportedProperties = new TwinCollection();
                reportedProperties["NwkSKey"]  = loraDeviceInfo.NwkSKey;
                reportedProperties["AppSKey"]  = loraDeviceInfo.AppSKey;
                reportedProperties["DevEUI"]   = loraDeviceInfo.DevEUI;
                reportedProperties["DevAddr"]  = loraDeviceInfo.DevAddr;
                reportedProperties["NetId"]    = loraDeviceInfo.NetId;
                reportedProperties["FCntUp"]   = loraDeviceInfo.FCntUp;
                reportedProperties["FCntDown"] = loraDeviceInfo.FCntDown;
                reportedProperties["DevNonce"] = loraDeviceInfo.DevNonce;
                await deviceClient.UpdateReportedPropertiesAsync(reportedProperties);

                Logger.Log(DevEUI, $"done saving join properties twins", Logger.LoggingLevel.Full);

                setRetry(false);

                return(true);
            }
            catch (Exception ex)
            {
                Logger.Log(DevEUI, $"could not save twins with error: {ex.Message}", Logger.LoggingLevel.Error);
                return(false);
            }
        }
Exemple #2
0
 public static void AddToCache(string devAddr, LoraDeviceInfo loraDeviceInfo)
 {
     using (var entry = Cache.MemoryCache.CreateEntry(devAddr))
     {
         entry.Value             = loraDeviceInfo;
         entry.SlidingExpiration = new TimeSpan(1, 0, 0, 0);
     }
 }
 public static bool TryGetJoinRequestValue(string key, out LoraDeviceInfo loraDeviceInfo)
 {
     if (Cache.MemoryCache.TryGetValue(key, out object loraDeviceInfoCache))
     {
         loraDeviceInfo = (LoraDeviceInfo)loraDeviceInfoCache;
         return(true);
     }
     else
     {
         loraDeviceInfo = null;
         return(false);
     }
 }
        public static void AddRequestToCache(string devAddr, LoraDeviceInfo deviceInfo)
        {
            var concurrentdict = Cache.MemoryCache.GetOrCreate
                                     (devAddr, entry =>
            {
                entry.SlidingExpiration = new TimeSpan(1, 0, 0, 0);
                return(new ConcurrentDictionary <string, LoraDeviceInfo>()
                {
                });
            });

            concurrentdict.GetOrAdd(deviceInfo.DevEUI, devinf =>
            {
                return(deviceInfo);
            });
        }
Exemple #5
0
        public static async Task <LoraDeviceInfo> PerformOTAAAsync(string GatewayID, string DevEUI, string AppEUI, string DevNonce)
        {
            var client = new HttpClient();

            var url = $"{FacadeServerUrl}PerformOTAA?code={FacadeAuthCode}&GatewayID={GatewayID}&DevEUI={DevEUI}&DevNonce={DevNonce}&AppEUI={AppEUI}";

            HttpResponseMessage response = await client.GetAsync(url);


            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine($"Error calling façade api: {response.ReasonPhrase} check the azure function log");
                return(null);
            }

            var result = response.Content.ReadAsStringAsync().Result;

            LoraDeviceInfo loraDeviceInfo = (LoraDeviceInfo)JsonConvert.DeserializeObject(result, typeof(LoraDeviceInfo));

            return(loraDeviceInfo);
        }
Exemple #6
0
        public static async Task <LoraDeviceInfo> GetLoraDeviceInfoAsync(string DevAddr)
        {
            var client = new HttpClient();

            var url = $"{FacadeServerUrl}GetNwkSKeyAppSKey?code={FacadeAuthCode}&devAddr={DevAddr}";

            HttpResponseMessage response = await client.GetAsync(url);



            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine($"Error calling façade api: {response.ReasonPhrase} check the azure function log");
                return(null);
            }

            var result = response.Content.ReadAsStringAsync().Result;

            LoraDeviceInfo loraDeviceInfo = (LoraDeviceInfo)JsonConvert.DeserializeObject(result, typeof(LoraDeviceInfo));

            return(loraDeviceInfo);
        }
        // Validate cloud to device message
        private bool ValidateCloudToDeviceMessage(LoraDeviceInfo loraDeviceInfo, Message c2dMessage)
        {
            // ensure fport property has been set
            if (!c2dMessage.Properties.TryGetValueCaseInsensitive(FPORT_MSG_PROPERTY_KEY, out var fportValue))
            {
                Logger.Log(loraDeviceInfo.DevEUI, $"missing {FPORT_MSG_PROPERTY_KEY} property in C2D message '{c2dMessage.MessageId}'", Logger.LoggingLevel.Error);
                return(false);
            }

            if (byte.TryParse(fportValue, out var fport))
            {
                // ensure fport follows LoRa specification
                // 0    => reserved for mac commands
                // 224+ => reserved for future applications
                if (fport != LORA_FPORT_RESERVED_MAC_MSG && fport < LORA_FPORT_RESERVED_FUTURE_START)
                {
                    return(true);
                }
            }

            Logger.Log(loraDeviceInfo.DevEUI, $"invalid fport '{fportValue}' in C2D message '{c2dMessage.MessageId}'", Logger.LoggingLevel.Error);
            return(false);
        }
Exemple #8
0
        public async Task <LoraDeviceInfo> GetLoraDeviceInfoAsync(string DevAddr, string GatewayId, LoRaTools.LoRaMessage.LoRaMessageWrapper loraMessage)
        {
            var client = this.serviceFacadeHttpClientProvider.GetHttpClient();
            var url    = $"{FacadeServerUrl}GetDevice?code={FacadeAuthCode}&DevAddr={DevAddr}&GatewayId={GatewayId}";
            HttpResponseMessage response = await client.GetAsync(url);

            if (!response.IsSuccessStatusCode)
            {
                Logger.Log(DevAddr, $"error calling façade api: {response.ReasonPhrase} check the azure function log", Logger.LoggingLevel.Error);
                return(null);
            }

            var result = await response.Content.ReadAsStringAsync();

            //TODO enable multi devices with the same devaddr

            List <IoTHubDeviceInfo> iotHubDeviceInfos = ((List <IoTHubDeviceInfo>)JsonConvert.DeserializeObject(result, typeof(List <IoTHubDeviceInfo>)));

            LoraDeviceInfo loraDeviceInfo = new LoraDeviceInfo();

            loraDeviceInfo.DevAddr = DevAddr;


            //we did not find a device with this devaddr so we assume is not ours
            if (iotHubDeviceInfos.Count == 0)
            {
                loraDeviceInfo.IsOurDevice = false;
            }
            else
            {
                foreach (IoTHubDeviceInfo iotHubDeviceInfo  in iotHubDeviceInfos)
                {
                    loraDeviceInfo.DevEUI     = iotHubDeviceInfo.DevEUI;
                    loraDeviceInfo.PrimaryKey = iotHubDeviceInfo.PrimaryKey;
                    loraDeviceInfo.HubSender  = new IoTHubConnector(iotHubDeviceInfo.DevEUI, iotHubDeviceInfo.PrimaryKey, this.configuration);
                    var twin = await loraDeviceInfo.HubSender.GetTwinAsync();

                    if (twin != null)
                    {
                        //ABP Case
                        if (twin.Properties.Desired.Contains("AppSKey"))
                        {
                            loraDeviceInfo.AppSKey = twin.Properties.Desired["AppSKey"];
                            loraDeviceInfo.NwkSKey = twin.Properties.Desired["NwkSKey"];
                            loraDeviceInfo.DevAddr = twin.Properties.Desired["DevAddr"];

                            //in this case it is not the correct device we would need to iterate
                            if (!loraMessage.CheckMic(loraDeviceInfo.NwkSKey))
                            {
                                break;
                            }
                        }
                        //OTAA Case
                        else if (twin.Properties.Reported.Contains("AppSKey"))
                        {
                            loraDeviceInfo.AppSKey  = twin.Properties.Reported["AppSKey"];
                            loraDeviceInfo.NwkSKey  = twin.Properties.Reported["NwkSKey"];
                            loraDeviceInfo.DevAddr  = twin.Properties.Reported["DevAddr"];
                            loraDeviceInfo.DevNonce = twin.Properties.Reported["DevNonce"];

                            //todo check if appkey and appeui is needed in the flow
                            loraDeviceInfo.AppEUI = twin.Properties.Desired["AppEUI"];
                            loraDeviceInfo.AppKey = twin.Properties.Desired["AppKey"];

                            //in this case it is not the correct device we would need to iterate
                            if (!loraMessage.CheckMic(loraDeviceInfo.NwkSKey))
                            {
                                break;
                            }
                        }
                        else
                        {
                            Logger.Log(loraDeviceInfo.DevEUI, $"AppSKey not present neither in Desired or in Reported properties", Logger.LoggingLevel.Error);
                        }



                        if (twin.Properties.Desired.Contains("GatewayID"))
                        {
                            loraDeviceInfo.GatewayID = twin.Properties.Desired["GatewayID"];
                        }
                        if (twin.Properties.Desired.Contains("SensorDecoder"))
                        {
                            loraDeviceInfo.SensorDecoder = twin.Properties.Desired["SensorDecoder"];
                        }
                        loraDeviceInfo.IsOurDevice = true;
                        if (twin.Properties.Reported.Contains("FCntUp"))
                        {
                            loraDeviceInfo.FCntUp = twin.Properties.Reported["FCntUp"];
                        }
                        if (twin.Properties.Reported.Contains("FCntDown"))
                        {
                            loraDeviceInfo.FCntDown = twin.Properties.Reported["FCntDown"];
                        }
                        Logger.Log(loraDeviceInfo.DevEUI, $"done getting twins", Logger.LoggingLevel.Info);

                        return(loraDeviceInfo);
                    }
                }
                Logger.Log(loraDeviceInfo.DevEUI, $"an exact matching device could not be found on IoT Hub", Logger.LoggingLevel.Info);
            }

            return(new LoraDeviceInfo()
            {
                DevAddr = DevAddr
            });
        }
Exemple #9
0
        /// <summary>
        /// Code Performing the OTAA
        /// </summary>
        /// <param name="GatewayID"></param>
        /// <param name="DevEUI"></param>
        /// <param name="AppEUI"></param>
        /// <param name="DevNonce"></param>
        /// <returns></returns>
        public async Task <LoraDeviceInfo> PerformOTAAAsync(string GatewayID, string DevEUI, string AppEUI, string DevNonce, LoraDeviceInfo joinLoraDeviceInfo)
        {
            string           AppSKey;
            string           NwkSKey;
            string           DevAddr;
            string           AppNonce;
            IoTHubDeviceInfo iotHubDeviceInfo;

            if (DevEUI == null || AppEUI == null || DevNonce == null)
            {
                string errorMsg = "Missing devEUI/AppEUI/DevNonce in the OTAARequest";
                //log.Error(errorMsg);
                Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error);
                return(null);
            }


            if (joinLoraDeviceInfo == null)
            {
                joinLoraDeviceInfo = new LoraDeviceInfo();
            }

            joinLoraDeviceInfo.DevEUI = DevEUI;


            ////we don't have the key to access iot hub query the registry
            //if (joinLoraDeviceInfo.PrimaryKey == null)
            //{

            Logger.Log(DevEUI, $"querying the registry for device key", Logger.LoggingLevel.Info);
            var client = this.serviceFacadeHttpClientProvider.GetHttpClient();
            var url    = String.Concat($"{FacadeServerUrl}GetDevice?", $"{FacadeAuthCode}" == "" ? "" : $"code={FacadeAuthCode}&", $"DevEUI={DevEUI}&DevNonce={DevNonce}&GatewayId={GatewayID}");

            HttpResponseMessage response = await client.GetAsync(url);

            if (!response.IsSuccessStatusCode)
            {
                if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
                {
                    var badReqResult = await response.Content.ReadAsStringAsync();

                    if (!String.IsNullOrEmpty(badReqResult) && badReqResult == "UsedDevNonce")
                    {
                        Logger.Log(DevEUI, $"DevNonce already used by this device", Logger.LoggingLevel.Info);
                        return(null);
                    }
                }


                Logger.Log(DevEUI, $"error calling façade api, check the azure function log. {response.ReasonPhrase}", Logger.LoggingLevel.Error);
                return(null);
            }

            var result = await response.Content.ReadAsStringAsync();

            List <IoTHubDeviceInfo> iotHubDeviceInfos = ((List <IoTHubDeviceInfo>)JsonConvert.DeserializeObject(result, typeof(List <IoTHubDeviceInfo>)));

            if (iotHubDeviceInfos.Count == 0)
            {
                joinLoraDeviceInfo.IsJoinValid = false;
                joinLoraDeviceInfo.IsOurDevice = false;
                return(joinLoraDeviceInfo);
            }
            else
            {
                iotHubDeviceInfo = iotHubDeviceInfos[0];
                joinLoraDeviceInfo.PrimaryKey = iotHubDeviceInfo.PrimaryKey;
            }
            //}


            joinLoraDeviceInfo.HubSender = new IoTHubConnector(joinLoraDeviceInfo.DevEUI, joinLoraDeviceInfo.PrimaryKey, this.configuration);


            //we don't have yet the twin data so we need to get it
            if (joinLoraDeviceInfo.AppKey == null || joinLoraDeviceInfo.AppEUI == null)
            {
                Logger.Log(DevEUI, $"getting twins for OTAA for device", Logger.LoggingLevel.Info);

                var twin = await joinLoraDeviceInfo.HubSender.GetTwinAsync();

                if (twin != null)
                {
                    joinLoraDeviceInfo.IsOurDevice = true;

                    if (!twin.Properties.Desired.Contains("AppEUI"))
                    {
                        string errorMsg = $"missing AppEUI for OTAA for device";
                        Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error);
                        return(null);
                    }
                    else
                    {
                        joinLoraDeviceInfo.AppEUI = twin.Properties.Desired["AppEUI"].Value;
                    }

                    //Make sure that there is the AppKey if not we cannot do the OTAA
                    if (!twin.Properties.Desired.Contains("AppKey"))
                    {
                        string errorMsg = $"missing AppKey for OTAA for device";
                        Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error);
                        return(null);
                    }
                    else
                    {
                        joinLoraDeviceInfo.AppKey = twin.Properties.Desired["AppKey"].Value;
                    }

                    //Make sure that is a new request and not a replay
                    if (twin.Properties.Reported.Contains("DevNonce"))
                    {
                        joinLoraDeviceInfo.DevNonce = twin.Properties.Reported["DevNonce"];
                    }

                    if (twin.Properties.Desired.Contains("GatewayID"))
                    {
                        joinLoraDeviceInfo.GatewayID = twin.Properties.Desired["GatewayID"].Value;
                    }

                    if (twin.Properties.Desired.Contains("SensorDecoder"))
                    {
                        joinLoraDeviceInfo.SensorDecoder = twin.Properties.Desired["SensorDecoder"].Value;
                    }

                    Logger.Log(DevEUI, $"done getting twins for OTAA device", Logger.LoggingLevel.Info);
                }
                else
                {
                    Logger.Log(DevEUI, $"failed getting twins for OTAA device", Logger.LoggingLevel.Error);
                    return(null);
                }
            }
            else
            {
                Logger.Log(DevEUI, $"using cached twins for OTAA device", Logger.LoggingLevel.Info);
            }


            //We add it to the cache so the next join has already the data, important for offline
            Cache.AddJoinRequestToCache(DevEUI, joinLoraDeviceInfo);

            //Make sure that there is the AppEUI and it matches if not we cannot do the OTAA
            if (joinLoraDeviceInfo.AppEUI != AppEUI)
            {
                string errorMsg = $"AppEUI for OTAA does not match for device";
                Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error);
                return(null);
            }

            //Make sure that is a new request and not a replay
            if (!String.IsNullOrEmpty(joinLoraDeviceInfo.DevNonce) && joinLoraDeviceInfo.DevNonce == DevNonce)
            {
                string errorMsg = $"DevNonce already used by this device";
                Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Info);
                joinLoraDeviceInfo.IsJoinValid = false;
                return(joinLoraDeviceInfo);
            }


            //Check that the device is joining through the linked gateway and not another

            if (!String.IsNullOrEmpty(joinLoraDeviceInfo.GatewayID) && joinLoraDeviceInfo.GatewayID.ToUpper() != GatewayID.ToUpper())
            {
                string errorMsg = $"not the right gateway device-gateway:{joinLoraDeviceInfo.GatewayID} current-gateway:{GatewayID}";
                Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Info);
                joinLoraDeviceInfo.IsJoinValid = false;
                return(joinLoraDeviceInfo);
            }

            byte[] netId = new byte[3] {
                0, 0, 1
            };
            AppNonce = OTAAKeysGenerator.getAppNonce();
            AppSKey  = OTAAKeysGenerator.calculateKey(new byte[1] {
                0x02
            }, ConversionHelper.StringToByteArray(AppNonce), netId, ConversionHelper.StringToByteArray(DevNonce), ConversionHelper.StringToByteArray(joinLoraDeviceInfo.AppKey));
            NwkSKey = OTAAKeysGenerator.calculateKey(new byte[1] {
                0x01
            }, ConversionHelper.StringToByteArray(AppNonce), netId, ConversionHelper.StringToByteArray(DevNonce), ConversionHelper.StringToByteArray(joinLoraDeviceInfo.AppKey));;
            DevAddr = OTAAKeysGenerator.getDevAddr(netId);
            joinLoraDeviceInfo.DevAddr  = DevAddr;
            joinLoraDeviceInfo.NwkSKey  = NwkSKey;
            joinLoraDeviceInfo.AppSKey  = AppSKey;
            joinLoraDeviceInfo.AppNonce = AppNonce;
            joinLoraDeviceInfo.DevNonce = DevNonce;
            joinLoraDeviceInfo.NetId    = ConversionHelper.ByteArrayToString(netId);
            //Accept the JOIN Request and the futher messages
            joinLoraDeviceInfo.IsJoinValid = true;

            return(joinLoraDeviceInfo);
        }
Exemple #10
0
        public static void TryGetValue(string key, out LoraDeviceInfo loraDeviceInfo)
        {
            Cache.MemoryCache.TryGetValue(key, out object loraDeviceInfoCache);

            loraDeviceInfo = (LoraDeviceInfo)loraDeviceInfoCache;
        }
        private async Task <byte[]> ProcessLoraMessage(LoRaMessageWrapper loraMessage)
        {
            bool validFrameCounter = false;

            byte[]         udpMsgForPktForwarder = new byte[0];
            string         devAddr        = ConversionHelper.ByteArrayToString(loraMessage.LoRaPayloadMessage.DevAddr.ToArray());
            Message        c2dMsg         = null;
            LoraDeviceInfo loraDeviceInfo = null;

            if (Cache.TryGetRequestValue(devAddr, out ConcurrentDictionary <string, LoraDeviceInfo> loraDeviceInfoCacheList))
            {
                loraDeviceInfo = loraDeviceInfoCacheList.Values.FirstOrDefault(x =>
                                                                               x.NwkSKey != null?loraMessage.CheckMic(x.NwkSKey):false);
            }
            if (loraDeviceInfo == null)
            {
                loraDeviceInfo = await this.loraDeviceInfoManager.GetLoraDeviceInfoAsync(devAddr, this.configuration.GatewayID, loraMessage);

                if (loraDeviceInfo != null && loraDeviceInfo.DevEUI != null)
                {
                    if (loraDeviceInfo.DevEUI != null)
                    {
                        Logger.Log(loraDeviceInfo.DevEUI, $"processing message, device not in cache", Logger.LoggingLevel.Info);
                    }
                    if (loraDeviceInfo.IsOurDevice)
                    {
                        Cache.AddRequestToCache(devAddr, loraDeviceInfo);
                    }
                }
            }
            else
            {
                Logger.Log(devAddr, $"processing message, device in cache", Logger.LoggingLevel.Info);
            }

            if (loraDeviceInfo != null && loraDeviceInfo.IsOurDevice)
            {
                //either there is no gateway linked to the device or the gateway is the one that the code is running
                if (string.IsNullOrEmpty(loraDeviceInfo.GatewayID) || string.Compare(loraDeviceInfo.GatewayID, this.configuration.GatewayID, ignoreCase: true) == 0)
                {
                    if (loraDeviceInfo.HubSender == null)
                    {
                        loraDeviceInfo.HubSender = new IoTHubConnector(loraDeviceInfo.DevEUI, loraDeviceInfo.PrimaryKey, this.configuration);
                    }
                    UInt16 fcntup = BitConverter.ToUInt16(loraMessage.LoRaPayloadMessage.GetLoRaMessage().Fcnt.ToArray(), 0);
                    byte[] linkCheckCmdResponse = null;

                    //check if the frame counter is valid: either is above the server one or is an ABP device resetting the counter (relaxed seqno checking)
                    if (fcntup > loraDeviceInfo.FCntUp || (fcntup == 0 && loraDeviceInfo.FCntUp == 0) || (fcntup == 1 && String.IsNullOrEmpty(loraDeviceInfo.AppEUI)))
                    {
                        //save the reset fcnt for ABP (relaxed seqno checking)
                        if (fcntup == 1 && String.IsNullOrEmpty(loraDeviceInfo.AppEUI))
                        {
                            _ = loraDeviceInfo.HubSender.UpdateFcntAsync(fcntup, 0, true);

                            //if the device is not attached to a gateway we need to reset the abp fcnt server side cache
                            if (String.IsNullOrEmpty(loraDeviceInfo.GatewayID))
                            {
                                bool rit = await this.loraDeviceInfoManager.ABPFcntCacheReset(loraDeviceInfo.DevEUI);
                            }
                        }

                        validFrameCounter = true;
                        Logger.Log(loraDeviceInfo.DevEUI, $"valid frame counter, msg: {fcntup} server: {loraDeviceInfo.FCntUp}", Logger.LoggingLevel.Info);

                        byte[] decryptedMessage = null;
                        try
                        {
                            decryptedMessage = loraMessage.DecryptPayload(loraDeviceInfo.AppSKey);
                        }
                        catch (Exception ex)
                        {
                            Logger.Log(loraDeviceInfo.DevEUI, $"failed to decrypt message: {ex.Message}", Logger.LoggingLevel.Error);
                        }
                        Rxpk    rxPk            = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0];
                        dynamic fullPayload     = JObject.FromObject(rxPk);
                        string  jsonDataPayload = "";
                        uint    fportUp         = 0;
                        bool    isAckFromDevice = false;
                        if (loraMessage.LoRaPayloadMessage.GetLoRaMessage().Fport.Span.Length > 0)
                        {
                            fportUp = (uint)loraMessage.LoRaPayloadMessage.GetLoRaMessage().Fport.Span[0];
                        }
                        else // this is an acknowledgment sent from the device
                        {
                            isAckFromDevice       = true;
                            fullPayload.deviceAck = true;
                        }
                        fullPayload.port = fportUp;
                        fullPayload.fcnt = fcntup;

                        // ACK from device or no decoder set in LoRa Device Twin. Simply return decryptedMessage
                        if (isAckFromDevice || String.IsNullOrEmpty(loraDeviceInfo.SensorDecoder))
                        {
                            if (String.IsNullOrEmpty(loraDeviceInfo.SensorDecoder))
                            {
                                Logger.Log(loraDeviceInfo.DevEUI, $"no decoder set in device twin. port: {fportUp}", Logger.LoggingLevel.Full);
                            }

                            jsonDataPayload  = Convert.ToBase64String(decryptedMessage);
                            fullPayload.data = jsonDataPayload;
                        }
                        // Decoder is set in LoRa Device Twin. Send decrpytedMessage (payload) and fportUp (fPort) to decoder.
                        else
                        {
                            Logger.Log(loraDeviceInfo.DevEUI, $"decoding with: {loraDeviceInfo.SensorDecoder} port: {fportUp}", Logger.LoggingLevel.Full);
                            fullPayload.data = await LoraDecoders.DecodeMessage(decryptedMessage, fportUp, loraDeviceInfo.SensorDecoder);
                        }

                        fullPayload.eui       = loraDeviceInfo.DevEUI;
                        fullPayload.gatewayid = this.configuration.GatewayID;
                        //Edge timestamp
                        fullPayload.edgets = (long)((startTimeProcessing - new DateTime(1970, 1, 1)).TotalMilliseconds);
                        List <KeyValuePair <String, String> > messageProperties = new List <KeyValuePair <String, String> >();

                        //Parsing MacCommands and add them as property of the message to be sent to the IoT Hub.
                        var macCommand = ((LoRaPayloadData)loraMessage.LoRaPayloadMessage).GetMacCommands();
                        if (macCommand.macCommand.Count > 0)
                        {
                            for (int i = 0; i < macCommand.macCommand.Count; i++)
                            {
                                messageProperties.Add(new KeyValuePair <string, string>(macCommand.macCommand[i].Cid.ToString(), value: JsonConvert.SerializeObject(macCommand.macCommand[i], Newtonsoft.Json.Formatting.None)));
                                //in case it is a link check mac, we need to send it downstream.
                                if (macCommand.macCommand[i].Cid == CidEnum.LinkCheckCmd)
                                {
                                    linkCheckCmdResponse = new LinkCheckCmd(rxPk.GetModulationMargin(), 1).ToBytes();
                                }
                            }
                        }
                        string iotHubMsg = fullPayload.ToString(Newtonsoft.Json.Formatting.None);
                        await loraDeviceInfo.HubSender.SendMessageAsync(iotHubMsg, messageProperties);

                        if (isAckFromDevice)
                        {
                            Logger.Log(loraDeviceInfo.DevEUI, $"ack from device sent to hub", Logger.LoggingLevel.Info);
                        }
                        else
                        {
                            var fullPayloadAsString = string.Empty;
                            if (fullPayload.data is JValue jvalue)
                            {
                                fullPayloadAsString = jvalue.ToString();
                            }
                            else if (fullPayload.data is JObject jobject)
                            {
                                fullPayloadAsString = jobject.ToString(Formatting.None);
                            }
                            Logger.Log(loraDeviceInfo.DevEUI, $"message '{fullPayloadAsString}' sent to hub", Logger.LoggingLevel.Info);
                        }
                        loraDeviceInfo.FCntUp = fcntup;
                    }
                    else
                    {
                        validFrameCounter = false;
                        Logger.Log(loraDeviceInfo.DevEUI, $"invalid frame counter, msg: {fcntup} server: {loraDeviceInfo.FCntUp}", Logger.LoggingLevel.Info);
                    }
                    byte[] fctrl = new byte[1] {
                        0
                    };

                    //we lock as fast as possible and get the down fcnt for multi gateway support for confirmed message
                    if (loraMessage.LoRaMessageType == LoRaMessageType.ConfirmedDataUp && String.IsNullOrEmpty(loraDeviceInfo.GatewayID))
                    {
                        fctrl[0] = (int)FctrlEnum.Ack;
                        ushort newFCntDown = await this.loraDeviceInfoManager.NextFCntDown(loraDeviceInfo.DevEUI, loraDeviceInfo.FCntDown, fcntup, this.configuration.GatewayID);

                        //ok to send down ack or msg
                        if (newFCntDown > 0)
                        {
                            loraDeviceInfo.FCntDown = newFCntDown;
                        }
                        //another gateway was first with this message we simply drop
                        else
                        {
                            Logger.Log(loraDeviceInfo.DevEUI, $"another gateway has already sent ack or downlink msg", Logger.LoggingLevel.Info);
                            Logger.Log(loraDeviceInfo.DevEUI, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info);
                            return(null);
                        }
                    }
                    //start checking for new c2d message, we do it even if the fcnt is invalid so we support replying to the ConfirmedDataUp
                    //todo ronnie should we wait up to 900 msec?
                    c2dMsg = await loraDeviceInfo.HubSender.ReceiveAsync(TimeSpan.FromMilliseconds(500));

                    if (c2dMsg != null && !ValidateCloudToDeviceMessage(loraDeviceInfo, c2dMsg))
                    {
                        _      = loraDeviceInfo.HubSender.CompleteAsync(c2dMsg);
                        c2dMsg = null;
                    }

                    byte[] bytesC2dMsg = null;
                    byte[] fport       = null;

                    //check if we got a c2d message to be added in the ack message and prepare the message
                    if (c2dMsg != null)
                    {
                        ////check if there is another message
                        var secondC2dMsg = await loraDeviceInfo.HubSender.ReceiveAsync(TimeSpan.FromMilliseconds(40));

                        if (secondC2dMsg != null)
                        {
                            //put it back to the queue for the next pickup
                            //todo ronnie check abbandon logic especially in case of mqtt
                            _ = await loraDeviceInfo.HubSender.AbandonAsync(secondC2dMsg);

                            //set the fpending flag so the lora device will call us back for the next message
                            fctrl[0] += (int)FctrlEnum.FpendingOrClassB;
                            Logger.Log(loraDeviceInfo.DevEUI, $"Additional C2D messages waiting, setting FPending to 1", Logger.LoggingLevel.Info);
                        }

                        bytesC2dMsg = c2dMsg.GetBytes();
                        //default fport
                        fport = new byte[1] {
                            1
                        };

                        if (bytesC2dMsg != null)
                        {
                            Logger.Log(loraDeviceInfo.DevEUI, $"C2D message: {Encoding.UTF8.GetString(bytesC2dMsg)}", Logger.LoggingLevel.Info);
                        }

                        //todo ronnie implement a better max payload size by datarate
                        //cut to the max payload of lora for any EU datarate
                        if (bytesC2dMsg.Length > 51)
                        {
                            Array.Resize(ref bytesC2dMsg, 51);
                        }

                        Array.Reverse(bytesC2dMsg);
                    }
                    //if confirmation or cloud to device msg send down the message
                    if (loraMessage.LoRaMessageType == LoRaMessageType.ConfirmedDataUp || c2dMsg != null)
                    {
                        if (loraMessage.LoRaMessageType == LoRaMessageType.ConfirmedDataUp)
                        {
                            fctrl[0] += (int)FctrlEnum.Ack;
                        }

                        //check if we are not too late for the second receive windows
                        if ((DateTime.UtcNow - startTimeProcessing) <= TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.receive_delay2 * 1000 - 100))
                        {
                            //if running in multigateway we need to use redis to sync the down fcnt
                            if (!String.IsNullOrEmpty(loraDeviceInfo.GatewayID))
                            {
                                loraDeviceInfo.FCntDown++;
                            }
                            else if (loraMessage.LoRaMessageType == LoRaMessageType.UnconfirmedDataUp)
                            {
                                ushort newFCntDown = await this.loraDeviceInfoManager.NextFCntDown(loraDeviceInfo.DevEUI, loraDeviceInfo.FCntDown, fcntup, this.configuration.GatewayID);

                                //ok to send down ack or msg
                                if (newFCntDown > 0)
                                {
                                    loraDeviceInfo.FCntDown = newFCntDown;
                                }
                                //another gateway was first with this message we simply drop
                                else
                                {
                                    Logger.Log(loraDeviceInfo.DevEUI, $"another gateway has already sent ack or downlink msg", Logger.LoggingLevel.Info);
                                    Logger.Log(loraDeviceInfo.DevEUI, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info);
                                    return(null);
                                }
                            }
                            Logger.Log(loraDeviceInfo.DevEUI, $"down frame counter: {loraDeviceInfo.FCntDown}", Logger.LoggingLevel.Info);

                            //Saving both fcnts to twins
                            _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, loraDeviceInfo.FCntDown);
                            //todo need implementation of current configuation to implement this as this depends on RX1DROffset
                            //var _datr = ((UplinkPktFwdMessage)loraMessage.LoraMetadata.FullPayload).rxpk[0].datr;
                            var datr = RegionFactory.CurrentRegion.GetDownstreamDR(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]);
                            //todo should discuss about the logic in case of multi channel gateway.
                            uint rfch = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].rfch;
                            //todo should discuss about the logic in case of multi channel gateway
                            double freq = RegionFactory.CurrentRegion.GetDownstreamChannel(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]);
                            //if we are already longer than 900 mssecond move to the 2 second window
                            //uncomment the following line to force second windows usage TODO change this to a proper expression?
                            //Thread.Sleep(901
                            long tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.receive_delay1 * 1000000;

                            if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.receive_delay1 * 1000 - 100))
                            {
                                if (string.IsNullOrEmpty(configuration.Rx2DataRate))
                                {
                                    Logger.Log(loraDeviceInfo.DevEUI, $"using standard second receive windows", Logger.LoggingLevel.Info);
                                    tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.receive_delay2 * 1000000;
                                    freq = RegionFactory.CurrentRegion.RX2DefaultReceiveWindows.frequency;
                                    datr = RegionFactory.CurrentRegion.DRtoConfiguration[RegionFactory.CurrentRegion.RX2DefaultReceiveWindows.dr].configuration;
                                }
                                //if specific twins are set, specify second channel to be as specified
                                else
                                {
                                    freq = configuration.Rx2DataFrequency;
                                    datr = configuration.Rx2DataRate;
                                    Logger.Log(loraDeviceInfo.DevEUI, $"using custom DR second receive windows freq : {freq}, datr:{datr}", Logger.LoggingLevel.Info);
                                }
                            }
                            Byte[] devAddrCorrect = new byte[4];
                            Array.Copy(loraMessage.LoRaPayloadMessage.DevAddr.ToArray(), devAddrCorrect, 4);
                            Array.Reverse(devAddrCorrect);
                            bool   requestForConfirmedResponse = false;
                            byte[] rndToken = new byte[2];
                            Random rnd      = new Random();
                            rnd.NextBytes(rndToken);

                            //check if the c2d message has a mac command
                            byte[] macbytes = null;
                            if (c2dMsg != null)
                            {
                                if (c2dMsg.Properties.TryGetValueCaseInsensitive("cidtype", out var cidTypeValue))
                                {
                                    Logger.Log(loraDeviceInfo.DevEUI, $"Cloud to device MAC command received", Logger.LoggingLevel.Info);
                                    MacCommandHolder macCommandHolder = new MacCommandHolder(Convert.ToByte(cidTypeValue));
                                    macbytes = macCommandHolder.macCommand[0].ToBytes();
                                }

                                if (c2dMsg.Properties.TryGetValueCaseInsensitive("confirmed", out var confirmedValue) && confirmedValue.Equals("true", StringComparison.OrdinalIgnoreCase))
                                {
                                    requestForConfirmedResponse = true;
                                    Logger.Log(loraDeviceInfo.DevEUI, $"Cloud to device message requesting a confirmation", Logger.LoggingLevel.Info);
                                }
                                if (c2dMsg.Properties.TryGetValueCaseInsensitive("fport", out var fPortValue))
                                {
                                    int fportint = int.Parse(fPortValue);

                                    fport[0] = BitConverter.GetBytes(fportint)[0];
                                    Logger.Log(loraDeviceInfo.DevEUI, $"Cloud to device message with a Fport of " + fPortValue, Logger.LoggingLevel.Info);
                                }
                                Logger.Log(loraDeviceInfo.DevEUI, String.Format("Sending a downstream message with ID {0}",
                                                                                ConversionHelper.ByteArrayToString(rndToken)),
                                           Logger.LoggingLevel.Full);
                            }

                            if (requestForConfirmedResponse)
                            {
                                fctrl[0] += (int)FctrlEnum.FpendingOrClassB;
                            }
                            if (macbytes != null && linkCheckCmdResponse != null)
                            {
                                macbytes = macbytes.Concat(linkCheckCmdResponse).ToArray();
                            }
                            LoRaPayloadData ackLoRaMessage = new LoRaPayloadData(
                                requestForConfirmedResponse ? MType.ConfirmedDataDown : MType.UnconfirmedDataDown,
                                //ConversionHelper.StringToByteArray(requestForConfirmedResponse?"A0":"60"),
                                devAddrCorrect,
                                fctrl,
                                BitConverter.GetBytes(loraDeviceInfo.FCntDown),
                                macbytes,
                                fport,
                                bytesC2dMsg,
                                1);

                            ackLoRaMessage.PerformEncryption(loraDeviceInfo.AppSKey);
                            ackLoRaMessage.SetMic(loraDeviceInfo.NwkSKey);


                            //todo ronnie should check the device twin preference if using confirmed or unconfirmed down
                            LoRaMessageWrapper ackMessage = new LoRaMessageWrapper(ackLoRaMessage, LoRaMessageType.UnconfirmedDataDown, rndToken, datr, 0, freq, tmst);
                            udpMsgForPktForwarder = ackMessage.PhysicalPayload.GetMessage();

                            linkCheckCmdResponse = null;
                            //confirm the message to iot hub only if we are in time for a delivery
                            if (c2dMsg != null)
                            {
                                //todo ronnie check if it is ok to do async so we make it in time to send the message
                                _ = loraDeviceInfo.HubSender.CompleteAsync(c2dMsg);
                                //bool rit = await loraDeviceInfo.HubSender.CompleteAsync(c2dMsg);
                                //if (rit)
                                //    Logger.Log(loraDeviceInfo.DevEUI, $"completed the c2d msg to IoT Hub", Logger.LoggingLevel.Info);
                                //else
                                //{
                                //    //we could not complete the msg so we send only a pushAck
                                //    PhysicalPayload pushAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null);
                                //    udpMsgForPktForwarder = pushAck.GetMessage();
                                //    Logger.Log(loraDeviceInfo.DevEUI, $"could not complete the c2d msg to IoT Hub", Logger.LoggingLevel.Info);

                                //}
                            }
                        }
                        else
                        {
                            //put back the c2d message to the queue for the next round
                            //todo ronnie check abbandon logic especially in case of mqtt
                            if (c2dMsg != null)
                            {
                                _ = await loraDeviceInfo.HubSender.AbandonAsync(c2dMsg);
                            }

                            Logger.Log(loraDeviceInfo.DevEUI, $"too late for down message, sending only ACK to gateway", Logger.LoggingLevel.Info);
                            _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, null);
                        }
                    }
                    //No ack requested and no c2d message we send the udp ack only to the gateway
                    else if (loraMessage.LoRaMessageType == LoRaMessageType.UnconfirmedDataUp && c2dMsg == null)
                    {
                        ////if ABP and 1 we reset the counter (loose frame counter) with force, if not we update normally
                        //if (fcntup == 1 && String.IsNullOrEmpty(loraDeviceInfo.AppEUI))
                        //    _ = loraDeviceInfo.HubSender.UpdateFcntAsync(fcntup, null, true);
                        if (validFrameCounter)
                        {
                            _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, null);
                        }
                    }

                    //If there is a link check command waiting
                    if (linkCheckCmdResponse != null)
                    {
                        Byte[] devAddrCorrect = new byte[4];
                        Array.Copy(loraMessage.LoRaPayloadMessage.DevAddr.ToArray(), devAddrCorrect, 4);
                        byte[] fctrl2 = new byte[1] {
                            0
                        };

                        Array.Reverse(devAddrCorrect);
                        LoRaPayloadData macReply = new LoRaPayloadData(MType.ConfirmedDataDown,
                                                                       devAddrCorrect,
                                                                       fctrl2,
                                                                       BitConverter.GetBytes(loraDeviceInfo.FCntDown),
                                                                       null,
                                                                       new byte[1] {
                            0
                        },
                                                                       linkCheckCmdResponse,
                                                                       1);

                        macReply.PerformEncryption(loraDeviceInfo.AppSKey);
                        macReply.SetMic(loraDeviceInfo.NwkSKey);

                        byte[] rndToken = new byte[2];
                        Random rnd      = new Random();
                        rnd.NextBytes(rndToken);

                        var datr = RegionFactory.CurrentRegion.GetDownstreamDR(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]);
                        //todo should discuss about the logic in case of multi channel gateway.
                        uint   rfch = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].rfch;
                        double freq = RegionFactory.CurrentRegion.GetDownstreamChannel(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]);
                        long   tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.receive_delay1 * 1000000;
                        //todo ronnie should check the device twin preference if using confirmed or unconfirmed down
                        LoRaMessageWrapper ackMessage = new LoRaMessageWrapper(macReply, LoRaMessageType.UnconfirmedDataDown, rndToken, datr, 0, freq, tmst);
                        udpMsgForPktForwarder = ackMessage.PhysicalPayload.GetMessage();
                    }
                }
                else
                {
                    Logger.Log(loraDeviceInfo.DevEUI, $"ignore message because is not linked to this GatewayID", Logger.LoggingLevel.Info);
                }
            }
            else
            {
                Logger.Log(devAddr, $"device is not our device, ignore message", Logger.LoggingLevel.Info);
            }



            Logger.Log(loraDeviceInfo?.DevEUI ?? devAddr, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info);

            return(udpMsgForPktForwarder);
        }