/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #pragma once #include #include #include #include #include #include #include #include namespace quic { class QuicSocketLite { public: /** * Information about the transport, similar to what TCP has. */ struct TransportInfo { // Time when the connection started. TimePoint connectionTime; std::chrono::microseconds srtt{0us}; std::chrono::microseconds rttvar{0us}; std::chrono::microseconds lrtt{0us}; OptionalMicros maybeLrtt; OptionalMicros maybeLrttAckDelay; OptionalMicros maybeMinRtt; OptionalMicros maybeMinRttNoAckDelay; uint64_t mss{kDefaultUDPSendPacketLen}; CongestionControlType congestionControlType{CongestionControlType::None}; uint64_t writableBytes{0}; uint64_t congestionWindow{0}; uint64_t pacingBurstSize{0}; std::chrono::microseconds pacingInterval{0us}; uint32_t packetsRetransmitted{0}; uint32_t totalPacketsSent{0}; uint32_t totalAckElicitingPacketsSent{0}; uint32_t totalPacketsMarkedLost{0}; uint32_t totalPacketsMarkedLostByTimeout{0}; uint32_t totalPacketsMarkedLostByReorderingThreshold{0}; uint32_t totalPacketsSpuriouslyMarkedLost{0}; uint32_t timeoutBasedLoss{0}; std::chrono::microseconds pto{0us}; // Number of Bytes (packet header + body) that were sent uint64_t bytesSent{0}; // Number of Bytes (packet header + body) that were acked uint64_t bytesAcked{0}; // Number of Bytes (packet header + body) that were received uint64_t bytesRecvd{0}; // Number of Bytes (packet header + body) that are in-flight uint64_t bytesInFlight{0}; // Number of Bytes (packet header + body) that were retxed uint64_t totalBytesRetransmitted{0}; // Number of Bytes (only the encoded packet's body) that were sent uint64_t bodyBytesSent{0}; // Number of Bytes (only the encoded packet's body) that were acked uint64_t bodyBytesAcked{0}; // Total number of stream bytes sent on this connection. // Includes retransmissions of stream bytes. uint64_t totalStreamBytesSent{0}; // Total number of 'new' stream bytes sent on this connection. // Does not include retransmissions of stream bytes. uint64_t totalNewStreamBytesSent{0}; uint32_t ptoCount{0}; uint32_t totalPTOCount{0}; Optional largestPacketAckedByPeer; Optional largestPacketSent; bool usedZeroRtt{false}; // State from congestion control module, if one is installed. Optional maybeCCState; }; /** * Callback for connection set up events. */ class ConnectionSetupCallback { public: virtual ~ConnectionSetupCallback() = default; /** * Called after the transport successfully processes the received packet. */ virtual void onFirstPeerPacketProcessed() noexcept {} /** * Invoked when the connection setup fails. */ virtual void onConnectionSetupError(QuicError code) noexcept = 0; /** * Called when the transport is ready to send/receive data. * This can be potentially triggered immediately when using 0-RTT. */ virtual void onTransportReady() noexcept {} /** * Client only. * Called when the transport becomes replay safe - both crypto keys derived. * Called after onTransportReady() and in case of 0-RTT, unlike * onTransportReady(), signifies full crypto handshake finished. */ virtual void onReplaySafe() noexcept {} /** * Essentially Server only as clients have onReplaySafe. * Called after onTransportReady() and in case of 0-RTT, unlike * onTransportReady(), signifies full crypto handshake finished. */ virtual void onFullHandshakeDone() noexcept {} }; /** * Callback for connection level events once connection is set up. * The name is temporary until we phase out the old monolithic callback. */ class ConnectionCallback { public: virtual ~ConnectionCallback() = default; /** * Invoked when stream id's flow control state changes. This is an edge * triggred API and will be only invoked at the point that the flow control * changes. */ virtual void onFlowControlUpdate(StreamId /*id*/) noexcept {} /** * Invoked when the peer creates a new bidirectional stream. The most * common flow would be to set the ReadCallback from here */ virtual void onNewBidirectionalStream(StreamId id) noexcept = 0; /** * Invoked when the peer creates a new bidirectional stream group. */ virtual void onNewBidirectionalStreamGroup(StreamGroupId) noexcept {} /** * Invoked when the peer creates a new bidirectional stream in a specific * group. */ virtual void onNewBidirectionalStreamInGroup( StreamId, StreamGroupId) noexcept {} /** * Invoked when the peer creates a new unidirectional stream. The most * common flow would be to set the ReadCallback from here */ virtual void onNewUnidirectionalStream(StreamId id) noexcept = 0; /** * Invoked when the peer creates a new unidirectional stream group. */ virtual void onNewUnidirectionalStreamGroup(StreamGroupId) noexcept {} /** * Invoked when the peer creates a new unidirectional stream in a specific * group. */ virtual void onNewUnidirectionalStreamInGroup( StreamId, StreamGroupId) noexcept {} /** * Invoked when a given stream has been closed and its state is about to * be reaped by the transport. This is the last chance to do any final * state querying operations on the stream. */ virtual void onStreamPreReaped(StreamId) noexcept {} /** * Invoked when a stream receives a StopSending frame from a peer. * The application should reset the stream as part of this callback. */ virtual void onStopSending( StreamId id, ApplicationErrorCode error) noexcept = 0; /** * Invoked when the transport initiates close. No callbacks will * be delivered after this */ virtual void onConnectionEnd() noexcept = 0; /** * Invoked when the connection closed in error */ virtual void onConnectionError(QuicError code) noexcept = 0; /** * Invoked on transport closure. No callbacks will be delivered after this. * onConnectionEnd() and onConnectionError(QuicError code) will be * deprecated in favor of this new combined callback soon. */ virtual void onConnectionEnd(QuicError /* error */) noexcept {} /** * Called when more bidirectional streams become available for creation * (max local bidirectional stream ID was increased). */ virtual void onBidirectionalStreamsAvailable( uint64_t /*numStreamsAvailable*/) noexcept {} /** * Called when more unidirectional streams become available for creation * (max local unidirectional stream ID was increased). */ virtual void onUnidirectionalStreamsAvailable( uint64_t /*numStreamsAvailable*/) noexcept {} /** * Invoked when transport is detected to be app rate limited. */ virtual void onAppRateLimited() noexcept {} /** * Invoked when we receive a KnobFrame from the peer */ virtual void onKnob(uint64_t /*knobSpace*/, uint64_t /*knobId*/, Buf /*knobBlob*/) {} }; /** * Information about the stream level transport info. Specific to QUIC. */ struct StreamTransportInfo { // Total time the stream has spent in head-of-line blocked state, // in microseconds std::chrono::microseconds totalHeadOfLineBlockedTime{0us}; // How many times the stream has entered the "head-of-line blocked" state uint32_t holbCount{0}; // Is the stream head-of-line blocked? bool isHolb{false}; // Number of packets transmitted that carry new STREAM frame for this stream uint64_t numPacketsTxWithNewData{0}; // Number of packets that contain STREAM frame for this stream and are // declared to be lost uint64_t streamLossCount{0}; // Total number of 'new' stream bytes sent on this stream. // Does not include retransmissions of stream bytes. Optional streamBytesSent{0}; // Total number of stream bytes received on this stream. Optional streamBytesReceived{0}; // Stream read error (if one occured) Optional streamReadError; // Stream write error (if one occured) Optional streamWriteError; }; /** * Get the flow control settings for the given stream (or connection flow * control by passing id=0). Settings include send and receive window * capacity and available. */ struct FlowControlState { // Number of bytes the peer has allowed me to send. uint64_t sendWindowAvailable; // The max offset provided by the peer. uint64_t sendWindowMaxOffset; // Number of bytes I have allowed the peer to send. uint64_t receiveWindowAvailable; // The max offset I have provided to the peer. uint64_t receiveWindowMaxOffset; FlowControlState( uint64_t sendWindowAvailableIn, uint64_t sendWindowMaxOffsetIn, uint64_t receiveWindowAvailableIn, uint64_t receiveWindowMaxOffsetIn) : sendWindowAvailable(sendWindowAvailableIn), sendWindowMaxOffset(sendWindowMaxOffsetIn), receiveWindowAvailable(receiveWindowAvailableIn), receiveWindowMaxOffset(receiveWindowMaxOffsetIn) {} }; /** * Creates a bidirectional stream. This assigns a stream ID but does not * send anything to the peer. * * If replaySafe is false, the transport will buffer (up to the send buffer * limits) any writes on this stream until the transport is replay safe. */ virtual folly::Expected createBidirectionalStream( bool replaySafe = true) = 0; /** * Creates a unidirectional stream. This assigns a stream ID but does not * send anything to the peer. * * If replaySafe is false, the transport will buffer (up to the send buffer * limits) any writes on this stream until the transport is replay safe. */ virtual folly::Expected createUnidirectionalStream( bool replaySafe = true) = 0; /** * Returns the number of bidirectional streams that can be opened. */ virtual uint64_t getNumOpenableBidirectionalStreams() const = 0; /** * Returns the number of unidirectional streams that can be opened. */ virtual uint64_t getNumOpenableUnidirectionalStreams() const = 0; /** * Returns whether a stream ID represents a unidirectional stream. */ virtual bool isUnidirectionalStream(StreamId stream) noexcept = 0; /** * Returns whether a stream ID represents a bidirectional stream. */ virtual bool isBidirectionalStream(StreamId stream) noexcept = 0; /** * ===== Read API ==== */ /** * Callback class for receiving data on a stream */ using ReadCallback = StreamReadCallback; /** * Read from the given stream, up to maxLen bytes. If maxLen is 0, transport * will return all available bytes. * * The return value is Expected. If the value hasError(), then a read error * occurred and it can be obtained with error(). If the value hasValue(), * then value() returns a pair of the data (if any) and the EOF marker. * * Calling read() when there is no data/eof to deliver will return an * EAGAIN-like error code. */ virtual folly::Expected, LocalErrorCode> read( StreamId id, size_t maxLen) = 0; /** * Set the read callback for the given stream. Note that read callback is * expected to be set all the time. Removing read callback indicates that * stream is no longer intended to be read again. This will issue a * StopSending if cb is being set to nullptr after previously being not * nullptr. The err parameter is used to control the error sent in the * StopSending. By default when cb is nullptr this function will cause the * transport to send a StopSending frame with * GenericApplicationErrorCode::NO_ERROR. If err is specified to be * none, no StopSending will be sent. * * Users should remove the callback via setReadCallback(id, nullptr) after * reading an error or eof to allow streams to be reaped by the transport. */ virtual folly::Expected setReadCallback( StreamId id, ReadCallback* cb, Optional err = GenericApplicationErrorCode::NO_ERROR) = 0; /** * ===== Peek/Consume API ===== */ /** * Usage: * class Application { * void onNewBidirectionalStream(StreamId id) { * socket_->setPeekCallback(id, this); * } * * virtual void onDataAvailable( * StreamId id, * const folly::Range& peekData) noexcept override * { * auto amount = tryInterpret(peekData); * if (amount) { * socket_->consume(id, amount); * } * } * }; */ using PeekIterator = CircularDeque::const_iterator; class PeekCallback { public: virtual ~PeekCallback() = default; /** * Called from the transport layer when there is new data available to * peek on a given stream. * Callback can be called multiple times and it is up to application to * de-dupe already peeked ranges. */ virtual void onDataAvailable( StreamId id, const folly::Range& peekData) noexcept = 0; /** * Called from the transport layer during peek time when there is an error * on the stream. */ virtual void peekError(StreamId id, QuicError error) noexcept = 0; }; /** * Callback class for pings */ class PingCallback { public: virtual ~PingCallback() = default; /** * Invoked when the ping is acknowledged */ virtual void pingAcknowledged() noexcept = 0; /** * Invoked if the ping times out */ virtual void pingTimeout() noexcept = 0; /** * Invoked when a ping is received */ virtual void onPing() noexcept = 0; }; /** * Structure used to communicate TX and ACK/Delivery notifications. */ struct ByteEvent { enum class Type { ACK = 1, TX = 2 }; static constexpr std::array kByteEventTypes = { {Type::ACK, Type::TX}}; StreamId id{0}; uint64_t offset{0}; Type type; // sRTT at time of event // TODO(bschlinker): Deprecate, caller can fetch transport state if // desired. std::chrono::microseconds srtt{0us}; }; /** * Structure used to communicate cancellation of a ByteEvent. * * According to Dictionary.com, cancellation is more frequent in American * English than cancellation. Yet in American English, the preferred style is * typically not to double the final L, so cancel generally becomes canceled. */ using ByteEventCancellation = ByteEvent; /** * Callback class for receiving byte event (TX/ACK) notifications. */ class ByteEventCallback { public: virtual ~ByteEventCallback() = default; /** * Invoked when a byte event has been successfully registered. * Since this is a convenience notification and not a mandatory callback, * not marking this as pure virtual. */ virtual void onByteEventRegistered(ByteEvent /* byteEvent */) {} /** * Invoked when the byte event has occurred. */ virtual void onByteEvent(ByteEvent byteEvent) = 0; /** * Invoked if byte event is canceled due to reset, shutdown, or other error. */ virtual void onByteEventCanceled(ByteEventCancellation cancellation) = 0; }; /** * Callback class for receiving ack notifications */ class DeliveryCallback : public ByteEventCallback { public: ~DeliveryCallback() override = default; /** * Invoked when the peer has acknowledged the receipt of the specified * offset. rtt is the current RTT estimate for the connection. */ virtual void onDeliveryAck( StreamId id, uint64_t offset, std::chrono::microseconds rtt) = 0; /** * Invoked on registered delivery callbacks when the bytes will never be * delivered (due to a reset or other error). */ virtual void onCanceled(StreamId id, uint64_t offset) = 0; private: // Temporary shim during transition to ByteEvent void onByteEventRegistered(ByteEvent /* byteEvent */) final { // Not supported } void onByteEvent(ByteEvent byteEvent) final { CHECK_EQ((int)ByteEvent::Type::ACK, (int)byteEvent.type); // sanity onDeliveryAck(byteEvent.id, byteEvent.offset, byteEvent.srtt); } // Temporary shim during transition to ByteEvent void onByteEventCanceled(ByteEventCancellation cancellation) final { CHECK_EQ((int)ByteEvent::Type::ACK, (int)cancellation.type); // sanity onCanceled(cancellation.id, cancellation.offset); } }; /** * Register a byte event to be triggered when specified event type occurs for * the specified stream and offset. * * If the registration fails, the callback (ByteEventCallback* cb) will NEVER * be invoked for anything. If the registration succeeds, the callback is * guaranteed to receive an onByteEventRegistered() notification. */ virtual folly::Expected registerByteEventCallback( const ByteEvent::Type type, const StreamId id, const uint64_t offset, ByteEventCallback* cb) = 0; /** * Get the number of pending byte events for the given stream. */ FOLLY_NODISCARD virtual size_t getNumByteEventCallbacksForStream( const StreamId streamId) const = 0; /** * Get the number of pending byte events of specified type for given stream. */ FOLLY_NODISCARD virtual size_t getNumByteEventCallbacksForStream( const ByteEvent::Type type, const StreamId streamId) const = 0; /** * Cancel all byte event callbacks of all streams. */ virtual void cancelAllByteEventCallbacks() = 0; /** * Cancel all byte event callbacks of all streams of the given type. */ virtual void cancelByteEventCallbacks(const ByteEvent::Type type) = 0; /** * ===== Datagram API ===== * * Datagram support is experimental. Currently there isn't delivery callback * or loss notification support for Datagram. */ class DatagramCallback { public: virtual ~DatagramCallback() = default; /** * Notifies the DatagramCallback that datagrams are available for read. */ virtual void onDatagramsAvailable() noexcept = 0; }; /** * Callback class for receiving write readiness notifications */ class WriteCallback : public quic::ConnectionWriteCallback, public quic::StreamWriteCallback { public: ~WriteCallback() override = default; }; /** * Write data/eof to the given stream. * * Passing a delivery callback registers a callback from the transport when * the peer has acknowledged the receipt of all the data/eof passed to write. * * An error code is present if there was an error with the write. */ using WriteResult = folly::Expected; virtual WriteResult writeChain( StreamId id, Buf data, bool eof, ByteEventCallback* cb = nullptr) = 0; /** * Write a data representation in the form of BufferMeta to the given stream. */ virtual WriteResult writeBufMeta( StreamId id, const BufferMeta& data, bool eof, ByteEventCallback* cb = nullptr) = 0; /** * Set the DSRPacketizationRequestSender for a stream. */ virtual WriteResult setDSRPacketizationRequestSender( StreamId id, std::unique_ptr sender) = 0; /** * Close the stream for writing. Equivalent to writeChain(id, nullptr, true). */ virtual Optional shutdownWrite(StreamId id) = 0; /** * Register a callback to be invoked when the peer has acknowledged the * given offset on the given stream. */ virtual folly::Expected registerDeliveryCallback( StreamId id, uint64_t offset, ByteEventCallback* cb) = 0; /** * Inform the transport that there is data to write on this connection * An app shouldn't mix connection and stream calls to this API * Use this if the app wants to do prioritization. */ virtual folly::Expected notifyPendingWriteOnConnection(ConnectionWriteCallback* wcb) = 0; /** * Inform the transport that there is data to write on a given stream. * An app shouldn't mix connection and stream calls to this API * Use the Connection call if the app wants to do prioritization. */ virtual folly::Expected notifyPendingWriteOnStream(StreamId id, StreamWriteCallback* wcb) = 0; virtual folly::Expected unregisterStreamWriteCallback(StreamId) = 0; /** * Application can invoke this function to signal the transport to * initiate migration. * @param socket The new socket that should be used by the transport. * If this is null then do not replace the underlying socket. */ virtual void onNetworkSwitch(std::unique_ptr /*unused*/) { } /** * Cancel the given stream */ virtual folly::Expected resetStream( StreamId id, ApplicationErrorCode error) = 0; /** * Determine if transport is open and ready to read or write. * * return true iff the transport is open and ready, false otherwise. */ virtual bool good() const = 0; /** * Determine if an error has occurred with this transport. */ virtual bool error() const = 0; /** * Close this socket with a drain period. If closing with an error, it may be * specified. */ virtual void close(Optional errorCode) = 0; /** * Close this socket without a drain period. If closing with an error, it may * be specified. */ virtual void closeNow(Optional errorCode) = 0; /** * Initiates sending of a StopSending frame for a given stream to the peer. * This is called a "solicited reset". On receipt of the StopSending frame * the peer should, but may not, send a ResetStream frame for the requested * stream. A caller can use this function when they are no longer processing * received data on the stream. */ virtual folly::Expected stopSending( StreamId id, ApplicationErrorCode error) = 0; /** * Sets connection setup callback. This callback must be set before using the * socket. */ virtual void setConnectionSetupCallback( folly::MaybeManagedPtr callback) = 0; /** * Sets connection streams callback. This callback must be set after * connection set up is finished and is ready for streams processing. */ virtual void setConnectionCallback( folly::MaybeManagedPtr callback) = 0; /** * Signal the transport that a certain stream is a control stream. * A control stream outlives all the other streams in a connection, therefore, * if the transport knows about it, can enable some optimizations. * Applications should declare all their control streams after either calling * createStream() or receiving onNewBidirectionalStream() */ virtual Optional setControlStream(StreamId id) = 0; /** * Invoke onCanceled on all the delivery callbacks registered for streamId. */ virtual void cancelDeliveryCallbacksForStream(StreamId streamId) = 0; /** * Invoke onCanceled on all the delivery callbacks registered for streamId for * offsets lower than the offset provided. */ virtual void cancelDeliveryCallbacksForStream( StreamId streamId, uint64_t offset) = 0; /** * Cancel byte event callbacks for given stream. * * If an offset is provided, cancels only callbacks with an offset less than * or equal to the provided offset, otherwise cancels all callbacks. */ virtual void cancelByteEventCallbacksForStream( const StreamId id, const Optional& offset = none) = 0; /** * Cancel byte event callbacks for given type and stream. * * If an offset is provided, cancels only callbacks with an offset less than * or equal to the provided offset, otherwise cancels all callbacks. */ virtual void cancelByteEventCallbacksForStream( const ByteEvent::Type type, const StreamId id, const Optional& offset = none) = 0; /** * Sets the size of the given stream's receive window, or the connection * receive window if stream id is 0. If the window size increases, a * window update will be sent to the peer. If it decreases, the transport * will delay future window updates until the sender's available window is * <= recvWindowSize. */ virtual void setReceiveWindow(StreamId id, size_t recvWindowSize) = 0; /** * Set the size of the transport send buffer for the given stream. * The maximum total amount of buffer space is the sum of maxUnacked and * maxUnsent. Bytes passed to writeChain count against unsent until the * transport flushes them to the wire, after which they count against unacked. */ virtual void setSendBuffer(StreamId id, size_t maxUnacked, size_t maxUnsent) = 0; /** * Settings for the transport. This takes effect only before the transport * is connected. */ virtual void setTransportSettings(TransportSettings transportSettings) = 0; /** * Set congestion control type. */ virtual void setCongestionControl(CongestionControlType type) = 0; /** * Add a packet processor */ virtual void addPacketProcessor( std::shared_ptr packetProcessor) = 0; /** * Set a "knob". This will emit a knob frame to the peer, which the peer * application can act on by e.g. changing transport settings during the * connection. */ virtual folly::Expected setKnob(uint64_t knobSpace, uint64_t knobId, Buf knobBlob) = 0; /** * Can Knob Frames be exchanged with the peer on this connection? */ FOLLY_NODISCARD virtual bool isKnobSupported() const = 0; /** * Set stream priority. * level: can only be in [0, 7]. */ folly::Expected setStreamPriority(StreamId id, PriorityLevel level, bool incremental) { return setStreamPriority(id, Priority(level, incremental)); } /** * Set stream priority. * level: can only be in [0, 7]. * incremental: true/false * orderId: uint64 */ virtual folly::Expected setStreamPriority( StreamId id, Priority priority) = 0; /** * Sets the maximum pacing rate in Bytes per second to be used * if pacing is enabled */ virtual folly::Expected setMaxPacingRate( uint64_t rateBytesPerSec) = 0; /** * Set a throttling signal provider */ virtual void setThrottlingSignalProvider( std::shared_ptr) = 0; /** * Returns the event base associated with this socket */ [[nodiscard]] virtual std::shared_ptr getEventBase() const = 0; /** * Get information on the state of the quic connection. Should only be used * for logging. */ virtual const QuicConnectionStateBase* getState() const = 0; /** * Get internal transport info similar to TCP information. * Returns LocalErrorCode::STREAM_NOT_EXISTS if the stream is not found */ virtual folly::Expected getStreamTransportInfo(StreamId id) const = 0; /** * Get the peer socket address */ virtual const folly::SocketAddress& getPeerAddress() const = 0; /** * Get the original peer socket address */ virtual const folly::SocketAddress& getOriginalPeerAddress() const = 0; /** * Get the local socket address */ virtual const folly::SocketAddress& getLocalAddress() const = 0; /** * Get the cert presented by peer */ FOLLY_NODISCARD virtual const std::shared_ptr< const folly::AsyncTransportCertificate> getPeerCertificate() const { return nullptr; } /** * Get the cert presented by self */ FOLLY_NODISCARD virtual const std::shared_ptr< const folly::AsyncTransportCertificate> getSelfCertificate() const { return nullptr; } /** * Derive exported key material (RFC5705) from the transport's TLS layer, if * the transport is capable. */ virtual Optional> getExportedKeyingMaterial( const std::string& label, const Optional& context, uint16_t keyLength) const = 0; /** * Get the negotiated ALPN. If called before the transport is ready * returns none */ virtual Optional getAppProtocol() const = 0; /** * Return the amount of transport buffer space available for writing */ virtual uint64_t getConnectionBufferAvailable() const = 0; // Returns none before the handshake is complete, otherwise is always // non-empty. virtual Optional> getPeerTransportParams() const = 0; /** * Returns the current flow control windows for the stream, id != 0. * Use getConnectionFlowControl for connection flow control window. */ virtual folly::Expected getStreamFlowControl(StreamId id) const = 0; virtual const TransportSettings& getTransportSettings() const = 0; /** * Get internal transport info similar to TCP information. */ virtual TransportInfo getTransportInfo() const = 0; /** * Similar to getMaxWritableOnStream() above, but returns the value for the * whole connection. */ [[nodiscard]] virtual uint64_t maxWritableOnConn() const = 0; /** * Returns initiator (self or peer) of a stream by ID. */ virtual StreamInitiator getStreamInitiator(StreamId stream) noexcept = 0; /** * Returns varios stats of the connection. */ FOLLY_NODISCARD virtual QuicConnectionStats getConnectionsStats() const = 0; using Observer = SocketObserverContainer::Observer; using ManagedObserver = SocketObserverContainer::ManagedObserver; /** * Adds an observer. * * If the observer is already added, this is a no-op. * * @param observer Observer to add. * @return Whether the observer was added (fails if no list). */ bool addObserver(Observer* observer) { if (auto list = getSocketObserverContainer()) { list->addObserver(observer); return true; } return false; } /** * Adds an observer. * * If the observer is already added, this is a no-op. * * @param observer Observer to add. * @return Whether the observer was added (fails if no list). */ bool addObserver(std::shared_ptr observer) { if (auto list = getSocketObserverContainer()) { list->addObserver(std::move(observer)); return true; } return false; } /** * Removes an observer. * * @param observer Observer to remove. * @return Whether the observer was found and removed. */ bool removeObserver(Observer* observer) { if (auto list = getSocketObserverContainer()) { return list->removeObserver(observer); } return false; } /** * Removes an observer. * * @param observer Observer to remove. * @return Whether the observer was found and removed. */ bool removeObserver(std::shared_ptr observer) { if (auto list = getSocketObserverContainer()) { return list->removeObserver(std::move(observer)); } return false; } /** * Get number of observers. * * @return Number of observers. */ [[nodiscard]] size_t numObservers() const { if (auto list = getSocketObserverContainer()) { return list->numObservers(); } return 0; } /** * Returns list of attached observers. * * @return List of observers. */ std::vector getObservers() { if (auto list = getSocketObserverContainer()) { return list->getObservers(); } return {}; } /** * Returns list of attached observers that are of type T. * * @return Attached observers of type T. */ template std::vector findObservers() { if (auto list = getSocketObserverContainer()) { return list->findObservers(); } return {}; } virtual ~QuicSocketLite() = default; protected: /** * Returns the SocketObserverList or nullptr if not available. * * QuicSocket implementations that support observers should override this * function and return the socket observer list that they hold in memory. * * We have a default implementation to ensure that there is no risk of a * pure-virtual function being called during constructon or destruction of * the socket. If this was to occur the derived class which implements this * function may be unavailable leading to undefined behavior. While this is * true for any pure-virtual function, the potential for this issue is * greater for observers. */ [[nodiscard]] virtual SocketObserverContainer* getSocketObserverContainer() const { return nullptr; } }; } // namespace quic