public void TestJoinAccept()
        {
            byte[] AppNonce = new byte[3] {
                87, 11, 199
            };
            byte[] NetId = new byte[3] {
                34, 17, 1
            };
            byte[] DevAddr = new byte[4] {
                2, 3, 25, 128
            };
            var netId = ConversionHelper.ByteArrayToString(NetId);
            LoRaPayloadJoinAccept joinAccept = new LoRaPayloadJoinAccept(netId, "00112233445566778899AABBCCDDEEFF", DevAddr, AppNonce, new byte[] { 0 }, new byte[] { 0 }, null);

            Console.WriteLine(BitConverter.ToString(joinAccept.GetByteMessage()));
            LoRaMessageWrapper joinAcceptMessage = new LoRaMessageWrapper(joinAccept, LoRaMessageType.JoinAccept, new byte[] { 0x01 });

            byte[] joinAcceptMic = new byte[4] {
                67, 72, 91, 188
            };

            Assert.True((((LoRaPayloadJoinAccept)joinAcceptMessage.LoRaPayloadMessage).Mic.ToArray().SequenceEqual(joinAcceptMic)));

            var msg = ConversionHelper.ByteArrayToString(joinAcceptMessage.LoRaPayloadMessage.GetByteMessage());

            Assert.Equal("20493EEB51FBA2116F810EDB3742975142", msg);
        }
예제 #2
0
        public void TestJoinAccept()
        {
            byte[] AppNonce = new byte[3] {
                87, 11, 199
            };
            byte[] NetId = new byte[3] {
                34, 17, 1
            };
            byte[] DevAddr = new byte[4] {
                2, 3, 25, 128
            };
            var netId = BitConverter.ToString(NetId).Replace("-", "");
            LoRaPayloadJoinAccept joinAccept = new LoRaPayloadJoinAccept(netId, "00112233445566778899AABBCCDDEEFF", DevAddr, AppNonce);

            Console.WriteLine(BitConverter.ToString(joinAccept.ToMessage()));
            LoRaMessage joinAcceptMessage = new LoRaMessage(joinAccept, LoRaMessageType.JoinAccept, new byte[] { 0x01 });

            byte[] joinAcceptMic = new byte[4] {
                67, 72, 91, 188
            };
            Assert.True((((LoRaPayloadJoinAccept)joinAcceptMessage.payloadMessage).mic.ToArray().SequenceEqual(joinAcceptMic)));

            var msg = BitConverter.ToString(((LoRaPayloadJoinAccept)joinAcceptMessage.payloadMessage).ToMessage()).Replace("-", String.Empty);

            Assert.Equal("20493EEB51FBA2116F810EDB3742975142", msg);
        }
예제 #3
0
        public void TestJoinAccept()
        {
            byte[] appNonce = new byte[3]
            {
                87, 11, 199,
            };
            byte[] netId1 = new byte[3]
            {
                34, 17, 1,
            };
            byte[] devAddr = new byte[4]
            {
                2, 3, 25, 128,
            };

            var netId  = ConversionHelper.ByteArrayToString(netId1);
            var appkey = "00112233445566778899AABBCCDDEEFF";
            LoRaPayloadJoinAccept joinAccept = new LoRaPayloadJoinAccept(netId, devAddr, appNonce, new byte[] { 0 }, 0, null);
            var joinacceptbyte    = joinAccept.Serialize(appkey, "SF10BW125", 866.349812, 10000, "test");
            var decodedJoinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(joinacceptbyte.Txpk.Data), appkey);

            byte[] joinAcceptMic = new byte[4]
            {
                67, 72, 91, 188,
            };

            Assert.True(decodedJoinAccept.Mic.ToArray().SequenceEqual(joinAcceptMic));
            var msg = ConversionHelper.ByteArrayToString(Convert.FromBase64String(joinacceptbyte.Txpk.Data));

            Assert.Equal("20493EEB51FBA2116F810EDB3742975142", msg);
        }
예제 #4
0
        public void TestJoinAccept()
        {
            byte[] appNonce = new byte[3]
            {
                87, 11, 199,
            };
            byte[] netId1 = new byte[3]
            {
                34, 17, 1,
            };
            byte[] devAddr = new byte[4]
            {
                2, 3, 25, 128,
            };

            var netId = ConversionHelper.ByteArrayToString(netId1);
            LoRaPayloadJoinAccept joinAccept = new LoRaPayloadJoinAccept(netId, "00112233445566778899AABBCCDDEEFF", devAddr, appNonce, new byte[] { 0 }, new byte[] { 0 }, null);

            Console.WriteLine(BitConverter.ToString(joinAccept.GetByteMessage()));

            byte[] joinAcceptMic = new byte[4]
            {
                67, 72, 91, 188,
            };

            Assert.True(joinAccept.Mic.ToArray().SequenceEqual(joinAcceptMic));
            var msg = ConversionHelper.ByteArrayToString(joinAccept.GetByteMessage());

            Assert.Equal("20493EEB51FBA2116F810EDB3742975142", msg);
        }
예제 #5
0
        public async Task When_Getting_DLSettings_From_Twin_Returns_JoinAccept_With_Correct_Settings(int rx1DROffset, int rx2datarate)
        {
            string deviceGatewayID = ServerGatewayID;
            var    simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var    joinRequest     = simulatedDevice.CreateJoinRequest();

            // Create Rxpk
            var rxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];

            var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce);
            var devAddr  = string.Empty;
            var devEUI   = simulatedDevice.LoRaDevice.DeviceID;
            var appEUI   = simulatedDevice.LoRaDevice.AppEUI;

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEUI;
            twin.Properties.Desired[TwinProperty.AppEUI]        = simulatedDevice.LoRaDevice.AppEUI;
            twin.Properties.Desired[TwinProperty.AppKey]        = simulatedDevice.LoRaDevice.AppKey;
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            twin.Properties.Desired[TwinProperty.RX1DROffset]   = rx1DROffset;
            twin.Properties.Desired[TwinProperty.RX2DataRate]   = rx2datarate;

            this.LoRaDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(twin);

            this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>()))
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui,
            this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, devNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList()));

            var loRaDeviceFactory = new TestLoRaDeviceFactory(this.LoRaDeviceClient.Object);

            var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, this.LoRaDeviceApi.Object, this.LoRaDeviceFactory);

            // Send to message processor
            var messageProcessor = new MessageDispatcher(
                this.ServerConfiguration,
                deviceRegistry,
                this.FrameCounterUpdateStrategyProvider);

            var request = this.CreateWaitableRequest(rxpk);

            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.NotNull(request.ResponseDownlink);
            Assert.Single(this.PacketForwarder.DownlinkMessages);
            var downlinkMessage = this.PacketForwarder.DownlinkMessages[0];
            var joinAccept      = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey);

            joinAccept.DlSettings.Span.Reverse();
            Assert.Equal(rx1DROffset, joinAccept.Rx1DrOffset);
            Assert.Equal(rx2datarate, joinAccept.Rx2Dr);
        }
        public async Task When_Getting_DLSettings_From_Twin_Returns_JoinAccept_With_Correct_Settings(int rx1DROffset, DataRateIndex rx2datarate)
        {
            var deviceGatewayID = ServerGatewayID;
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequest     = simulatedDevice.CreateJoinRequest();

            var devAddr = (DevAddr?)null;
            var devEui  = simulatedDevice.LoRaDevice.DevEui;

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEui.ToString();
            twin.Properties.Desired[TwinProperty.AppEui]        = simulatedDevice.LoRaDevice.AppEui?.ToString();
            twin.Properties.Desired[TwinProperty.AppKey]        = simulatedDevice.LoRaDevice.AppKey?.ToString();
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            twin.Properties.Desired[TwinProperty.RX1DROffset]   = rx1DROffset;
            twin.Properties.Desired[TwinProperty.RX2DataRate]   = rx2datarate;

            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)).ReturnsAsync(twin);

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui,
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequest.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), joinRequest);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.NotNull(request.ResponseDownlink);
            Assert.Single(DownstreamMessageSender.DownlinkMessages);
            var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0];
            var joinAccept      = new LoRaPayloadJoinAccept(downlinkMessage.Data, simulatedDevice.LoRaDevice.AppKey.Value);

            Assert.Equal(rx1DROffset, joinAccept.Rx1DrOffset);
            Assert.Equal(rx2datarate, joinAccept.Rx2Dr);
        }
예제 #7
0
        // Performs join
        public async Task <bool> JoinAsync(SimulatedPacketForwarder packetForwarder, int timeoutInMs = 30 * 1000)
        {
            if (this.IsJoined)
            {
                return(true);
            }

            var token = await RandomTokenGenerator.GetTokenAsync();

            this.LastPayload = new PhysicalPayload(token, PhysicalIdentifier.PUSH_DATA, null);
            var header = this.LastPayload.GetSyncHeader(packetForwarder.MacAddress);

            var joinRequest   = this.CreateJoinRequest();
            var joinCompleted = new SemaphoreSlim(0);

            var joinRequestUplinkMessage = joinRequest.SerializeUplink(this.AppKey);

            packetForwarder.SubscribeOnce((response) =>
            {
                // handle join
                var txpk = Txpk.CreateTxpk(response, this.LoRaDevice.AppKey);
                byte[] convertedInputMessage = Convert.FromBase64String(txpk.Data);

                var joinAccept = new LoRaPayloadJoinAccept(convertedInputMessage, this.AppKey);

                var result = this.HandleJoinAccept(joinAccept); // may need to return bool and only release if true.
                joinCompleted.Release();

                return(result);
            });

            await packetForwarder.SendAsync(header, joinRequest.GetByteMessage());

            TestLogger.Log($"[{this.LoRaDevice.DeviceID}] Join request: {BitConverter.ToString(header).Replace("-", string.Empty)}");

#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                timeoutInMs = 60 * 1000;
            }
#endif

            return(await joinCompleted.WaitAsync(timeoutInMs));
        }
예제 #8
0
        bool HandleJoinAccept(LoRaPayloadJoinAccept payload)
        {
            try
            {
                // Calculate the keys
                var netid = payload.NetID.ToArray();
                Array.Reverse(netid);
                var appNonce = payload.AppNonce.ToArray();
                Array.Reverse(appNonce);
                var devNonce = ConversionHelper.StringToByteArray(this.DevNonce);
                Array.Reverse(devNonce);
                var deviceAppKey = ConversionHelper.StringToByteArray(this.LoRaDevice.AppKey);
                var appSKey      = payload.CalculateKey(LoRaPayloadKeyType.AppSKey, appNonce, netid, devNonce, deviceAppKey);
                var nwkSKey      = payload.CalculateKey(LoRaPayloadKeyType.NwkSkey, appNonce, netid, devNonce, deviceAppKey);
                var devAddr      = payload.DevAddr;

                // if mic check failed, return false

                /*
                 * if (!payload.CheckMic(BitConverter.ToString(nwkSKey).Replace("-", "")))
                 * {
                 *  return false;
                 * }
                 */

                this.LoRaDevice.AppSKey = BitConverter.ToString(appSKey).Replace("-", string.Empty);
                this.LoRaDevice.NwkSKey = BitConverter.ToString(nwkSKey).Replace("-", string.Empty);
                this.NetId              = BitConverter.ToString(netid).Replace("-", string.Empty);
                this.AppNonce           = BitConverter.ToString(appNonce).Replace("-", string.Empty);
                this.LoRaDevice.DevAddr = BitConverter.ToString(devAddr.ToArray()).Replace("-", string.Empty);

                return(true);
            }
            catch
            {
            }

            return(false);
        }
예제 #9
0
        public async Task When_Join_Fails_Due_To_GetTwin_Error_Second_Try_Should_Reload_Device_Twin(string deviceGatewayID)
        {
            var simulatedDevice     = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequestPayload1 = simulatedDevice.CreateJoinRequest();

            // Create Rxpk
            var joinRequestRxpk1 = joinRequestPayload1.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];

            var joinRequestDevNonce1 = ConversionHelper.ByteArrayToString(joinRequestPayload1.DevNonce);
            var devAddr = string.Empty;
            var devEUI  = simulatedDevice.LoRaDevice.DeviceID;
            var appEUI  = simulatedDevice.LoRaDevice.AppEUI;

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEUI;
            twin.Properties.Desired[TwinProperty.AppEUI]        = simulatedDevice.LoRaDevice.AppEUI;
            twin.Properties.Desired[TwinProperty.AppKey]        = simulatedDevice.LoRaDevice.AppKey;
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            this.LoRaDeviceClient.SetupSequence(x => x.GetTwinAsync())
            .ReturnsAsync((Twin)null)
            .ReturnsAsync(twin);

            // Device twin will be updated
            string afterJoinAppSKey = null;
            string afterJoinNwkSKey = null;
            string afterJoinDevAddr = null;

            this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>()))
            .Callback <TwinCollection>((updatedTwin) =>
            {
                afterJoinAppSKey = updatedTwin[TwinProperty.AppSKey];
                afterJoinNwkSKey = updatedTwin[TwinProperty.NwkSKey];
                afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr];
            })
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui,
            this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, joinRequestDevNonce1))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList()));

            // using factory to create mock of
            var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, this.NewMemoryCache(), this.LoRaDeviceApi.Object, this.LoRaDeviceFactory);

            var messageProcessor = new MessageDispatcher(
                this.ServerConfiguration,
                deviceRegistry,
                this.FrameCounterUpdateStrategyProvider);

            // 1st join request
            // Should fail
            var joinRequest1 = this.CreateWaitableRequest(joinRequestRxpk1);

            messageProcessor.DispatchRequest(joinRequest1);
            Assert.True(await joinRequest1.WaitCompleteAsync());
            Assert.True(joinRequest1.ProcessingFailed);
            Assert.Null(joinRequest1.ResponseDownlink);

            // 2nd attempt
            var joinRequestPayload2 = simulatedDevice.CreateJoinRequest();

            // will reload the device matched by deveui
            this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, ConversionHelper.ByteArrayToString(joinRequestPayload2.DevNonce)))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList()));

            var joinRequestRxpk2     = joinRequestPayload2.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];
            var joinRequestDevNonce2 = LoRaTools.Utils.ConversionHelper.ByteArrayToString(joinRequestPayload2.DevNonce);
            var joinRequest2         = this.CreateWaitableRequest(joinRequestRxpk2);

            messageProcessor.DispatchRequest(joinRequest2);
            Assert.True(await joinRequest2.WaitCompleteAsync());
            Assert.NotNull(joinRequest2.ResponseDownlink);
            Assert.True(joinRequest2.ProcessingSucceeded);
            Assert.Single(this.PacketForwarder.DownlinkMessages);
            var joinRequestDownlinkMessage2 = this.PacketForwarder.DownlinkMessages[0];
            var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(joinRequestDownlinkMessage2.Txpk.Data), simulatedDevice.LoRaDevice.AppKey);

            Assert.Equal(joinAccept.DevAddr.ToArray(), ConversionHelper.StringToByteArray(afterJoinDevAddr));

            var devicesForDevAddr = deviceRegistry.InternalGetCachedDevicesForDevAddr(afterJoinDevAddr);

            Assert.Single(devicesForDevAddr); // should have the single device
            Assert.True(devicesForDevAddr.TryGetValue(devEUI, out var loRaDevice));

            Assert.Equal(simulatedDevice.AppKey, loRaDevice.AppKey);
            Assert.Equal(simulatedDevice.AppEUI, loRaDevice.AppEUI);
            Assert.Equal(afterJoinAppSKey, loRaDevice.AppSKey);
            Assert.Equal(afterJoinNwkSKey, loRaDevice.NwkSKey);
            Assert.Equal(afterJoinDevAddr, loRaDevice.DevAddr);
            if (deviceGatewayID == null)
            {
                Assert.Null(loRaDevice.GatewayID);
            }
            else
            {
                Assert.Equal(deviceGatewayID, loRaDevice.GatewayID);
            }

            // fcnt is restarted
            Assert.Equal(0U, loRaDevice.FCntUp);
            Assert.Equal(0U, loRaDevice.FCntDown);
            Assert.False(loRaDevice.HasFrameCountChanges);

            // should get twin 2x (1st failed)
            this.LoRaDeviceClient.Verify(x => x.GetTwinAsync(), Times.Exactly(2));

            // should get device for join 2x
            this.LoRaDeviceApi.Verify(x => x.SearchAndLockForJoinAsync(ServerGatewayID, devEUI, appEUI, It.IsAny <string>()));

            this.LoRaDeviceClient.VerifyAll();
            this.LoRaDeviceApi.VerifyAll();
        }
예제 #10
0
        /// <summary>
        /// Process OTAA join request
        /// </summary>
        async Task ProcessJoinRequestAsync(LoRaRequest request)
        {
            LoRaDevice loRaDevice = null;
            string     devEUI     = null;
            var        loraRegion = request.Region;

            try
            {
                var timeWatcher = new LoRaOperationTimeWatcher(loraRegion, request.StartTime);

                var    joinReq = (LoRaPayloadJoinRequest)request.Payload;
                byte[] udpMsgForPktForwarder = new byte[0];

                devEUI = joinReq.GetDevEUIAsString();
                var appEUI = joinReq.GetAppEUIAsString();

                var devNonce = joinReq.GetDevNonceAsString();
                Logger.Log(devEUI, $"join request received", LogLevel.Information);

                loRaDevice = await this.deviceRegistry.GetDeviceForJoinRequestAsync(devEUI, appEUI, devNonce);

                if (loRaDevice == null)
                {
                    request.NotifyFailed(devEUI, LoRaDeviceRequestFailedReason.UnknownDevice);
                    // we do not log here as we assume that the deviceRegistry does a more informed logging if returning null
                    return;
                }

                if (string.IsNullOrEmpty(loRaDevice.AppKey))
                {
                    Logger.Log(loRaDevice.DevEUI, "join refused: missing AppKey for OTAA device", LogLevel.Error);
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidJoinRequest);
                    return;
                }

                if (loRaDevice.AppEUI != appEUI)
                {
                    Logger.Log(devEUI, "join refused: AppEUI for OTAA does not match device", LogLevel.Error);
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidJoinRequest);
                    return;
                }

                if (!joinReq.CheckMic(loRaDevice.AppKey))
                {
                    Logger.Log(devEUI, "join refused: invalid MIC", LogLevel.Error);
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.JoinMicCheckFailed);
                    return;
                }

                // Make sure that is a new request and not a replay
                if (!string.IsNullOrEmpty(loRaDevice.DevNonce) && loRaDevice.DevNonce == devNonce)
                {
                    if (string.IsNullOrEmpty(loRaDevice.GatewayID))
                    {
                        Logger.Log(devEUI, "join refused: join already processed by another gateway", LogLevel.Information);
                    }
                    else
                    {
                        Logger.Log(devEUI, "join refused: DevNonce already used by this device", LogLevel.Error);
                    }

                    loRaDevice.IsOurDevice = false;
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.JoinDevNonceAlreadyUsed);
                    return;
                }

                // Check that the device is joining through the linked gateway and not another
                if (!string.IsNullOrEmpty(loRaDevice.GatewayID) && !string.Equals(loRaDevice.GatewayID, this.configuration.GatewayID, StringComparison.InvariantCultureIgnoreCase))
                {
                    Logger.Log(devEUI, $"join refused: trying to join not through its linked gateway, ignoring join request", LogLevel.Information);
                    loRaDevice.IsOurDevice = false;
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.HandledByAnotherGateway);
                    return;
                }

                var netIdBytes = BitConverter.GetBytes(this.configuration.NetId);
                var netId      = new byte[3]
                {
                    netIdBytes[0],
                    netIdBytes[1],
                    netIdBytes[2]
                };

                var appNonce      = OTAAKeysGenerator.GetAppNonce();
                var appNonceBytes = LoRaTools.Utils.ConversionHelper.StringToByteArray(appNonce);
                var appKeyBytes   = LoRaTools.Utils.ConversionHelper.StringToByteArray(loRaDevice.AppKey);
                var appSKey       = OTAAKeysGenerator.CalculateKey(new byte[1] {
                    0x02
                }, appNonceBytes, netId, joinReq.DevNonce, appKeyBytes);
                var nwkSKey = OTAAKeysGenerator.CalculateKey(new byte[1] {
                    0x01
                }, appNonceBytes, netId, joinReq.DevNonce, appKeyBytes);
                var devAddr = OTAAKeysGenerator.GetNwkId(netId);

                var oldDevAddr = loRaDevice.DevAddr;

                if (!timeWatcher.InTimeForJoinAccept())
                {
                    // in this case it's too late, we need to break and avoid saving twins
                    Logger.Log(devEUI, $"join refused: processing of the join request took too long, sending no message", LogLevel.Information);
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.ReceiveWindowMissed);
                    return;
                }

                var updatedProperties = new LoRaDeviceJoinUpdateProperties
                {
                    DevAddr            = devAddr,
                    NwkSKey            = nwkSKey,
                    AppSKey            = appSKey,
                    AppNonce           = appNonce,
                    DevNonce           = devNonce,
                    NetID              = ConversionHelper.ByteArrayToString(netId),
                    Region             = request.Region.LoRaRegion,
                    PreferredGatewayID = this.configuration.GatewayID,
                };

                if (loRaDevice.ClassType == LoRaDeviceClassType.C)
                {
                    updatedProperties.SavePreferredGateway = true;
                    updatedProperties.SaveRegion           = true;
                }

                var deviceUpdateSucceeded = await loRaDevice.UpdateAfterJoinAsync(updatedProperties);

                if (!deviceUpdateSucceeded)
                {
                    Logger.Log(devEUI, $"join refused: join request could not save twin", LogLevel.Error);
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.ApplicationError);
                    return;
                }

                var windowToUse = timeWatcher.ResolveJoinAcceptWindowToUse(loRaDevice);
                if (windowToUse == Constants.INVALID_RECEIVE_WINDOW)
                {
                    Logger.Log(devEUI, $"join refused: processing of the join request took too long, sending no message", LogLevel.Information);
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.ReceiveWindowMissed);
                    return;
                }

                double freq = 0;
                string datr = null;
                uint   tmst = 0;
                if (windowToUse == Constants.RECEIVE_WINDOW_1)
                {
                    datr = loraRegion.GetDownstreamDR(request.Rxpk);
                    if (!loraRegion.TryGetUpstreamChannelFrequency(request.Rxpk, out freq) || datr == null)
                    {
                        Logger.Log(loRaDevice.DevEUI, "could not resolve DR and/or frequency for downstream", LogLevel.Error);
                        request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidRxpk);
                        return;
                    }

                    // set tmst for the normal case
                    tmst = request.Rxpk.Tmst + loraRegion.Join_accept_delay1 * 1000000;
                }
                else
                {
                    Logger.Log(devEUI, $"processing of the join request took too long, using second join accept receive window", LogLevel.Debug);
                    tmst = request.Rxpk.Tmst + loraRegion.Join_accept_delay2 * 1000000;

                    (freq, datr) = loraRegion.GetDownstreamRX2DRAndFreq(devEUI, this.configuration.Rx2DataRate, this.configuration.Rx2DataFrequency, null);
                }

                loRaDevice.IsOurDevice = true;
                this.deviceRegistry.UpdateDeviceAfterJoin(loRaDevice, oldDevAddr);

                // Build join accept downlink message
                Array.Reverse(netId);
                Array.Reverse(appNonceBytes);

                // Build the DlSettings fields that is a superposition of RX2DR and RX1DROffset field
                byte[] dlSettings = new byte[1];

                if (request.Region.DRtoConfiguration.ContainsKey(loRaDevice.DesiredRX2DataRate))
                {
                    dlSettings[0] =
                        (byte)(loRaDevice.DesiredRX2DataRate & 0b00001111);
                }
                else
                {
                    Logger.Log(devEUI, $"twin RX2 DR value is not within acceptable values", LogLevel.Error);
                }

                if (request.Region.IsValidRX1DROffset(loRaDevice.DesiredRX1DROffset))
                {
                    var rx1droffset = (byte)(loRaDevice.DesiredRX1DROffset << 4);
                    dlSettings[0] = (byte)(dlSettings[0] + rx1droffset);
                }
                else
                {
                    Logger.Log(devEUI, $"twin RX1 offset DR value is not within acceptable values", LogLevel.Error);
                }

                ushort rxDelay = 0;
                if (request.Region.IsValidRXDelay(loRaDevice.DesiredRXDelay))
                {
                    rxDelay = loRaDevice.DesiredRXDelay;
                }
                else
                {
                    Logger.Log(devEUI, $"twin RX delay value is not within acceptable values", LogLevel.Error);
                }

                var loRaPayloadJoinAccept = new LoRaPayloadJoinAccept(
                    LoRaTools.Utils.ConversionHelper.ByteArrayToString(netId), // NETID 0 / 1 is default test
                    ConversionHelper.StringToByteArray(devAddr),               // todo add device address management
                    appNonceBytes,
                    dlSettings,
                    rxDelay,
                    null);

                var joinAccept = loRaPayloadJoinAccept.Serialize(loRaDevice.AppKey, datr, freq, tmst, devEUI);
                if (joinAccept != null)
                {
                    _ = request.PacketForwarder.SendDownstreamAsync(joinAccept);
                    request.NotifySucceeded(loRaDevice, joinAccept);

                    if (Logger.LoggerLevel <= LogLevel.Debug)
                    {
                        var jsonMsg = JsonConvert.SerializeObject(joinAccept);
                        Logger.Log(devEUI, $"{LoRaMessageType.JoinAccept.ToString()} {jsonMsg}", LogLevel.Debug);
                    }
                    else if (Logger.LoggerLevel == LogLevel.Information)
                    {
                        Logger.Log(devEUI, "join accepted", LogLevel.Information);
                    }
                }
            }
            catch (Exception ex)
            {
                var deviceId = devEUI ?? ConversionHelper.ByteArrayToString(request.Payload.DevAddr);
                Logger.Log(deviceId, $"failed to handle join request. {ex.Message}", LogLevel.Error);
                request.NotifyFailed(loRaDevice, ex);
            }
        }
예제 #11
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);
        }
        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);
        }
예제 #13
0
        public async Task When_Join_Fails_Due_To_Timeout_Second_Try_Should_Reuse_Cached_Device_Twin(string deviceGatewayID)
        {
            var simulatedDevice     = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequestPayload1 = simulatedDevice.CreateJoinRequest();

            var devAddr = (DevAddr?)null;
            var devEui  = simulatedDevice.LoRaDevice.DevEui;

            // Device twin will be queried twice, 1st time will take 7 seconds, 2nd time 0.1 second
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI] = devEui.ToString();
            twin.Properties.Desired[TwinProperty.AppEui] = simulatedDevice.LoRaDevice.AppEui?.ToString();
            twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey?.ToString();
            if (deviceGatewayID != null)
            {
                twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID;
            }
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None))
            .ReturnsAsync(twin);

            // Device twin will be updated
            AppSessionKey?    afterJoinAppSKey = null;
            NetworkSessionKey?afterJoinNwkSKey = null;
            string            afterJoinDevAddr = null;

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .Callback <TwinCollection, CancellationToken>((updatedTwin, _) =>
            {
                afterJoinAppSKey = AppSessionKey.Parse(updatedTwin[TwinProperty.AppSKey].Value);
                afterJoinNwkSKey = NetworkSessionKey.Parse(updatedTwin[TwinProperty.NwkSKey].Value);
                afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr];
            })
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui,
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload1.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // 1st join request
            // Should fail
            using var joinRequest1 = CreateWaitableRequest(joinRequestPayload1, constantElapsedTime: TimeSpan.FromSeconds(7));
            messageProcessor.DispatchRequest(joinRequest1);
            Assert.True(await joinRequest1.WaitCompleteAsync());
            Assert.True(joinRequest1.ProcessingFailed);
            Assert.Null(joinRequest1.ResponseDownlink);
            Assert.Equal(LoRaDeviceRequestFailedReason.ReceiveWindowMissed, joinRequest1.ProcessingFailedReason);

            // 2nd attempt
            var joinRequestPayload2 = simulatedDevice.CreateJoinRequest();

            // setup response to this device search
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload2.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            using var joinRequest2 = CreateWaitableRequest(joinRequestPayload2);
            messageProcessor.DispatchRequest(joinRequest2);
            Assert.True(await joinRequest2.WaitCompleteAsync());
            Assert.True(joinRequest2.ProcessingSucceeded);
            Assert.NotNull(joinRequest2.ResponseDownlink);
            Assert.Single(DownstreamMessageSender.DownlinkMessages);
            var joinRequestDownlinkMessage = DownstreamMessageSender.DownlinkMessages[0];
            var joinAccept = new LoRaPayloadJoinAccept(joinRequestDownlinkMessage.Data, simulatedDevice.LoRaDevice.AppKey.Value);

            Assert.Equal(joinAccept.DevAddr.ToString(), afterJoinDevAddr);

            Assert.True(DeviceCache.TryGetByDevEui(devEui, out var loRaDevice));

            Assert.Equal(simulatedDevice.AppKey, loRaDevice.AppKey);
            Assert.Equal(simulatedDevice.AppEui, loRaDevice.AppEui);
            Assert.Equal(afterJoinAppSKey, loRaDevice.AppSKey);
            Assert.Equal(afterJoinNwkSKey, loRaDevice.NwkSKey);
            Assert.Equal(joinAccept.DevAddr, loRaDevice.DevAddr);
            if (deviceGatewayID == null)
            {
                Assert.Null(loRaDevice.GatewayID);
            }
            else
            {
                Assert.Equal(deviceGatewayID, loRaDevice.GatewayID);
            }

            // fcnt is restarted
            Assert.Equal(0U, loRaDevice.FCntUp);
            Assert.Equal(0U, loRaDevice.FCntDown);
            Assert.False(loRaDevice.HasFrameCountChanges);

            // searching the device should happen twice
            LoRaDeviceApi.Verify(x => x.SearchAndLockForJoinAsync(ServerGatewayID, devEui, It.IsAny <DevNonce>()), Times.Exactly(2));

            // getting the device twin should happens once
            LoRaDeviceClient.Verify(x => x.GetTwinAsync(CancellationToken.None), Times.Once());

            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
예제 #14
0
        public async Task When_Device_Is_Found_In_Api_Should_Update_Twin_And_Return()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: this.ServerConfiguration.GatewayID));

            simulatedDevice.LoRaDevice.NwkSKey = string.Empty;
            simulatedDevice.LoRaDevice.AppSKey = string.Empty;
            var joinRequest = simulatedDevice.CreateJoinRequest();

            // Create Rxpk
            var rxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];

            var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce);
            var devEUI   = simulatedDevice.LoRaDevice.DeviceID;
            var appEUI   = simulatedDevice.LoRaDevice.AppEUI;

            this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(It.IsNotNull <string>(), devEUI, appEUI, devNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(string.Empty, devEUI, "123").AsList()));

            // Ensure that the device twin was updated
            this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.Is <TwinCollection>((t) =>
                                                                                                    t.Contains(TwinProperty.DevAddr) && t.Contains(TwinProperty.FCntDown))))
            .ReturnsAsync(true);

            this.LoRaDeviceClient.Setup(x => x.GetTwinAsync())
            .ReturnsAsync(simulatedDevice.CreateOTAATwin());

            var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, this.NewMemoryCache(), this.LoRaDeviceApi.Object, this.LoRaDeviceFactory);

            // Send to message processor
            var messageProcessor = new MessageDispatcher(
                this.ServerConfiguration,
                deviceRegistry,
                this.FrameCounterUpdateStrategyProvider);

            var request = this.CreateWaitableRequest(rxpk);

            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.NotNull(request.ResponseDownlink);
            Assert.Single(this.PacketForwarder.DownlinkMessages);

            var pktFwdMessage = this.PacketForwarder.DownlinkMessages[0];

            Assert.NotNull(pktFwdMessage.Txpk);
            var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(pktFwdMessage.Txpk.Data), simulatedDevice.AppKey);

            var joinedDeviceDevAddr = ConversionHelper.ByteArrayToString(joinAccept.DevAddr);
            var cachedDevices       = deviceRegistry.InternalGetCachedDevicesForDevAddr(joinedDeviceDevAddr);

            Assert.Single(cachedDevices);
            Assert.True(cachedDevices.TryGetValue(devEUI, out var loRaDevice));
            Assert.Equal(joinAccept.DevAddr.ToArray(), this.ByteArray(loRaDevice.DevAddr).ToArray());

            // Device properties were set with the computes values of the join operation
            Assert.Equal(joinAccept.AppNonce.ToArray(), this.ReversedByteArray(loRaDevice.AppNonce).ToArray());
            Assert.NotEmpty(loRaDevice.NwkSKey);
            Assert.NotEmpty(loRaDevice.AppSKey);
            Assert.True(loRaDevice.IsOurDevice);

            // Device frame counts were reset
            Assert.Equal(0U, loRaDevice.FCntDown);
            Assert.Equal(0U, loRaDevice.FCntUp);
            Assert.False(loRaDevice.HasFrameCountChanges);

            // Twin property were updated
            this.LoRaDeviceClient.VerifyAll();
            this.LoRaDeviceApi.VerifyAll();
        }
예제 #15
0
        public async Task When_Getting_Device_Information_From_Twin_Returns_JoinAccept(string deviceGatewayID)
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequest     = simulatedDevice.CreateJoinRequest();

            // Create Rxpk
            var rxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];

            var payloadDecoder = new Mock <ILoRaPayloadDecoder>();

            var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce);
            var devAddr  = string.Empty;
            var devEUI   = simulatedDevice.LoRaDevice.DeviceID;
            var appEUI   = simulatedDevice.LoRaDevice.AppEUI;

            var loRaDeviceClient = new Mock <ILoRaDeviceClient>(MockBehavior.Strict);

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEUI;
            twin.Properties.Desired[TwinProperty.AppEUI]        = simulatedDevice.LoRaDevice.AppEUI;
            twin.Properties.Desired[TwinProperty.AppKey]        = simulatedDevice.LoRaDevice.AppKey;
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            loRaDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(twin);

            // Device twin will be updated
            string afterJoinAppSKey = null;
            string afterJoinNwkSKey = null;
            string afterJoinDevAddr = null;

            loRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>()))
            .Callback <TwinCollection>((updatedTwin) =>
            {
                afterJoinAppSKey = updatedTwin[TwinProperty.AppSKey];
                afterJoinNwkSKey = updatedTwin[TwinProperty.NwkSKey];
                afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr];
            })
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui,
            var loRaDeviceApi = new Mock <LoRaDeviceAPIServiceBase>(MockBehavior.Strict);

            loRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, devNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList()));

            var loRaDeviceFactory = new TestLoRaDeviceFactory(loRaDeviceClient.Object);

            var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, loRaDeviceApi.Object, loRaDeviceFactory);

            // Setup frame counter strategy for FrameCounterLoRaDeviceInitializer
            if (deviceGatewayID == null)
            {
                this.FrameCounterUpdateStrategyFactory.Setup(x => x.GetMultiGatewayStrategy())
                .Returns(this.FrameCounterUpdateStrategy.Object);
            }
            else
            {
                this.FrameCounterUpdateStrategyFactory.Setup(x => x.GetSingleGatewayStrategy())
                .Returns(this.FrameCounterUpdateStrategy.Object);
            }

            // Send to message processor
            var messageProcessor = new MessageProcessor(
                this.ServerConfiguration,
                deviceRegistry,
                this.FrameCounterUpdateStrategyFactory.Object,
                payloadDecoder.Object);

            var downlinkMessage = await messageProcessor.ProcessMessageAsync(rxpk);

            Assert.NotNull(downlinkMessage);
            var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey);

            Assert.Equal(joinAccept.DevAddr.ToArray(), ConversionHelper.StringToByteArray(afterJoinDevAddr));

            // check that the device is in cache
            Assert.True(memoryCache.TryGetValue <DevEUIToLoRaDeviceDictionary>(afterJoinDevAddr, out var cachedDevices));
            Assert.True(cachedDevices.TryGetValue(devEUI, out var cachedDevice));
            Assert.Equal(afterJoinAppSKey, cachedDevice.AppSKey);
            Assert.Equal(afterJoinNwkSKey, cachedDevice.NwkSKey);
            Assert.Equal(afterJoinDevAddr, cachedDevice.DevAddr);
            Assert.True(cachedDevice.IsOurDevice);
            if (deviceGatewayID == null)
            {
                Assert.Null(cachedDevice.GatewayID);
            }
            else
            {
                Assert.Equal(deviceGatewayID, cachedDevice.GatewayID);
            }

            // fcnt is restarted
            Assert.Equal(0, cachedDevice.FCntUp);
            Assert.Equal(0, cachedDevice.FCntDown);
            Assert.False(cachedDevice.HasFrameCountChanges);
        }
예제 #16
0
        public async Task Join_And_Send_Unconfirmed_And_Confirmed_Messages(string deviceGatewayID, int initialFcntUp, int initialFcntDown, int startingPayloadFcnt, uint netId)
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequest     = simulatedDevice.CreateJoinRequest();

            // Create Rxpk
            var joinRxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];

            var devNonce = LoRaTools.Utils.ConversionHelper.ByteArrayToString(joinRequest.DevNonce);
            var devAddr  = string.Empty;
            var devEUI   = simulatedDevice.LoRaDevice.DeviceID;
            var appEUI   = simulatedDevice.LoRaDevice.AppEUI;

            var loRaDeviceClient = new Mock <ILoRaDeviceClient>(MockBehavior.Strict);

            this.ServerConfiguration.NetId = netId;
            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEUI;
            twin.Properties.Desired[TwinProperty.AppEUI]        = simulatedDevice.LoRaDevice.AppEUI;
            twin.Properties.Desired[TwinProperty.AppKey]        = simulatedDevice.LoRaDevice.AppKey;
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            twin.Properties.Reported[TwinProperty.FCntUp]       = initialFcntUp;
            twin.Properties.Reported[TwinProperty.FCntDown]     = initialFcntDown;
            loRaDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(twin);

            // Device twin will be updated
            string afterJoinAppSKey  = null;
            string afterJoinNwkSKey  = null;
            string afterJoinDevAddr  = null;
            int    afterJoinFcntDown = -1;
            int    afterJoinFcntUp   = -1;

            loRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>()))
            .Callback <TwinCollection>((updatedTwin) =>
            {
                afterJoinAppSKey  = updatedTwin[TwinProperty.AppSKey];
                afterJoinNwkSKey  = updatedTwin[TwinProperty.NwkSKey];
                afterJoinDevAddr  = updatedTwin[TwinProperty.DevAddr];
                afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown];
                afterJoinFcntUp   = updatedTwin[TwinProperty.FCntUp];
            })
            .ReturnsAsync(true);

            // message will be sent
            var sentTelemetry = new List <LoRaDeviceTelemetry>();

            loRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .Callback <LoRaDeviceTelemetry, Dictionary <string, string> >((t, _) => sentTelemetry.Add(t))
            .ReturnsAsync(true);

            // C2D message will be checked
            loRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            // Lora device api will be search by devices with matching deveui,
            var loRaDeviceApi = new Mock <LoRaDeviceAPIServiceBase>(MockBehavior.Strict);

            loRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, devNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList()));

            // multi gateway will request a next frame count down from the lora device api, prepare it
            if (string.IsNullOrEmpty(deviceGatewayID))
            {
                loRaDeviceApi.Setup(x => x.NextFCntDownAsync(devEUI, 0, startingPayloadFcnt + 1, this.ServerConfiguration.GatewayID))
                .ReturnsAsync((ushort)1);
            }

            // using factory to create mock of
            var loRaDeviceFactory = new TestLoRaDeviceFactory(loRaDeviceClient.Object);

            var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, loRaDeviceApi.Object, loRaDeviceFactory);

            var frameCounterUpdateStrategyFactory = new LoRaDeviceFrameCounterUpdateStrategyFactory(this.ServerConfiguration.GatewayID, loRaDeviceApi.Object);

            // Send to message processor
            var messageProcessor = new MessageProcessor(
                this.ServerConfiguration,
                deviceRegistry,
                frameCounterUpdateStrategyFactory,
                new LoRaPayloadDecoder());

            var downlinkJoinAcceptMessage = await messageProcessor.ProcessMessageAsync(joinRxpk);

            Assert.NotNull(downlinkJoinAcceptMessage);
            var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkJoinAcceptMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey);

            Assert.Equal(joinAccept.DevAddr.ToArray(), ConversionHelper.StringToByteArray(afterJoinDevAddr));

            // check that the device is in cache
            var devicesForDevAddr = deviceRegistry.InternalGetCachedDevicesForDevAddr(afterJoinDevAddr);

            Assert.Single(devicesForDevAddr); // should have the single device
            Assert.True(devicesForDevAddr.TryGetValue(devEUI, out var loRaDevice));
            Assert.Equal(afterJoinAppSKey, loRaDevice.AppSKey);
            Assert.Equal(afterJoinNwkSKey, loRaDevice.NwkSKey);
            Assert.Equal(afterJoinDevAddr, loRaDevice.DevAddr);
            var netIdBytes = BitConverter.GetBytes(netId);

            Assert.Equal((uint)(netIdBytes[0] & 0b01111111), NetIdHelper.GetNwkIdPart(afterJoinDevAddr));
            if (deviceGatewayID == null)
            {
                Assert.Null(loRaDevice.GatewayID);
            }
            else
            {
                Assert.Equal(deviceGatewayID, loRaDevice.GatewayID);
            }

            // fcnt is restarted
            Assert.Equal(0, afterJoinFcntDown);
            Assert.Equal(0, afterJoinFcntUp);
            Assert.Equal(0, loRaDevice.FCntUp);
            Assert.Equal(0, loRaDevice.FCntDown);
            Assert.False(loRaDevice.HasFrameCountChanges);

            simulatedDevice.LoRaDevice.AppSKey = afterJoinAppSKey;
            simulatedDevice.LoRaDevice.NwkSKey = afterJoinNwkSKey;
            simulatedDevice.LoRaDevice.DevAddr = afterJoinDevAddr;

            // sends unconfirmed message
            var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("100", fcnt: startingPayloadFcnt);
            var unconfirmedMessageResult  = await messageProcessor.ProcessMessageAsync(unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0]);

            Assert.Null(unconfirmedMessageResult);

            // fcnt up was updated
            Assert.Equal(startingPayloadFcnt, loRaDevice.FCntUp);
            Assert.Equal(0, loRaDevice.FCntDown);

            if (startingPayloadFcnt != 0)
            {
                // Frame change flag will be set, only saving every 10 messages
                Assert.True(loRaDevice.HasFrameCountChanges);
            }

            Assert.Single(sentTelemetry);

            // sends confirmed message
            var confirmedMessagePayload = simulatedDevice.CreateConfirmedDataUpMessage("200", fcnt: startingPayloadFcnt + 1);
            var confirmedMessageRxpk    = confirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
            var confirmedMessage        = await messageProcessor.ProcessMessageAsync(confirmedMessageRxpk);

            Assert.NotNull(confirmedMessage);
            Assert.NotNull(confirmedMessage.Txpk);
            Assert.Equal(2, sentTelemetry.Count);

            // validates txpk according to eu region
            Assert.Equal(RegionFactory.CreateEU868Region().GetDownstreamChannelFrequency(confirmedMessageRxpk), confirmedMessage.Txpk.Freq);
            Assert.Equal("4/5", confirmedMessage.Txpk.Codr);
            Assert.False(confirmedMessage.Txpk.Imme);
            Assert.True(confirmedMessage.Txpk.Ipol);
            Assert.Equal("LORA", confirmedMessage.Txpk.Modu);

            // fcnt up was updated
            Assert.Equal(startingPayloadFcnt + 1, loRaDevice.FCntUp);
            Assert.Equal(1, loRaDevice.FCntDown);

            // Frame change flag will be set, only saving every 10 messages
            Assert.True(loRaDevice.HasFrameCountChanges);

            // C2D message will be checked twice
            loRaDeviceClient.Verify(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()), Times.Exactly(2));

            // has telemetry with both fcnt
            Assert.Single(sentTelemetry, (t) => t.Fcnt == startingPayloadFcnt);
            Assert.Single(sentTelemetry, (t) => t.Fcnt == (startingPayloadFcnt + 1));

            loRaDeviceClient.VerifyAll();
            loRaDeviceApi.VerifyAll();
        }
예제 #17
0
        public async Task When_Join_With_Custom_Join_Update_Old_Desired_Properties()
        {
            var    beforeJoinValues = 2;
            var    afterJoinValues  = 3;
            int    reportedBeforeJoinRx1DROffsetValue = 0;
            int    reportedBeforeJoinRx2DRValue       = 0;
            int    reportedBeforeJoinRxDelayValue     = 0;
            string deviceGatewayID   = ServerGatewayID;
            var    simulatedDevice   = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            string afterJoinAppSKey  = null;
            string afterJoinNwkSKey  = null;
            string afterJoinDevAddr  = null;
            int    afterJoinFcntDown = -1;
            int    afterJoinFcntUp   = -1;
            var    devAddr           = string.Empty;
            var    devEUI            = simulatedDevice.LoRaDevice.DeviceID;
            var    appEUI            = simulatedDevice.LoRaDevice.AppEUI;

            // message will be sent
            var sentTelemetry = new List <LoRaDeviceTelemetry>();

            this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .Callback <LoRaDeviceTelemetry, Dictionary <string, string> >((t, _) => sentTelemetry.Add(t))
            .ReturnsAsync(true);

            // C2D message will be checked
            this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEUI;
            twin.Properties.Desired[TwinProperty.AppEUI]        = simulatedDevice.LoRaDevice.AppEUI;
            twin.Properties.Desired[TwinProperty.AppKey]        = simulatedDevice.LoRaDevice.AppKey;
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            twin.Properties.Desired[TwinProperty.RX2DataRate]   = afterJoinValues;
            twin.Properties.Desired[TwinProperty.RX1DROffset]   = afterJoinValues;
            twin.Properties.Desired[TwinProperty.RXDelay]       = afterJoinValues;
            this.LoRaDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(twin);

            this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>()))
            .Callback <TwinCollection>((updatedTwin) =>
            {
                if (updatedTwin.Contains(TwinProperty.AppSKey))
                {
                    afterJoinAppSKey  = updatedTwin[TwinProperty.AppSKey].Value;
                    afterJoinNwkSKey  = updatedTwin[TwinProperty.NwkSKey].Value;
                    afterJoinDevAddr  = updatedTwin[TwinProperty.DevAddr].Value;
                    afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown].Value;
                    afterJoinFcntUp   = updatedTwin[TwinProperty.FCntUp].Value;
                }
                else
                {
                    reportedBeforeJoinRx1DROffsetValue = updatedTwin[TwinProperty.RX1DROffset].Value;
                    reportedBeforeJoinRx2DRValue       = updatedTwin[TwinProperty.RX2DataRate].Value;
                    reportedBeforeJoinRxDelayValue     = updatedTwin[TwinProperty.RXDelay].Value;
                }
            })
            .ReturnsAsync(true);

            // create a state before the join
            var startingTwin = new TwinCollection();

            startingTwin[TwinProperty.RX2DataRate] = beforeJoinValues;
            startingTwin[TwinProperty.RX1DROffset] = beforeJoinValues;
            startingTwin[TwinProperty.RXDelay]     = beforeJoinValues;
            await this.LoRaDeviceClient.Object.UpdateReportedPropertiesAsync(startingTwin);

            var memoryCache       = new MemoryCache(new MemoryCacheOptions());
            var deviceRegistry    = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, this.LoRaDeviceApi.Object, this.LoRaDeviceFactory);
            var loRaDeviceFactory = new TestLoRaDeviceFactory(this.LoRaDeviceClient.Object);
            // Send to message processor
            var messageProcessor = new MessageDispatcher(
                this.ServerConfiguration,
                deviceRegistry,
                this.FrameCounterUpdateStrategyProvider);
            var joinRequest = simulatedDevice.CreateJoinRequest();
            // Create Rxpk
            var rxpk     = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];
            var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce);

            // Lora device api will be search by devices with matching deveui,
            this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, devNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList()));
            var request = this.CreateWaitableRequest(rxpk);

            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.NotNull(request.ResponseDownlink);
            twin.Properties.Desired[TwinProperty.RX2DataRate] = 3;
            await Task.Delay(TimeSpan.FromMilliseconds(10));

            var downlinkMessage = this.PacketForwarder.DownlinkMessages[0];
            var joinAccept      = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey);

            joinAccept.DlSettings.Span.Reverse();
            Assert.Equal(afterJoinValues, joinAccept.Rx2Dr);
            Assert.Equal(afterJoinValues, joinAccept.Rx1DrOffset);
            Assert.Equal(beforeJoinValues, reportedBeforeJoinRx1DROffsetValue);
            Assert.Equal(beforeJoinValues, reportedBeforeJoinRx2DRValue);
            Assert.Equal(afterJoinValues, (int)joinAccept.RxDelay.Span[0]);
            Assert.Equal(beforeJoinValues, reportedBeforeJoinRxDelayValue);
        }
        public async Task When_Getting_Custom_RX2_DR_From_Twin_Returns_JoinAccept_With_Correct_Settings_And_Behaves_Correctly(DataRateIndex rx2datarate)
        {
            var               deviceGatewayID     = ServerGatewayID;
            var               simulatedDevice     = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var               joinRequest         = simulatedDevice.CreateJoinRequest();
            AppSessionKey?    afterJoinAppSKey    = null;
            NetworkSessionKey?afterJoinNwkSKey    = null;
            string            afterJoinDevAddr    = null;
            var               afterJoinFcntDown   = -1;
            var               afterJoinFcntUp     = -1;
            uint              startingPayloadFcnt = 0;

            var devAddr = (DevAddr?)null;
            var devEui  = simulatedDevice.LoRaDevice.DevEui;

            // message will be sent
            var sentTelemetry = new List <LoRaDeviceTelemetry>();

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .Callback <LoRaDeviceTelemetry, Dictionary <string, string> >((t, _) => sentTelemetry.Add(t))
            .ReturnsAsync(true);

            // C2D message will be checked
            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]          = devEui.ToString();
            twin.Properties.Desired[TwinProperty.AppEui]          = simulatedDevice.LoRaDevice.AppEui?.ToString();
            twin.Properties.Desired[TwinProperty.AppKey]          = simulatedDevice.LoRaDevice.AppKey?.ToString();
            twin.Properties.Desired[TwinProperty.GatewayID]       = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder]   = simulatedDevice.LoRaDevice.SensorDecoder;
            twin.Properties.Desired[TwinProperty.RX2DataRate]     = rx2datarate;
            twin.Properties.Desired[TwinProperty.PreferredWindow] = 2;

            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)).ReturnsAsync(twin);

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .Callback <TwinCollection, CancellationToken>((updatedTwin, _) =>
            {
                afterJoinAppSKey  = AppSessionKey.Parse(updatedTwin[TwinProperty.AppSKey].Value);
                afterJoinNwkSKey  = NetworkSessionKey.Parse(updatedTwin[TwinProperty.NwkSKey].Value);
                afterJoinDevAddr  = updatedTwin[TwinProperty.DevAddr].Value;
                afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown].Value;
                afterJoinFcntUp   = updatedTwin[TwinProperty.FCntUp].Value;
            })
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui,
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequest.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), joinRequest);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.NotNull(request.ResponseDownlink);
            Assert.Single(DownstreamMessageSender.DownlinkMessages);
            var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0];
            var joinAccept      = new LoRaPayloadJoinAccept(downlinkMessage.Data, simulatedDevice.LoRaDevice.AppKey.Value);

            if (rx2datarate is > DR0 and < DR8)
            {
                Assert.Equal(rx2datarate, joinAccept.Rx2Dr);
            }
예제 #19
0
        public async Task When_Getting_RXDelay_Offset_From_Twin_Returns_JoinAccept_With_Correct_Settings_And_Behaves_Correctly(int rxDelay, uint expectedDelay)
        {
            string deviceGatewayID     = ServerGatewayID;
            var    simulatedDevice     = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var    joinRequest         = simulatedDevice.CreateJoinRequest();
            string afterJoinAppSKey    = null;
            string afterJoinNwkSKey    = null;
            string afterJoinDevAddr    = null;
            int    afterJoinFcntDown   = -1;
            int    afterJoinFcntUp     = -1;
            uint   startingPayloadFcnt = 0;

            // Create Rxpk
            var rxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];

            var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce);
            var devAddr  = string.Empty;
            var devEUI   = simulatedDevice.LoRaDevice.DeviceID;
            var appEUI   = simulatedDevice.LoRaDevice.AppEUI;

            // message will be sent
            var sentTelemetry = new List <LoRaDeviceTelemetry>();

            this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .Callback <LoRaDeviceTelemetry, Dictionary <string, string> >((t, _) => sentTelemetry.Add(t))
            .ReturnsAsync(true);

            // C2D message will be checked
            this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]          = devEUI;
            twin.Properties.Desired[TwinProperty.AppEUI]          = simulatedDevice.LoRaDevice.AppEUI;
            twin.Properties.Desired[TwinProperty.AppKey]          = simulatedDevice.LoRaDevice.AppKey;
            twin.Properties.Desired[TwinProperty.GatewayID]       = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder]   = simulatedDevice.LoRaDevice.SensorDecoder;
            twin.Properties.Desired[TwinProperty.RXDelay]         = rxDelay;
            twin.Properties.Desired[TwinProperty.PreferredWindow] = 1;

            this.LoRaDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(twin);

            this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>()))
            .Callback <TwinCollection>((updatedTwin) =>
            {
                afterJoinAppSKey  = updatedTwin[TwinProperty.AppSKey].Value;
                afterJoinNwkSKey  = updatedTwin[TwinProperty.NwkSKey].Value;
                afterJoinDevAddr  = updatedTwin[TwinProperty.DevAddr].Value;
                afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown].Value;
                afterJoinFcntUp   = updatedTwin[TwinProperty.FCntUp].Value;
            })
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui,
            this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, devNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList()));

            var loRaDeviceFactory = new TestLoRaDeviceFactory(this.LoRaDeviceClient.Object);

            var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, this.LoRaDeviceApi.Object, this.LoRaDeviceFactory);

            // Send to message processor
            var messageProcessor = new MessageDispatcher(
                this.ServerConfiguration,
                deviceRegistry,
                this.FrameCounterUpdateStrategyProvider);

            var request = this.CreateWaitableRequest(rxpk);

            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.NotNull(request.ResponseDownlink);
            Assert.Single(this.PacketForwarder.DownlinkMessages);
            var downlinkMessage = this.PacketForwarder.DownlinkMessages[0];
            var joinAccept      = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey);

            joinAccept.RxDelay.Span.Reverse();
            if (rxDelay > 0 && rxDelay < 16)
            {
                Assert.Equal((int)expectedDelay, (int)joinAccept.RxDelay.Span[0]);
            }
            else
            {
                Assert.Equal(0, (int)joinAccept.RxDelay.Span[0]);
            }

            // Send a message
            simulatedDevice.LoRaDevice.AppSKey = afterJoinAppSKey;
            simulatedDevice.LoRaDevice.NwkSKey = afterJoinNwkSKey;
            simulatedDevice.LoRaDevice.DevAddr = afterJoinDevAddr;

            // sends confirmed message
            var confirmedMessagePayload = simulatedDevice.CreateConfirmedDataUpMessage("200", fcnt: startingPayloadFcnt + 1);
            var confirmedMessageRxpk    = confirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
            var confirmedRequest        = this.CreateWaitableRequest(confirmedMessageRxpk);

            messageProcessor.DispatchRequest(confirmedRequest);
            Assert.True(await confirmedRequest.WaitCompleteAsync());
            Assert.True(confirmedRequest.ProcessingSucceeded);
            Assert.NotNull(confirmedRequest.ResponseDownlink);
            Assert.NotNull(confirmedRequest.ResponseDownlink.Txpk);
            Assert.Equal(2, this.PacketForwarder.DownlinkMessages.Count);
            var downstreamMessage = this.PacketForwarder.DownlinkMessages[1];

            // Message was sent on RX1 with correct delay and with a correct datarate offset
            if (rxDelay > 0 && rxDelay < 16)
            {
                Assert.Equal(expectedDelay * 1000000, downstreamMessage.Txpk.Tmst - confirmedMessageRxpk.Tmst);
            }
            else
            {
                Assert.Equal(expectedDelay * 1000000, downstreamMessage.Txpk.Tmst - confirmedMessageRxpk.Tmst);
            }
        }
        public async Task When_Getting_Device_Information_From_Twin_Returns_JoinAccept(string deviceGatewayID)
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequest     = simulatedDevice.CreateJoinRequest();

            var devAddr = (DevAddr?)null;
            var devEui  = simulatedDevice.LoRaDevice.DevEui;

            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI] = devEui.ToString();
            twin.Properties.Desired[TwinProperty.AppEui] = simulatedDevice.LoRaDevice.AppEui?.ToString();
            twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey?.ToString();
            if (deviceGatewayID != null)
            {
                twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID;
            }
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)).ReturnsAsync(twin);

            // Device twin will be updated
            AppSessionKey?    afterJoinAppSKey = null;
            NetworkSessionKey?afterJoinNwkSKey = null;
            string            afterJoinDevAddr = null;

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .Callback <TwinCollection, CancellationToken>((updatedTwin, _) =>
            {
                afterJoinAppSKey = AppSessionKey.Parse(updatedTwin[TwinProperty.AppSKey].Value);
                afterJoinNwkSKey = NetworkSessionKey.Parse(updatedTwin[TwinProperty.NwkSKey].Value);
                afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr];
            })
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui,
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequest.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), joinRequest);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.NotNull(request.ResponseDownlink);
            Assert.Single(DownstreamMessageSender.DownlinkMessages);
            var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0];
            var joinAccept      = new LoRaPayloadJoinAccept(downlinkMessage.Data, simulatedDevice.LoRaDevice.AppKey.Value);

            Assert.Equal(joinAccept.DevAddr.ToString(), afterJoinDevAddr);

            // check that the device is in cache
            Assert.True(DeviceCache.HasRegistrations(joinAccept.DevAddr));
            Assert.True(DeviceCache.TryGetByDevEui(devEui, out var cachedDevice));
            Assert.Equal(afterJoinAppSKey, cachedDevice.AppSKey);
            Assert.Equal(afterJoinNwkSKey, cachedDevice.NwkSKey);
            Assert.Equal(joinAccept.DevAddr, cachedDevice.DevAddr);
            Assert.True(cachedDevice.IsOurDevice);
            if (deviceGatewayID == null)
            {
                Assert.Null(cachedDevice.GatewayID);
            }
            else
            {
                Assert.Equal(deviceGatewayID, cachedDevice.GatewayID);
            }

            // fcnt is restarted
            Assert.Equal(0U, cachedDevice.FCntUp);
            Assert.Equal(0U, cachedDevice.FCntDown);
            Assert.False(cachedDevice.HasFrameCountChanges);
        }
예제 #21
0
        public async Task Join_And_Send_Unconfirmed_And_Confirmed_Messages(string deviceGatewayID, uint initialFcntUp, uint initialFcntDown, uint startingPayloadFcnt, uint netId)
        {
            var simulatedDevice    = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequestPayload = simulatedDevice.CreateJoinRequest();

            // Create Rxpk
            var joinRxpk = joinRequestPayload.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];

            var devNonce = LoRaTools.Utils.ConversionHelper.ByteArrayToString(joinRequestPayload.DevNonce);
            var devAddr  = string.Empty;
            var devEUI   = simulatedDevice.LoRaDevice.DeviceID;
            var appEUI   = simulatedDevice.LoRaDevice.AppEUI;

            this.ServerConfiguration.NetId = netId;
            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEUI;
            twin.Properties.Desired[TwinProperty.AppEUI]        = simulatedDevice.LoRaDevice.AppEUI;
            twin.Properties.Desired[TwinProperty.AppKey]        = simulatedDevice.LoRaDevice.AppKey;
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            twin.Properties.Reported[TwinProperty.FCntUp]       = initialFcntUp;
            twin.Properties.Reported[TwinProperty.FCntDown]     = initialFcntDown;
            this.LoRaDeviceClient.Setup(x => x.GetTwinAsync()).ReturnsAsync(twin);

            // Device twin will be updated
            string afterJoinAppSKey  = null;
            string afterJoinNwkSKey  = null;
            string afterJoinDevAddr  = null;
            uint   afterJoinFcntDown = 0;
            uint   afterJoinFcntUp   = 0;

            this.LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>()))
            .Callback <TwinCollection>((updatedTwin) =>
            {
                afterJoinAppSKey  = updatedTwin[TwinProperty.AppSKey];
                afterJoinNwkSKey  = updatedTwin[TwinProperty.NwkSKey];
                afterJoinDevAddr  = updatedTwin[TwinProperty.DevAddr];
                afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown];
                afterJoinFcntUp   = updatedTwin[TwinProperty.FCntUp];

                // should not save class C device properties
                Assert.False(updatedTwin.Contains(TwinProperty.Region));
                Assert.False(updatedTwin.Contains(TwinProperty.PreferredGatewayID));
            })
            .ReturnsAsync(true);

            // message will be sent
            var sentTelemetry = new List <LoRaDeviceTelemetry>();

            this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .Callback <LoRaDeviceTelemetry, Dictionary <string, string> >((t, _) => sentTelemetry.Add(t))
            .ReturnsAsync(true);

            // C2D message will be checked
            this.LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            // Lora device api will be search by devices with matching deveui,
            this.LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, devNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList()));

            // multi gateway will request a next frame count down from the lora device api, prepare it
            if (string.IsNullOrEmpty(deviceGatewayID))
            {
                this.LoRaDeviceApi.Setup(x => x.NextFCntDownAsync(devEUI, 0, startingPayloadFcnt + 1, this.ServerConfiguration.GatewayID))
                .ReturnsAsync((ushort)1);
                this.LoRaDeviceApi
                .Setup(x => x.ExecuteFunctionBundlerAsync(devEUI, It.IsAny <FunctionBundlerRequest>()))
                .ReturnsAsync(() => new FunctionBundlerResult
                {
                    AdrResult = new LoRaTools.ADR.LoRaADRResult
                    {
                        CanConfirmToDevice = false,
                        FCntDown           = 1,
                        NbRepetition       = 1,
                        TxPower            = 0
                    },
                    NextFCntDown = 1
                });
            }

            // using factory to create mock of
            var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, this.LoRaDeviceApi.Object, this.LoRaDeviceFactory);

            // Send to message processor
            var messageProcessor = new MessageDispatcher(
                this.ServerConfiguration,
                deviceRegistry,
                this.FrameCounterUpdateStrategyProvider);

            var joinRequest = this.CreateWaitableRequest(joinRxpk, constantElapsedTime: TimeSpan.FromMilliseconds(300));

            messageProcessor.DispatchRequest(joinRequest);
            Assert.True(await joinRequest.WaitCompleteAsync());
            Assert.True(joinRequest.ProcessingSucceeded);
            Assert.NotNull(joinRequest.ResponseDownlink);
            Assert.Single(this.PacketForwarder.DownlinkMessages);
            var downlinkJoinAcceptMessage = this.PacketForwarder.DownlinkMessages[0];
            var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(downlinkJoinAcceptMessage.Txpk.Data), simulatedDevice.LoRaDevice.AppKey);

            Assert.Equal(joinAccept.DevAddr.ToArray(), ConversionHelper.StringToByteArray(afterJoinDevAddr));

            // check that the device is in cache
            var devicesForDevAddr = deviceRegistry.InternalGetCachedDevicesForDevAddr(afterJoinDevAddr);

            Assert.Single(devicesForDevAddr); // should have the single device
            Assert.True(devicesForDevAddr.TryGetValue(devEUI, out var loRaDevice));
            Assert.Equal(afterJoinAppSKey, loRaDevice.AppSKey);
            Assert.Equal(afterJoinNwkSKey, loRaDevice.NwkSKey);
            Assert.Equal(afterJoinDevAddr, loRaDevice.DevAddr);
            var netIdBytes = BitConverter.GetBytes(netId);

            Assert.Equal((uint)(netIdBytes[0] & 0b01111111), NetIdHelper.GetNwkIdPart(afterJoinDevAddr));
            if (deviceGatewayID == null)
            {
                Assert.Null(loRaDevice.GatewayID);
            }
            else
            {
                Assert.Equal(deviceGatewayID, loRaDevice.GatewayID);
            }

            // fcnt is restarted
            Assert.Equal(0U, afterJoinFcntDown);
            Assert.Equal(0U, afterJoinFcntUp);
            Assert.Equal(0U, loRaDevice.FCntUp);
            Assert.Equal(0U, loRaDevice.FCntDown);
            Assert.False(loRaDevice.HasFrameCountChanges);

            simulatedDevice.LoRaDevice.AppSKey = afterJoinAppSKey;
            simulatedDevice.LoRaDevice.NwkSKey = afterJoinNwkSKey;
            simulatedDevice.LoRaDevice.DevAddr = afterJoinDevAddr;

            // sends unconfirmed message
            var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("100", fcnt: startingPayloadFcnt);
            var unconfirmedRequest        = this.CreateWaitableRequest(unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0], constantElapsedTime: TimeSpan.FromMilliseconds(300));

            messageProcessor.DispatchRequest(unconfirmedRequest);
            Assert.True(await unconfirmedRequest.WaitCompleteAsync());
            Assert.Null(unconfirmedRequest.ResponseDownlink);
            Assert.True(unconfirmedRequest.ProcessingSucceeded);

            // fcnt up was updated
            Assert.Equal(startingPayloadFcnt, loRaDevice.FCntUp);
            Assert.Equal(0U, loRaDevice.FCntDown);

            if (startingPayloadFcnt != 0)
            {
                // Frame change flag will be set, only saving every 10 messages
                Assert.True(loRaDevice.HasFrameCountChanges);
            }

            Assert.Single(sentTelemetry);

            // sends confirmed message
            var confirmedMessagePayload = simulatedDevice.CreateConfirmedDataUpMessage("200", fcnt: startingPayloadFcnt + 1);
            var confirmedMessageRxpk    = confirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
            var confirmedRequest        = this.CreateWaitableRequest(confirmedMessageRxpk, constantElapsedTime: TimeSpan.FromMilliseconds(300));

            messageProcessor.DispatchRequest(confirmedRequest);
            Assert.True(await confirmedRequest.WaitCompleteAsync());
            Assert.True(confirmedRequest.ProcessingSucceeded);
            Assert.NotNull(confirmedRequest.ResponseDownlink);
            Assert.NotNull(confirmedRequest.ResponseDownlink.Txpk);
            Assert.Equal(2, this.PacketForwarder.DownlinkMessages.Count);
            Assert.Equal(2, sentTelemetry.Count);
            var downstreamMessage = this.PacketForwarder.DownlinkMessages[1];

            // validates txpk according to eu region
            Assert.True(RegionManager.EU868.TryGetDownstreamChannelFrequency(confirmedMessageRxpk, out double frequency));
            Assert.Equal(frequency, downstreamMessage.Txpk.Freq);
            Assert.Equal("4/5", downstreamMessage.Txpk.Codr);
            Assert.False(downstreamMessage.Txpk.Imme);
            Assert.True(downstreamMessage.Txpk.Ipol);
            Assert.Equal("LORA", downstreamMessage.Txpk.Modu);

            // fcnt up was updated
            Assert.Equal(startingPayloadFcnt + 1, loRaDevice.FCntUp);
            Assert.Equal(1U, loRaDevice.FCntDown);

            // Frame change flag will be set, only saving every 10 messages
            Assert.True(loRaDevice.HasFrameCountChanges);

            // C2D message will be checked twice
            this.LoRaDeviceClient.Verify(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()), Times.Exactly(2));

            // has telemetry with both fcnt
            Assert.Single(sentTelemetry, (t) => t.Fcnt == startingPayloadFcnt);
            Assert.Single(sentTelemetry, (t) => t.Fcnt == (startingPayloadFcnt + 1));

            this.LoRaDeviceClient.VerifyAll();
        }
예제 #22
0
        private async Task Join_With_Subsequent_Unconfirmed_And_Confirmed_Messages(string deviceGatewayID, uint initialFcntUp, uint initialFcntDown, uint startingPayloadFcnt, int netId, Region region)
        {
            var simulatedDevice    = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequestPayload = simulatedDevice.CreateJoinRequest();

            var devAddr = (DevAddr?)null;
            var devEui  = simulatedDevice.LoRaDevice.DevEui;

            ServerConfiguration.NetId = new NetId(netId);
            // Device twin will be queried
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI] = devEui.ToString();
            twin.Properties.Desired[TwinProperty.AppEui] = simulatedDevice.LoRaDevice.AppEui?.ToString();
            twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey?.ToString();
            if (deviceGatewayID != null)
            {
                twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID;
            }
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            twin.Properties.Reported[TwinProperty.FCntUp]       = initialFcntUp;
            twin.Properties.Reported[TwinProperty.FCntDown]     = initialFcntDown;
            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)).ReturnsAsync(twin);

            // Device twin will be updated
            AppSessionKey?    afterJoinAppSKey = null;
            NetworkSessionKey?afterJoinNwkSKey = null;
            string            afterJoinDevAddr = null;
            uint afterJoinFcntDown             = 0;
            uint afterJoinFcntUp = 0;

            TwinCollection actualSavedTwin = null;

            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>()))
            .Callback <TwinCollection, CancellationToken>((updatedTwin, _) =>
            {
                if (updatedTwin.Contains(TwinProperty.AppSKey))
                {
                    afterJoinAppSKey = AppSessionKey.Parse(updatedTwin[TwinProperty.AppSKey].Value);
                }
                if (updatedTwin.Contains(TwinProperty.NwkSKey))
                {
                    afterJoinNwkSKey = NetworkSessionKey.Parse(updatedTwin[TwinProperty.NwkSKey].Value);
                }
                if (updatedTwin.Contains(TwinProperty.DevAddr))
                {
                    afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr];
                }
                afterJoinFcntDown = updatedTwin[TwinProperty.FCntDown];
                afterJoinFcntUp   = updatedTwin[TwinProperty.FCntUp];
                actualSavedTwin   = updatedTwin;
            })
            .ReturnsAsync(true);

            // message will be sent
            var sentTelemetry = new List <LoRaDeviceTelemetry>();

            LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null))
            .Callback <LoRaDeviceTelemetry, Dictionary <string, string> >((t, _) => sentTelemetry.Add(t))
            .ReturnsAsync(true);

            // C2D message will be checked
            LoRaDeviceClient.Setup(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()))
            .ReturnsAsync((Message)null);

            // Lora device api will be search by devices with matching deveui,
            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList()));

            // multi gateway will request a next frame count down from the lora device api, prepare it
            if (string.IsNullOrEmpty(deviceGatewayID))
            {
                LoRaDeviceApi.Setup(x => x.NextFCntDownAsync(devEui, 0, startingPayloadFcnt + 1, ServerConfiguration.GatewayID))
                .ReturnsAsync((ushort)1);
                LoRaDeviceApi
                .Setup(x => x.ExecuteFunctionBundlerAsync(devEui, It.IsAny <FunctionBundlerRequest>()))
                .ReturnsAsync(() => new FunctionBundlerResult
                {
                    AdrResult = new LoRaTools.ADR.LoRaADRResult
                    {
                        CanConfirmToDevice = false,
                        FCntDown           = 1,
                        NbRepetition       = 1,
                        TxPower            = 0
                    },
                    NextFCntDown = 1
                });
            }

            // using factory to create mock of
            using var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, memoryCache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            // Create a join request and join with the device.
            using var joinRequest =
                      CreateWaitableRequest(joinRequestPayload, constantElapsedTime: TimeSpan.FromMilliseconds(300), region: region);
            messageProcessor.DispatchRequest(joinRequest);
            Assert.True(await joinRequest.WaitCompleteAsync());
            Assert.True(joinRequest.ProcessingSucceeded, $"Failed due to '{joinRequest.ProcessingFailedReason}'.");
            Assert.NotNull(joinRequest.ResponseDownlink);
            Assert.Single(DownstreamMessageSender.DownlinkMessages);
            var downlinkJoinAcceptMessage = DownstreamMessageSender.DownlinkMessages[0];
            var joinAccept = new LoRaPayloadJoinAccept(downlinkJoinAcceptMessage.Data, simulatedDevice.LoRaDevice.AppKey.Value);

            Assert.Equal(joinAccept.DevAddr.ToString(), afterJoinDevAddr);

            // check that the device is in cache
            Assert.True(DeviceCache.TryGetByDevEui(devEui, out var loRaDevice));
            Assert.Equal(afterJoinAppSKey, loRaDevice.AppSKey);
            Assert.Equal(afterJoinNwkSKey, loRaDevice.NwkSKey);
            Assert.Equal(afterJoinDevAddr, loRaDevice.DevAddr.ToString());
            var netIdBytes = BitConverter.GetBytes(netId);

            Assert.Equal(netIdBytes[0] & 0b01111111, DevAddr.Parse(afterJoinDevAddr).NetworkId);
            if (deviceGatewayID == null)
            {
                Assert.Null(loRaDevice.GatewayID);
            }
            else
            {
                Assert.Equal(deviceGatewayID, loRaDevice.GatewayID);
            }

            // Assert that after a join the fcnt is restarted
            Assert.Equal(0U, afterJoinFcntDown);
            Assert.Equal(0U, afterJoinFcntUp);
            Assert.Equal(0U, loRaDevice.FCntUp);
            Assert.Equal(0U, loRaDevice.FCntDown);
            Assert.False(loRaDevice.HasFrameCountChanges);

            simulatedDevice.LoRaDevice.AppSKey = afterJoinAppSKey;
            simulatedDevice.LoRaDevice.NwkSKey = afterJoinNwkSKey;
            simulatedDevice.LoRaDevice.DevAddr = DevAddr.Parse(afterJoinDevAddr);

            // sends unconfirmed message with a given starting frame counter
            var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("100", fcnt: startingPayloadFcnt);
            var radioMetadata             = TestUtils.GenerateTestRadioMetadata();

            using var unconfirmedRequest =
                      CreateWaitableRequest(radioMetadata, unconfirmedMessagePayload,
                                            constantElapsedTime: TimeSpan.FromMilliseconds(300));
            messageProcessor.DispatchRequest(unconfirmedRequest);
            Assert.True(await unconfirmedRequest.WaitCompleteAsync());
            Assert.Null(unconfirmedRequest.ResponseDownlink);
            Assert.True(unconfirmedRequest.ProcessingSucceeded);

            // fcnt up was updated
            Assert.Equal(startingPayloadFcnt, loRaDevice.FCntUp);
            Assert.Equal(0U, loRaDevice.FCntDown);

            // If the starting payload was not 0, it is expected that it updates the framecounter char
            // The device will perform the frame counter update and at this point in time it will have the same frame counter as the desired
            // Therefore savechangesasync will set the hasframcounter change to false
            // if (startingPayloadFcnt != 0)
            // {
            //    // Frame change flag will be set, only saving every 10 messages
            //    Assert.True(loRaDevice.HasFrameCountChanges);
            // }

            Assert.Single(sentTelemetry);

            // sends confirmed message
            var confirmedMessagePayload = simulatedDevice.CreateConfirmedDataUpMessage("200", fcnt: startingPayloadFcnt + 1);

            using var confirmedRequest = CreateWaitableRequest(confirmedMessagePayload, constantElapsedTime: TimeSpan.FromMilliseconds(300), region: region);
            messageProcessor.DispatchRequest(confirmedRequest);
            Assert.True(await confirmedRequest.WaitCompleteAsync());
            Assert.True(confirmedRequest.ProcessingSucceeded);
            Assert.NotNull(confirmedRequest.ResponseDownlink);
            Assert.Equal(2, DownstreamMessageSender.DownlinkMessages.Count);
            Assert.Equal(2, sentTelemetry.Count);
            var downstreamMessage = DownstreamMessageSender.DownlinkMessages[1];

            // validates txpk according to region
            DeviceJoinInfo deviceJoinInfo = null;

            if (region is RegionCN470RP2 cnRegion && cnRegion.TryGetJoinChannelIndex(confirmedRequest.RadioMetadata.Frequency, out var channelIndex))
            {
                deviceJoinInfo = new DeviceJoinInfo(channelIndex);
            }
            Assert.True(region.TryGetDownstreamChannelFrequency(confirmedRequest.RadioMetadata.Frequency, confirmedRequest.RadioMetadata.DataRate, deviceJoinInfo, downstreamFrequency: out var frequency));
            Assert.Equal(frequency, downstreamMessage.Rx1?.Frequency);

            var rx2Freq = region.GetDownstreamRX2Freq(null, deviceJoinInfo, NullLogger.Instance);

            Assert.Equal(rx2Freq, downstreamMessage.Rx2.Frequency);

            var rx2DataRate = region.GetDownstreamRX2DataRate(null, null, deviceJoinInfo, NullLogger.Instance);

            Assert.Equal(rx2DataRate, downstreamMessage.Rx2.DataRate);

            // fcnt up was updated
            Assert.Equal(startingPayloadFcnt + 1, loRaDevice.FCntUp);
            Assert.Equal(1U, loRaDevice.FCntDown);

            // Frame change flag will be set, only saving every 10 messages
            Assert.True(loRaDevice.HasFrameCountChanges);

            // C2D message will be checked twice (for AS923 only once, since we use the first C2D message to send the dwell time MAC command)
            var numberOfC2DMessageChecks = region is RegionAS923 ? 1 : 2;

            LoRaDeviceClient.Verify(x => x.ReceiveAsync(It.IsNotNull <TimeSpan>()), Times.Exactly(numberOfC2DMessageChecks));

            // has telemetry with both fcnt
            Assert.Single(sentTelemetry, (t) => t.Fcnt == startingPayloadFcnt);
            Assert.Single(sentTelemetry, (t) => t.Fcnt == (startingPayloadFcnt + 1));

            // should not save class C device properties
            Assert.False(actualSavedTwin.Contains(TwinProperty.Region));
            Assert.False(actualSavedTwin.Contains(TwinProperty.PreferredGatewayID));

            LoRaDeviceClient.VerifyAll();
        }
        public async Task When_Device_Is_Found_In_Api_Should_Update_Twin_And_Return()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: ServerConfiguration.GatewayID));

            simulatedDevice.LoRaDevice.NwkSKey = null;
            simulatedDevice.LoRaDevice.AppSKey = null;

            var joinRequest = simulatedDevice.CreateJoinRequest();

            // this former join request is just to set the loradevice cache to another devnonce.
            _ = simulatedDevice.CreateJoinRequest();

            var devEui = simulatedDevice.LoRaDevice.DevEui;

            LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(It.IsNotNull <string>(), devEui, joinRequest.DevNonce))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(null, devEui, "123")
            {
                GatewayId = ServerConfiguration.GatewayID
            }.AsList()));

            // Ensure that the device twin was updated
            LoRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.Is <TwinCollection>((t) =>
                                                                                               t.Contains(TwinProperty.DevAddr) && t.Contains(TwinProperty.FCntDown)), It.IsAny <CancellationToken>()))
            .ReturnsAsync(true);

            LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None))
            .ReturnsAsync(simulatedDevice.CreateOTAATwin());

            using var cache          = NewMemoryCache();
            using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache);

            // Send to message processor
            using var messageProcessor = new MessageDispatcher(
                      ServerConfiguration,
                      deviceRegistry,
                      FrameCounterUpdateStrategyProvider);

            using var request = CreateWaitableRequest(TestUtils.GenerateTestRadioMetadata(), joinRequest);
            messageProcessor.DispatchRequest(request);
            Assert.True(await request.WaitCompleteAsync());
            Assert.NotNull(request.ResponseDownlink);
            Assert.Single(DownstreamMessageSender.DownlinkMessages);

            var downlinkMessage = DownstreamMessageSender.DownlinkMessages[0];
            var joinAccept      = new LoRaPayloadJoinAccept(downlinkMessage.Data, simulatedDevice.AppKey.Value);

            Assert.Equal(1, DeviceCache.RegistrationCount(joinAccept.DevAddr));
            Assert.True(DeviceCache.TryGetByDevEui(devEui, out var loRaDevice));
            Assert.Equal(joinAccept.DevAddr, loRaDevice.DevAddr);

            // Device properties were set with the computes values of the join operation
            Assert.Equal(joinAccept.AppNonce, loRaDevice.AppNonce);
            Assert.NotNull(loRaDevice.NwkSKey);
            Assert.NotNull(loRaDevice.AppSKey);
            Assert.True(loRaDevice.IsOurDevice);

            // Device frame counts were reset
            Assert.Equal(0U, loRaDevice.FCntDown);
            Assert.Equal(0U, loRaDevice.FCntUp);
            Assert.False(loRaDevice.HasFrameCountChanges);

            // Twin property were updated
            LoRaDeviceClient.VerifyAll();
            LoRaDeviceApi.VerifyAll();
        }
예제 #24
0
        public async Task When_Device_Is_Found_In_Api_Should_Update_Twin_And_Return()
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: this.ServerConfiguration.GatewayID));

            simulatedDevice.LoRaDevice.NwkSKey = string.Empty;
            simulatedDevice.LoRaDevice.AppSKey = string.Empty;
            var joinRequest = simulatedDevice.CreateJoinRequest();

            var loRaDeviceClient = new Mock <ILoRaDeviceClient>();
            var loRaDevice       = TestUtils.CreateFromSimulatedDevice(simulatedDevice, loRaDeviceClient.Object);

            loRaDevice.SetFcntDown(10);
            loRaDevice.SetFcntUp(20);

            // Create Rxpk
            var rxpk = joinRequest.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];

            var payloadDecoder = new Mock <ILoRaPayloadDecoder>();

            var devNonce = ConversionHelper.ByteArrayToString(joinRequest.DevNonce);
            var devEUI   = simulatedDevice.LoRaDevice.DeviceID;
            var appEUI   = simulatedDevice.LoRaDevice.AppEUI;

            this.LoRaDeviceRegistry.Setup(x => x.GetDeviceForJoinRequestAsync(devEUI, appEUI, devNonce))
            .ReturnsAsync(() => loRaDevice);

            this.LoRaDeviceRegistry.Setup(x => x.UpdateDeviceAfterJoin(loRaDevice));

            // Ensure that the device twin was updated
            loRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.Is <TwinCollection>((t) =>
                                                                                               t.Contains(TwinProperty.DevAddr) && t.Contains(TwinProperty.FCntDown))))
            .ReturnsAsync(true);

            // Send to message processor
            var messageProcessor = new MessageProcessor(
                this.ServerConfiguration,
                this.LoRaDeviceRegistry.Object,
                this.FrameCounterUpdateStrategyFactory.Object,
                payloadDecoder.Object);

            var actual = await messageProcessor.ProcessMessageAsync(rxpk);

            Assert.NotNull(actual);

            var pktFwdMessage = actual.GetPktFwdMessage();

            Assert.NotNull(pktFwdMessage.Txpk);
            var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(pktFwdMessage.Txpk.Data), loRaDevice.AppKey);

            Assert.Equal(joinAccept.DevAddr.ToArray(), this.ByteArray(loRaDevice.DevAddr).ToArray());

            // Device properties were set with the computes values of the join operation
            Assert.Equal(joinAccept.AppNonce.ToArray(), this.ReversedByteArray(loRaDevice.AppNonce).ToArray());
            Assert.NotEmpty(loRaDevice.NwkSKey);
            Assert.NotEmpty(loRaDevice.AppSKey);
            Assert.True(loRaDevice.IsOurDevice);

            // Device frame counts were reset
            Assert.Equal(0, loRaDevice.FCntDown);
            Assert.Equal(0, loRaDevice.FCntUp);
            Assert.False(loRaDevice.HasFrameCountChanges);

            // Twin property were updated
            loRaDeviceClient.VerifyAll();
        }
        internal async Task ProcessJoinRequestAsync(LoRaRequest request)
        {
            LoRaDevice loRaDevice = null;
            var        loraRegion = request.Region;

            try
            {
                var timeWatcher       = request.GetTimeWatcher();
                var processingTimeout = timeWatcher.GetRemainingTimeToJoinAcceptSecondWindow() - TimeSpan.FromMilliseconds(100);
                using var joinAcceptCancellationToken = new CancellationTokenSource(processingTimeout > TimeSpan.Zero ? processingTimeout : TimeSpan.Zero);

                var joinReq = (LoRaPayloadJoinRequest)request.Payload;

                var devEui = joinReq.DevEUI;

                using var scope = this.logger.BeginDeviceScope(devEui);

                this.logger.LogInformation("join request received");

                if (this.concentratorDeduplication.CheckDuplicateJoin(request) is ConcentratorDeduplicationResult.Duplicate)
                {
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.DeduplicationDrop);
                    // we do not log here as the concentratorDeduplication service already does more detailed logging
                    return;
                }

                loRaDevice = await this.deviceRegistry.GetDeviceForJoinRequestAsync(devEui, joinReq.DevNonce);

                if (loRaDevice == null)
                {
                    request.NotifyFailed(devEui.ToString(), LoRaDeviceRequestFailedReason.UnknownDevice);
                    // we do not log here as we assume that the deviceRegistry does a more informed logging if returning null
                    return;
                }

                if (loRaDevice.AppKey is null)
                {
                    this.logger.LogError("join refused: missing AppKey for OTAA device");
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidJoinRequest);
                    return;
                }

                var appKey = loRaDevice.AppKey.Value;

                this.joinRequestCounter?.Add(1);

                if (loRaDevice.AppEui != joinReq.AppEui)
                {
                    this.logger.LogError("join refused: AppEUI for OTAA does not match device");
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidJoinRequest);
                    return;
                }

                if (!joinReq.CheckMic(appKey))
                {
                    this.logger.LogError("join refused: invalid MIC");
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.JoinMicCheckFailed);
                    return;
                }

                // Make sure that is a new request and not a replay
                if (loRaDevice.DevNonce is { } devNonce&& devNonce == joinReq.DevNonce)
                {
                    if (string.IsNullOrEmpty(loRaDevice.GatewayID))
                    {
                        this.logger.LogInformation("join refused: join already processed by another gateway");
                    }
                    else
                    {
                        this.logger.LogError("join refused: DevNonce already used by this device");
                    }

                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.JoinDevNonceAlreadyUsed);
                    return;
                }

                // Check that the device is joining through the linked gateway and not another
                if (!loRaDevice.IsOurDevice)
                {
                    this.logger.LogInformation("join refused: trying to join not through its linked gateway, ignoring join request");
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.HandledByAnotherGateway);
                    return;
                }

                var netId    = this.configuration.NetId;
                var appNonce = new AppNonce(RandomNumberGenerator.GetInt32(toExclusive: AppNonce.MaxValue + 1));
                var appSKey  = OTAAKeysGenerator.CalculateAppSessionKey(appNonce, netId, joinReq.DevNonce, appKey);
                var nwkSKey  = OTAAKeysGenerator.CalculateNetworkSessionKey(appNonce, netId, joinReq.DevNonce, appKey);
                var address  = RandomNumberGenerator.GetInt32(toExclusive: DevAddr.MaxNetworkAddress + 1);
                // The 7 LBS of the NetID become the NwkID of a DevAddr:
                var devAddr = new DevAddr(unchecked ((byte)netId.NetworkId), address);

                var oldDevAddr = loRaDevice.DevAddr;

                if (!timeWatcher.InTimeForJoinAccept())
                {
                    this.receiveWindowMisses?.Add(1);
                    // in this case it's too late, we need to break and avoid saving twins
                    this.logger.LogInformation("join refused: processing of the join request took too long, sending no message");
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.ReceiveWindowMissed);
                    return;
                }

                var updatedProperties = new LoRaDeviceJoinUpdateProperties
                {
                    DevAddr            = devAddr,
                    NwkSKey            = nwkSKey,
                    AppSKey            = appSKey,
                    AppNonce           = appNonce,
                    DevNonce           = joinReq.DevNonce,
                    NetId              = netId,
                    Region             = request.Region.LoRaRegion,
                    PreferredGatewayID = this.configuration.GatewayID,
                };

                if (loRaDevice.ClassType == LoRaDeviceClassType.C)
                {
                    updatedProperties.SavePreferredGateway = true;
                    updatedProperties.SaveRegion           = true;
                    updatedProperties.StationEui           = request.StationEui;
                }

                DeviceJoinInfo deviceJoinInfo = null;
                if (request.Region.LoRaRegion == LoRaRegionType.CN470RP2)
                {
                    if (request.Region.TryGetJoinChannelIndex(request.RadioMetadata.Frequency, out var channelIndex))
                    {
                        updatedProperties.CN470JoinChannel = channelIndex;
                        deviceJoinInfo = new DeviceJoinInfo(channelIndex);
                    }
                    else
                    {
                        this.logger.LogError("failed to retrieve the join channel index for device");
                    }
                }

                var deviceUpdateSucceeded = await loRaDevice.UpdateAfterJoinAsync(updatedProperties, joinAcceptCancellationToken.Token);

                if (!deviceUpdateSucceeded)
                {
                    this.logger.LogError("join refused: join request could not save twin");
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.IoTHubProblem);
                    return;
                }

                var windowToUse = timeWatcher.ResolveJoinAcceptWindowToUse();
                if (windowToUse is null)
                {
                    this.receiveWindowMisses?.Add(1);
                    this.logger.LogInformation("join refused: processing of the join request took too long, sending no message");
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.ReceiveWindowMissed);
                    return;
                }

                this.deviceRegistry.UpdateDeviceAfterJoin(loRaDevice, oldDevAddr);

                // Build join accept downlink message

                // Build the DlSettings fields that is a superposition of RX2DR and RX1DROffset field
                var dlSettings = new byte[1];

                if (loRaDevice.DesiredRX2DataRate.HasValue)
                {
                    if (request.Region.DRtoConfiguration.ContainsKey(loRaDevice.DesiredRX2DataRate.Value))
                    {
                        dlSettings[0] = (byte)((byte)loRaDevice.DesiredRX2DataRate & 0b00001111);
                    }
                    else
                    {
                        this.logger.LogError("twin RX2 DR value is not within acceptable values");
                    }
                }

                if (request.Region.IsValidRX1DROffset(loRaDevice.DesiredRX1DROffset))
                {
                    var rx1droffset = (byte)(loRaDevice.DesiredRX1DROffset << 4);
                    dlSettings[0] = (byte)(dlSettings[0] + rx1droffset);
                }
                else
                {
                    this.logger.LogError("twin RX1 offset DR value is not within acceptable values");
                }

                // The following DesiredRxDelay is different than the RxDelay to be passed to Serialize function
                // This one is a delay between TX and RX for any message to be processed by joining device
                // The field accepted by Serialize method is an indication of the delay (compared to receive time of join request)
                // of when the message Join Accept message should be sent
                var loraSpecDesiredRxDelay = RxDelay.RxDelay0;
                if (Enum.IsDefined(loRaDevice.DesiredRXDelay))
                {
                    loraSpecDesiredRxDelay = loRaDevice.DesiredRXDelay;
                }
                else
                {
                    this.logger.LogError("twin RX delay value is not within acceptable values");
                }

                var loRaPayloadJoinAccept = new LoRaPayloadJoinAccept(
                    netId,   // NETID 0 / 1 is default test
                    devAddr, // todo add device address management
                    appNonce,
                    dlSettings,
                    loraSpecDesiredRxDelay,
                    null);

                if (!loraRegion.TryGetDownstreamChannelFrequency(request.RadioMetadata.Frequency, upstreamDataRate: request.RadioMetadata.DataRate, deviceJoinInfo: deviceJoinInfo, downstreamFrequency: out var freq))
                {
                    this.logger.LogError("could not resolve DR and/or frequency for downstream");
                    request.NotifyFailed(loRaDevice, LoRaDeviceRequestFailedReason.InvalidUpstreamMessage);
                    return;
                }

                var joinAcceptBytes = loRaPayloadJoinAccept.Serialize(appKey);

                var rx1 = windowToUse is not ReceiveWindow2
                        ? new ReceiveWindow(loraRegion.GetDownstreamDataRate(request.RadioMetadata.DataRate, loRaDevice.ReportedRX1DROffset), freq)
                        : (ReceiveWindow?)null;

                var rx2 = new ReceiveWindow(loraRegion.GetDownstreamRX2DataRate(this.configuration.Rx2DataRate, null, deviceJoinInfo, this.logger),
                                            loraRegion.GetDownstreamRX2Freq(this.configuration.Rx2Frequency, deviceJoinInfo, this.logger));

                var downlinkMessage = new DownlinkMessage(joinAcceptBytes,
                                                          request.RadioMetadata.UpInfo.Xtime,
                                                          rx1,
                                                          rx2,
                                                          loRaDevice.DevEUI,
                                                          loraRegion.JoinAcceptDelay1,
                                                          loRaDevice.ClassType,
                                                          request.StationEui,
                                                          request.RadioMetadata.UpInfo.AntennaPreference);

                this.receiveWindowHits?.Add(1, KeyValuePair.Create(MetricRegistry.ReceiveWindowTagName, (object)windowToUse));
                _ = request.DownstreamMessageSender.SendDownstreamAsync(downlinkMessage);
                request.NotifySucceeded(loRaDevice, downlinkMessage);

                if (this.logger.IsEnabled(LogLevel.Debug))
                {
                    var jsonMsg = JsonConvert.SerializeObject(downlinkMessage);
                    this.logger.LogDebug($"{MacMessageType.JoinAccept} {jsonMsg}");
                }
                else
                {
                    this.logger.LogInformation("join accepted");
                }
            }
            catch (Exception ex) when
                (ExceptionFilterUtility.True(() => this.logger.LogError(ex, $"failed to handle join request. {ex.Message}", LogLevel.Error),
                                             () => this.unhandledExceptionCount?.Add(1)))
            {
                request.NotifyFailed(loRaDevice, ex);
                throw;
            }
        }
        public async Task When_Join_Fails_Due_To_Timeout_Second_Try_Should_Reuse_Cached_Device_Twin(string deviceGatewayID)
        {
            var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID));
            var joinRequest1    = simulatedDevice.CreateJoinRequest();

            // Create Rxpk
            var joinRequestRxpk1 = joinRequest1.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];

            var joinRequestDevNonce1 = ConversionHelper.ByteArrayToString(joinRequest1.DevNonce);
            var devAddr = string.Empty;
            var devEUI  = simulatedDevice.LoRaDevice.DeviceID;
            var appEUI  = simulatedDevice.LoRaDevice.AppEUI;

            var loRaDeviceClient = new Mock <ILoRaDeviceClient>(MockBehavior.Strict);

            // Device twin will be queried twice, 1st time will take 7 seconds, 2nd time 0.1 second
            var twin = new Twin();

            twin.Properties.Desired[TwinProperty.DevEUI]        = devEUI;
            twin.Properties.Desired[TwinProperty.AppEUI]        = simulatedDevice.LoRaDevice.AppEUI;
            twin.Properties.Desired[TwinProperty.AppKey]        = simulatedDevice.LoRaDevice.AppKey;
            twin.Properties.Desired[TwinProperty.GatewayID]     = deviceGatewayID;
            twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder;
            loRaDeviceClient.Setup(x => x.GetTwinAsync())
            .Returns(async() =>
            {
                await Task.Delay(TimeSpan.FromSeconds(7));
                return(twin);
            });

            // Device twin will be updated
            string afterJoinAppSKey = null;
            string afterJoinNwkSKey = null;
            string afterJoinDevAddr = null;

            loRaDeviceClient.Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>()))
            .Callback <TwinCollection>((updatedTwin) =>
            {
                afterJoinAppSKey = updatedTwin[TwinProperty.AppSKey];
                afterJoinNwkSKey = updatedTwin[TwinProperty.NwkSKey];
                afterJoinDevAddr = updatedTwin[TwinProperty.DevAddr];
            })
            .ReturnsAsync(true);

            // Lora device api will be search by devices with matching deveui,
            var loRaDeviceApi = new Mock <LoRaDeviceAPIServiceBase>(MockBehavior.Strict);

            loRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, joinRequestDevNonce1))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList()));

            // using factory to create mock of
            var loRaDeviceFactory = new TestLoRaDeviceFactory(loRaDeviceClient.Object);

            var memoryCache    = new MemoryCache(new MemoryCacheOptions());
            var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, memoryCache, loRaDeviceApi.Object, loRaDeviceFactory);

            var frameCounterUpdateStrategyFactory = new LoRaDeviceFrameCounterUpdateStrategyFactory(this.ServerConfiguration.GatewayID, loRaDeviceApi.Object);

            var messageProcessor = new MessageProcessor(
                this.ServerConfiguration,
                deviceRegistry,
                frameCounterUpdateStrategyFactory,
                new LoRaPayloadDecoder());

            // 1st join request
            // Should fail
            var joinRequestDownlinkMessage1 = await messageProcessor.ProcessMessageAsync(joinRequestRxpk1);

            Assert.Null(joinRequestDownlinkMessage1);

            // 2nd attempt
            var joinRequest2         = simulatedDevice.CreateJoinRequest();
            var joinRequestRxpk2     = joinRequest2.SerializeUplink(simulatedDevice.LoRaDevice.AppKey).Rxpk[0];
            var joinRequestDevNonce2 = LoRaTools.Utils.ConversionHelper.ByteArrayToString(joinRequest2.DevNonce);

            // setup response to this device search
            loRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(this.ServerConfiguration.GatewayID, devEUI, appEUI, joinRequestDevNonce2))
            .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEUI, "aabb").AsList()));

            var joinRequestDownlinkMessage2 = await messageProcessor.ProcessMessageAsync(joinRequestRxpk2);

            Assert.NotNull(joinRequestDownlinkMessage2);
            var joinAccept = new LoRaPayloadJoinAccept(Convert.FromBase64String(joinRequestDownlinkMessage2.Txpk.Data), simulatedDevice.LoRaDevice.AppKey);

            Assert.Equal(joinAccept.DevAddr.ToArray(), ConversionHelper.StringToByteArray(afterJoinDevAddr));

            var devicesForDevAddr = deviceRegistry.InternalGetCachedDevicesForDevAddr(afterJoinDevAddr);

            Assert.Single(devicesForDevAddr); // should have the single device
            Assert.True(devicesForDevAddr.TryGetValue(devEUI, out var loRaDevice));

            Assert.Equal(simulatedDevice.AppKey, loRaDevice.AppKey);
            Assert.Equal(simulatedDevice.AppEUI, loRaDevice.AppEUI);
            Assert.Equal(afterJoinAppSKey, loRaDevice.AppSKey);
            Assert.Equal(afterJoinNwkSKey, loRaDevice.NwkSKey);
            Assert.Equal(afterJoinDevAddr, loRaDevice.DevAddr);
            if (deviceGatewayID == null)
            {
                Assert.Null(loRaDevice.GatewayID);
            }
            else
            {
                Assert.Equal(deviceGatewayID, loRaDevice.GatewayID);
            }

            // fcnt is restarted
            Assert.Equal(0, loRaDevice.FCntUp);
            Assert.Equal(0, loRaDevice.FCntDown);
            Assert.False(loRaDevice.HasFrameCountChanges);

            // searching the device should happen twice
            loRaDeviceApi.Verify(x => x.SearchAndLockForJoinAsync(ServerGatewayID, devEUI, appEUI, It.IsNotNull <string>()), Times.Exactly(2));

            // getting the device twin should happens once
            loRaDeviceClient.Verify(x => x.GetTwinAsync(), Times.Once());

            loRaDeviceClient.VerifyAll();
            loRaDeviceApi.VerifyAll();
        }