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; } }
/// <summary> /// Code Performing the OTAA /// </summary> /// <param name="GatewayID"></param> /// <param name="DevEUI"></param> /// <param name="AppEUI"></param> /// <param name="DevNonce"></param> /// <returns></returns> public async Task <LoraDeviceInfo> PerformOTAAAsync(string GatewayID, string DevEUI, string AppEUI, string DevNonce, LoraDeviceInfo joinLoraDeviceInfo) { string AppSKey; string NwkSKey; string DevAddr; string AppNonce; IoTHubDeviceInfo iotHubDeviceInfo; if (DevEUI == null || AppEUI == null || DevNonce == null) { string errorMsg = "Missing devEUI/AppEUI/DevNonce in the OTAARequest"; //log.Error(errorMsg); Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error); return(null); } if (joinLoraDeviceInfo == null) { joinLoraDeviceInfo = new LoraDeviceInfo(); } joinLoraDeviceInfo.DevEUI = DevEUI; ////we don't have the key to access iot hub query the registry //if (joinLoraDeviceInfo.PrimaryKey == null) //{ Logger.Log(DevEUI, $"querying the registry for device key", Logger.LoggingLevel.Info); var client = this.serviceFacadeHttpClientProvider.GetHttpClient(); var url = String.Concat($"{FacadeServerUrl}GetDevice?", $"{FacadeAuthCode}" == "" ? "" : $"code={FacadeAuthCode}&", $"DevEUI={DevEUI}&DevNonce={DevNonce}&GatewayId={GatewayID}"); HttpResponseMessage response = await client.GetAsync(url); if (!response.IsSuccessStatusCode) { if (response.StatusCode == System.Net.HttpStatusCode.BadRequest) { var badReqResult = await response.Content.ReadAsStringAsync(); if (!String.IsNullOrEmpty(badReqResult) && badReqResult == "UsedDevNonce") { Logger.Log(DevEUI, $"DevNonce already used by this device", Logger.LoggingLevel.Info); return(null); } } Logger.Log(DevEUI, $"error calling façade api, check the azure function log. {response.ReasonPhrase}", Logger.LoggingLevel.Error); return(null); } var result = await response.Content.ReadAsStringAsync(); List <IoTHubDeviceInfo> iotHubDeviceInfos = ((List <IoTHubDeviceInfo>)JsonConvert.DeserializeObject(result, typeof(List <IoTHubDeviceInfo>))); if (iotHubDeviceInfos.Count == 0) { joinLoraDeviceInfo.IsJoinValid = false; joinLoraDeviceInfo.IsOurDevice = false; return(joinLoraDeviceInfo); } else { iotHubDeviceInfo = iotHubDeviceInfos[0]; joinLoraDeviceInfo.PrimaryKey = iotHubDeviceInfo.PrimaryKey; } //} joinLoraDeviceInfo.HubSender = new IoTHubConnector(joinLoraDeviceInfo.DevEUI, joinLoraDeviceInfo.PrimaryKey, this.configuration); //we don't have yet the twin data so we need to get it if (joinLoraDeviceInfo.AppKey == null || joinLoraDeviceInfo.AppEUI == null) { Logger.Log(DevEUI, $"getting twins for OTAA for device", Logger.LoggingLevel.Info); var twin = await joinLoraDeviceInfo.HubSender.GetTwinAsync(); if (twin != null) { joinLoraDeviceInfo.IsOurDevice = true; if (!twin.Properties.Desired.Contains("AppEUI")) { string errorMsg = $"missing AppEUI for OTAA for device"; Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error); return(null); } else { joinLoraDeviceInfo.AppEUI = twin.Properties.Desired["AppEUI"].Value; } //Make sure that there is the AppKey if not we cannot do the OTAA if (!twin.Properties.Desired.Contains("AppKey")) { string errorMsg = $"missing AppKey for OTAA for device"; Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error); return(null); } else { joinLoraDeviceInfo.AppKey = twin.Properties.Desired["AppKey"].Value; } //Make sure that is a new request and not a replay if (twin.Properties.Reported.Contains("DevNonce")) { joinLoraDeviceInfo.DevNonce = twin.Properties.Reported["DevNonce"]; } if (twin.Properties.Desired.Contains("GatewayID")) { joinLoraDeviceInfo.GatewayID = twin.Properties.Desired["GatewayID"].Value; } if (twin.Properties.Desired.Contains("SensorDecoder")) { joinLoraDeviceInfo.SensorDecoder = twin.Properties.Desired["SensorDecoder"].Value; } Logger.Log(DevEUI, $"done getting twins for OTAA device", Logger.LoggingLevel.Info); } else { Logger.Log(DevEUI, $"failed getting twins for OTAA device", Logger.LoggingLevel.Error); return(null); } } else { Logger.Log(DevEUI, $"using cached twins for OTAA device", Logger.LoggingLevel.Info); } //We add it to the cache so the next join has already the data, important for offline Cache.AddJoinRequestToCache(DevEUI, joinLoraDeviceInfo); //Make sure that there is the AppEUI and it matches if not we cannot do the OTAA if (joinLoraDeviceInfo.AppEUI != AppEUI) { string errorMsg = $"AppEUI for OTAA does not match for device"; Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error); return(null); } //Make sure that is a new request and not a replay if (!String.IsNullOrEmpty(joinLoraDeviceInfo.DevNonce) && joinLoraDeviceInfo.DevNonce == DevNonce) { string errorMsg = $"DevNonce already used by this device"; Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Info); joinLoraDeviceInfo.IsJoinValid = false; return(joinLoraDeviceInfo); } //Check that the device is joining through the linked gateway and not another if (!String.IsNullOrEmpty(joinLoraDeviceInfo.GatewayID) && joinLoraDeviceInfo.GatewayID.ToUpper() != GatewayID.ToUpper()) { string errorMsg = $"not the right gateway device-gateway:{joinLoraDeviceInfo.GatewayID} current-gateway:{GatewayID}"; Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Info); joinLoraDeviceInfo.IsJoinValid = false; return(joinLoraDeviceInfo); } byte[] netId = new byte[3] { 0, 0, 1 }; AppNonce = OTAAKeysGenerator.getAppNonce(); AppSKey = OTAAKeysGenerator.calculateKey(new byte[1] { 0x02 }, ConversionHelper.StringToByteArray(AppNonce), netId, ConversionHelper.StringToByteArray(DevNonce), ConversionHelper.StringToByteArray(joinLoraDeviceInfo.AppKey)); NwkSKey = OTAAKeysGenerator.calculateKey(new byte[1] { 0x01 }, ConversionHelper.StringToByteArray(AppNonce), netId, ConversionHelper.StringToByteArray(DevNonce), ConversionHelper.StringToByteArray(joinLoraDeviceInfo.AppKey));; DevAddr = OTAAKeysGenerator.getDevAddr(netId); joinLoraDeviceInfo.DevAddr = DevAddr; joinLoraDeviceInfo.NwkSKey = NwkSKey; joinLoraDeviceInfo.AppSKey = AppSKey; joinLoraDeviceInfo.AppNonce = AppNonce; joinLoraDeviceInfo.DevNonce = DevNonce; joinLoraDeviceInfo.NetId = ConversionHelper.ByteArrayToString(netId); //Accept the JOIN Request and the futher messages joinLoraDeviceInfo.IsJoinValid = true; return(joinLoraDeviceInfo); }
/// <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); } }
/// <summary> /// Process OTAA join request /// </summary> async Task <DownlinkPktFwdMessage> ProcessJoinRequestAsync(Rxpk rxpk, LoRaPayloadJoinRequest joinReq, DateTime startTimeProcessing) { var timeWatcher = new LoRaOperationTimeWatcher(this.loraRegion, startTimeProcessing); using (var processLogger = new ProcessLogger(timeWatcher)) { byte[] udpMsgForPktForwarder = new byte[0]; var devEUI = joinReq.GetDevEUIAsString(); var appEUI = joinReq.GetAppEUIAsString(); // set context to logger processLogger.SetDevEUI(devEUI); var devNonce = joinReq.GetDevNonceAsString(); Logger.Log(devEUI, $"join request received", LogLevel.Information); var loRaDevice = await this.deviceRegistry.GetDeviceForJoinRequestAsync(devEUI, appEUI, devNonce); if (loRaDevice == null) { return(null); } if (string.IsNullOrEmpty(loRaDevice.AppKey)) { Logger.Log(loRaDevice.DevEUI, "join refused: missing AppKey for OTAA device", LogLevel.Error); return(null); } if (loRaDevice.AppEUI != appEUI) { Logger.Log(devEUI, "join refused: AppEUI for OTAA does not match device", LogLevel.Error); return(null); } if (!joinReq.CheckMic(loRaDevice.AppKey)) { Logger.Log(devEUI, "join refused: invalid MIC", LogLevel.Error); return(null); } // Make sure that is a new request and not a replay if (!string.IsNullOrEmpty(loRaDevice.DevNonce) && loRaDevice.DevNonce == devNonce) { Logger.Log(devEUI, "join refused: DevNonce already used by this device", LogLevel.Information); loRaDevice.IsOurDevice = false; return(null); } // 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; return(null); } 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); 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); return(null); } Logger.Log(loRaDevice.DevEUI, $"saving join properties twins", LogLevel.Debug); var deviceUpdateSucceeded = await loRaDevice.UpdateAfterJoinAsync(devAddr, nwkSKey, appSKey, appNonce, devNonce, LoRaTools.Utils.ConversionHelper.ByteArrayToString(netId)); Logger.Log(loRaDevice.DevEUI, $"done saving join properties twins", LogLevel.Debug); if (!deviceUpdateSucceeded) { Logger.Log(devEUI, $"join refused: join request could not save twins", LogLevel.Error); return(null); } var windowToUse = timeWatcher.ResolveJoinAcceptWindowToUse(loRaDevice); if (windowToUse == 0) { Logger.Log(devEUI, $"join refused: processing of the join request took too long, sending no message", LogLevel.Information); return(null); } double freq = 0; string datr = null; uint tmst = 0; if (windowToUse == 1) { try { datr = this.loraRegion.GetDownstreamDR(rxpk); freq = this.loraRegion.GetDownstreamChannelFrequency(rxpk); } catch (RegionLimitException ex) { Logger.Log(devEUI, ex.ToString(), LogLevel.Error); } // set tmst for the normal case tmst = rxpk.Tmst + this.loraRegion.Join_accept_delay1 * 1000000; } else { Logger.Log(devEUI, $"processing of the join request took too long, using second join accept receive window", LogLevel.Information); tmst = rxpk.Tmst + this.loraRegion.Join_accept_delay2 * 1000000; if (string.IsNullOrEmpty(this.configuration.Rx2DataRate)) { Logger.Log(devEUI, $"using standard second receive windows for join request", LogLevel.Information); // using EU fix DR for RX2 freq = this.loraRegion.RX2DefaultReceiveWindows.frequency; datr = this.loraRegion.DRtoConfiguration[RegionFactory.CurrentRegion.RX2DefaultReceiveWindows.dr].configuration; } else { Logger.Log(devEUI, $"using custom second receive windows for join request", LogLevel.Information); freq = this.configuration.Rx2DataFrequency; datr = this.configuration.Rx2DataRate; } } loRaDevice.IsOurDevice = true; this.deviceRegistry.UpdateDeviceAfterJoin(loRaDevice); // Build join accept downlink message Array.Reverse(netId); Array.Reverse(appNonceBytes); return(this.CreateJoinAcceptDownlinkMessage( netId, loRaDevice.AppKey, devAddr, appNonceBytes, datr, freq, tmst, devEUI)); } }