Beispiel #1
0
        /// <summary>
        /// Method to create a Rxpk object from a byte array.
        /// This is typically used for an upstream communication.
        /// </summary>
        /// <param name="inputMessage">Input byte array</param>
        /// <returns>List of rxpk or null if no Rxpk was found</returns>
        public static List <Rxpk> CreateRxpk(byte[] inputMessage)
        {
            PhysicalPayload physicalPayload = new PhysicalPayload(inputMessage);

            if (physicalPayload.Message != null)
            {
                var payload = Encoding.UTF8.GetString(physicalPayload.Message);
                if (!payload.StartsWith("{\"stat"))
                {
                    Logger.Log($"Physical dataUp {payload}", LogLevel.Debug);
                    var payloadObject = JsonConvert.DeserializeObject <UplinkPktFwdMessage>(payload);
                    if (payloadObject != null)
                    {
                        if (payloadObject.Rxpk != null)
                        {
                            return(payloadObject.Rxpk);
                        }
                    }
                }
                else
                {
                    Logger.Log($"Statistic: {payload}", LogLevel.Debug);
                }
            }

            return(new List <Rxpk>());
        }
        async Task IPacketForwarder.SendDownstreamAsync(DownlinkPktFwdMessage downstreamMessage)
        {
            try
            {
                if (downstreamMessage?.Txpk != null)
                {
                    var jsonMsg     = JsonConvert.SerializeObject(downstreamMessage);
                    var messageByte = Encoding.UTF8.GetBytes(jsonMsg);
                    var token       = await this.GetTokenAsync();

                    PhysicalPayload pyld = new PhysicalPayload(token, PhysicalIdentifier.PULL_RESP, messageByte);
                    if (this.pullAckRemoteLoRaAggregatorPort != 0 && !string.IsNullOrEmpty(this.pullAckRemoteLoRaAddress))
                    {
                        Logger.Log("UDP", $"sending message with ID {ConversionHelper.ByteArrayToString(token)}, to {this.pullAckRemoteLoRaAddress}:{this.pullAckRemoteLoRaAggregatorPort}", LogLevel.Debug);
                        await this.UdpSendMessageAsync(pyld.GetMessage(), this.pullAckRemoteLoRaAddress, this.pullAckRemoteLoRaAggregatorPort);

                        Logger.Log("UDP", $"message sent with ID {ConversionHelper.ByteArrayToString(token)}", LogLevel.Debug);
                    }
                    else
                    {
                        Logger.Log(
                            "UDP",
                            "waiting for first pull_ack message from the packet forwarder. The received message was discarded as the network server is still starting.",
                            LogLevel.Debug);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Log("UDP", $"error processing the message {ex.Message}, {ex.StackTrace}", LogLevel.Error);
            }
        }
        private async Task ProcessRxpkAsync(string remoteIp, Rxpk rxpk, DateTime startTimeProcessing)
        {
            try
            {
                var downstreamMessage = await this.messageProcessor.ProcessMessageAsync(rxpk, startTimeProcessing);

                if (downstreamMessage?.Txpk != null)
                {
                    var jsonMsg     = JsonConvert.SerializeObject(downstreamMessage);
                    var messageByte = Encoding.UTF8.GetBytes(jsonMsg);
                    var token       = await this.GetTokenAsync();

                    PhysicalPayload pyld = new PhysicalPayload(token, PhysicalIdentifier.PULL_RESP, messageByte);
                    if (this.pullAckRemoteLoRaAggregatorPort != 0)
                    {
                        await this.UdpSendMessage(pyld.GetMessage(), remoteIp, this.pullAckRemoteLoRaAggregatorPort);

                        Logger.Log("UDP", $"message sent with ID {ConversionHelper.ByteArrayToString(token)}", LogLevel.Information);
                    }
                    else
                    {
                        Logger.Log(
                            "UDP",
                            "Waiting for first pull_ack message from the packet forwarder. The received message was discarded as the network server is still starting.",
                            LogLevel.Debug);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Log($"Error processing the message {ex.Message}, {ex.StackTrace}", LogLevel.Error);
            }
        }
        async Task ListenAsync(CancellationToken cts)
        {
            try
            {
                var currentToken = new byte[2];
                while (!cts.IsCancellationRequested)
                {
                    var receivedResults = await udpClient.ReceiveAsync();

                    // If 4, it may mean we received a confirmation
                    if (receivedResults.Buffer.Length >= 4)
                    {
                        var identifier = PhysicalPayload.GetIdentifierFromPayload(receivedResults.Buffer);
                        currentToken[0] = receivedResults.Buffer[1];
                        currentToken[1] = receivedResults.Buffer[2];
                        var tokenKey = CreateTokenKey(currentToken, identifier);
                        TestLogger.Log($"[PKTFORWARDER] Received {identifier.ToString()} with token {tokenKey}");

                        if (this.subscribers.TryGetValue(tokenKey, out var subscriber))
                        {
                            subscriber(receivedResults.Buffer);
                            this.subscribers.Remove(tokenKey, out _);
                        }
                    }
                }
            }
            catch (ObjectDisposedException)
            {
            }
        }
        async Task RunUdpListener()
        {
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, PORT);

            this.udpClient = new UdpClient(endPoint);

            Logger.LogAlways($"LoRaWAN server started on port {PORT}");

            while (true)
            {
                UdpReceiveResult receivedResults = await this.udpClient.ReceiveAsync();

                var startTimeProcessing = DateTime.UtcNow;

                switch (PhysicalPayload.GetIdentifierFromPayload(receivedResults.Buffer))
                {
                // In this case we have a keep-alive PULL_DATA packet we don't need to start the engine and can return immediately a response to the challenge
                case PhysicalIdentifier.PULL_DATA:
                    if (this.pullAckRemoteLoRaAggregatorPort == 0)
                    {
                        this.pullAckRemoteLoRaAggregatorPort = receivedResults.RemoteEndPoint.Port;
                        this.pullAckRemoteLoRaAddress        = receivedResults.RemoteEndPoint.Address.ToString();
                    }

                    this.SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PULL_ACK, receivedResults.RemoteEndPoint);
                    break;

                // This is a PUSH_DATA (upstream message).
                case PhysicalIdentifier.PUSH_DATA:
                    this.SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PUSH_ACK, receivedResults.RemoteEndPoint);
                    this.DispatchMessages(receivedResults.Buffer, startTimeProcessing);

                    break;

                // This is a ack to a transmission we did previously
                case PhysicalIdentifier.TX_ACK:
                    if (receivedResults.Buffer.Length == 12)
                    {
                        Logger.Log(
                            "UDP",
                            $"packet with id {ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2))} successfully transmitted by the aggregator",
                            LogLevel.Debug);
                    }
                    else
                    {
                        var logMsg = string.Format(
                            "packet with id {0} had a problem to be transmitted over the air :{1}",
                            receivedResults.Buffer.Length > 2 ? ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2)) : string.Empty,
                            receivedResults.Buffer.Length > 12 ? Encoding.UTF8.GetString(receivedResults.Buffer.RangeSubset(12, receivedResults.Buffer.Length - 12)) : string.Empty);
                        Logger.Log("UDP", logMsg, LogLevel.Error);
                    }

                    break;

                default:
                    Logger.Log("UDP", "unknown packet type or length being received", LogLevel.Error);
                    break;
                }
            }
        }
Beispiel #6
0
        public DecodedLoraPacket DecodeMessage(string encodedPhysicalPayload)
        {
            // conver to hex
            var byteArray     = Convert.FromBase64String(encodedPhysicalPayload);
            var hexValuesList = BitConverter.ToString(byteArray).Split("-").ToList();

            // create packet
            DecodedLoraPacket decodedPacket = new DecodedLoraPacket()
            {
                OriginalHexString = string.Join("", hexValuesList)
            };

            var physicalPayload = new PhysicalPayload
            {
                MacHeader = hexValuesList[0],
                MIC       = string.Join("", hexValuesList).Substring((hexValuesList.Count * 2) - 8, 8)
            };

            hexValuesList.RemoveRange(hexValuesList.Count - 4, 4);
            hexValuesList.RemoveAt(0);

            var frameHeader = new FrameHeader
            {
                DeviceAddress     = hexValuesList[3] + hexValuesList[2] + hexValuesList[1] + hexValuesList[0], // big endian
                FrameControlOctet = hexValuesList[4],
                FrameCounter      = hexValuesList[6] + hexValuesList[5]                                        // big endian
            };

            // frame options is optional, need to work out size / if its present using octet
            var binaryFrameControl = Convert.ToString(Convert.ToInt32(frameHeader.FrameControlOctet, 16), 2);
            var frameOptionsOffset = Convert.ToInt32(binaryFrameControl.Substring(binaryFrameControl.Length - 3, 3));

            if (frameOptionsOffset != 0)
            {
                for (int i = 1; i <= frameOptionsOffset; i++)
                {
                    frameHeader.FrameOptions += hexValuesList[6 + i];
                }
            }

            // FramePayload
            var values        = string.Join("", hexValuesList);
            var payloadOffset = 16 + (frameOptionsOffset * 2);

            var macPayload = new MacPayload
            {
                FramePort    = hexValuesList[7 + frameOptionsOffset],
                FramePayload = values.Substring(payloadOffset, values.Length - payloadOffset),
                FrameHeader  = frameHeader
            };

            // combine structures
            physicalPayload.MacPayload    = macPayload;
            decodedPacket.PhysicalPayload = physicalPayload;

            return(decodedPacket);
        }
Beispiel #7
0
        public LoRaMessageWrapper(LoRaPayload payload, LoRaMessageType type, byte[] physicalToken, string datr, uint rfch, double freq, long tmst)
        {
            LoRaPayloadMessage = payload;
            PktFwdPayload      = new DownlinkPktFwdMessage(Convert.ToBase64String(LoRaPayloadMessage.GetByteMessage()), datr, rfch, freq, tmst);
            var jsonMsg = JsonConvert.SerializeObject(PktFwdPayload);

            Logger.Log(ConversionHelper.ByteArrayToString(payload.GetLoRaMessage().DevEUI.ToArray()), $"{((MType)(payload.Mhdr.Span[0])).ToString()} {jsonMsg}", Logger.LoggingLevel.Full);
            var messageBytes = Encoding.Default.GetBytes(jsonMsg);

            PhysicalPayload = new PhysicalPayload(physicalToken, PhysicalIdentifier.PULL_RESP, messageBytes);
        }
Beispiel #8
0
        private byte[] ProcessNonLoraMessage(LoRaMessageWrapper loraMessage)
        {
            byte[] udpMsgForPktForwarder = new byte[0];
            if (loraMessage.PhysicalPayload.identifier == PhysicalIdentifier.PULL_DATA)
            {
                PhysicalPayload pullAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PULL_ACK, null);
                udpMsgForPktForwarder = pullAck.GetMessage();
            }

            return(udpMsgForPktForwarder);
        }
Beispiel #9
0
        internal async Task <PhysicalPayload> SendAsync(byte[] syncHeader, byte[] data)
        {
            var rxpkgateway = this.CreateMessagePacket(data);
            var msg         = "{\"rxpk\":[" + rxpkgateway + "]}";

            var gatewayInfo = Encoding.UTF8.GetBytes(msg);

            byte[] packetData = new byte[syncHeader.Length + gatewayInfo.Length];
            Array.Copy(syncHeader, packetData, syncHeader.Length);
            Array.Copy(gatewayInfo, 0, packetData, syncHeader.Length, gatewayInfo.Length);

            var physicalPayload = new PhysicalPayload(packetData);

            await this.udpClient.SendAsync(packetData, packetData.Length, this.networkServerIPEndpoint);

            return(physicalPayload);
        }
Beispiel #10
0
        async Task ListenAsync(CancellationToken cts)
        {
            try
            {
                var currentToken = new byte[2];
                while (!cts.IsCancellationRequested)
                {
                    var receivedResults = await this.udpClient.ReceiveAsync();

                    // If 4, it may mean we received a confirmation
                    if (receivedResults.Buffer.Length >= 4)
                    {
                        var identifier = PhysicalPayload.GetIdentifierFromPayload(receivedResults.Buffer);
                        currentToken[0] = receivedResults.Buffer[1];
                        currentToken[1] = receivedResults.Buffer[2];
                        TestLogger.Log($"[PKTFORWARDER] Received {identifier.ToString()}");

                        if (identifier == PhysicalIdentifier.PULL_RESP)
                        {
                            if (this.subscribers.Count > 0)
                            {
                                Func <byte[], bool> subscriberToRemove = null;

                                foreach (var subscriber in this.subscribers)
                                {
                                    if (subscriber(receivedResults.Buffer))
                                    {
                                        subscriberToRemove = subscriber;
                                        break;
                                    }
                                }

                                if (subscriberToRemove != null)
                                {
                                    this.subscribers.Remove(subscriberToRemove);
                                }
                            }
                        }
                    }
                }
            }
            catch (ObjectDisposedException)
            {
            }
        }
Beispiel #11
0
 /// <summary>
 /// This contructor is used in case of downlink message
 /// </summary>
 /// <param name="inputMessage"></param>
 /// <param name="type">
 /// 0 = Join Request
 /// 1 = Join Accept
 /// 2 = Unconfirmed Data up
 /// 3 = Unconfirmed Data down
 /// 4 = Confirmed Data up
 /// 5 = Confirmed Data down
 /// 6 = Rejoin Request</param>
 public LoRaMessageWrapper(LoRaPayload payload, LoRaMessageType type, byte[] physicalToken)
 {
     LoRaPayloadMessage = payload;
     // construct a Join Accept Message
     if (type == LoRaMessageType.JoinAccept)
     {
         var downlinkmsg = new DownlinkPktFwdMessage(Convert.ToBase64String(payload.GetByteMessage()));
         PktFwdPayload = downlinkmsg;
         var jsonMsg      = JsonConvert.SerializeObject(downlinkmsg);
         var messageBytes = Encoding.Default.GetBytes(jsonMsg);
         PhysicalPayload = new PhysicalPayload(physicalToken, PhysicalIdentifier.PULL_RESP, messageBytes);
     }
     else if (type == LoRaMessageType.UnconfirmedDataDown)
     {
         throw new NotImplementedException();
     }
     else if (type == LoRaMessageType.ConfirmedDataDown)
     {
         throw new NotImplementedException();
     }
 }
        async Task PushDataAsync(CancellationToken cts)
        {
            try
            {
                while (!cts.IsCancellationRequested)
                {
                    var sync = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null);
                    var data = sync.GetSyncHeader(this.MacAddress);
                    await udpClient.SendAsync(data, data.Length, this.networkServerIPEndpoint);

                    await Task.Delay(10000, cts);
                }
            }
            catch (TaskCanceledException)
            {
            }
            catch (Exception ex)
            {
                TestLogger.Log($"Error in {nameof(PushDataAsync)}. {ex.ToString()}");
            }
        }
Beispiel #13
0
        /// <summary>
        /// This method is used as part of Simulated device for testing purposes.
        /// </summary>
        /// <param name="inputMessage">The Input Message bytes.</param>
        /// <param name="appKey">The appKey.</param>
        public static Txpk CreateTxpk(byte[] inputMessage, string appKey)
        {
            PhysicalPayload physicalPayload = new PhysicalPayload(inputMessage, true);
            var             payload         = Encoding.UTF8.GetString(physicalPayload.Message);

            // deserialize for a downlink message
            // checkwith franc
            var payloadDownObject = JsonConvert.DeserializeObject <DownlinkPktFwdMessage>(payload);

            if (payloadDownObject != null)
            {
                if (payloadDownObject.Txpk != null)
                {
                    return(payloadDownObject.Txpk);
                }
                else
                {
                    Logger.Log("Error: " + payloadDownObject.Txpk, LogLevel.Error);
                }
            }

            return(null);
        }
        public async Task RunServer()
        {
            mac = GetMacAddress();
            Logger.Log("Starting LoRaWAN Simulator...", Logger.LoggingLevel.Always);

            if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SIMULATOR_PORT")))
            {
                PORT = Convert.ToInt32(Environment.GetEnvironmentVariable("SIMULATOR_PORT"));
                Logger.Log($"Changing port to {PORT}", Logger.LoggingLevel.Always);
            }

            // Creating the endpoint
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, PORT);

            udpClient = new UdpClient(endPoint);

            Logger.Log($"LoRaWAN Simulator started on port {PORT}", Logger.LoggingLevel.Always);
            //send first sync
            _ = Task.Factory.StartNew(async() =>
            {
                while (true)
                {
                    var sync = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null);
                    await UdpSendMessage(sync.GetSyncHeader(mac));
                    await Task.Delay(10000);
                }
            });
            // Reading the test configuration
            string json = System.IO.File.ReadAllText(@"testconfig.json");

            var theObjects = JsonConvert.DeserializeObject <JObject>(json);

            var rxpk = theObjects["rxpk"];

            gateway = new GatewayDevice(rxpk.ToString());

            var devices = theObjects["Devices"];

            // TODO: Need to create simulated devices
            foreach (var device in devices)
            {
                SimulatedDevice simulated = new SimulatedDevice(device.ToString());
                listDevices.Add(simulated);

                // create a new thread that will post content
                _ = Task.Factory.StartNew(async() =>
                {
                    DateTimeOffset dt = DateTimeOffset.Now;

                    while (true)
                    {
                        if (dt.AddSeconds(simulated.Interval) < DateTimeOffset.Now)
                        {
                            dt = DateTimeOffset.Now;
                            // send a message
                            if (simulated.LoRaDevice.IsJoined)
                            {
                                simulated.LastPayload = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null);
                                var header            = simulated.LastPayload.GetSyncHeader(mac);

                                var simdata     = simulated.GetUnconfirmedDataUpMessage();
                                var rxpkgateway = gateway.GetMessage(simdata);
                                var msg         = "{\"rxpk\":[" + rxpkgateway + "]}";

                                var gat     = Encoding.Default.GetBytes(msg);
                                byte[] data = new byte[header.Length + gat.Length];
                                Array.Copy(header, data, header.Length);
                                Array.Copy(gat, 0, data, header.Length, gat.Length);
                                Logger.Log(simulated.LoRaDevice.DevAddr, $"Sending data: {BitConverter.ToString(header).Replace("-", "")}{Encoding.Default.GetString(gat)}", Logger.LoggingLevel.Always);
                                await UdpSendMessage(data);
                            }
                            else
                            {
                                simulated.LastPayload = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null);
                                var header            = simulated.LastPayload.GetSyncHeader(mac);

                                var join        = simulated.GetJoinRequest();
                                var rxpkgateway = gateway.GetMessage(join);
                                var msg         = "{\"rxpk\":[" + rxpkgateway + "]}";

                                var gat     = Encoding.Default.GetBytes(msg);
                                byte[] data = new byte[header.Length + gat.Length];
                                Array.Copy(header, data, header.Length);
                                Array.Copy(gat, 0, data, header.Length, gat.Length);

                                await UdpSendMessage(data);
                            }
                        }
                    }
                });
            }



            // TODO: Need to start this in a thread
            await RunUdpListener();
        }
        async Task RunUdpListener()
        {
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, PORT);

            this.udpClient = new UdpClient(endPoint);

            Logger.LogAlways($"LoRaWAN server started on port {PORT}");

            while (true)
            {
                UdpReceiveResult receivedResults = await this.udpClient.ReceiveAsync();

                var startTimeProcessing = DateTime.UtcNow;
                // Logger.LogAlways($"UDP message received ({receivedResults.Buffer[3]}) from port: {receivedResults.RemoteEndPoint.Port} and IP: {receivedResults.RemoteEndPoint.Address.ToString()}");
                switch (PhysicalPayload.GetIdentifierFromPayload(receivedResults.Buffer))
                {
                // In this case we have a keep-alive PULL_DATA packet we don't need to start the engine and can return immediately a response to the challenge
                case PhysicalIdentifier.PULL_DATA:
                    if (this.pullAckRemoteLoRaAggregatorPort == 0)
                    {
                        this.pullAckRemoteLoRaAggregatorPort = receivedResults.RemoteEndPoint.Port;
                    }

                    this.SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PULL_ACK, receivedResults.RemoteEndPoint);
                    break;

                // This is a PUSH_DATA (upstream message).
                case PhysicalIdentifier.PUSH_DATA:
                    this.SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PUSH_ACK, receivedResults.RemoteEndPoint);

                    // Message processing runs in the background
                    var remoteEndPointAddress = receivedResults.RemoteEndPoint.Address.ToString();
                    _ = Task.Run(async() =>
                    {
                        List <Rxpk> messageRxpks = Rxpk.CreateRxpk(receivedResults.Buffer);
                        if (messageRxpks != null)
                        {
                            if (messageRxpks.Count == 1)
                            {
                                await this.ProcessRxpkAsync(receivedResults.RemoteEndPoint.Address.ToString(), messageRxpks[0], startTimeProcessing);
                            }
                            else if (messageRxpks.Count > 1)
                            {
                                Task toWait = null;
                                for (int i = 0; i < messageRxpks.Count; i++)
                                {
                                    var t = this.ProcessRxpkAsync(remoteEndPointAddress, messageRxpks[i], startTimeProcessing);
                                    if (toWait == null)
                                    {
                                        toWait = t;
                                    }
                                }

                                await toWait;
                            }
                        }
                    });

                    break;

                // This is a ack to a transmission we did previously
                case PhysicalIdentifier.TX_ACK:
                    if (receivedResults.Buffer.Length == 12)
                    {
                        Logger.Log(
                            "UDP",
                            $"Packet with id {ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2))} successfully transmitted by the aggregator",
                            LogLevel.Debug);
                    }
                    else
                    {
                        var logMsg = string.Format(
                            "Packet with id {0} had a problem to be transmitted over the air :{1}",
                            receivedResults.Buffer.Length > 2 ? ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2)) : string.Empty,
                            receivedResults.Buffer.Length > 12 ? Encoding.UTF8.GetString(receivedResults.Buffer.RangeSubset(12, receivedResults.Buffer.Length - 12)) : string.Empty);
                        Logger.Log("UDP", logMsg, LogLevel.Error);
                    }

                    break;

                default:
                    Logger.Log("UDP", "Unknown packet type or length being received", LogLevel.Error);
                    break;
                }
            }
        }
Beispiel #16
0
        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;

            Cache.TryGetValue(devAddr, out LoraDeviceInfo loraDeviceInfo);

            if (loraDeviceInfo == null || !loraDeviceInfo.IsOurDevice)
            {
                loraDeviceInfo = await LoraDeviceInfoManager.GetLoraDeviceInfoAsync(devAddr, GatewayID);

                if (loraDeviceInfo.DevEUI != null)
                {
                    Logger.Log(loraDeviceInfo.DevEUI, $"processing message, device not in cache", Logger.LoggingLevel.Info);
                }
                else
                {
                    Logger.Log(devAddr, $"processing message, device not in cache", Logger.LoggingLevel.Info);
                }
                Cache.AddToCache(devAddr, loraDeviceInfo);
            }
            else
            {
                Logger.Log(loraDeviceInfo.DevEUI, $"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) || loraDeviceInfo.GatewayID.ToUpper() == GatewayID.ToUpper())
                {
                    if (loraMessage.CheckMic(loraDeviceInfo.NwkSKey))
                    {
                        if (loraDeviceInfo.HubSender == null)
                        {
                            loraDeviceInfo.HubSender = new IoTHubConnector(loraDeviceInfo.DevEUI, loraDeviceInfo.PrimaryKey);
                        }
                        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 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;

                            if (isAckFromDevice)
                            {
                                jsonDataPayload  = Convert.ToBase64String(decryptedMessage);
                                fullPayload.data = jsonDataPayload;
                            }
                            else
                            {
                                Logger.Log(loraDeviceInfo.DevEUI, $"decoding with: {loraDeviceInfo.SensorDecoder} port: {fportUp}", Logger.LoggingLevel.Info);
                                fullPayload.data = await LoraDecoders.DecodeMessage(decryptedMessage, fportUp, loraDeviceInfo.SensorDecoder);
                            }

                            fullPayload.eui       = loraDeviceInfo.DevEUI;
                            fullPayload.gatewayid = 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 = fullPayload.data as string;
                                if (fullPayloadAsString == null)
                                {
                                    fullPayloadAsString = ((JObject)fullPayload.data).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);
                        }


                        //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))
                        {
                            ushort newFCntDown = await LoraDeviceInfoManager.NextFCntDown(loraDeviceInfo.DevEUI, loraDeviceInfo.FCntDown, fcntup, 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
                            {
                                PhysicalPayload pushAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null);
                                udpMsgForPktForwarder = pushAck.GetMessage();
                                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(udpMsgForPktForwarder);
                            }
                        }
                        //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(20));

                        byte[] bytesC2dMsg = null;
                        byte[] fport       = null;
                        //Todo revamp fctrl
                        byte[] fctrl = new byte[1] {
                            32
                        };
                        //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(20));

                            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 = new byte[1] {
                                    48
                                };
                            }

                            bytesC2dMsg = c2dMsg.GetBytes();
                            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)
                        {
                            //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 LoraDeviceInfoManager.NextFCntDown(loraDeviceInfo.DevEUI, loraDeviceInfo.FCntDown, fcntup, 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
                                    {
                                        PhysicalPayload pushAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null);
                                        udpMsgForPktForwarder = pushAck.GetMessage();
                                        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(udpMsgForPktForwarder);
                                    }
                                }
                                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))
                                {
                                    fctrl = new byte[1] {
                                        32
                                    };
                                    if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RX2_DATR")))
                                    {
                                        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 = double.Parse(Environment.GetEnvironmentVariable("RX2_FREQ"));
                                        datr = Environment.GetEnvironmentVariable("RX2_DATR");
                                        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;

                                //check if the c2d message has a mac command
                                byte[] macbytes = null;
                                if (c2dMsg != null)
                                {
                                    var macCmd = c2dMsg.Properties.Where(o => o.Key == "CidType");
                                    if (macCmd.Count() != 0)
                                    {
                                        MacCommandHolder macCommandHolder = new MacCommandHolder(Convert.ToByte(macCmd.First().Value));
                                        macbytes = macCommandHolder.macCommand[0].ToBytes();
                                    }
                                    var confirmCmd = c2dMsg.Properties.Where(o => o.Key == "Confirmed");
                                    if (confirmCmd.Count() != 0)
                                    {
                                        requestForConfirmedResponse = true;
                                    }
                                }
                                if (requestForConfirmedResponse)
                                {
                                    fctrl[0] += 16;
                                }
                                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);

                                byte[] rndToken = new byte[2];
                                Random rnd      = new Random();
                                rnd.NextBytes(rndToken);
                                //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
                            {
                                PhysicalPayload pushAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null);
                                udpMsgForPktForwarder = pushAck.GetMessage();

                                //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)
                        {
                            PhysicalPayload pushAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null);
                            udpMsgForPktForwarder = pushAck.GetMessage();

                            ////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] {
                                32
                            };

                            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, $"with devAddr {devAddr} check MIC failed. Device will be ignored from now on", Logger.LoggingLevel.Info);
                        loraDeviceInfo.IsOurDevice = false;
                    }
                }
                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, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info);


            return(udpMsgForPktForwarder);
        }
Beispiel #17
0
        async Task RunUdpListener()
        {
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, PORT);

            udpClient = new UdpClient(endPoint);

            Logger.Log($"LoRaWAN server started on port {PORT}", Logger.LoggingLevel.Always);


            while (true)
            {
                UdpReceiveResult receivedResults = await udpClient.ReceiveAsync();

                //Logger.Log($"UDP message received ({receivedResults.Buffer[3]}) from port: {receivedResults.RemoteEndPoint.Port} and IP: {receivedResults.RemoteEndPoint.Address.ToString()}",LoggingLevel.Always);


                switch (PhysicalPayload.GetIdentifierFromPayload(receivedResults.Buffer))
                {
                //In this case we have a keep-alive PULL_DATA packet we don't need to start the engine and can return immediately a response to the challenge
                case PhysicalIdentifier.PULL_DATA:
                    if (pullAckRemoteLoRaAggregatorPort == 0)
                    {
                        pullAckRemoteLoRaAggregatorPort = receivedResults.RemoteEndPoint.Port;
                    }
                    SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PULL_ACK, receivedResults.RemoteEndPoint);
                    break;

                //This is a PUSH_DATA (upstream message).
                case PhysicalIdentifier.PUSH_DATA:
                    SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PUSH_ACK, receivedResults.RemoteEndPoint);

                    // Message processing runs in the background
                    MessageProcessor messageProcessor = new MessageProcessor(this.configuration, this.loraDeviceInfoManager);

                    // do not wait for it to return
                    _ = Task.Run(async() => {
                        try
                        {
                            var resultMessage = await messageProcessor.ProcessMessageAsync(receivedResults.Buffer);
                            if (resultMessage != null && resultMessage.Length > 0)
                            {
                                var port = pullAckRemoteLoRaAggregatorPort;
                                if (port <= 0)
                                {
                                    port = receivedResults.RemoteEndPoint.Port;
                                }
                                await this.UdpSendMessage(resultMessage, receivedResults.RemoteEndPoint.Address.ToString(), port);
                            }
                        }
                        catch (Exception ex)
                        {
                            Logger.Log($"Error processing the message {ex.Message}, {ex.StackTrace}", Logger.LoggingLevel.Error);
                        }
                    });

                    break;

                //This is a ack to a transmission we did previously
                case PhysicalIdentifier.TX_ACK:
                    if (receivedResults.Buffer.Length == 12)
                    {
                        Logger.Log(String.Format("Packet with id {0} successfully transmitted by the aggregator",
                                                 ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2)))
                                   , LoggingLevel.Full);
                    }
                    else
                    {
                        Logger.Log(String.Format("Packet with id {0} had a problem to be transmitted over the air :{1}",
                                                 receivedResults.Buffer.Length > 2 ? ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2)) : "",
                                                 receivedResults.Buffer.Length > 12 ? Encoding.UTF8.GetString(receivedResults.Buffer.RangeSubset(12, receivedResults.Buffer.Length - 12)) : "")
                                   , LoggingLevel.Error);
                    }
                    break;

                default:
                    Logger.Log("Unknown packet type or length being received", LoggingLevel.Error);
                    break;
                }
#pragma warning restore CS4014
            }
        }
Beispiel #18
0
        private async Task <byte[]> ProcessJoinRequest(LoRaMessageWrapper loraMessage)
        {
            byte[]         udpMsgForPktForwarder = new Byte[0];
            LoraDeviceInfo joinLoraDeviceInfo;
            var            joinReq = (LoRaPayloadJoinRequest)loraMessage.LoRaPayloadMessage;

            joinReq.DevEUI.Span.Reverse();
            joinReq.AppEUI.Span.Reverse();
            string devEui   = ConversionHelper.ByteArrayToString(joinReq.DevEUI.ToArray());
            string devNonce = ConversionHelper.ByteArrayToString(joinReq.DevNonce.ToArray());

            Logger.Log(devEui, $"join request received", Logger.LoggingLevel.Info);
            //checking if this devnonce was already processed or the deveui was already refused
            Cache.TryGetValue(devEui, out joinLoraDeviceInfo);

            //check if join request is valid.
            //we have a join request in the cache
            if (joinLoraDeviceInfo != null)
            {
                //we query every time in case the device is turned one while not yet in the registry
                //it is not our device so ingore the join
                //if (!joinLoraDeviceInfo.IsOurDevice)
                //{
                //    Logger.Log(devEui, $"join request refused the device is not ours", Logger.LoggingLevel.Info);
                //    return null;
                //}
                //is our device but the join was not valid
                if (!joinLoraDeviceInfo.IsJoinValid)
                {
                    //if the devNonce is equal to the current it is a potential replay attck
                    if (joinLoraDeviceInfo.DevNonce == devNonce)
                    {
                        Logger.Log(devEui, $"join request refused devNonce already used", Logger.LoggingLevel.Info);
                        return(null);
                    }

                    //Check if the device is trying to join through the wrong gateway
                    if (!String.IsNullOrEmpty(joinLoraDeviceInfo.GatewayID) && joinLoraDeviceInfo.GatewayID.ToUpper() != GatewayID.ToUpper())
                    {
                        Logger.Log(devEui, $"trying to join not through its linked gateway, ignoring join request", Logger.LoggingLevel.Info);
                        return(null);
                    }
                }
            }

            joinLoraDeviceInfo = await LoraDeviceInfoManager.PerformOTAAAsync(GatewayID, devEui, ConversionHelper.ByteArrayToString(joinReq.AppEUI.ToArray()), devNonce, joinLoraDeviceInfo);


            if (joinLoraDeviceInfo != null && joinLoraDeviceInfo.IsJoinValid)
            {
                //TODO to be fixed by Mik
                //if (!loraMessage.LoRaPayloadMessage.CheckMic(joinLoraDeviceInfo.AppKey))
                //{
                //    Logger.Log(devEui, $"join request MIC invalid", Logger.LoggingLevel.Info);
                //    return null;
                //}
                //join request resets the frame counters
                joinLoraDeviceInfo.FCntUp   = 0;
                joinLoraDeviceInfo.FCntDown = 0;
                //in this case it's too late, we need to break and awoid saving twins
                if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.join_accept_delay2 * 1000))
                {
                    Logger.Log(devEui, $"processing of the join request took too long, sending no message", Logger.LoggingLevel.Info);
                    var physicalResponse = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null);
                    return(physicalResponse.GetMessage());
                }
                //update reported properties and frame Counter
                await joinLoraDeviceInfo.HubSender.UpdateReportedPropertiesOTAAasync(joinLoraDeviceInfo);

                byte[] appNonce = ConversionHelper.StringToByteArray(joinLoraDeviceInfo.AppNonce);
                byte[] netId    = ConversionHelper.StringToByteArray(joinLoraDeviceInfo.NetId);
                byte[] devAddr  = ConversionHelper.StringToByteArray(joinLoraDeviceInfo.DevAddr);
                string appKey   = joinLoraDeviceInfo.AppKey;
                Array.Reverse(netId);
                Array.Reverse(appNonce);
                LoRaPayloadJoinAccept loRaPayloadJoinAccept = new LoRaPayloadJoinAccept(
                    //NETID 0 / 1 is default test
                    ConversionHelper.ByteArrayToString(netId),
                    //todo add app key management
                    appKey,
                    //todo add device address management
                    devAddr,
                    appNonce,
                    new byte[] { 0 },
                    new byte[] { 0 },
                    null
                    );
                var    datr = RegionFactory.CurrentRegion.GetDownstreamDR(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]);
                uint   rfch = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].rfch;
                double freq = RegionFactory.CurrentRegion.GetDownstreamChannel(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]);
                //set tmst for the normal case
                long tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.join_accept_delay1 * 1000000;

                ////in this case it's too late, we need to break
                //if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.join_accept_delay2 * 1000))
                //{
                //    Logger.Log(devEui, $"processing of the join request took too long, sending no message", Logger.LoggingLevel.Info);
                //    var physicalResponse = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PULL_RESP, null);

                //    Logger.Log(devEui, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info);

                //    return physicalResponse.GetMessage();
                //}
                //in this case the second join windows must be used
                if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.join_accept_delay1 * 1000 - 100))
                {
                    Logger.Log(devEui, $"processing of the join request took too long, using second join accept receive window", Logger.LoggingLevel.Info);
                    tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.join_accept_delay2 * 1000000;
                    if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RX2_DATR")))
                    {
                        Logger.Log(devEui, $"using standard second receive windows for join request", Logger.LoggingLevel.Info);
                        //using EU fix DR for RX2
                        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
                    {
                        Logger.Log(devEui, $"using custom  second receive windows for join request", Logger.LoggingLevel.Info);
                        freq = double.Parse(Environment.GetEnvironmentVariable("RX2_FREQ"));
                        datr = Environment.GetEnvironmentVariable("RX2_DATR");
                    }
                }
                LoRaMessageWrapper joinAcceptMessage = new LoRaMessageWrapper(loRaPayloadJoinAccept, LoRaMessageType.JoinAccept, loraMessage.PhysicalPayload.token, datr, 0, freq, tmst);
                udpMsgForPktForwarder = joinAcceptMessage.PhysicalPayload.GetMessage();

                //add to cache for processing normal messages. This awoids one additional call to the server.
                Cache.AddToCache(joinLoraDeviceInfo.DevAddr, joinLoraDeviceInfo);
                Logger.Log(devEui, $"join accept sent", Logger.LoggingLevel.Info);
            }
            else
            {
                Logger.Log(devEui, $"join request refused", Logger.LoggingLevel.Info);
            }

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


            return(udpMsgForPktForwarder);
        }
        public async Task RunServer()
        {
            mac = GetMacAddress();
            Logger.LogAlways("Starting LoRaWAN Simulator...");

            if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SIMULATOR_PORT")))
            {
                PORT = Convert.ToInt32(Environment.GetEnvironmentVariable("SIMULATOR_PORT"));
                Logger.LogAlways($"Changing port to {PORT}");
            }

            // Creating the endpoint
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, PORT);

            udpClient = new UdpClient(endPoint);

            Logger.LogAlways($"LoRaWAN Simulator started on port {PORT}");

            // send first sync
            _ = Task.Factory.StartNew(async() =>
            {
                while (true)
                {
                    var sync = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PULL_DATA, null);
                    await UdpSendMessage(sync.GetSyncHeader(mac));
                    await Task.Delay(10000);
                }
            });

            // Reading the test configuration
            string json = System.IO.File.ReadAllText(@"testconfig.json");

            var theObjects = JsonConvert.DeserializeObject <JObject>(json);

            var rxpk = theObjects["rxpk"];

            this.gateway = new GatewayDevice(rxpk.ToString());

            var devices = theObjects["Devices"];

            // TODO: Need to create simulated devices
            foreach (var device in devices)
            {
                SimulatedDevice simulated = new SimulatedDevice(device.ToString());
                this.listDevices.Add(simulated);

                // create a new thread that will post content
                _ = Task.Factory.StartNew(async() =>
                {
                    simulated.dt = DateTimeOffset.Now;

                    while (true)
                    {
                        Random random = new Random();
                        var rand      = (random.NextDouble() - 0.5) * simulated.RandomInterval;
                        if (simulated.dt.AddSeconds(simulated.Interval + rand) < DateTimeOffset.Now)
                        {
                            // simulated.dt = DateTimeOffset.Now;
                            // check if the device is part of a group
                            // if yes, then find all the devices and make the loop all together
                            // send all the messages together
                            simulated.dt = DateTimeOffset.Now;
                            List <SimulatedDevice> devgroup = new List <SimulatedDevice>();
                            if (simulated.GroupRxpk != 0)
                            {
                                devgroup = this.listDevices.Where(x => x.GroupRxpk == simulated.GroupRxpk).ToList();
                            }
                            else
                            {
                                devgroup.Add(simulated);
                            }

                            // Debug.WriteLine(JsonConvert.SerializeObject(devgroup));
                            var msg          = "{\"rxpk\":[";
                            var devicetosend = string.Empty;

                            foreach (var simul in devgroup)
                            {
                                byte[] tosend;
                                simul.LastPayload = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null);

                                if (simul.LoRaDevice.IsJoined)
                                {
                                    tosend = simul.GetUnconfirmedDataUpMessage();
                                }
                                else
                                {
                                    // simulated.LastPayload = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null);
                                    // var header = simulated.LastPayload.GetSyncHeader(mac);
                                    tosend = simul.GetJoinRequest();

                                    // var rxpkgateway = gateway.GetMessage(tosend);
                                    //    var msg = "{\"rxpk\":[" + rxpkgateway + "]}";
                                    //    var gat = Encoding.Default.GetBytes(msg);
                                    //    byte[] data = new byte[header.Length + gat.Length];
                                    //    Array.Copy(header, data, header.Length);
                                    //    Array.Copy(gat, 0, data, header.Length, gat.Length);
                                    //    await UdpSendMessage(data);
                                }
                                var rxpkgateway = this.gateway.GetMessage(tosend);

                                msg          += rxpkgateway + ",";
                                devicetosend += simul.LoRaDevice.DevEUI + ",";
                                simul.dt      = DateTimeOffset.Now;
                            }
                            byte[] header = simulated.LastPayload.GetSyncHeader(mac);

                            // get rid of the the last ","
                            msg          = msg.Substring(0, msg.Length - 1);
                            msg         += "]}";
                            devicetosend = devicetosend.Substring(0, devicetosend.Length - 1);
                            var gat      = Encoding.Default.GetBytes(msg);
                            byte[] data  = new byte[header.Length + gat.Length];
                            Array.Copy(header, data, header.Length);
                            Array.Copy(gat, 0, data, header.Length, gat.Length);

                            await UdpSendMessage(data);
                            Logger.LogAlways(devicetosend, $"Sending data: {BitConverter.ToString(header).Replace("-", string.Empty)}{Encoding.Default.GetString(gat)}");
                        }
                    }
                });
            }

            // TODO: Need to start this in a thread
            await this.RunUdpListener();
        }
        private async Task <byte[]> ProcessLoraMessage(LoRaMessage loraMessage)
        {
            bool validFrameCounter = false;

            byte[]  udpMsgForPktForwarder = new byte[0];
            string  devAddr = BitConverter.ToString(loraMessage.payloadMessage.devAddr).Replace("-", "");
            Message c2dMsg  = null;



            Cache.TryGetValue(devAddr, out LoraDeviceInfo loraDeviceInfo);



            if (loraDeviceInfo == null)
            {
                loraDeviceInfo = await LoraDeviceInfoManager.GetLoraDeviceInfoAsync(devAddr);

                Logger.Log(loraDeviceInfo.DevEUI, $"processing message, device not in cache", Logger.LoggingLevel.Info);

                Cache.AddToCache(devAddr, loraDeviceInfo);
            }
            else
            {
                Logger.Log(loraDeviceInfo.DevEUI, $"processing message, device in cache", Logger.LoggingLevel.Info);
            }



            if (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) || loraDeviceInfo.GatewayID.ToUpper() == GatewayID.ToUpper())
                {
                    if (loraMessage.CheckMic(loraDeviceInfo.NwkSKey))
                    {
                        if (loraDeviceInfo.HubSender == null)
                        {
                            loraDeviceInfo.HubSender = new IoTHubSender(loraDeviceInfo.DevEUI, loraDeviceInfo.PrimaryKey);
                        }


                        UInt16 fcntup = BitConverter.ToUInt16(((LoRaPayloadStandardData)loraMessage.payloadMessage).fcnt, 0);


                        //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 == 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);
                            }

                            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 = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0];


                            dynamic fullPayload = JObject.FromObject(rxPk);

                            string jsonDataPayload = "";

                            uint fportUp = (uint)((LoRaPayloadStandardData)loraMessage.payloadMessage).fport[0];

                            fullPayload.port = fportUp;

                            fullPayload.fcnt = fcntup;

                            if (String.IsNullOrEmpty(loraDeviceInfo.SensorDecoder))
                            {
                                jsonDataPayload  = Convert.ToBase64String(decryptedMessage);
                                fullPayload.data = jsonDataPayload;
                            }
                            else
                            {
                                Logger.Log(loraDeviceInfo.DevEUI, $"decoding with: {loraDeviceInfo.SensorDecoder} port: {fportUp}", Logger.LoggingLevel.Info);
                                jsonDataPayload  = LoraDecoders.DecodeMessage(decryptedMessage, fportUp, loraDeviceInfo.SensorDecoder);
                                fullPayload.data = JObject.Parse(jsonDataPayload);
                            }


                            fullPayload.eui       = loraDeviceInfo.DevEUI;
                            fullPayload.gatewayid = GatewayID;

                            //todo check what the other ts are if milliseconds or seconds
                            fullPayload.edgets = (long)((startTimeProcessing - new DateTime(1970, 1, 1)).TotalMilliseconds);


                            string iotHubMsg = fullPayload.ToString(Newtonsoft.Json.Formatting.None);


                            await loraDeviceInfo.HubSender.SendMessageAsync(iotHubMsg);

                            Logger.Log(loraDeviceInfo.DevEUI, $"sent message '{jsonDataPayload}' 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);
                        }

                        //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(20));

                        byte[] bytesC2dMsg = null;
                        byte[] fport       = null;
                        byte[] fctl        = new byte[1] {
                            32
                        };

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

                            if (secondC2dMsg != null)
                            {
                                //put it back to the queue for the next pickup
                                _ = loraDeviceInfo.HubSender.AbandonAsync(secondC2dMsg);

                                //set the fpending flag so the lora device will call us back for the next message
                                fctl = new byte[1] {
                                    48
                                };
                            }

                            bytesC2dMsg = c2dMsg.GetBytes();


                            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)
                        {
                            //check if we are not too late for the 1 and 2 window
                            if (((DateTime.UtcNow - startTimeProcessing) <= TimeSpan.FromMilliseconds(1900)))
                            {
                                //increase the fcnt down and save it to iot hub twins
                                loraDeviceInfo.FCntDown++;

                                Logger.Log(loraDeviceInfo.DevEUI, $"down frame counter: {loraDeviceInfo.FCntDown}", Logger.LoggingLevel.Info);


                                //Saving both fcnts to twins
                                _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, loraDeviceInfo.FCntDown);

                                var _datr = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].datr;

                                uint _rfch = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].rfch;

                                double _freq = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].freq;

                                uint txDelay = 0;


                                //if we are already longer than 900 mssecond move to the 2 second window
                                //uncomment to force second windows usage
                                //Thread.Sleep(901);
                                if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(900))
                                {
                                    if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RX2_DATR")))
                                    {
                                        Logger.Log(loraDeviceInfo.DevEUI, $"using standard second receive windows", Logger.LoggingLevel.Info);

                                        //using EU fix DR for RX2
                                        _freq = 869.525;
                                        _datr = "SF12BW125";
                                    }
                                    //if specific twins are set, specify second channel to be as specified
                                    else
                                    {
                                        _freq = double.Parse(Environment.GetEnvironmentVariable("RX2_FREQ"));
                                        _datr = Environment.GetEnvironmentVariable("RX2_DATR");
                                        Logger.Log(loraDeviceInfo.DevEUI, $"using custom DR second receive windows freq : {_freq}, datr:{_datr}", Logger.LoggingLevel.Info);
                                    }

                                    txDelay = 1000000;
                                }


                                long _tmst = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].tmst + txDelay;


                                Byte[] devAddrCorrect = new byte[4];
                                Array.Copy(loraMessage.payloadMessage.devAddr, devAddrCorrect, 4);
                                Array.Reverse(devAddrCorrect);

                                //todo mik check what is the A0
                                LoRaPayloadStandardData ackLoRaMessage = new LoRaPayloadStandardData(StringToByteArray("A0"),
                                                                                                     devAddrCorrect,
                                                                                                     fctl,
                                                                                                     BitConverter.GetBytes(loraDeviceInfo.FCntDown),
                                                                                                     null,
                                                                                                     fport,
                                                                                                     bytesC2dMsg,
                                                                                                     1);


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



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

                                //todo ronnie should check the device twin preference if using confirmed or unconfirmed down
                                LoRaMessage ackMessage = new LoRaMessage(ackLoRaMessage, LoRaMessageType.ConfirmedDataDown, rndToken, _datr, 0, _freq, _tmst);

                                udpMsgForPktForwarder = ackMessage.physicalPayload.GetMessage();


                                //confirm the message to iot hub only if we are in time for a delivery
                                if (c2dMsg != null)
                                {
                                    _ = loraDeviceInfo.HubSender.CompleteAsync(c2dMsg);
                                    Logger.Log(loraDeviceInfo.DevEUI, $"complete the c2d msg to IoT Hub", Logger.LoggingLevel.Info);
                                }
                            }
                            else
                            {
                                PhysicalPayload pushAck = new PhysicalPayload(loraMessage.physicalPayload.token, PhysicalIdentifier.PUSH_ACK, null);
                                udpMsgForPktForwarder = pushAck.GetMessage();


                                _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, null);


                                //put back the c2d message to the queue for the next round
                                _ = loraDeviceInfo.HubSender.AbandonAsync(c2dMsg);

                                Logger.Log(loraDeviceInfo.DevEUI, $"too late for down message, sending only ACK to gateway", Logger.LoggingLevel.Info);
                            }
                        }
                        //No ack requested and no c2d message we send the udp ack only to the gateway
                        else if (loraMessage.loRaMessageType == LoRaMessageType.UnconfirmedDataUp && c2dMsg == null)
                        {
                            PhysicalPayload pushAck = new PhysicalPayload(loraMessage.physicalPayload.token, PhysicalIdentifier.PUSH_ACK, null);
                            udpMsgForPktForwarder = pushAck.GetMessage();



                            if (validFrameCounter)
                            {
                                _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, null);
                            }
                        }
                    }
                    else
                    {
                        Logger.Log(loraDeviceInfo.DevEUI, $"with devAddr {devAddr} check MIC failed. Device will be ignored from now on", Logger.LoggingLevel.Info);
                        loraDeviceInfo.IsOurDevice = false;
                    }
                }
                else
                {
                    Logger.Log(loraDeviceInfo.DevEUI, $"ignore message because is not linked to this GatewayID", Logger.LoggingLevel.Info);
                }
            }
            else
            {
                Logger.Log(devAddr, $"device with devAddr {devAddr} is not our device, ignore message", Logger.LoggingLevel.Info);
            }

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

            return(udpMsgForPktForwarder);
        }
        private async Task <byte[]> ProcessJoinRequest(LoRaMessage loraMessage)
        {
            byte[] udpMsgForPktForwarder = new Byte[0];

            LoraDeviceInfo joinLoraDeviceInfo;

            var joinReq = (LoRaPayloadJoinRequest)loraMessage.payloadMessage;

            Array.Reverse(joinReq.devEUI);
            Array.Reverse(joinReq.appEUI);


            string devEui   = BitConverter.ToString(joinReq.devEUI).Replace("-", "");
            string devNonce = BitConverter.ToString(joinReq.devNonce).Replace("-", "");

            Logger.Log(devEui, $"join request received", Logger.LoggingLevel.Info);

            //checking if this devnonce was already processed or the deveui was already refused
            Cache.TryGetValue(devEui, out joinLoraDeviceInfo);


            //we have a join request in the cache
            if (joinLoraDeviceInfo != null)
            {
                //it is not our device so ingore the join
                if (!joinLoraDeviceInfo.IsOurDevice)
                {
                    Logger.Log(devEui, $"join request refused the device is not ours", Logger.LoggingLevel.Info);
                    return(null);
                }
                //is our device but the join was not valid
                else if (!joinLoraDeviceInfo.IsJoinValid)
                {
                    //if the devNonce is equal to the current it is a potential replay attck
                    if (joinLoraDeviceInfo.DevNonce == devNonce)
                    {
                        Logger.Log(devEui, $"join request refused devNonce already used", Logger.LoggingLevel.Info);
                        return(null);
                    }

                    //Check if the device is trying to join through the wrong gateway
                    if (!String.IsNullOrEmpty(joinLoraDeviceInfo.GatewayID) && joinLoraDeviceInfo.GatewayID.ToUpper() != GatewayID.ToUpper())
                    {
                        Logger.Log(devEui, $"trying to join not through its linked gateway, ignoring join request", Logger.LoggingLevel.Info);
                        return(null);
                    }
                }
            }



            joinLoraDeviceInfo = await LoraDeviceInfoManager.PerformOTAAAsync(GatewayID, devEui, BitConverter.ToString(joinReq.appEUI).Replace("-", ""), devNonce);

            if (joinLoraDeviceInfo.IsJoinValid)
            {
                byte[] appNonce = StringToByteArray(joinLoraDeviceInfo.AppNonce);

                byte[] netId = StringToByteArray(joinLoraDeviceInfo.NetId);



                byte[] devAddr = StringToByteArray(joinLoraDeviceInfo.DevAddr);

                string appKey = joinLoraDeviceInfo.AppKey;

                Array.Reverse(netId);
                Array.Reverse(appNonce);

                LoRaPayloadJoinAccept loRaPayloadJoinAccept = new LoRaPayloadJoinAccept(
                    //NETID 0 / 1 is default test
                    BitConverter.ToString(netId).Replace("-", ""),
                    //todo add app key management
                    appKey,
                    //todo add device address management
                    devAddr,
                    appNonce
                    );

                var _datr = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].datr;

                uint _rfch = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].rfch;

                double _freq = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].freq;
                //set tmst for the normal case
                long _tmst = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].tmst + 5000000;

                //uncomment to force second windows usage
                //Thread.Sleep(4600-(int)(DateTime.Now - startTimeProcessing).TotalMilliseconds);
                //in this case it's too late, we need to break
                if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(6000))
                {
                    Logger.Log(devEui, $"processing of the join request took too long, sending no message", Logger.LoggingLevel.Info);
                    var physicalResponse = new PhysicalPayload(loraMessage.physicalPayload.token, PhysicalIdentifier.PULL_RESP, null);

                    return(physicalResponse.GetMessage());
                }
                //in this case the second join windows must be used
                else if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(4500))
                {
                    Logger.Log(devEui, $"processing of the join request took too long, using second join accept receive window", Logger.LoggingLevel.Info);
                    _tmst = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].tmst + 6000000;
                    if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RX2_DATR")))
                    {
                        Logger.Log(devEui, $"using standard second receive windows for join request", Logger.LoggingLevel.Info);

                        //using EU fix DR for RX2
                        _freq = 869.525;
                        _datr = "SF12BW125";
                    }
                    //if specific twins are set, specify second channel to be as specified
                    else
                    {
                        Logger.Log(devEui, $"using custom DR second receive windows for join request", Logger.LoggingLevel.Info);

                        _freq = double.Parse(Environment.GetEnvironmentVariable("RX2_FREQ"));
                        _datr = Environment.GetEnvironmentVariable("RX2_DATR");
                    }
                }


                LoRaMessage joinAcceptMessage = new LoRaMessage(loRaPayloadJoinAccept, LoRaMessageType.JoinAccept, loraMessage.physicalPayload.token, _datr, 0, _freq, _tmst);

                udpMsgForPktForwarder = joinAcceptMessage.physicalPayload.GetMessage();


                joinLoraDeviceInfo.HubSender = new IoTHubSender(joinLoraDeviceInfo.DevEUI, joinLoraDeviceInfo.PrimaryKey);

                //open the connection to iot hub without waiting for perf optimization in case of the device start sending a msg just after join request
                _ = joinLoraDeviceInfo.HubSender.OpenAsync();

                //join request resets the frame counters
                joinLoraDeviceInfo.FCntUp   = 0;
                joinLoraDeviceInfo.FCntDown = 0;


                //update the frame counter on the server
                _ = joinLoraDeviceInfo.HubSender.UpdateFcntAsync(joinLoraDeviceInfo.FCntUp, joinLoraDeviceInfo.FCntDown);



                //add to cache for processing normal messages. This awoids one additional call to the server.
                Cache.AddToCache(joinLoraDeviceInfo.DevAddr, joinLoraDeviceInfo);

                Logger.Log(devEui, $"join accept sent", Logger.LoggingLevel.Info);
            }

            //add to cache to avoid replay attack, btw server side does the check too.
            Cache.AddToCache(devEui, joinLoraDeviceInfo);

            return(udpMsgForPktForwarder);
        }
Beispiel #22
0
        /// <summary>
        /// This contructor is used in case of uplink message, hence we don't know the message type yet
        /// </summary>
        /// <param name="inputMessage"></param>
        public LoRaMessageWrapper(byte[] inputMessage, bool server = false, string AppKey = "")
        {
            // packet normally sent by the gateway as heartbeat. TODO find more elegant way to integrate.
            PhysicalPayload = new PhysicalPayload(inputMessage, server);
            if (PhysicalPayload.message != null)
            {
                var payload = Encoding.Default.GetString(PhysicalPayload.message);

                // todo ronnie implement a better logging by message type
                if (!payload.StartsWith("{\"stat"))
                {
                    Logger.Log($"Physical dataUp {payload}", Logger.LoggingLevel.Full);

                    // Deserialized for uplink messages
                    var payloadObject = JsonConvert.DeserializeObject <UplinkPktFwdMessage>(payload);
                    PktFwdPayload = payloadObject;
                    // set up the parts of the raw message
                    // status message
                    if (PktFwdPayload != null)
                    {
                        // if there is no packet, then it maybe a downlink message
                        if (PktFwdPayload.GetPktFwdMessage().Rxpks.Count > 0)
                        {
                            if (PktFwdPayload.GetPktFwdMessage().Rxpks[0].data != null)
                            {
                                byte[] convertedInputMessage = Convert.FromBase64String(PktFwdPayload.GetPktFwdMessage().Rxpks[0].data);
                                var    messageType           = convertedInputMessage[0] >> 5;
                                LoRaMessageType = (LoRaMessageType)messageType;
                                // Uplink Message
                                if (messageType == (int)LoRaMessageType.UnconfirmedDataUp)
                                {
                                    LoRaPayloadMessage = new LoRaPayloadData(convertedInputMessage);
                                }
                                else if (messageType == (int)LoRaMessageType.ConfirmedDataUp)
                                {
                                    LoRaPayloadMessage = new LoRaPayloadData(convertedInputMessage);
                                }
                                else if (messageType == (int)LoRaMessageType.JoinRequest)
                                {
                                    LoRaPayloadMessage = new LoRaPayloadJoinRequest(convertedInputMessage);
                                }

                                IsLoRaMessage = true;
                            }
                        }
                        else
                        {
                            // deselrialize for a downlink message
                            var payloadDownObject = JsonConvert.DeserializeObject <DownlinkPktFwdMessage>(payload);
                            if (payloadDownObject != null)
                            {
                                if (payloadDownObject.txpk != null)
                                {
                                    // if we have data, it is a downlink message
                                    if (payloadDownObject.txpk.data != null)
                                    {
                                        byte[] convertedInputMessage = Convert.FromBase64String(payloadDownObject.txpk.data);
                                        var    messageType           = convertedInputMessage[0] >> 5;
                                        LoRaMessageType = (LoRaMessageType)messageType;
                                        if (messageType == (int)LoRaMessageType.JoinAccept)
                                        {
                                            LoRaPayloadMessage = new LoRaPayloadJoinAccept(convertedInputMessage, AppKey);
                                        }
                                        IsLoRaMessage = true;
                                    }
                                }
                                else
                                {
                                    Logger.Log("Error: " + payload, Logger.LoggingLevel.Full);
                                }
                            }
                        }
                    }
                }
                else
                {
                    Logger.Log($"Statistic: {payload}", Logger.LoggingLevel.Full);
                    IsLoRaMessage = false;
                }
            }
            else
            {
                IsLoRaMessage = false;
            }
        }