/// <summary> /// Assigns an incremented message id (if none is already set) and performs a blocking Send operation. /// <para>If the mssage is <see cref="CoapMessageType.Confirmable"/>, the client will wait for a response with a coresponding message Id for the <see cref="RetransmitTimeout"/>* * <see cref="MaxRetransmitAttempts"/></para> /// </summary> /// <param name="message">The CoAP message to send. It's <see cref="CoapMessage.Id"/> may be set if it is unassigned.</param> /// <param name="endpoint">The remote endpoint to send the message to. /// <para>The endpoint must implement the same underlying transport to succeed.</para> /// </param> /// <param name="token">A token used to cancel the blocking Send operation or retransmission attempts.</param> /// <returns>The message Id</returns> /// <exception cref="CoapClientException">If the timeout period * maximum retransmission attempts was reached.</exception> public virtual async Task <CoapMessageIdentifier> SendAsync(CoapMessage message, ICoapEndpoint endpoint, CancellationToken token) { if (message == null) { throw new ArgumentNullException(nameof(message)); } if (Endpoint == null) { throw new CoapEndpointException($"{nameof(CoapClient)} has an invalid {nameof(Endpoint)}"); } if (message.Id == 0) { message.Id = GetNextMessageId(); } if (message.IsMulticast && message.Type != CoapMessageType.NonConfirmable) { throw new CoapClientException("Can not send confirmable (CON) CoAP message to a multicast endpoint"); } var messageId = message.GetIdentifier(endpoint, message.Type == CoapMessageType.Confirmable || message.Type == CoapMessageType.NonConfirmable); _messageResponses.TryAdd(messageId, new TaskCompletionSource <CoapMessage>(TaskCreationOptions.RunContinuationsAsynchronously)); if (message.Type != CoapMessageType.Confirmable) { await SendAsyncInternal(message, endpoint, token).ConfigureAwait(false); return(messageId); } if (!_messageResponses.TryGetValue(messageId, out var responseTaskSource)) { throw new CoapClientException("Race condition? This shouldn't happen. Congratuations!"); } for (var attempt = 1; attempt <= MaxRetransmitAttempts; attempt++) { StartReceiveAsyncInternal(); await SendAsyncInternal(message, endpoint, token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); var timeout = TimeSpan.FromMilliseconds(RetransmitTimeout.TotalMilliseconds * attempt); await Task.WhenAny(responseTaskSource.Task, Task.Delay(timeout, token)).ConfigureAwait(false); token.ThrowIfCancellationRequested(); if (responseTaskSource.Task.IsCompleted) { return(messageId); } } throw new CoapClientException($"Max retransmission attempts reached for Message Id: {message.Id}"); }
/// <summary> /// Checks if a <see cref="CoapReceiveResult"/> has been received for the coresponding <paramref name="request"/> and returns it. /// Otherwise waits until a new result is received unless cancelled by the <paramref name="token"/> or the <see cref="MaxRetransmitAttempts"/> is reached. /// </summary> /// <param name="request">Waits for a result with a coresponding request message.</param> /// <param name="token">Token to cancel the blocking Receive operation</param> /// <returns>Valid result if a result is received, <c>null</c> if canceled.</returns> /// <exception cref="CoapClientException">If the timeout period * maximum retransmission attempts was reached.</exception> public Task <CoapMessage> GetResponseAsync(CoapMessage request, ICoapEndpoint endpoint = null, bool isRequest = false, CancellationToken token = default(CancellationToken), bool dequeue = true) => GetResponseAsync(request.GetIdentifier(endpoint, isRequest), token, dequeue);
private async Task ReceiveAsyncInternal() { var isMulticast = Endpoint?.IsMulticast ?? false; try { while (true) { Task <CoapPacket> payloadTask; lock (this) { if (Endpoint == null) { return; } payloadTask = Endpoint.ReceiveAsync(_receiveTaskCTS.Token); } var payload = await payloadTask; var receivedAt = DateTime.Now; var message = new CoapMessage { IsMulticast = isMulticast }; try { message.FromBytes(payload.Payload); // Ignore non-empty reset messages if (message.Type == CoapMessageType.Reset && message.Code != CoapMessageCode.None) { continue; } // Reject confirmable empty messages if (message.Type == CoapMessageType.Confirmable && message.Code == CoapMessageCode.None) { await SendAsync(new CoapMessage { Id = message.Id, Type = CoapMessageType.Reset }, payload.Endpoint); continue; } // Ignore repeated messages if (IsRepeated(payload.Endpoint, message.Id)) { continue; } } catch (CoapMessageFormatException) { if (message.Type == CoapMessageType.Confirmable && !isMulticast) { await SendAsync(new CoapMessage { Id = message.Id, Type = CoapMessageType.Reset }, payload.Endpoint); } if (message.Type == CoapMessageType.Acknowledgement && Coap.ReservedMessageCodeClasses.Contains(message.Code.Class)) { continue; } throw; } lock (_recentMessages) { var messageId = message.GetIdentifier(payload.Endpoint); if (_messageResponses.ContainsKey(messageId)) { _messageResponses[messageId].TrySetResult(message); } _recentMessages.Enqueue(Tuple.Create(receivedAt, payload.Endpoint, message)); } _receiveQueue.Enqueue(Task.FromResult(new CoapReceiveResult(payload.Endpoint, message))); _receiveEvent.Set(); } } catch (Exception ex) { if (ex is CoapEndpointException) { var endpoint = Endpoint; lock (this) Endpoint = null; endpoint?.Dispose(); foreach (var response in _messageResponses.Values) { response.TrySetCanceled(); } } // Gona cheat and enque that exception so it gets thrown as if this detached-infinite-loop never existed... _receiveQueue.Enqueue(Task.FromException <CoapReceiveResult>(ex)); _receiveEvent.Set(); } }