public ConcentratorDeduplicationResult CheckDuplicateData(LoRaRequest loRaRequest, LoRaDevice loRaDevice) { _ = loRaRequest ?? throw new ArgumentNullException(nameof(loRaRequest)); _ = loRaDevice ?? throw new ArgumentNullException(nameof(loRaDevice)); var key = CreateCacheKey((LoRaPayloadData)loRaRequest.Payload, loRaDevice); if (EnsureFirstMessageInCache(key, loRaRequest, out var previousStation)) { return(ConcentratorDeduplicationResult.NotDuplicate); } var result = ConcentratorDeduplicationResult.SoftDuplicateDueToDeduplicationStrategy; if (previousStation == loRaRequest.StationEui) { result = ConcentratorDeduplicationResult.DuplicateDueToResubmission; } else if (loRaDevice.Deduplication == DeduplicationMode.Drop) { result = ConcentratorDeduplicationResult.Duplicate; } this.logger.LogDebug($"Data message received from station {loRaRequest.StationEui}. Marked as {result} {Constants.MessageAlreadyEncountered}."); return(result); }
void HandlePreferredGatewayChanges( LoRaRequest request, LoRaDevice loRaDevice, FunctionBundlerResult bundlerResult) { var preferredGatewayResult = bundlerResult.PreferredGatewayResult; if (preferredGatewayResult.IsSuccessful()) { var currentIsPreferredGateway = bundlerResult.PreferredGatewayResult.PreferredGatewayID == this.configuration.GatewayID; var preferredGatewayChanged = bundlerResult.PreferredGatewayResult.PreferredGatewayID != loRaDevice.PreferredGatewayID; if (preferredGatewayChanged) { Logger.Log(loRaDevice.DevEUI, $"preferred gateway changed from '{loRaDevice.PreferredGatewayID}' to '{preferredGatewayResult.PreferredGatewayID}'", LogLevel.Debug); } if (preferredGatewayChanged) { loRaDevice.UpdatePreferredGatewayID(bundlerResult.PreferredGatewayResult.PreferredGatewayID, acceptChanges: !currentIsPreferredGateway); } // Save the region if we are the winning gateway and it changed if (request.Region.LoRaRegion != loRaDevice.LoRaRegion) { loRaDevice.UpdateRegion(request.Region.LoRaRegion, acceptChanges: !currentIsPreferredGateway); } } else { Logger.Log(loRaDevice.DevEUI, $"failed to resolve preferred gateway: {preferredGatewayResult}", LogLevel.Error); } }
private async Task <LoRaADRResult> PerformADR(LoRaRequest request, LoRaDevice loRaDevice, LoRaPayloadData loraPayload, uint payloadFcnt, LoRaADRResult loRaADRResult, ILoRaDeviceFrameCounterUpdateStrategy frameCounterStrategy) { var loRaADRManager = this.loRaADRManagerFactory.Create(this.loRaADRStrategyProvider, frameCounterStrategy, loRaDevice); var loRaADRTableEntry = new LoRaADRTableEntry() { DevEUI = loRaDevice.DevEUI, FCnt = payloadFcnt, GatewayId = this.configuration.GatewayID, Snr = request.Rxpk.Lsnr }; // If the ADR req bit is not set we don't perform rate adaptation. if (!loraPayload.IsAdrReq) { _ = loRaADRManager.StoreADREntryAsync(loRaADRTableEntry); } else { loRaADRResult = await loRaADRManager.CalculateADRResultAndAddEntryAsync( loRaDevice.DevEUI, this.configuration.GatewayID, payloadFcnt, loRaDevice.FCntDown, (float)request.Rxpk.RequiredSnr, request.Region.GetDRFromFreqAndChan(request.Rxpk.Datr), request.Region.TXPowertoMaxEIRP.Count - 1, request.Region.MaxADRDataRate, loRaADRTableEntry); Logger.Log(loRaDevice.DevEUI, $"device sent ADR ack request, computing an answer", LogLevel.Debug); } return(loRaADRResult); }
public ILoRaDeviceRequestQueue GetLoRaRequestQueue(LoRaRequest request) { if (request is null) { throw new ArgumentNullException(nameof(request)); } var devAddr = request.Payload.DevAddr; if (this.cache.TryGetValue <DeviceLoaderSynchronizer>(GetDevLoaderCacheKey(devAddr), out var deviceLoader)) { return(deviceLoader); } if (this.deviceCache.TryGetForPayload(request.Payload, out var cachedDevice)) { this.logger.LogDebug("device in cache"); if (cachedDevice.IsOurDevice) { return(cachedDevice); } return(new ExternalGatewayLoRaRequestQueue(cachedDevice, this.loggerFactory.CreateLogger <ExternalGatewayLoRaRequestQueue>())); } // not in cache, need to make a single search by dev addr return(GetOrCreateLoadingDevicesRequestQueue(devAddr)); }
/// <summary> /// Dispatches a request. /// </summary> public void DispatchRequest(LoRaRequest request) { if (request is null) { throw new ArgumentNullException(nameof(request)); } if (request.Payload is null) { throw new LoRaProcessingException(nameof(request.Payload), LoRaProcessingErrorCode.PayloadNotSet); } if (request.Region is null) { throw new LoRaProcessingException(nameof(request.Region), LoRaProcessingErrorCode.RegionNotSet); } var loggingRequest = new LoggingLoRaRequest(request, this.loggerFactory.CreateLogger <LoggingLoRaRequest>(), this.d2cMessageDeliveryLatencyHistogram); if (request.Payload.MessageType == MacMessageType.JoinRequest) { DispatchLoRaJoinRequest(loggingRequest); } else if (request.Payload.MessageType is MacMessageType.UnconfirmedDataUp or MacMessageType.ConfirmedDataUp) { DispatchLoRaDataMessage(loggingRequest); }
public FunctionBundler CreateIfRequired( string gatewayId, LoRaPayloadData loRaPayload, LoRaDevice loRaDevice, IDeduplicationStrategyFactory deduplicationFactory, LoRaRequest request) { if (!string.IsNullOrEmpty(loRaDevice.GatewayID)) { // single gateway mode return(null); } var context = new FunctionBundlerExecutionContext { DeduplicationFactory = deduplicationFactory, FCntDown = loRaDevice.FCntDown, FCntUp = loRaPayload.GetFcnt(), GatewayId = gatewayId, LoRaDevice = loRaDevice, LoRaPayload = loRaPayload, Request = request }; var qualifyingExecutionItems = new List <IFunctionBundlerExecutionItem>(functionItems.Count); for (var i = 0; i < functionItems.Count; i++) { var itm = functionItems[i]; if (itm.RequiresExecution(context)) { qualifyingExecutionItems.Add(itm); } } if (qualifyingExecutionItems.Count == 0) { return(null); } var bundlerRequest = new FunctionBundlerRequest { ClientFCntDown = context.FCntDown, ClientFCntUp = context.FCntUp, GatewayId = gatewayId, Rssi = context.Request.Rxpk.Rssi, }; for (var i = 0; i < qualifyingExecutionItems.Count; i++) { qualifyingExecutionItems[i].Prepare(context, bundlerRequest); } Logger.Log(loRaDevice.DevEUI, "FunctionBundler request: ", bundlerRequest, LogLevel.Debug); return(new FunctionBundler(loRaDevice.DevEUI, this.deviceApi, bundlerRequest, qualifyingExecutionItems, context)); }
private bool ValidateCloudToDeviceMessage(LoRaDevice loRaDevice, LoRaRequest request, IReceivedLoRaCloudToDeviceMessage cloudToDeviceMsg) { if (!cloudToDeviceMsg.IsValid(out var errorMessage)) { Logger.Log(loRaDevice.DevEUI, errorMessage, LogLevel.Error); return(false); } var rxpk = request.Rxpk; var loRaRegion = request.Region; uint maxPayload; // If preferred Window is RX2, this is the max. payload if (loRaDevice.PreferredWindow == Constants.RECEIVE_WINDOW_2) { // Get max. payload size for RX2, considering possilbe user provided Rx2DataRate if (string.IsNullOrEmpty(this.configuration.Rx2DataRate)) { maxPayload = loRaRegion.DRtoConfiguration[loRaRegion.RX2DefaultReceiveWindows.dr].maxPyldSize; } else { maxPayload = loRaRegion.GetMaxPayloadSize(this.configuration.Rx2DataRate); } } // Otherwise, it is RX1. else { maxPayload = loRaRegion.GetMaxPayloadSize(loRaRegion.GetDownstreamDR(rxpk)); } // Deduct 8 bytes from max payload size. maxPayload -= Constants.LORA_PROTOCOL_OVERHEAD_SIZE; // Calculate total C2D message size based on optional C2D Mac commands. var totalPayload = cloudToDeviceMsg.GetPayload()?.Length ?? 0; if (cloudToDeviceMsg.MacCommands?.Count > 0) { foreach (MacCommand macCommand in cloudToDeviceMsg.MacCommands) { totalPayload += macCommand.Length; } } // C2D message and optional C2D Mac commands are bigger than max. payload size: REJECT. // This message can never be delivered. if (totalPayload > maxPayload) { Logger.Log(loRaDevice.DevEUI, $"message payload size ({totalPayload}) exceeds maximum allowed payload size ({maxPayload}) in cloud to device message", LogLevel.Error); return(false); } return(true); }
private void AddToDeviceQueue(LoRaDevice device, LoRaRequest request) { if (device.IsOurDevice) { device.Queue(request); } else { Logger.Log(device.DevEUI, $"device is not our device, ignore message", LogLevel.Debug); request.NotifyFailed(device, LoRaDeviceRequestFailedReason.BelongsToAnotherGateway); } }
public FunctionBundlerExecutionContext(string gatewayId, uint fCntUp, uint fCntDown, LoRaPayloadData loRaPayload, LoRaDevice loRaDevice, IDeduplicationStrategyFactory deduplicationFactory, LoRaRequest request) { GatewayId = gatewayId; FCntUp = fCntUp; FCntDown = fCntDown; LoRaPayload = loRaPayload; LoRaDevice = loRaDevice; DeduplicationFactory = deduplicationFactory; Request = request; }
void DispatchLoRaDataMessage(LoRaRequest request) { var loRaPayload = (LoRaPayloadData)request.Payload; if (!this.IsValidNetId(loRaPayload)) { Logger.Log(ConversionHelper.ByteArrayToString(loRaPayload.DevAddr), $"device is using another network id, ignoring this message (network: {this.configuration.NetId}, devAddr: {loRaPayload.GetDevAddrNetID()})", LogLevel.Debug); request.NotifyFailed(LoRaDeviceRequestFailedReason.InvalidNetId); return; } this.deviceRegistry.GetLoRaRequestQueue(request).Queue(request); }
public ConcentratorDeduplicationResult CheckDuplicateJoin(LoRaRequest loRaRequest) { _ = loRaRequest ?? throw new ArgumentNullException(nameof(loRaRequest)); var key = CreateCacheKey((LoRaPayloadJoinRequest)loRaRequest.Payload); if (EnsureFirstMessageInCache(key, loRaRequest, out _)) { return(ConcentratorDeduplicationResult.NotDuplicate); } var result = ConcentratorDeduplicationResult.Duplicate; this.logger.LogDebug($"Join received from station {loRaRequest.StationEui}. Marked as {result} {Constants.MessageAlreadyEncountered}."); return(result); }
public void Queue(LoRaRequest request) { var requestAddedToQueue = false; if (this.state != LoaderState.Finished) { lock (this.queueLock) { // add to the queue only if loading is not yet finished if (this.state < LoaderState.DispatchingQueuedItems) { this.queuedRequests.Add(request); requestAddedToQueue = true; } } } if (!requestAddedToQueue) { var hasDeviceFromAnotherGateway = false; foreach (var device in this.existingDevices.Values) { if (device.IsOurDevice) { if (device.ValidateMic(request.Payload)) { this.AddToDeviceQueue(device, request); return; } } else { hasDeviceFromAnotherGateway = true; } } // not handled, raised failed event var failedReason = hasDeviceFromAnotherGateway ? LoRaDeviceRequestFailedReason.BelongsToAnotherGateway : this.loadingDevicesFailed ? LoRaDeviceRequestFailedReason.ApplicationError : this.existingDevices.Count > 0 ? LoRaDeviceRequestFailedReason.NotMatchingDeviceByMicCheck : LoRaDeviceRequestFailedReason.NotMatchingDeviceByDevAddr; this.LogRequestFailed(request, failedReason); request.NotifyFailed(failedReason); } }
private bool EnsureFirstMessageInCache(object key, LoRaRequest loRaRequest, out StationEui previousStation) { var stationEui = loRaRequest.StationEui; lock (cacheLock) { if (!this.cache.TryGetValue(key, out previousStation)) { _ = this.cache.Set(key, stationEui, new MemoryCacheEntryOptions() { SlidingExpiration = DefaultExpiration }); return(true); } } return(false); }
private void LogRequestFailed(LoRaRequest request, LoRaDeviceRequestFailedReason failedReason) { var deviceId = ConversionHelper.ByteArrayToString(request.Payload.DevAddr); switch (failedReason) { case LoRaDeviceRequestFailedReason.NotMatchingDeviceByMicCheck: Logger.Log(deviceId, $"with devAddr {ConversionHelper.ByteArrayToString(request.Payload.DevAddr)} check MIC failed", LogLevel.Debug); break; case LoRaDeviceRequestFailedReason.NotMatchingDeviceByDevAddr: Logger.Log(deviceId, $"device is not our device, ignore message", LogLevel.Debug); break; case LoRaDeviceRequestFailedReason.ApplicationError: Logger.Log(deviceId, "problem resolving device", LogLevel.Error); break; } }
private static bool ValidateRequest(LoRaRequest request, bool isFrameCounterFromNewlyStartedDevice, uint payloadFcnt, LoRaDevice loRaDevice, bool requiresConfirmation, out bool isConfirmedResubmit, out LoRaDeviceRequestProcessResult result) { isConfirmedResubmit = false; result = null; if (!isFrameCounterFromNewlyStartedDevice && payloadFcnt <= loRaDevice.FCntUp) { // if it is confirmed most probably we did not ack in time before or device lost the ack packet so we should continue but not send the msg to iothub if (requiresConfirmation && payloadFcnt == loRaDevice.FCntUp) { if (!loRaDevice.ValidateConfirmResubmit(payloadFcnt)) { Logger.Log(loRaDevice.DevEUI, $"resubmit from confirmed message exceeds threshold of {LoRaDevice.MaxConfirmationResubmitCount}, message ignored, msg: {payloadFcnt} server: {loRaDevice.FCntUp}", LogLevel.Error); result = new LoRaDeviceRequestProcessResult(loRaDevice, request, LoRaDeviceRequestFailedReason.ConfirmationResubmitThresholdExceeded); return(false); } isConfirmedResubmit = true; Logger.Log(loRaDevice.DevEUI, $"resubmit from confirmed message detected, msg: {payloadFcnt} server: {loRaDevice.FCntUp}", LogLevel.Information); } else { Logger.Log(loRaDevice.DevEUI, $"invalid frame counter, message ignored, msg: {payloadFcnt} server: {loRaDevice.FCntUp}", LogLevel.Debug); result = new LoRaDeviceRequestProcessResult(loRaDevice, request, LoRaDeviceRequestFailedReason.InvalidFrameCounter); return(false); } } // ensuring the framecount difference between the node and the server // is <= MAX_FCNT_GAP var diff = payloadFcnt > loRaDevice.FCntUp ? payloadFcnt - loRaDevice.FCntUp : loRaDevice.FCntUp - payloadFcnt; var valid = diff <= Constants.MAX_FCNT_GAP; if (!valid) { Logger.Log(loRaDevice.DevEUI, $"invalid frame counter (diverges too much), message ignored, msg: {payloadFcnt} server: {loRaDevice.FCntUp}", LogLevel.Error); result = new LoRaDeviceRequestProcessResult(loRaDevice, request, LoRaDeviceRequestFailedReason.InvalidFrameCounter); } return(valid); }
private async Task <bool> SendDeviceEventAsync(LoRaRequest request, LoRaDevice loRaDevice, LoRaOperationTimeWatcher timeWatcher, object decodedValue, DeduplicationResult deduplicationResult, byte[] decryptedPayloadData) { var loRaPayloadData = (LoRaPayloadData)request.Payload; var deviceTelemetry = new LoRaDeviceTelemetry(request.Rxpk, loRaPayloadData, decodedValue, decryptedPayloadData) { DeviceEUI = loRaDevice.DevEUI, GatewayID = this.configuration.GatewayID, Edgets = (long)(timeWatcher.Start - DateTime.UnixEpoch).TotalMilliseconds }; if (deduplicationResult != null && deduplicationResult.IsDuplicate) { deviceTelemetry.DupMsg = true; } Dictionary <string, string> eventProperties = null; if (loRaPayloadData.IsUpwardAck()) { eventProperties = new Dictionary <string, string>(); Logger.Log(loRaDevice.DevEUI, $"message ack received for cloud to device message id {loRaDevice.LastConfirmedC2DMessageID}", LogLevel.Information); eventProperties.Add(Constants.C2D_MSG_PROPERTY_VALUE_NAME, loRaDevice.LastConfirmedC2DMessageID ?? Constants.C2D_MSG_ID_PLACEHOLDER); loRaDevice.LastConfirmedC2DMessageID = null; } this.ProcessAndSendMacCommands(loRaPayloadData, ref eventProperties); if (await loRaDevice.SendEventAsync(deviceTelemetry, eventProperties)) { string payloadAsRaw = null; if (deviceTelemetry.Data != null) { payloadAsRaw = JsonConvert.SerializeObject(deviceTelemetry.Data, Formatting.None); } Logger.Log(loRaDevice.DevEUI, $"message '{payloadAsRaw}' sent to hub", LogLevel.Information); return(true); } return(false); }
protected virtual void ProcessRequest(LoRaRequest request) { if (LoadingDevicesFailed) { LogAndNotifyFailedRequest(LoRaDeviceRequestFailedReason.ApplicationError); return; } if (!this.loraDeviceCache.HasRegistrations(this.devAddr)) { LogAndNotifyFailedRequest(LoRaDeviceRequestFailedReason.NotMatchingDeviceByDevAddr); return; } if (this.loraDeviceCache.TryGetForPayload(request.Payload, out var device)) { if (device.IsOurDevice) { device.Queue(request); } else { LogAndNotifyFailedRequest(LoRaDeviceRequestFailedReason.BelongsToAnotherGateway); } } else if (this.loraDeviceCache.HasRegistrationsForOtherGateways(this.devAddr)) { LogAndNotifyFailedRequest(LoRaDeviceRequestFailedReason.BelongsToAnotherGateway); } else { LogAndNotifyFailedRequest(LoRaDeviceRequestFailedReason.NotMatchingDeviceByMicCheck); } void LogAndNotifyFailedRequest(LoRaDeviceRequestFailedReason reason) { LogRequestFailed(request, reason); request.NotifyFailed(reason); } }
/// <summary> /// Dispatches a request /// </summary> public void DispatchRequest(LoRaRequest request) { if (!LoRaPayload.TryCreateLoRaPayload(request.Rxpk, out LoRaPayload loRaPayload)) { Logger.Log("There was a problem in decoding the Rxpk", LogLevel.Error); request.NotifyFailed(LoRaDeviceRequestFailedReason.InvalidRxpk); return; } if (this.loraRegion == null) { if (!RegionManager.TryResolveRegion(request.Rxpk, out var currentRegion)) { // log is generated in Region factory // move here once V2 goes GA request.NotifyFailed(LoRaDeviceRequestFailedReason.InvalidRegion); return; } this.loraRegion = currentRegion; } request.SetPayload(loRaPayload); request.SetRegion(this.loraRegion); var loggingRequest = new LoggingLoRaRequest(request); if (loRaPayload.LoRaMessageType == LoRaMessageType.JoinRequest) { this.DispatchLoRaJoinRequest(loggingRequest); } else if (loRaPayload.LoRaMessageType == LoRaMessageType.UnconfirmedDataUp || loRaPayload.LoRaMessageType == LoRaMessageType.ConfirmedDataUp) { this.DispatchLoRaDataMessage(loggingRequest); } else { Logger.Log("Unknwon message type in rxpk, message ignored", LogLevel.Error); } }
public void Queue(LoRaRequest request) { var requestAddedToQueue = false; if (this.state != LoaderState.Finished) { lock (this.queueLock) { // add to the queue only if loading is not yet finished if (this.state < LoaderState.DispatchingQueuedItems) { this.queuedRequests.Add(request); requestAddedToQueue = true; } } } if (!requestAddedToQueue) { ProcessRequest(request); } }
private async Task <FunctionBundlerResult> TryUseBundler(LoRaRequest request, LoRaDevice loRaDevice, LoRaPayloadData loraPayload, bool useMultipleGateways) { FunctionBundlerResult bundlerResult = null; if (useMultipleGateways) { var bundler = this.functionBundlerProvider.CreateIfRequired(this.configuration.GatewayID, loraPayload, loRaDevice, this.deduplicationFactory, request); if (bundler != null) { bundlerResult = await bundler.Execute(); if (bundlerResult.NextFCntDown.HasValue) { // we got a new framecounter down. Make sure this // gets saved eventually to the twins loRaDevice.SetFcntDown(bundlerResult.NextFCntDown.Value); } } } return(bundlerResult); }
public ILoRaDeviceRequestQueue GetLoRaRequestQueue(LoRaRequest request) { var devAddr = ConversionHelper.ByteArrayToString(request.Payload.DevAddr); var devicesMatchingDevAddr = this.InternalGetCachedDevicesForDevAddr(devAddr); // If already in cache, return quickly if (devicesMatchingDevAddr.Count > 0) { var cachedMatchingDevice = devicesMatchingDevAddr.Values.FirstOrDefault(x => this.IsValidDeviceForPayload(x, (LoRaPayloadData)request.Payload, logError: false)); if (cachedMatchingDevice != null) { Logger.Log(cachedMatchingDevice.DevEUI, "device in cache", LogLevel.Debug); if (cachedMatchingDevice.IsOurDevice) { return(cachedMatchingDevice); } return(new ExternalGatewayLoRaRequestQueue(cachedMatchingDevice)); } } // not in cache, need to make a single search by dev addr return(this.GetOrCreateLoadingDevicesRequestQueue(devAddr)); }
private void LogRequestFailed(LoRaRequest request, LoRaDeviceRequestFailedReason failedReason) { switch (failedReason) { case LoRaDeviceRequestFailedReason.NotMatchingDeviceByMicCheck: this.logger.LogDebug($"with devAddr {request.Payload.DevAddr} check MIC failed"); break; case LoRaDeviceRequestFailedReason.BelongsToAnotherGateway: case LoRaDeviceRequestFailedReason.NotMatchingDeviceByDevAddr: this.logger.LogDebug("device is not our device, ignore message"); break; case LoRaDeviceRequestFailedReason.ApplicationError: this.logger.LogError("problem resolving device"); break; case LoRaDeviceRequestFailedReason.InvalidNetId: case LoRaDeviceRequestFailedReason.InvalidUpstreamMessage: case LoRaDeviceRequestFailedReason.InvalidRegion: case LoRaDeviceRequestFailedReason.UnknownDevice: case LoRaDeviceRequestFailedReason.InvalidJoinRequest: case LoRaDeviceRequestFailedReason.HandledByAnotherGateway: case LoRaDeviceRequestFailedReason.JoinDevNonceAlreadyUsed: case LoRaDeviceRequestFailedReason.JoinMicCheckFailed: case LoRaDeviceRequestFailedReason.ReceiveWindowMissed: case LoRaDeviceRequestFailedReason.ConfirmationResubmitThresholdExceeded: case LoRaDeviceRequestFailedReason.InvalidFrameCounter: case LoRaDeviceRequestFailedReason.IoTHubProblem: case LoRaDeviceRequestFailedReason.DeduplicationDrop: case LoRaDeviceRequestFailedReason.DeviceClientConnectionFailed: default: this.logger.LogDebug("device request failed"); break; } }
/// <summary> /// Creates downlink message with ack for confirmation or cloud to device message. /// </summary> internal static DownlinkMessageBuilderResponse CreateDownlinkMessage( NetworkServerConfiguration configuration, LoRaDevice loRaDevice, LoRaRequest request, LoRaOperationTimeWatcher timeWatcher, IReceivedLoRaCloudToDeviceMessage cloudToDeviceMessage, bool fpending, uint fcntDown, LoRaADRResult loRaADRResult, ILogger logger) { var fcntDownToSend = ValidateAndConvert16bitFCnt(fcntDown); var upstreamPayload = (LoRaPayloadData)request.Payload; var radioMetadata = request.RadioMetadata; var loRaRegion = request.Region; var isMessageTooLong = false; // default fport var fctrl = FrameControlFlags.None; if (upstreamPayload.MessageType == MacMessageType.ConfirmedDataUp) { // Confirm receiving message to device fctrl = FrameControlFlags.Ack; } // Calculate receive window var receiveWindow = timeWatcher.ResolveReceiveWindowToUse(loRaDevice); if (receiveWindow is null) { // No valid receive window. Abandon the message isMessageTooLong = true; return(new DownlinkMessageBuilderResponse(null, isMessageTooLong, receiveWindow)); } var rndToken = new byte[2]; RndKeysGenerator.GetBytes(rndToken); DataRateIndex datr; Hertz freq; var deviceJoinInfo = request.Region.LoRaRegion == LoRaRegionType.CN470RP2 ? new DeviceJoinInfo(loRaDevice.ReportedCN470JoinChannel, loRaDevice.DesiredCN470JoinChannel) : null; if (loRaRegion is DwellTimeLimitedRegion someRegion) { someRegion.UseDwellTimeSetting(loRaDevice.ReportedDwellTimeSetting); } if (receiveWindow is ReceiveWindow2) { freq = loRaRegion.GetDownstreamRX2Freq(configuration.Rx2Frequency, deviceJoinInfo, logger); datr = loRaRegion.GetDownstreamRX2DataRate(configuration.Rx2DataRate, loRaDevice.ReportedRX2DataRate, deviceJoinInfo, logger); } else { datr = loRaRegion.GetDownstreamDataRate(radioMetadata.DataRate, loRaDevice.ReportedRX1DROffset); // The logic for passing CN470 join channel will change as part of #561 if (!loRaRegion.TryGetDownstreamChannelFrequency(radioMetadata.Frequency, upstreamDataRate: radioMetadata.DataRate, deviceJoinInfo: deviceJoinInfo, downstreamFrequency: out freq)) { logger.LogError("there was a problem in setting the frequency in the downstream message settings"); return(new DownlinkMessageBuilderResponse(null, false, receiveWindow)); } } var rx2 = new ReceiveWindow(loRaRegion.GetDownstreamRX2DataRate(configuration.Rx2DataRate, loRaDevice.ReportedRX2DataRate, deviceJoinInfo, logger), loRaRegion.GetDownstreamRX2Freq(configuration.Rx2Frequency, deviceJoinInfo, logger)); // get max. payload size based on data rate from LoRaRegion var maxPayloadSize = loRaRegion.GetMaxPayloadSize(datr); // Deduct 8 bytes from max payload size. maxPayloadSize -= Constants.LoraProtocolOverheadSize; var availablePayloadSize = maxPayloadSize; var macCommands = new List <MacCommand>(); FramePort?fport = null; var requiresDeviceAcknowlegement = false; var macCommandType = Cid.Zero; byte[] frmPayload = null; if (cloudToDeviceMessage != null) { // Get C2D Mac coomands var macCommandsC2d = PrepareMacCommandAnswer(null, cloudToDeviceMessage.MacCommands, request, null, logger); // Calculate total C2D payload size var totalC2dSize = cloudToDeviceMessage.GetPayload()?.Length ?? 0; totalC2dSize += macCommandsC2d?.Sum(x => x.Length) ?? 0; // Total C2D payload will fit if (availablePayloadSize >= totalC2dSize) { // Add frmPayload frmPayload = cloudToDeviceMessage.GetPayload(); // Add C2D Mac commands if (macCommandsC2d?.Count > 0) { foreach (var macCommand in macCommandsC2d) { macCommands.Add(macCommand); } } // Deduct frmPayload size from available payload size, continue processing and log availablePayloadSize -= (uint)totalC2dSize; if (cloudToDeviceMessage.Confirmed) { requiresDeviceAcknowlegement = true; loRaDevice.LastConfirmedC2DMessageID = cloudToDeviceMessage.MessageId ?? Constants.C2D_MSG_ID_PLACEHOLDER; } if (cloudToDeviceMessage.Fport.IsAppSpecific() || cloudToDeviceMessage.Fport.IsReserved()) { fport = cloudToDeviceMessage.Fport; } logger.LogInformation($"cloud to device message: {((frmPayload?.Length ?? 0) == 0 ? "empty" : frmPayload.ToHex())}, id: {cloudToDeviceMessage.MessageId ?? "undefined"}, fport: {(byte)(fport ?? FramePort.MacCommand)}, confirmed: {requiresDeviceAcknowlegement}, cidType: {macCommandType}, macCommand: {macCommands.Count > 0}"); } else { // Flag message to be abandoned and log` logger.LogDebug($"cloud to device message: empty, id: {cloudToDeviceMessage.MessageId ?? "undefined"}, fport: 0, confirmed: {requiresDeviceAcknowlegement} too long for current receive window. Abandoning."); isMessageTooLong = true; } } // Get request Mac commands var macCommandsRequest = PrepareMacCommandAnswer(upstreamPayload.MacCommands, null, request, loRaADRResult, logger); // Calculate request Mac commands size var macCommandsRequestSize = macCommandsRequest?.Sum(x => x.Length) ?? 0; // Try adding request Mac commands if (availablePayloadSize >= macCommandsRequestSize) { if (macCommandsRequest?.Count > 0) { foreach (var macCommand in macCommandsRequest) { macCommands.Add(macCommand); } } } if (fpending || isMessageTooLong) { fctrl |= FrameControlFlags.DownlinkFramePending; } if (upstreamPayload.IsDataRateNetworkControlled) { fctrl |= FrameControlFlags.Adr; } var msgType = requiresDeviceAcknowlegement ? MacMessageType.ConfirmedDataDown : MacMessageType.UnconfirmedDataDown; var ackLoRaMessage = new LoRaPayloadData( msgType, upstreamPayload.DevAddr, fctrl, fcntDownToSend, macCommands, fport, frmPayload, 1, loRaDevice.Supports32BitFCnt ? fcntDown : null); // following calculation is making sure that ReportedRXDelay is chosen if not default, // todo: check the device twin preference if using confirmed or unconfirmed down var downlinkMessage = BuildDownstreamMessage(loRaDevice, request.StationEui, logger, radioMetadata.UpInfo.Xtime, receiveWindow is ReceiveWindow2 ? null : new ReceiveWindow(datr, freq), rx2, loRaDevice.ReportedRXDelay, ackLoRaMessage, loRaDevice.ClassType, radioMetadata.UpInfo.AntennaPreference); if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug($"{ackLoRaMessage.MessageType} {JsonConvert.SerializeObject(downlinkMessage)}"); } return(new DownlinkMessageBuilderResponse(downlinkMessage, isMessageTooLong, receiveWindow)); }
public void Queue(LoRaRequest request) { logger.LogDebug("device is not our device, ignore message"); request.NotifyFailed(this.loRaDevice, LoRaDeviceRequestFailedReason.BelongsToAnotherGateway); }
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 void DispatchRequest(LoRaRequest request) { // Unobserved task exceptions are logged as part of ProcessJoinRequestAsync. _ = Task.Run(() => ProcessJoinRequestAsync(request)); }
/// <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); } }
public void DispatchRequest(LoRaRequest request) { Task.Run(async() => await this.ProcessJoinRequestAsync(request)); }
/// <summary> /// Creates downlink message with ack for confirmation or cloud to device message /// </summary> internal static DownlinkMessageBuilderResponse CreateDownlinkMessage( NetworkServerConfiguration configuration, LoRaDevice loRaDevice, LoRaRequest request, LoRaOperationTimeWatcher timeWatcher, IReceivedLoRaCloudToDeviceMessage cloudToDeviceMessage, bool fpending, uint fcntDown, LoRaADRResult loRaADRResult) { var fcntDownToSend = ValidateAndConvert16bitFCnt(fcntDown); var upstreamPayload = (LoRaPayloadData)request.Payload; var rxpk = request.Rxpk; var loRaRegion = request.Region; bool isMessageTooLong = false; // default fport byte fctrl = 0; if (upstreamPayload.LoRaMessageType == LoRaMessageType.ConfirmedDataUp) { // Confirm receiving message to device fctrl = (byte)FctrlEnum.Ack; } // Calculate receive window var receiveWindow = timeWatcher.ResolveReceiveWindowToUse(loRaDevice); if (receiveWindow == Constants.INVALID_RECEIVE_WINDOW) { // No valid receive window. Abandon the message isMessageTooLong = true; return(new DownlinkMessageBuilderResponse(null, isMessageTooLong)); } byte[] rndToken = new byte[2]; lock (RndLock) { RndDownlinkMessageBuilder.NextBytes(rndToken); } string datr; double freq; long tmst; if (receiveWindow == Constants.RECEIVE_WINDOW_2) { tmst = rxpk.Tmst + CalculateTime(timeWatcher.GetReceiveWindow2Delay(loRaDevice), loRaDevice.ReportedRXDelay); (freq, datr) = loRaRegion.GetDownstreamRX2DRAndFreq(loRaDevice.DevEUI, configuration.Rx2DataRate, configuration.Rx2DataFrequency, loRaDevice.ReportedRX2DataRate); } else { datr = loRaRegion.GetDownstreamDR(rxpk, (uint)loRaDevice.ReportedRX1DROffset); if (datr == null) { Logger.Log(loRaDevice.DevEUI, "there was a problem in setting the data rate in the downstream message packet forwarder settings", LogLevel.Error); return(new DownlinkMessageBuilderResponse(null, false)); } if (!loRaRegion.TryGetUpstreamChannelFrequency(rxpk, out freq)) { Logger.Log(loRaDevice.DevEUI, "there was a problem in setting the frequency in the downstream message packet forwarder settings", LogLevel.Error); return(new DownlinkMessageBuilderResponse(null, false)); } tmst = rxpk.Tmst + CalculateTime(timeWatcher.GetReceiveWindow1Delay(loRaDevice), loRaDevice.ReportedRXDelay); } // get max. payload size based on data rate from LoRaRegion var maxPayloadSize = loRaRegion.GetMaxPayloadSize(datr); // Deduct 8 bytes from max payload size. maxPayloadSize -= Constants.LORA_PROTOCOL_OVERHEAD_SIZE; var availablePayloadSize = maxPayloadSize; var macCommands = new List <MacCommand>(); byte?fport = null; var requiresDeviceAcknowlegement = false; var macCommandType = CidEnum.Zero; byte[] frmPayload = null; if (cloudToDeviceMessage != null) { // Get C2D Mac coomands var macCommandsC2d = PrepareMacCommandAnswer(loRaDevice.DevEUI, null, cloudToDeviceMessage?.MacCommands, rxpk, null); // Calculate total C2D payload size var totalC2dSize = cloudToDeviceMessage.GetPayload()?.Length ?? 0; totalC2dSize += macCommandsC2d?.Sum(x => x.Length) ?? 0; // Total C2D payload will fit if (availablePayloadSize >= totalC2dSize) { // Add frmPayload frmPayload = cloudToDeviceMessage.GetPayload(); // Add C2D Mac commands if (macCommandsC2d?.Count > 0) { foreach (MacCommand macCommand in macCommandsC2d) { macCommands.Add(macCommand); } } // Deduct frmPayload size from available payload size, continue processing and log availablePayloadSize -= (uint)totalC2dSize; if (cloudToDeviceMessage.Confirmed) { requiresDeviceAcknowlegement = true; loRaDevice.LastConfirmedC2DMessageID = cloudToDeviceMessage.MessageId ?? Constants.C2D_MSG_ID_PLACEHOLDER; } if (cloudToDeviceMessage.Fport > 0) { fport = cloudToDeviceMessage.Fport; } Logger.Log(loRaDevice.DevEUI, $"cloud to device message: {((frmPayload?.Length ?? 0) == 0 ? "empty" : ConversionHelper.ByteArrayToString(frmPayload))}, id: {cloudToDeviceMessage.MessageId ?? "undefined"}, fport: {fport ?? 0}, confirmed: {requiresDeviceAcknowlegement}, cidType: {macCommandType}, macCommand: {macCommands.Count > 0}", LogLevel.Information); Array.Reverse(frmPayload); } else { // Flag message to be abandoned and log Logger.Log(loRaDevice.DevEUI, $"cloud to device message: {((frmPayload?.Length ?? 0) == 0 ? "empty" : Encoding.UTF8.GetString(frmPayload))}, id: {cloudToDeviceMessage.MessageId ?? "undefined"}, fport: {fport ?? 0}, confirmed: {requiresDeviceAcknowlegement} too long for current receive window. Abandoning.", LogLevel.Debug); isMessageTooLong = true; } } // Get request Mac commands var macCommandsRequest = PrepareMacCommandAnswer(loRaDevice.DevEUI, upstreamPayload.MacCommands, null, rxpk, loRaADRResult); // Calculate request Mac commands size var macCommandsRequestSize = macCommandsRequest?.Sum(x => x.Length) ?? 0; // Try adding request Mac commands if (availablePayloadSize >= macCommandsRequestSize) { if (macCommandsRequest?.Count > 0) { foreach (MacCommand macCommand in macCommandsRequest) { macCommands.Add(macCommand); } } } if (fpending || isMessageTooLong) { fctrl |= (int)FctrlEnum.FpendingOrClassB; } if (upstreamPayload.IsAdrEnabled) { fctrl |= (byte)FctrlEnum.ADR; } var srcDevAddr = upstreamPayload.DevAddr.Span; var reversedDevAddr = new byte[srcDevAddr.Length]; for (int i = reversedDevAddr.Length - 1; i >= 0; --i) { reversedDevAddr[i] = srcDevAddr[srcDevAddr.Length - (1 + i)]; } var msgType = requiresDeviceAcknowlegement ? LoRaMessageType.ConfirmedDataDown : LoRaMessageType.UnconfirmedDataDown; var ackLoRaMessage = new LoRaPayloadData( msgType, reversedDevAddr, new byte[] { fctrl }, BitConverter.GetBytes(fcntDownToSend), macCommands, fport.HasValue ? new byte[] { fport.Value } : null, frmPayload, 1, loRaDevice.Supports32BitFCnt ? fcntDown : (uint?)null); // todo: check the device twin preference if using confirmed or unconfirmed down Logger.Log(loRaDevice.DevEUI, $"sending a downstream message with ID {ConversionHelper.ByteArrayToString(rndToken)}", LogLevel.Information); return(new DownlinkMessageBuilderResponse( ackLoRaMessage.Serialize(loRaDevice.AppSKey, loRaDevice.NwkSKey, datr, freq, tmst, loRaDevice.DevEUI), isMessageTooLong)); }
/// <summary> /// Prepare the Mac Commands to be sent in the downstream message. /// </summary> private static ICollection <MacCommand> PrepareMacCommandAnswer( IEnumerable <MacCommand> requestedMacCommands, IEnumerable <MacCommand> serverMacCommands, LoRaRequest loRaRequest, LoRaADRResult loRaADRResult, ILogger logger) { var cids = new HashSet <Cid>(); var macCommands = new List <MacCommand>(); if (requestedMacCommands != null) { foreach (var requestedMacCommand in requestedMacCommands) { switch (requestedMacCommand.Cid) { case Cid.LinkCheckCmd: case Cid.Zero: case Cid.One: case Cid.LinkADRCmd: if (loRaRequest != null) { var linkCheckAnswer = new LinkCheckAnswer(checked ((byte)loRaRequest.Region.GetModulationMargin(loRaRequest.RadioMetadata.DataRate, loRaRequest.RadioMetadata.UpInfo.SignalNoiseRatio)), 1); if (cids.Add(Cid.LinkCheckCmd)) { macCommands.Add(linkCheckAnswer); logger.LogInformation($"answering to a MAC command request {linkCheckAnswer}"); } } break; case Cid.DutyCycleCmd: case Cid.RXParamCmd: case Cid.DevStatusCmd: case Cid.NewChannelCmd: case Cid.RXTimingCmd: case Cid.TxParamSetupCmd: default: break; } } } if (serverMacCommands != null) { foreach (var macCmd in serverMacCommands) { if (macCmd != null) { try { if (cids.Add(macCmd.Cid)) { macCommands.Add(macCmd); } else { logger.LogError($"could not send the cloud to device MAC command {macCmd.Cid}, as such a property was already present in the message. Please resend the cloud to device message"); } logger.LogInformation($"cloud to device MAC command {macCmd.Cid} received {macCmd}"); } catch (MacCommandException ex) when(ExceptionFilterUtility.True(() => logger.LogError(ex.ToString()))) { // continue } } } } // ADR Part. // Currently only replying on ADR Req if (loRaADRResult?.CanConfirmToDevice == true) { const int placeholderChannel = 25; var linkADR = new LinkADRRequest((byte)loRaADRResult.DataRate, (byte)loRaADRResult.TxPower, placeholderChannel, 0, (byte)loRaADRResult.NbRepetition); macCommands.Add(linkADR); logger.LogInformation($"performing a rate adaptation: DR {loRaADRResult.DataRate}, transmit power {loRaADRResult.TxPower}, #repetition {loRaADRResult.NbRepetition}"); } return(macCommands); }