diff --git a/quic/api/Observer.h b/quic/api/Observer.h index 5997c187c..6b3702528 100644 --- a/quic/api/Observer.h +++ b/quic/api/Observer.h @@ -42,6 +42,7 @@ class Observer { bool spuriousLossEvents{false}; bool pmtuEvents{false}; bool rttSamples{false}; + bool knobFrameEvents{false}; /** * Returns a config where all events are enabled. @@ -54,6 +55,7 @@ class Observer { config.lossEvents = true; config.spuriousLossEvents = true; config.pmtuEvents = true; + config.knobFrameEvents = true; return config; } }; @@ -200,6 +202,13 @@ class Observer { std::vector spuriousPackets; }; + struct KnobFrameEvent { + explicit KnobFrameEvent(TimePoint rcvTimeIn, quic::KnobFrame knobFrame) + : rcvTime(rcvTimeIn), knobFrame(std::move(knobFrame)) {} + const TimePoint rcvTime; + const quic::KnobFrame knobFrame; + }; + /** * observerAttach() will be invoked when an observer is added. * @@ -328,6 +337,16 @@ class Observer { QuicSocket*, /* socket */ const SpuriousLossEvent& /* lost packet */) {} + /** + * knobFrameReceived() is invoked when a knob frame is received. + * + * @param socket Socket when the callback is processed. + * @param event const reference to the KnobFrameEvent. + */ + virtual void knobFrameReceived( + QuicSocket*, /* socket */ + const KnobFrameEvent& /* event */) {} + protected: // observer configuration; cannot be changed post instantiation const Config observerConfig_; diff --git a/quic/api/QuicTransportBase.cpp b/quic/api/QuicTransportBase.cpp index 9b9d9ea87..94b44748e 100644 --- a/quic/api/QuicTransportBase.cpp +++ b/quic/api/QuicTransportBase.cpp @@ -1640,6 +1640,13 @@ void QuicTransportBase::processCallbacksAfterWriteData() { void QuicTransportBase::handleKnobCallbacks() { for (auto& knobFrame : conn_->pendingEvents.knobs) { if (knobFrame.knobSpace != kDefaultQuicTransportKnobSpace) { + for (const auto& cb : *observers_) { + if (cb->getConfig().knobFrameEvents) { + cb->knobFrameReceived( + this, quic::Observer::KnobFrameEvent(Clock::now(), knobFrame)); + } + } + connCallback_->onKnob( knobFrame.knobSpace, knobFrame.id, std::move(knobFrame.blob)); } else { diff --git a/quic/api/test/Mocks.h b/quic/api/test/Mocks.h index 0a739a01b..12746df32 100644 --- a/quic/api/test/Mocks.h +++ b/quic/api/test/Mocks.h @@ -118,6 +118,16 @@ class MockConnectionCallback : public QuicSocket::ConnectionCallback { onUnidirectionalStreamsAvailable, void(uint64_t)); GMOCK_METHOD0_(, noexcept, , onAppRateLimited, void()); + GMOCK_METHOD3_( + , + noexcept, + , + onKnobMock, + void(uint64_t, uint64_t, folly::IOBuf*)); + + void onKnob(uint64_t knobSpace, uint64_t knobId, Buf knobBlob) override { + onKnobMock(knobSpace, knobId, knobBlob.get()); + } }; class MockDeliveryCallback : public QuicSocket::DeliveryCallback { @@ -357,6 +367,12 @@ class MockObserver : public Observer { , spuriousLossDetected, void(QuicSocket*, const SpuriousLossEvent&)); + GMOCK_METHOD2_( + , + noexcept, + , + knobFrameReceived, + void(QuicSocket*, const KnobFrameEvent&)); static auto getLossPacketNum(PacketNum packetNum) { return testing::Field( diff --git a/quic/api/test/QuicTransportBaseTest.cpp b/quic/api/test/QuicTransportBaseTest.cpp index d27b7182f..f25681e96 100644 --- a/quic/api/test/QuicTransportBaseTest.cpp +++ b/quic/api/test/QuicTransportBaseTest.cpp @@ -294,6 +294,10 @@ class TestQuicTransport handlePingCallback(); } + void invokeHandleKnobCallbacks() { + handleKnobCallbacks(); + } + bool isPingTimeoutScheduled() { if (pingTimeout_.isScheduled()) { return true; @@ -3385,6 +3389,40 @@ TEST_F(QuicTransportImplTest, FailedPing) { EXPECT_EQ(conn->pendingEvents.cancelPingTimeout, false); } +TEST_F(QuicTransportImplTest, HandleKnobCallbacks) { + auto conn = transport->transportConn; + + // attach an observer to the socket + Observer::Config config = {}; + config.knobFrameEvents = true; + auto cb = std::make_unique>(config); + EXPECT_CALL(*cb, observerAttach(transport.get())); + transport->addObserver(cb.get()); + Mock::VerifyAndClearExpectations(cb.get()); + + // set test knob frame + uint64_t knobSpace = 0xfaceb00c; + uint64_t knobId = 42; + folly::StringPiece data = "test knob data"; + Buf buf(folly::IOBuf::create(data.size())); + memcpy(buf->writableData(), data.data(), data.size()); + buf->append(data.size()); + conn->pendingEvents.knobs.emplace_back( + KnobFrame(knobSpace, knobId, std::move(buf))); + + EXPECT_CALL(connCallback, onKnobMock(knobSpace, knobId, _)) + .WillOnce(Invoke([](Unused, Unused, Unused) { /* do nothing */ })); + EXPECT_CALL(*cb, knobFrameReceived(transport.get(), _)).Times(1); + transport->invokeHandleKnobCallbacks(); + evb->loopOnce(); + EXPECT_EQ(conn->pendingEvents.knobs.size(), 0); + + // detach the observer from the socket + EXPECT_CALL(*cb, observerDetach(transport.get())); + EXPECT_TRUE(transport->removeObserver(cb.get())); + Mock::VerifyAndClearExpectations(cb.get()); +} + TEST_F(QuicTransportImplTest, StreamWriteCallbackUnregister) { auto stream = transport->createBidirectionalStream().value(); // Unset before set