/* * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef NET_DCSCTP_PUBLIC_DCSCTP_SOCKET_H_ #define NET_DCSCTP_PUBLIC_DCSCTP_SOCKET_H_ #include #include #include #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "api/array_view.h" #include "api/task_queue/task_queue_base.h" #include "net/dcsctp/public/dcsctp_handover_state.h" #include "net/dcsctp/public/dcsctp_message.h" #include "net/dcsctp/public/dcsctp_options.h" #include "net/dcsctp/public/packet_observer.h" #include "net/dcsctp/public/timeout.h" #include "net/dcsctp/public/types.h" namespace dcsctp { // The socket/association state enum class SocketState { // The socket is closed. kClosed, // The socket has initiated a connection, which is not yet established. Note // that for incoming connections and for reconnections when the socket is // already connected, the socket will not transition to this state. kConnecting, // The socket is connected, and the connection is established. kConnected, // The socket is shutting down, and the connection is not yet closed. kShuttingDown, }; // Send options for sending messages struct SendOptions { // If the message should be sent with unordered message delivery. IsUnordered unordered = IsUnordered(false); // If set, will discard messages that haven't been correctly sent and // received before the lifetime has expired. This is only available if the // peer supports Partial Reliability Extension (RFC3758). absl::optional lifetime = absl::nullopt; // If set, limits the number of retransmissions. This is only available // if the peer supports Partial Reliability Extension (RFC3758). absl::optional max_retransmissions = absl::nullopt; // If set, will generate lifecycle events for this message. See e.g. // `DcSctpSocketCallbacks::OnLifecycleMessageFullySent`. This value is decided // by the client and the library will provide it to all lifecycle callbacks. LifecycleId lifecycle_id = LifecycleId::NotSet(); }; enum class ErrorKind { // Indicates that no error has occurred. This will never be the case when // `OnError` or `OnAborted` is called. kNoError, // There have been too many retries or timeouts, and the library has given up. kTooManyRetries, // A command was received that is only possible to execute when the socket is // connected, which it is not. kNotConnected, // Parsing of the command or its parameters failed. kParseFailed, // Commands are received in the wrong sequence, which indicates a // synchronisation mismatch between the peers. kWrongSequence, // The peer has reported an issue using ERROR or ABORT command. kPeerReported, // The peer has performed a protocol violation. kProtocolViolation, // The receive or send buffers have been exhausted. kResourceExhaustion, // The client has performed an invalid operation. kUnsupportedOperation, }; inline constexpr absl::string_view ToString(ErrorKind error) { switch (error) { case ErrorKind::kNoError: return "NO_ERROR"; case ErrorKind::kTooManyRetries: return "TOO_MANY_RETRIES"; case ErrorKind::kNotConnected: return "NOT_CONNECTED"; case ErrorKind::kParseFailed: return "PARSE_FAILED"; case ErrorKind::kWrongSequence: return "WRONG_SEQUENCE"; case ErrorKind::kPeerReported: return "PEER_REPORTED"; case ErrorKind::kProtocolViolation: return "PROTOCOL_VIOLATION"; case ErrorKind::kResourceExhaustion: return "RESOURCE_EXHAUSTION"; case ErrorKind::kUnsupportedOperation: return "UNSUPPORTED_OPERATION"; } } enum class SendStatus { // The message was enqueued successfully. As sending the message is done // asynchronously, this is no guarantee that the message has been actually // sent. kSuccess, // The message was rejected as the payload was empty (which is not allowed in // SCTP). kErrorMessageEmpty, // The message was rejected as the payload was larger than what has been set // as `DcSctpOptions.max_message_size`. kErrorMessageTooLarge, // The message could not be enqueued as the socket is out of resources. This // mainly indicates that the send queue is full. kErrorResourceExhaustion, // The message could not be sent as the socket is shutting down. kErrorShuttingDown, }; inline constexpr absl::string_view ToString(SendStatus error) { switch (error) { case SendStatus::kSuccess: return "SUCCESS"; case SendStatus::kErrorMessageEmpty: return "ERROR_MESSAGE_EMPTY"; case SendStatus::kErrorMessageTooLarge: return "ERROR_MESSAGE_TOO_LARGE"; case SendStatus::kErrorResourceExhaustion: return "ERROR_RESOURCE_EXHAUSTION"; case SendStatus::kErrorShuttingDown: return "ERROR_SHUTTING_DOWN"; } } // Return value of ResetStreams. enum class ResetStreamsStatus { // If the connection is not yet established, this will be returned. kNotConnected, // Indicates that ResetStreams operation has been successfully initiated. kPerformed, // Indicates that ResetStreams has failed as it's not supported by the peer. kNotSupported, }; inline constexpr absl::string_view ToString(ResetStreamsStatus error) { switch (error) { case ResetStreamsStatus::kNotConnected: return "NOT_CONNECTED"; case ResetStreamsStatus::kPerformed: return "PERFORMED"; case ResetStreamsStatus::kNotSupported: return "NOT_SUPPORTED"; } } // Return value of DcSctpSocketCallbacks::SendPacketWithStatus. enum class SendPacketStatus { // Indicates that the packet was successfully sent. As sending is unreliable, // there are no guarantees that the packet was actually delivered. kSuccess, // The packet was not sent due to a temporary failure, such as the local send // buffer becoming exhausted. This return value indicates that the socket will // recover and sending that packet can be retried at a later time. kTemporaryFailure, // The packet was not sent due to other reasons. kError, }; // Represent known SCTP implementations. enum class SctpImplementation { // There is not enough information toto determine any SCTP implementation. kUnknown, // This implementation. kDcsctp, // https://github.com/sctplab/usrsctp. kUsrSctp, // Any other implementation. kOther, }; inline constexpr absl::string_view ToString(SctpImplementation implementation) { switch (implementation) { case SctpImplementation::kUnknown: return "unknown"; case SctpImplementation::kDcsctp: return "dcsctp"; case SctpImplementation::kUsrSctp: return "usrsctp"; case SctpImplementation::kOther: return "other"; } } // Tracked metrics, which is the return value of GetMetrics. Optional members // will be unset when they are not yet known. struct Metrics { // Transmission stats and metrics. // Number of packets sent. size_t tx_packets_count = 0; // Number of messages requested to be sent. size_t tx_messages_count = 0; // Number of packets retransmitted. Since SCTP packets can contain both // retransmitted DATA chunks and DATA chunks that are transmitted for the // first time, this represents an upper bound as it's incremented every time a // packet contains a retransmitted DATA chunk. size_t rtx_packets_count = 0; // Total number of bytes retransmitted. This includes the payload and // DATA/I-DATA headers, but not SCTP packet headers. uint64_t rtx_bytes_count = 0; // The current congestion window (cwnd) in bytes, corresponding to spinfo_cwnd // defined in RFC6458. size_t cwnd_bytes = 0; // Smoothed round trip time, corresponding to spinfo_srtt defined in RFC6458. int srtt_ms = 0; // Number of data items in the retransmission queue that haven’t been // acked/nacked yet and are in-flight. Corresponding to sstat_unackdata // defined in RFC6458. This may be an approximation when there are messages in // the send queue that haven't been fragmented/packetized yet. size_t unack_data_count = 0; // Receive stats and metrics. // Number of packets received. size_t rx_packets_count = 0; // Number of messages received. size_t rx_messages_count = 0; // The peer’s last announced receiver window size, corresponding to // sstat_rwnd defined in RFC6458. uint32_t peer_rwnd_bytes = 0; // Returns the detected SCTP implementation of the peer. As this is not // explicitly signalled during the connection establishment, heuristics is // used to analyze e.g. the state cookie in the INIT-ACK chunk. SctpImplementation peer_implementation = SctpImplementation::kUnknown; // Indicates if RFC8260 User Message Interleaving has been negotiated by both // peers. bool uses_message_interleaving = false; // Indicates if draft-tuexen-tsvwg-sctp-zero-checksum-00 has been negotiated // by both peers. bool uses_zero_checksum = false; // The number of negotiated incoming and outgoing streams, which is configured // locally as `DcSctpOptions::announced_maximum_incoming_streams` and // `DcSctpOptions::announced_maximum_outgoing_streams`, and which will be // signaled by the peer during connection. uint16_t negotiated_maximum_incoming_streams = 0; uint16_t negotiated_maximum_outgoing_streams = 0; }; // Callbacks that the DcSctpSocket will call synchronously to the owning // client. It is allowed to call back into the library from callbacks that start // with "On". It has been explicitly documented when it's not allowed to call // back into this library from within a callback. // // Theses callbacks are only synchronously triggered as a result of the client // calling a public method in `DcSctpSocketInterface`. class DcSctpSocketCallbacks { public: virtual ~DcSctpSocketCallbacks() = default; // Called when the library wants the packet serialized as `data` to be sent. // // TODO(bugs.webrtc.org/12943): This method is deprecated, see // `SendPacketWithStatus`. // // Note that it's NOT ALLOWED to call into this library from within this // callback. virtual void SendPacket(rtc::ArrayView data) {} // Called when the library wants the packet serialized as `data` to be sent. // // Note that it's NOT ALLOWED to call into this library from within this // callback. virtual SendPacketStatus SendPacketWithStatus( rtc::ArrayView data) { SendPacket(data); return SendPacketStatus::kSuccess; } // Called when the library wants to create a Timeout. The callback must return // an object that implements that interface. // // Low precision tasks are scheduled more efficiently by using leeway to // reduce Idle Wake Ups and is the preferred precision whenever possible. High // precision timeouts do not have this leeway, but is still limited by OS // timer precision. At the time of writing, kLow's additional leeway may be up // to 17 ms, but please see webrtc::TaskQueueBase::DelayPrecision for // up-to-date information. // // Note that it's NOT ALLOWED to call into this library from within this // callback. virtual std::unique_ptr CreateTimeout( webrtc::TaskQueueBase::DelayPrecision precision) { // TODO(hbos): When dependencies have migrated to this new signature, make // this pure virtual and delete the other version. return CreateTimeout(); } // TODO(hbos): When dependencies have migrated to the other signature, delete // this version. virtual std::unique_ptr CreateTimeout() { return CreateTimeout(webrtc::TaskQueueBase::DelayPrecision::kLow); } // Returns the current time in milliseconds (from any epoch). // // Note that it's NOT ALLOWED to call into this library from within this // callback. virtual TimeMs TimeMillis() = 0; // Called when the library needs a random number uniformly distributed between // `low` (inclusive) and `high` (exclusive). The random numbers used by the // library are not used for cryptographic purposes. There are no requirements // that the random number generator must be secure. // // Note that it's NOT ALLOWED to call into this library from within this // callback. virtual uint32_t GetRandomInt(uint32_t low, uint32_t high) = 0; // Triggered when the outgoing message buffer is empty, meaning that there are // no more queued messages, but there can still be packets in-flight or to be // retransmitted. (in contrast to SCTP_SENDER_DRY_EVENT). // // Note that it's NOT ALLOWED to call into this library from within this // callback. ABSL_DEPRECATED("Use OnTotalBufferedAmountLow instead") virtual void NotifyOutgoingMessageBufferEmpty() {} // Called when the library has received an SCTP message in full and delivers // it to the upper layer. // // It is allowed to call into this library from within this callback. virtual void OnMessageReceived(DcSctpMessage message) = 0; // Triggered when an non-fatal error is reported by either this library or // from the other peer (by sending an ERROR command). These should be logged, // but no other action need to be taken as the association is still viable. // // It is allowed to call into this library from within this callback. virtual void OnError(ErrorKind error, absl::string_view message) = 0; // Triggered when the socket has aborted - either as decided by this socket // due to e.g. too many retransmission attempts, or by the peer when // receiving an ABORT command. No other callbacks will be done after this // callback, unless reconnecting. // // It is allowed to call into this library from within this callback. virtual void OnAborted(ErrorKind error, absl::string_view message) = 0; // Called when calling `Connect` succeeds, but also for incoming successful // connection attempts. // // It is allowed to call into this library from within this callback. virtual void OnConnected() = 0; // Called when the socket is closed in a controlled way. No other // callbacks will be done after this callback, unless reconnecting. // // It is allowed to call into this library from within this callback. virtual void OnClosed() = 0; // On connection restarted (by peer). This is just a notification, and the // association is expected to work fine after this call, but there could have // been packet loss as a result of restarting the association. // // It is allowed to call into this library from within this callback. virtual void OnConnectionRestarted() = 0; // Indicates that a stream reset request has failed. // // It is allowed to call into this library from within this callback. virtual void OnStreamsResetFailed( rtc::ArrayView outgoing_streams, absl::string_view reason) = 0; // Indicates that a stream reset request has been performed. // // It is allowed to call into this library from within this callback. virtual void OnStreamsResetPerformed( rtc::ArrayView outgoing_streams) = 0; // When a peer has reset some of its outgoing streams, this will be called. An // empty list indicates that all streams have been reset. // // It is allowed to call into this library from within this callback. virtual void OnIncomingStreamsReset( rtc::ArrayView incoming_streams) = 0; // Will be called when the amount of data buffered to be sent falls to or // below the threshold set when calling `SetBufferedAmountLowThreshold`. // // It is allowed to call into this library from within this callback. virtual void OnBufferedAmountLow(StreamID stream_id) {} // Will be called when the total amount of data buffered (in the entire send // buffer, for all streams) falls to or below the threshold specified in // `DcSctpOptions::total_buffered_amount_low_threshold`. virtual void OnTotalBufferedAmountLow() {} // == Lifecycle Events == // // If a `lifecycle_id` is provided as `SendOptions`, lifecycle callbacks will // be triggered as the message is processed by the library. // // The possible transitions are shown in the graph below: // // DcSctpSocket::Send ────────────────────────┐ // │ │ // │ │ // v v // OnLifecycleMessageFullySent ───────> OnLifecycleMessageExpired // │ │ // │ │ // v v // OnLifeCycleMessageDelivered ────────────> OnLifecycleEnd // OnLifecycleMessageFullySent will be called when a message has been fully // sent, meaning that the last fragment has been produced from the send queue // and sent on the network. Note that this will trigger at most once per // message even if the message was retransmitted due to packet loss. // // This is a lifecycle event. // // Note that it's NOT ALLOWED to call into this library from within this // callback. virtual void OnLifecycleMessageFullySent(LifecycleId lifecycle_id) {} // OnLifecycleMessageExpired will be called when a message has expired. If it // was expired with data remaining in the send queue that had not been sent // ever, `maybe_delivered` will be set to false. If `maybe_delivered` is true, // the message has at least once been sent and may have been correctly // received by the peer, but it has expired before the receiver managed to // acknowledge it. This means that if `maybe_delivered` is true, it's unknown // if the message was lost or was delivered, and if `maybe_delivered` is // false, it's guaranteed to not be delivered. // // It's guaranteed that `OnLifecycleMessageDelivered` is not called if this // callback has triggered. // // This is a lifecycle event. // // Note that it's NOT ALLOWED to call into this library from within this // callback. virtual void OnLifecycleMessageExpired(LifecycleId lifecycle_id, bool maybe_delivered) {} // OnLifecycleMessageDelivered will be called when a non-expired message has // been acknowledged by the peer as delivered. // // Note that this will trigger only when the peer moves its cumulative TSN ack // beyond this message, and will not fire for messages acked using // gap-ack-blocks as those are renegable. This means that this may fire a bit // later than the message was actually first "acked" by the peer, as - // according to the protocol - those acks may be unacked later by the client. // // It's guaranteed that `OnLifecycleMessageExpired` is not called if this // callback has triggered. // // This is a lifecycle event. // // Note that it's NOT ALLOWED to call into this library from within this // callback. virtual void OnLifecycleMessageDelivered(LifecycleId lifecycle_id) {} // OnLifecycleEnd will be called when a lifecycle event has reached its end. // It will be called when processing of a message is complete, no matter how // it completed. It will be called after all other lifecycle events, if any. // // Note that it's possible that this callback triggers without any other // lifecycle callbacks having been called before in case of errors, such as // attempting to send an empty message or failing to enqueue a message if the // send queue is full. // // NOTE: When the socket is deallocated, there will be no `OnLifecycleEnd` // callbacks sent for messages that were enqueued. But as long as the socket // is alive, `OnLifecycleEnd` callbacks are guaranteed to be sent as messages // are either expired or successfully acknowledged. // // This is a lifecycle event. // // Note that it's NOT ALLOWED to call into this library from within this // callback. virtual void OnLifecycleEnd(LifecycleId lifecycle_id) {} }; // The DcSctpSocket implementation implements the following interface. // This class is thread-compatible. class DcSctpSocketInterface { public: virtual ~DcSctpSocketInterface() = default; // To be called when an incoming SCTP packet is to be processed. virtual void ReceivePacket(rtc::ArrayView data) = 0; // To be called when a timeout has expired. The `timeout_id` is provided // when the timeout was initiated. virtual void HandleTimeout(TimeoutID timeout_id) = 0; // Connects the socket. This is an asynchronous operation, and // `DcSctpSocketCallbacks::OnConnected` will be called on success. virtual void Connect() = 0; // Puts this socket to the state in which the original socket was when its // `DcSctpSocketHandoverState` was captured by `GetHandoverStateAndClose`. // `RestoreFromState` is allowed only on the closed socket. // `DcSctpSocketCallbacks::OnConnected` will be called if a connected socket // state is restored. // `DcSctpSocketCallbacks::OnError` will be called on error. virtual void RestoreFromState(const DcSctpSocketHandoverState& state) = 0; // Gracefully shutdowns the socket and sends all outstanding data. This is an // asynchronous operation and `DcSctpSocketCallbacks::OnClosed` will be called // on success. virtual void Shutdown() = 0; // Closes the connection non-gracefully. Will send ABORT if the connection is // not already closed. No callbacks will be made after Close() has returned. virtual void Close() = 0; // The socket state. virtual SocketState state() const = 0; // The options it was created with. virtual const DcSctpOptions& options() const = 0; // Update the options max_message_size. virtual void SetMaxMessageSize(size_t max_message_size) = 0; // Sets the priority of an outgoing stream. The initial value, when not set, // is `DcSctpOptions::default_stream_priority`. virtual void SetStreamPriority(StreamID stream_id, StreamPriority priority) = 0; // Returns the currently set priority for an outgoing stream. The initial // value, when not set, is `DcSctpOptions::default_stream_priority`. virtual StreamPriority GetStreamPriority(StreamID stream_id) const = 0; // Sends the message `message` using the provided send options. // Sending a message is an asynchronous operation, and the `OnError` callback // may be invoked to indicate any errors in sending the message. // // The association does not have to be established before calling this method. // If it's called before there is an established association, the message will // be queued. virtual SendStatus Send(DcSctpMessage message, const SendOptions& send_options) = 0; // Resetting streams is an asynchronous operation and the results will // be notified using `DcSctpSocketCallbacks::OnStreamsResetDone()` on success // and `DcSctpSocketCallbacks::OnStreamsResetFailed()` on failure. Note that // only outgoing streams can be reset. // // When it's known that the peer has reset its own outgoing streams, // `DcSctpSocketCallbacks::OnIncomingStreamReset` is called. // // Note that resetting a stream will also remove all queued messages on those // streams, but will ensure that the currently sent message (if any) is fully // sent before closing the stream. // // Resetting streams can only be done on an established association that // supports stream resetting. Calling this method on e.g. a closed association // or streams that don't support resetting will not perform any operation. virtual ResetStreamsStatus ResetStreams( rtc::ArrayView outgoing_streams) = 0; // Returns the number of bytes of data currently queued to be sent on a given // stream. virtual size_t buffered_amount(StreamID stream_id) const = 0; // Returns the number of buffered outgoing bytes that is considered "low" for // a given stream. See `SetBufferedAmountLowThreshold`. virtual size_t buffered_amount_low_threshold(StreamID stream_id) const = 0; // Used to specify the number of bytes of buffered outgoing data that is // considered "low" for a given stream, which will trigger an // OnBufferedAmountLow event. The default value is zero (0). virtual void SetBufferedAmountLowThreshold(StreamID stream_id, size_t bytes) = 0; // Retrieves the latest metrics. If the socket is not fully connected, // `absl::nullopt` will be returned. Note that metrics are not guaranteed to // be carried over if this socket is handed over by calling // `GetHandoverStateAndClose`. virtual absl::optional GetMetrics() const = 0; // Returns empty bitmask if the socket is in the state in which a snapshot of // the state can be made by `GetHandoverStateAndClose()`. Return value is // invalidated by a call to any non-const method. virtual HandoverReadinessStatus GetHandoverReadiness() const = 0; // Collects a snapshot of the socket state that can be used to reconstruct // this socket in another process. On success this socket object is closed // synchronously and no callbacks will be made after the method has returned. // The method fails if the socket is not in a state ready for handover. // nullopt indicates the failure. `DcSctpSocketCallbacks::OnClosed` will be // called on success. virtual absl::optional GetHandoverStateAndClose() = 0; // Returns the detected SCTP implementation of the peer. As this is not // explicitly signalled during the connection establishment, heuristics is // used to analyze e.g. the state cookie in the INIT-ACK chunk. // // If this method is called too early (before // `DcSctpSocketCallbacks::OnConnected` has triggered), this will likely // return `SctpImplementation::kUnknown`. ABSL_DEPRECATED("See Metrics::peer_implementation instead") virtual SctpImplementation peer_implementation() const { return SctpImplementation::kUnknown; } }; } // namespace dcsctp #endif // NET_DCSCTP_PUBLIC_DCSCTP_SOCKET_H_