1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-11-24 04:01:07 +03:00
Files
mvfst/quic/client/test/ClientStateMachineTest.cpp
Ilango Purushothaman 17b8763bd0 Cache Ack Rx timestamps transport settings in PSK cache for 0-rtt
Summary:
Ack Rx timestamps are currently disabled on 0-rtt connections, which is enabled on mvfst for all non-video connections.

This is because the timestamp config is negotiated with transport settings during handshake which doesn't happen on a new 0-rtt connection (resumption).

Solution:
Store the peer's rx timestamp config in PSKCache on the client. On 0-rtt resumption of the connection, this peer config is restored and used to send Rx timestamps (if enabled).

Note that if the server had changed its timestamp config during this period (through configerator) then the server needs to reject this 0-rtt connection and start anew. Server code changes to support this will be in a followup diff. With this client diff though,  server will drop the Rx timestamps if its own config is suddenly disabled (this is a waste of network resource).

Reviewed By: jbeshay

Differential Revision: D58572572

fbshipit-source-id: d95720c177ac4bc8dcbe40362f19b279b3f8e708
2024-06-14 23:06:39 -07:00

276 lines
11 KiB
C++

/*
* 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.
*/
#include <quic/api/QuicTransportFunctions.h>
#include <quic/api/test/MockQuicSocket.h>
#include <quic/client/handshake/CachedServerTransportParameters.h>
#include <quic/client/handshake/ClientHandshake.h>
#include <quic/client/state/ClientStateMachine.h>
#include <quic/client/test/Mocks.h>
#include <quic/common/events/FollyQuicEventBase.h>
#include <quic/common/udpsocket/FollyQuicAsyncUDPSocket.h>
#include <quic/fizz/client/handshake/FizzClientQuicHandshakeContext.h>
#include <quic/handshake/CryptoFactory.h>
#include <quic/handshake/TransportParameters.h>
using namespace ::testing;
namespace quic::test {
namespace {
// Use non-default values to test for nops
constexpr auto idleTimeout = kDefaultIdleTimeout + 1s;
constexpr auto maxRecvPacketSize = 1420;
constexpr auto initialMaxData = kDefaultConnectionFlowControlWindow + 2;
constexpr auto initialMaxStreamDataBidiLocal =
kDefaultStreamFlowControlWindow + 3;
constexpr auto initialMaxStreamDataBidiRemote =
kDefaultStreamFlowControlWindow + 4;
constexpr auto initialMaxStreamDataUni = kDefaultStreamFlowControlWindow + 5;
constexpr auto initialMaxStreamsBidi = kDefaultMaxStreamsBidirectional + 6;
constexpr auto initialMaxStreamsUni = kDefaultMaxStreamsUnidirectional + 7;
constexpr auto knobFrameSupport = true;
constexpr auto ackReceiveTimestampsEnabled = true;
constexpr auto maxReceiveTimestampsPerAck = 10;
constexpr auto ackReceiveTimestampsExponent = 0;
const CachedServerTransportParameters kParams{
std::chrono::milliseconds(idleTimeout).count(),
maxRecvPacketSize,
initialMaxData,
initialMaxStreamDataBidiLocal,
initialMaxStreamDataBidiRemote,
initialMaxStreamDataUni,
initialMaxStreamsBidi,
initialMaxStreamsUni,
knobFrameSupport,
ackReceiveTimestampsEnabled,
maxReceiveTimestampsPerAck,
ackReceiveTimestampsExponent};
} // namespace
class ClientStateMachineTest : public Test {
public:
void SetUp() override {
mockFactory_ = std::make_shared<MockClientHandshakeFactory>();
EXPECT_CALL(*mockFactory_, _makeClientHandshake(_))
.WillRepeatedly(Invoke(
[&](QuicClientConnectionState* conn)
-> std::unique_ptr<quic::ClientHandshake> {
auto handshake = std::make_unique<MockClientHandshake>(conn);
mockHandshake_ = handshake.get();
return handshake;
}));
client_ = std::make_unique<QuicClientConnectionState>(mockFactory_);
}
std::shared_ptr<MockClientHandshakeFactory> mockFactory_;
MockClientHandshake* mockHandshake_;
std::unique_ptr<QuicClientConnectionState> client_;
};
TEST_F(ClientStateMachineTest, TestUpdateTransportParamsNotIgnorePathMTU) {
updateTransportParamsFromCachedEarlyParams(*client_, kParams);
EXPECT_EQ(client_->udpSendPacketLen, kDefaultUDPSendPacketLen);
}
TEST_F(ClientStateMachineTest, TestUpdateTransportParamsFromCachedEarlyParams) {
client_->transportSettings.canIgnorePathMTU = true;
client_->peerAdvertisedKnobFrameSupport = false;
client_->maybePeerAckReceiveTimestampsConfig.assign(
{.maxReceiveTimestampsPerAck = 10, .receiveTimestampsExponent = 0});
updateTransportParamsFromCachedEarlyParams(*client_, kParams);
EXPECT_EQ(client_->peerIdleTimeout, idleTimeout);
EXPECT_NE(client_->udpSendPacketLen, maxRecvPacketSize);
EXPECT_EQ(client_->flowControlState.peerAdvertisedMaxOffset, initialMaxData);
EXPECT_EQ(
client_->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiLocal,
initialMaxStreamDataBidiLocal);
EXPECT_EQ(
client_->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote,
initialMaxStreamDataBidiRemote);
EXPECT_EQ(
client_->flowControlState.peerAdvertisedInitialMaxStreamOffsetUni,
initialMaxStreamDataUni);
EXPECT_EQ(client_->peerAdvertisedKnobFrameSupport, knobFrameSupport);
ASSERT_TRUE(client_->maybePeerAckReceiveTimestampsConfig.has_value());
EXPECT_EQ(
client_->maybePeerAckReceiveTimestampsConfig.value()
.maxReceiveTimestampsPerAck,
maxReceiveTimestampsPerAck);
EXPECT_EQ(
client_->maybePeerAckReceiveTimestampsConfig.value()
.receiveTimestampsExponent,
ackReceiveTimestampsExponent);
for (unsigned long i = 0; i < initialMaxStreamsBidi; i++) {
EXPECT_TRUE(
client_->streamManager->createNextBidirectionalStream().hasValue());
}
EXPECT_TRUE(
client_->streamManager->createNextBidirectionalStream().hasError());
for (unsigned long i = 0; i < initialMaxStreamsUni; i++) {
EXPECT_TRUE(
client_->streamManager->createNextUnidirectionalStream().hasValue());
}
EXPECT_TRUE(
client_->streamManager->createNextUnidirectionalStream().hasError());
}
TEST_F(ClientStateMachineTest, PreserveHappyeyabllsDuringUndo) {
folly::EventBase evb;
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
client_->clientConnectionId = ConnectionId::createRandom(8);
client_->happyEyeballsState.finished = true;
client_->happyEyeballsState.secondSocket =
std::make_unique<FollyQuicAsyncUDPSocket>(qEvb);
auto newConn = undoAllClientStateForRetry(std::move(client_));
EXPECT_TRUE(newConn->happyEyeballsState.finished);
EXPECT_NE(nullptr, newConn->happyEyeballsState.secondSocket);
}
TEST_F(ClientStateMachineTest, PreserveObserverContainer) {
auto socket = std::make_shared<MockQuicSocket>();
const auto observerContainer =
std::make_shared<SocketObserverContainer>(socket.get());
SocketObserverContainer::ManagedObserver obs;
observerContainer->addObserver(&obs);
client_->clientConnectionId = ConnectionId::createRandom(8);
client_->observerContainer = observerContainer;
EXPECT_EQ(
1,
CHECK_NOTNULL(client_->observerContainer.lock().get())->numObservers());
EXPECT_THAT(
CHECK_NOTNULL(client_->observerContainer.lock().get())->findObservers(),
UnorderedElementsAre(&obs));
auto newConn = undoAllClientStateForRetry(std::move(client_));
EXPECT_EQ(newConn->observerContainer.lock(), observerContainer);
EXPECT_EQ(
1,
CHECK_NOTNULL(newConn->observerContainer.lock().get())->numObservers());
EXPECT_THAT(
CHECK_NOTNULL(newConn->observerContainer.lock().get())->findObservers(),
UnorderedElementsAre(&obs));
}
TEST_F(ClientStateMachineTest, PreserveObserverContainerNullptr) {
client_->clientConnectionId = ConnectionId::createRandom(8);
ASSERT_THAT(client_->observerContainer.lock(), IsNull());
auto newConn = undoAllClientStateForRetry(std::move(client_));
EXPECT_THAT(newConn->observerContainer.lock(), IsNull());
}
TEST_F(ClientStateMachineTest, TestProcessMaxDatagramSizeBelowMin) {
QuicClientConnectionState clientConn(
FizzClientQuicHandshakeContext::Builder().build());
std::vector<TransportParameter> transportParams;
transportParams.push_back(encodeIntegerParameter(
TransportParameterId::max_datagram_frame_size,
kMaxDatagramPacketOverhead - 1));
ServerTransportParameters serverTransportParams = {
std::move(transportParams)};
try {
processServerInitialParams(clientConn, serverTransportParams, 0);
FAIL()
<< "Expect transport exception due to max datagram frame size too small";
} catch (QuicTransportException& e) {
EXPECT_EQ(e.errorCode(), TransportErrorCode::TRANSPORT_PARAMETER_ERROR);
}
}
TEST_F(ClientStateMachineTest, TestProcessMaxDatagramSizeZeroOk) {
QuicClientConnectionState clientConn(
FizzClientQuicHandshakeContext::Builder().build());
std::vector<TransportParameter> transportParams;
transportParams.push_back(
encodeIntegerParameter(TransportParameterId::max_datagram_frame_size, 0));
ServerTransportParameters serverTransportParams = {
std::move(transportParams)};
processServerInitialParams(clientConn, serverTransportParams, 0);
EXPECT_EQ(clientConn.datagramState.maxWriteFrameSize, 0);
}
TEST_F(ClientStateMachineTest, TestProcessMaxDatagramSizeOk) {
QuicClientConnectionState clientConn(
FizzClientQuicHandshakeContext::Builder().build());
std::vector<TransportParameter> transportParams;
transportParams.push_back(encodeIntegerParameter(
TransportParameterId::max_datagram_frame_size,
kMaxDatagramPacketOverhead + 1));
ServerTransportParameters serverTransportParams = {
std::move(transportParams)};
processServerInitialParams(clientConn, serverTransportParams, 0);
EXPECT_EQ(
clientConn.datagramState.maxWriteFrameSize,
kMaxDatagramPacketOverhead + 1);
}
TEST_F(ClientStateMachineTest, TestProcessKnobFramesSupportedParamEnabled) {
QuicClientConnectionState clientConn(
FizzClientQuicHandshakeContext::Builder().build());
std::vector<TransportParameter> transportParams;
transportParams.push_back(
encodeIntegerParameter(TransportParameterId::knob_frames_supported, 1));
ServerTransportParameters serverTransportParams = {
std::move(transportParams)};
processServerInitialParams(clientConn, serverTransportParams, 0);
EXPECT_TRUE(clientConn.peerAdvertisedKnobFrameSupport);
}
TEST_F(ClientStateMachineTest, TestProcessKnobFramesSupportedParamDisabled) {
QuicClientConnectionState clientConn(
FizzClientQuicHandshakeContext::Builder().build());
std::vector<TransportParameter> transportParams;
transportParams.push_back(
encodeIntegerParameter(TransportParameterId::knob_frames_supported, 0));
ServerTransportParameters serverTransportParams = {
std::move(transportParams)};
processServerInitialParams(clientConn, serverTransportParams, 0);
EXPECT_FALSE(clientConn.peerAdvertisedKnobFrameSupport);
}
struct maxStreamGroupsAdvertizedtestStruct {
uint64_t peerMaxGroupsIn;
OptionalIntegral<uint64_t> expectedTransportSettingVal;
};
class ClientStateMachineMaxStreamGroupsAdvertizedParamTest
: public ClientStateMachineTest,
public ::testing::WithParamInterface<
maxStreamGroupsAdvertizedtestStruct> {};
TEST_P(
ClientStateMachineMaxStreamGroupsAdvertizedParamTest,
TestMaxStreamGroupsAdvertizedParam) {
QuicClientConnectionState clientConn(
FizzClientQuicHandshakeContext::Builder().build());
std::vector<TransportParameter> transportParams;
if (GetParam().peerMaxGroupsIn > 0) {
transportParams.push_back(encodeIntegerParameter(
TransportParameterId::stream_groups_enabled,
GetParam().peerMaxGroupsIn));
}
ServerTransportParameters serverTransportParams = {
std::move(transportParams)};
processServerInitialParams(clientConn, serverTransportParams, 0);
EXPECT_EQ(
clientConn.peerAdvertisedMaxStreamGroups,
GetParam().expectedTransportSettingVal);
}
INSTANTIATE_TEST_SUITE_P(
ClientStateMachineMaxStreamGroupsAdvertizedParamTest,
ClientStateMachineMaxStreamGroupsAdvertizedParamTest,
::testing::Values(
maxStreamGroupsAdvertizedtestStruct{0, std::nullopt},
maxStreamGroupsAdvertizedtestStruct{16, 16}));
} // namespace quic::test