public async Task GetDeviceForJoinRequestAsync_When_Join_Handled_By_Other_Cache_Is_Updated(bool joinedDevice) { var devNonce = new DevNonce(1); var apiService = new Mock <LoRaDeviceAPIServiceBase>(); var otaaDevice = TestDeviceInfo.CreateOTAADevice(1); if (joinedDevice) { otaaDevice.AppSKey = new AppSessionKey(); } var simulatedDevice = new SimulatedDevice(otaaDevice); apiService.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, simulatedDevice.DevEUI, devNonce)) .ReturnsAsync(new SearchDevicesResult() { IsDevNonceAlreadyUsed = true }); DeviceCache.Register(CreateLoRaDevice(simulatedDevice)); using var target = new LoRaDeviceRegistry(ServerConfiguration, this.cache, apiService.Object, this.loraDeviceFactoryMock.Object, DeviceCache); Assert.Null(await target.GetDeviceForJoinRequestAsync(simulatedDevice.DevEUI, devNonce)); Assert.Equal(joinedDevice, !DeviceCache.TryGetByDevEui(simulatedDevice.DevEUI, out _)); }
public static byte[] GetBytes(this DevNonce devNonce) { var bytes = new byte[DevNonce.Size]; _ = devNonce.Write(bytes); return(bytes); }
public LoRaPayloadJoinRequest(JoinEui joinEui, DevEui devEui, DevNonce devNonce, Mic mic) { MHdr = new MacHeader(MacMessageType.JoinRequest); AppEui = joinEui; DevEUI = devEui; DevNonce = devNonce; Mic = mic; }
public JoinRequestFrame(MacHeader mHdr, JoinEui joinEui, DevEui devEui, DevNonce devNonce, Mic mic, RadioMetadata radioMetadata) { MacHeader = mHdr; JoinEui = joinEui; DevEui = devEui; DevNonce = devNonce; Mic = mic; RadioMetadata = radioMetadata; }
/// <summary> /// Searchs for devices that match the join request. /// </summary> public async Task <LoRaDevice> GetDeviceForJoinRequestAsync(DevEui devEUI, DevNonce devNonce) { this.logger.LogDebug("querying the registry for OTAA device"); var searchDeviceResult = await this.loRaDeviceAPIService.SearchAndLockForJoinAsync( gatewayID : this.configuration.GatewayID, devEUI : devEUI, devNonce : devNonce); if (searchDeviceResult.IsDevNonceAlreadyUsed) { // another gateway processed the join request. If we have it in the cache // with existing session keys, we need to invalidate that entry, to ensure // it gets re-fetched on the next message if (this.deviceCache.TryGetByDevEui(devEUI, out var someDevice) && someDevice.AppSKey != null) { _ = this.deviceCache.Remove(someDevice); this.logger.LogDebug("Device was removed from cache."); } this.logger.LogInformation("join refused: Join already processed by another gateway."); return(null); } if (searchDeviceResult?.Devices == null || searchDeviceResult.Devices.Count == 0) { this.logger.LogInformation(searchDeviceResult.RefusedMessage ?? "join refused: no devices found matching join request"); return(null); } var matchingDeviceInfo = searchDeviceResult.Devices[0]; if (deviceCache.TryGetByDevEui(matchingDeviceInfo.DevEUI, out var cachedDevice)) { // if we already have the device in the cache, then it is either from a previous // join rquest or it's a re-join. Both scenarios are ok, and we can use the cached // information. return(cachedDevice); } var loader = GetOrCreateJoinDeviceLoader(matchingDeviceInfo); var loRaDevice = await loader.LoadAsync(); if (!loader.CanCache) { RemoveJoinDeviceLoader(devEUI); } return(loRaDevice); }
public async Task GetDeviceForJoinRequestAsync_When_Device_Api_Throws_Error_Should_Not_Catch() { var devEui = new DevEui(1); var devNonce = new DevNonce(1); var apiService = new Mock <LoRaDeviceAPIServiceBase>(); apiService.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, devNonce)) .Throws(new InvalidOperationException()); using var target = new LoRaDeviceRegistry(ServerConfiguration, this.cache, apiService.Object, this.loraDeviceFactoryMock.Object, DeviceCache); Task Act() => target.GetDeviceForJoinRequestAsync(devEui, devNonce); _ = await Assert.ThrowsAsync <InvalidOperationException>(Act); // Device was searched by DevAddr apiService.VerifyAll(); }
// don't work with CFLIST atm private static byte[] CalculateKey(SessionKeyType type, AppNonce appNonce, NetId netId, DevNonce devNonce, AppKey appKey) { using var aes = Aes.Create("AesManaged"); var rawAppKey = new byte[AppKey.Size]; _ = appKey.Write(rawAppKey); aes.Key = rawAppKey; #pragma warning disable CA5358 // Review cipher mode usage with cryptography experts // Cipher is part of the LoRaWAN specification aes.Mode = CipherMode.ECB; #pragma warning restore CA5358 // Review cipher mode usage with cryptography experts aes.Padding = PaddingMode.None; var buffer = new byte[1 + AppNonce.Size + NetId.Size + DevNonce.Size + 7]; var pt = buffer.AsSpan(); Debug.Assert(pt.Length == 16); pt[0] = unchecked ((byte)type); pt = pt[1..];
public static AppSessionKey CalculateAppSessionKey(AppNonce appNonce, NetId netId, DevNonce devNonce, AppKey appKey) => AppSessionKey.Read(CalculateKey(SessionKeyType.Application, appNonce, netId, devNonce, appKey));
public static NetworkSessionKey CalculateNetworkSessionKey(AppNonce appNonce, NetId netId, DevNonce devNonce, AppKey appKey) => NetworkSessionKey.Read(CalculateKey(SessionKeyType.Network, appNonce, netId, devNonce, appKey));
/// <summary> /// Search and locks device for join request. /// </summary> public abstract Task <SearchDevicesResult> SearchAndLockForJoinAsync(string gatewayID, DevEui devEUI, DevNonce devNonce);
internal sealed record JoinMessageKey(JoinEui JoinEui, DevEui DevEui, DevNonce DevNonce);
/// <inheritdoc /> public sealed override Task <SearchDevicesResult> SearchAndLockForJoinAsync(string gatewayID, DevEui devEUI, DevNonce devNonce) => SearchDevicesAsync(gatewayID: gatewayID, devEui: devEUI, devNonce: devNonce);