blob: 9989ae8d4333c39ac79a83e22f6aaf4b6681fecf [file] [log] [blame]
/*
* 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 <cstdint>
#include <memory>
#include <utility>
#include <vector>
#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 "api/units/timestamp.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<DurationMs> lifetime = absl::nullopt;
// If set, limits the number of retransmissions. This is only available
// if the peer supports Partial Reliability Extension (RFC3758).
absl::optional<size_t> 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<const uint8_t> 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<const uint8_t> 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<Timeout> 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<Timeout> CreateTimeout() {
return CreateTimeout(webrtc::TaskQueueBase::DelayPrecision::kLow);
}
// Returns the current time in milliseconds (from any epoch).
//
// TODO(bugs.webrtc.org/15593): This method is deprecated, see `Now`.
//
// Note that it's NOT ALLOWED to call into this library from within this
// callback.
virtual TimeMs TimeMillis() { return TimeMs(0); }
// Returns the current time (from any epoch).
//
// This callback will eventually replace `TimeMillis()`.
//
// Note that it's NOT ALLOWED to call into this library from within this
// callback.
virtual webrtc::Timestamp Now() {
return webrtc::Timestamp::Millis(*TimeMillis());
}
// 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<const StreamID> 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<const StreamID> 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<const StreamID> 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<const uint8_t> 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;
// Sends the messages `messages` 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.
//
// This has identical semantics to Send, except that it may coalesce many
// messages into a single SCTP packet if they would fit.
virtual std::vector<SendStatus> SendMany(
rtc::ArrayView<DcSctpMessage> messages,
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<const StreamID> 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<Metrics> 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<DcSctpSocketHandoverState>
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_