/* * Copyright (c) Facebook, Inc. and its 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 #include #include #include #include #include namespace quic { enum class CloseState { OPEN, GRACEFUL_CLOSING, CLOSED }; /** * Base class for the QUIC Transport. Implements common behavior for both * clients and servers. QuicTransportBase assumes the following: * 1. It is intended to be sub-classed and used via the subclass directly. * 2. Assumes that the sub-class manages its ownership via a shared_ptr. * This is needed in order for QUIC to be able to live beyond the lifetime * of the object that holds it to send graceful close messages to the peer. */ class QuicTransportBase : public QuicSocket { public: QuicTransportBase( folly::EventBase* evb, std::unique_ptr socket); ~QuicTransportBase() override; void setPacingTimer(TimerHighRes::SharedPtr pacingTimer) noexcept; folly::EventBase* getEventBase() const override; folly::Optional getClientConnectionId() const override; folly::Optional getServerConnectionId() const override; folly::Optional getClientChosenDestConnectionId() const override; const folly::SocketAddress& getPeerAddress() const override; const folly::SocketAddress& getOriginalPeerAddress() const override; const folly::SocketAddress& getLocalAddress() const override; const std::shared_ptr getQLogger() const; // QuicSocket interface bool good() const override; bool replaySafe() const override; bool error() const override; void close( folly::Optional> error) override; void closeGracefully() override; void closeNow( folly::Optional> error) override; folly::Expected getStreamReadOffset( StreamId id) const override; folly::Expected getStreamWriteOffset( StreamId id) const override; folly::Expected getStreamWriteBufferedBytes( StreamId id) const override; TransportInfo getTransportInfo() const override; folly::Expected getStreamTransportInfo( StreamId id) const override; folly::Optional getAppProtocol() const override; void setReceiveWindow(StreamId id, size_t recvWindowSize) override; void setSendBuffer(StreamId id, size_t maxUnacked, size_t maxUnsent) override; uint64_t getConnectionBufferAvailable() const override; uint64_t bufferSpaceAvailable() const; folly::Expected getConnectionFlowControl() const override; folly::Expected getStreamFlowControl(StreamId id) const override; folly::Expected setConnectionFlowControlWindow( uint64_t windowSize) override; folly::Expected setStreamFlowControlWindow( StreamId id, uint64_t windowSize) override; folly::Expected setReadCallback( StreamId id, ReadCallback* cb) override; void unsetAllReadCallbacks() override; void unsetAllPeekCallbacks() override; void unsetAllDeliveryCallbacks() override; folly::Expected pauseRead(StreamId id) override; folly::Expected resumeRead(StreamId id) override; folly::Expected stopSending( StreamId id, ApplicationErrorCode error) override; folly::Expected, LocalErrorCode> read( StreamId id, size_t maxLen) override; folly::Expected setPeekCallback( StreamId id, PeekCallback* cb) override; folly::Expected pausePeek(StreamId id) override; folly::Expected resumePeek(StreamId id) override; folly::Expected peek( StreamId id, const folly::Function&) const>& peekCallback) override; folly::Expected consume( StreamId id, size_t amount) override; folly::Expected< folly::Unit, std::pair>> consume(StreamId id, uint64_t offset, size_t amount) override; folly::Expected setDataExpiredCallback( StreamId id, DataExpiredCallback* cb) override; folly::Expected, LocalErrorCode> sendDataExpired( StreamId id, uint64_t offset) override; folly::Expected setDataRejectedCallback( StreamId id, DataRejectedCallback* cb) override; folly::Expected, LocalErrorCode> sendDataRejected( StreamId id, uint64_t offset) override; folly::Expected createBidirectionalStream( bool replaySafe = true) override; folly::Expected createUnidirectionalStream( bool replaySafe = true) override; uint64_t getNumOpenableBidirectionalStreams() const override; uint64_t getNumOpenableUnidirectionalStreams() const override; bool isClientStream(StreamId stream) noexcept override; bool isServerStream(StreamId stream) noexcept override; bool isUnidirectionalStream(StreamId stream) noexcept override; bool isBidirectionalStream(StreamId stream) noexcept override; folly::Expected notifyPendingWriteOnStream( StreamId id, WriteCallback* wcb) override; folly::Expected notifyPendingWriteOnConnection( WriteCallback* wcb) override; folly::Expected unregisterStreamWriteCallback( StreamId id) override; WriteResult writeChain( StreamId id, Buf data, bool eof, bool cork, DeliveryCallback* cb = nullptr) override; folly::Expected registerDeliveryCallback( StreamId id, uint64_t offset, DeliveryCallback* cb) override; folly::Optional shutdownWrite(StreamId id) override; folly::Expected resetStream( StreamId id, ApplicationErrorCode errorCode) override; folly::Expected maybeResetStreamFromReadError( StreamId id, QuicErrorCode error) override; void sendPing(PingCallback* callback, std::chrono::milliseconds pingTimeout) override; const QuicConnectionStateBase* getState() const override { return conn_.get(); } // Interface with the Transport layer when data is available. // This is invoked when new data is received from the UDP socket. virtual void onNetworkData( const folly::SocketAddress& peer, NetworkData&& data) noexcept; virtual void setSupportedVersions(const std::vector& versions); void setConnectionCallback(ConnectionCallback* callback) final; void setEarlyDataAppParamsFunctions( folly::Function&, const Buf&) const> validator, folly::Function getter) final; bool isDetachable() override; void detachEventBase() override; void attachEventBase(folly::EventBase* evb) override; folly::Optional setControlStream(StreamId id) override; /** * Invoke onCanceled for all the delivery callbacks in the deliveryCallbacks * passed in. This is supposed to be a copy of the real deque of the delivery * callbacks for the stream, so there is no need to pop anything off of it. */ static void cancelDeliveryCallbacks( StreamId id, const std::deque>& deliveryCallbacks); /** * Invoke onCanceled for all the delivery callbacks in the deliveryCallbacks * map. This is supposed to be a copy of the real map of the delivery * callbacks of the transport, so there is no need to erase anything from it. */ static void cancelDeliveryCallbacks( const folly::F14FastMap< StreamId, std::deque>>& deliveryCallbacks); /** * Set the initial flow control window for the connection. */ void setTransportSettings(TransportSettings transportSettings) override; /** * Set factory to create specific congestion controller instances * for a given connection */ virtual void setCongestionControllerFactory( std::shared_ptr factory); /** * Retrieve the transport settings */ const TransportSettings& getTransportSettings() const override; // Subclass API. /** * Invoked when a new packet is read from the network. * peer is the address of the peer that was in the packet. * The sub-class may throw an exception if there was an error in processing * the packet in which case the connection will be closed. */ virtual void onReadData( const folly::SocketAddress& peer, NetworkDataSingle&& networkData) = 0; /** * Invoked when we have to write some data to the wire. * The subclass may use this to start writing data to the socket. * It may also throw an exception in case of an error in which case the * connection will be closed. */ virtual void writeData() = 0; /** * closeTransport is invoked on the sub-class when the transport is closed. * The sub-class may clean up any state during this call. The transport * may still be draining after this call. */ virtual void closeTransport() = 0; /** * Invoked after the drain timeout has exceeded and the connection state will * be destroyed. */ virtual void unbindConnection() = 0; /** * Returns whether or not the connection has a write cipher. This will be used * to decide to return the onTransportReady() callbacks. */ virtual bool hasWriteCipher() const = 0; /** * Returns a shared_ptr which can be used as a guard to keep this * object alive. */ virtual std::shared_ptr sharedGuard() = 0; bool isPartiallyReliableTransport() const override; /** * Invoke onCanceled on all the delivery callbacks registered for streamId. */ void cancelDeliveryCallbacksForStream(StreamId streamId) override; /** * Invoke onCanceled on all the delivery callbacks registered for streamId for * offsets lower than the offset provided. */ void cancelDeliveryCallbacksForStream(StreamId streamId, uint64_t offset) override; // Timeout functions class LossTimeout : public folly::HHWheelTimer::Callback { public: ~LossTimeout() override = default; explicit LossTimeout(QuicTransportBase* transport) : transport_(transport) {} void timeoutExpired() noexcept override { transport_->lossTimeoutExpired(); } virtual void callbackCanceled() noexcept override { // ignore. this usually means that the eventbase is dying, so we will be // canceled anyway return; } private: QuicTransportBase* transport_; }; class AckTimeout : public folly::HHWheelTimer::Callback { public: ~AckTimeout() override = default; explicit AckTimeout(QuicTransportBase* transport) : transport_(transport) {} void timeoutExpired() noexcept override { transport_->ackTimeoutExpired(); } virtual void callbackCanceled() noexcept override { // ignore. this usually means that the eventbase is dying, so we will be // canceled anyway return; } private: QuicTransportBase* transport_; }; class PingTimeout : public folly::HHWheelTimer::Callback { public: ~PingTimeout() override = default; explicit PingTimeout(QuicTransportBase* transport) : transport_(transport) {} void timeoutExpired() noexcept override { transport_->pingTimeoutExpired(); } void callbackCanceled() noexcept override { // ignore, as this happens only when event base dies return; } private: QuicTransportBase* transport_; }; class PathValidationTimeout : public folly::HHWheelTimer::Callback { public: ~PathValidationTimeout() override = default; explicit PathValidationTimeout(QuicTransportBase* transport) : transport_(transport) {} void timeoutExpired() noexcept override { transport_->pathValidationTimeoutExpired(); } virtual void callbackCanceled() noexcept override { // ignore. this usually means that the eventbase is dying, so we will be // canceled anyway return; } private: QuicTransportBase* transport_; }; class IdleTimeout : public folly::HHWheelTimer::Callback { public: ~IdleTimeout() override = default; explicit IdleTimeout(QuicTransportBase* transport) : transport_(transport) {} void timeoutExpired() noexcept override { transport_->idleTimeoutExpired(true /* drain */); } void callbackCanceled() noexcept override { // skip drain when canceling the timeout, to avoid scheduling a new // drain timeout transport_->idleTimeoutExpired(false /* drain */); } private: QuicTransportBase* transport_; }; // DrainTimeout is a bit different from other timeouts. It needs to hold a // shared_ptr to the transport, since if a DrainTimeout is scheduled, // transport cannot die. class DrainTimeout : public folly::HHWheelTimer::Callback { public: ~DrainTimeout() override = default; explicit DrainTimeout(QuicTransportBase* transport) : transport_(transport) {} void timeoutExpired() noexcept override { transport_->drainTimeoutExpired(); } private: QuicTransportBase* transport_; }; void scheduleLossTimeout(std::chrono::milliseconds timeout); void cancelLossTimeout(); bool isLossTimeoutScheduled() const; // If you don't set it, the default is Cubic void setCongestionControl(CongestionControlType type) override; void describe(std::ostream& os) const; void setLogger(std::shared_ptr logger) { conn_->logger = std::move(logger); } virtual void setQLogger(std::shared_ptr qLogger) { conn_->qLogger = std::move(qLogger); } void setLoopDetectorCallback(std::shared_ptr callback) { conn_->loopDetectorCallback = std::move(callback); } virtual void cancelAllAppCallbacks( const std::pair& error) noexcept; protected: void processCallbacksAfterNetworkData(); void invokeReadDataAndCallbacks(); void invokePeekDataAndCallbacks(); void invokeDataExpiredCallbacks(); void invokeDataRejectedCallbacks(); void updateReadLooper(); void updatePeekLooper(); void updateWriteLooper(bool thisIteration); void handlePingCallback(); void runOnEvbAsync( folly::Function)> func); void closeImpl( folly::Optional> error, bool drainConnection = true, bool sendCloseImmediately = true); folly::Expected pauseOrResumeRead( StreamId id, bool resume); folly::Expected pauseOrResumePeek( StreamId id, bool resume); void checkForClosedStream(); folly::Expected setReadCallbackInternal( StreamId id, ReadCallback* cb) noexcept; folly::Expected setPeekCallbackInternal( StreamId id, PeekCallback* cb) noexcept; folly::Expected createStreamInternal( bool bidirectional); /** * write data to socket * * At transport layer, this is the simplest form of write. It writes data * out to the network, and schedule necessary timers (ack, idle, loss). It is * both pacing oblivious and writeLooper oblivious. Caller needs to explicitly * invoke updateWriteLooper afterwards if that's desired. */ void writeSocketData(); /** * A wrapper around writeSocketData * * writeSocketDataAndCatch protects writeSocketData in a try-catch. It also * dispatch the next write loop. */ void writeSocketDataAndCatch(); /** * Paced write data to socket when connection is paced. * * Whether connection is based will be decided by TransportSettings and * congection controller. When the connection is paced, this function writes * out a burst size of packets and let the writeLooper schedule a callback to * write another burst after a pacing interval if there are more data to * write. When the connection isn't paced, this function do a normal write. */ void pacedWriteDataToSocket(bool fromTimer); uint64_t maxWritableOnStream(const QuicStreamState&); uint64_t maxWritableOnConn(); void lossTimeoutExpired() noexcept; void ackTimeoutExpired() noexcept; void pathValidationTimeoutExpired() noexcept; void idleTimeoutExpired(bool drain) noexcept; void drainTimeoutExpired() noexcept; void pingTimeoutExpired() noexcept; void setIdleTimer(); void scheduleAckTimeout(); void schedulePathValidationTimeout(); void schedulePingTimeout( PingCallback* callback, std::chrono::milliseconds pingTimeout); std::atomic evb_; std::unique_ptr socket_; ConnectionCallback* connCallback_{nullptr}; std:: unique_ptr conn_; struct ReadCallbackData { ReadCallback* readCb; bool resumed{true}; bool deliveredEOM{false}; ReadCallbackData(ReadCallback* readCallback) : readCb(readCallback) {} }; struct PeekCallbackData { PeekCallback* peekCb; bool resumed{true}; PeekCallbackData(PeekCallback* peekCallback) : peekCb(peekCallback) {} }; struct DataExpiredCallbackData { DataExpiredCallback* dataExpiredCb; bool resumed{true}; DataExpiredCallbackData(DataExpiredCallback* cb) : dataExpiredCb(cb) {} }; struct DataRejectedCallbackData { DataRejectedCallback* dataRejectedCb; bool resumed{true}; DataRejectedCallbackData(DataRejectedCallback* cb) : dataRejectedCb(cb) {} }; folly::F14FastMap readCallbacks_; folly::F14FastMap peekCallbacks_; folly:: F14FastMap>> deliveryCallbacks_; folly::F14FastMap dataExpiredCallbacks_; folly::F14FastMap dataRejectedCallbacks_; PingCallback* pingCallback_; WriteCallback* connWriteCallback_{nullptr}; std::map pendingWriteCallbacks_; CloseState closeState_{CloseState::OPEN}; bool transportReadyNotified_{false}; LossTimeout lossTimeout_; AckTimeout ackTimeout_; PathValidationTimeout pathValidationTimeout_; IdleTimeout idleTimeout_; DrainTimeout drainTimeout_; PingTimeout pingTimeout_; FunctionLooper::Ptr readLooper_; FunctionLooper::Ptr peekLooper_; FunctionLooper::Ptr writeLooper_; // TODO: This is silly. We need a better solution. // Uninitialied local address as a fallback answer when socket isn't bound. folly::SocketAddress localFallbackAddress; // CongestionController factory std::shared_ptr ccFactory_{nullptr}; folly::Function&, const Buf&) const> earlyDataAppParamsValidator_; folly::Function earlyDataAppParamsGetter_; }; std::ostream& operator<<(std::ostream& os, const QuicTransportBase& qt); } // namespace quic