1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-08-09 20:42:44 +03:00
Files
mvfst/quic/api/test/QuicStreamAsyncTransportTest.cpp
Alan Frindell 7be403c697 QuicStreamAsyncTransport fixes
Summary:
I was using this for hq-interop testing, and I discovered a couple bugs.

1) readCb_ may not be set initially, so only attempt an initial read if it's non-null

2) When this transport closes, we shouldn't close the underlying QUIC socket.  Instead we should attempt to write a FIN (if we haven't already).  If that doesn't immediately succeed (perhaps queued writes are blocked on flow control), send a reset.

Reviewed By: kvtsoy

Differential Revision: D40741000

fbshipit-source-id: f3f925b884ae30feac0d86cbca13084248566099
2022-11-16 18:25:26 -08:00

299 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 <folly/MoveWrapper.h>
#include <folly/container/F14Map.h>
#include <folly/futures/Future.h>
#include <folly/io/async/test/MockAsyncTransport.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
#include <quic/api/QuicStreamAsyncTransport.h>
#include <quic/api/test/Mocks.h>
#include <quic/client/QuicClientTransport.h>
#include <quic/common/test/TestClientUtils.h>
#include <quic/common/test/TestUtils.h>
#include <quic/fizz/client/handshake/FizzClientHandshake.h>
#include <quic/fizz/client/handshake/FizzClientQuicHandshakeContext.h>
#include <quic/server/QuicServer.h>
#include <quic/server/QuicServerTransport.h>
#include <quic/server/test/Mocks.h>
using namespace testing;
namespace quic::test {
class QuicStreamAsyncTransportTest : public Test {
protected:
struct Stream {
Stream() = default;
Stream(const Stream&) = delete;
Stream& operator=(const Stream&) = delete;
Stream(Stream&&) = delete;
Stream& operator=(Stream&&) = delete;
folly::test::MockWriteCallback writeCb;
folly::test::MockReadCallback readCb;
QuicStreamAsyncTransport::UniquePtr transport;
std::array<uint8_t, 1024> buf;
uint8_t serverDone{2}; // need to finish reads & writes
};
public:
void SetUp() override {
folly::ssl::init();
createServer();
connect();
}
void createServer() {
auto serverTransportFactory =
std::make_unique<MockQuicServerTransportFactory>();
EXPECT_CALL(*serverTransportFactory, _make(_, _, _, _))
.WillOnce(Invoke(
[&](folly::EventBase* evb,
std::unique_ptr<folly::AsyncUDPSocket>& socket,
const folly::SocketAddress& /*addr*/,
std::shared_ptr<const fizz::server::FizzServerContext> ctx) {
auto transport = quic::QuicServerTransport::make(
evb,
std::move(socket),
&serverConnectionSetupCB_,
&serverConnectionCB_,
std::move(ctx));
CHECK(serverSocket_.get() == nullptr);
serverSocket_ = transport;
return transport;
}));
server_ = QuicServer::createQuicServer();
auto serverCtx = test::createServerCtx();
server_->setFizzContext(serverCtx);
server_->setQuicServerTransportFactory(std::move(serverTransportFactory));
folly::SocketAddress addr("::1", 0);
server_->start(addr, 1);
server_->waitUntilInitialized();
serverAddr_ = server_->getAddress();
}
void expectNewServerStream() {
EXPECT_CALL(serverConnectionCB_, onNewBidirectionalStream(_))
.WillOnce(Invoke([&](StreamId id) {
auto res = streams_.emplace(
std::piecewise_construct,
std::forward_as_tuple(id),
std::forward_as_tuple(std::make_unique<Stream>()));
auto& newStream = *res.first->second;
newStream.transport =
QuicStreamAsyncTransport::createWithExistingStream(
serverSocket_, id);
EXPECT_CALL(newStream.readCb, readEOF_()).WillOnce(Invoke([this, id] {
auto& stream = *streams_[id];
if (--stream.serverDone == 0) {
stream.transport->close();
}
}));
EXPECT_CALL(newStream.readCb, isBufferMovable_())
.WillRepeatedly(Return(false));
EXPECT_CALL(newStream.readCb, getReadBuffer(_, _))
.WillRepeatedly(Invoke([this, id](void** buf, size_t* len) {
auto& stream = *streams_[id];
*buf = stream.buf.data();
*len = stream.buf.size();
}));
EXPECT_CALL(newStream.readCb, readDataAvailable_(_))
.WillRepeatedly(Invoke([this, id](auto len) {
auto& stream = *streams_[id];
auto echoData = folly::IOBuf::copyBuffer("echo ");
echoData->appendChain(
folly::IOBuf::wrapBuffer(stream.buf.data(), len));
EXPECT_CALL(stream.writeCb, writeSuccess_())
.WillOnce(Return())
.RetiresOnSaturation();
if (stream.transport->good()) {
// Echo the first readDataAvailable_ only
stream.transport->writeChain(
&stream.writeCb, std::move(echoData));
stream.transport->shutdownWrite();
if (--stream.serverDone == 0) {
stream.transport->close();
}
}
}));
newStream.transport->setReadCB(&newStream.readCb);
}))
.RetiresOnSaturation();
}
std::unique_ptr<Stream> createClient(bool setReadCB = true) {
auto clientStream = std::make_unique<Stream>();
clientStream->transport =
QuicStreamAsyncTransport::createWithNewStream(client_);
CHECK(clientStream->transport);
EXPECT_CALL(clientStream->readCb, isBufferMovable_())
.WillRepeatedly(Return(false));
EXPECT_CALL(clientStream->readCb, getReadBuffer(_, _))
.WillRepeatedly(Invoke(
[clientStream = clientStream.get()](void** buf, size_t* len) {
*buf = clientStream->buf.data();
*len = clientStream->buf.size();
}));
if (setReadCB) {
clientStream->transport->setReadCB(&clientStream->readCb);
}
return clientStream;
}
void connect() {
auto [promiseX, future] = folly::makePromiseContract<folly::Unit>();
auto promise = std::move(promiseX);
EXPECT_CALL(clientConnectionSetupCB_, onTransportReady())
.WillOnce(Invoke([&promise]() mutable { promise.setValue(); }));
clientEvb_.runInLoop([&]() {
auto sock = std::make_unique<folly::AsyncUDPSocket>(&clientEvb_);
auto fizzClientContext =
FizzClientQuicHandshakeContext::Builder()
.setCertificateVerifier(test::createTestCertificateVerifier())
.build();
client_ = std::make_shared<QuicClientTransport>(
&clientEvb_, std::move(sock), std::move(fizzClientContext));
client_->setHostname("echo.com");
client_->addNewPeerAddress(serverAddr_);
client_->start(&clientConnectionSetupCB_, &clientConnectionCB_);
});
std::move(future).via(&clientEvb_).waitVia(&clientEvb_);
}
void TearDown() override {
if (client_) {
client_->close(folly::none);
}
clientEvb_.loop();
server_->shutdown();
server_ = nullptr;
client_ = nullptr;
}
protected:
std::shared_ptr<QuicServer> server_;
folly::SocketAddress serverAddr_;
NiceMock<MockConnectionSetupCallback> serverConnectionSetupCB_;
NiceMock<MockConnectionCallback> serverConnectionCB_;
std::shared_ptr<quic::QuicSocket> serverSocket_;
folly::F14FastMap<quic::StreamId, std::unique_ptr<Stream>> streams_;
std::shared_ptr<QuicClientTransport> client_;
folly::EventBase clientEvb_;
NiceMock<MockConnectionSetupCallback> clientConnectionSetupCB_;
NiceMock<MockConnectionCallback> clientConnectionCB_;
};
TEST_F(QuicStreamAsyncTransportTest, ReadWrite) {
expectNewServerStream();
auto clientStream = createClient();
EXPECT_CALL(clientStream->readCb, readEOF_()).WillOnce(Return());
auto [promiseX, future] = folly::makePromiseContract<std::string>();
auto promise = std::move(promiseX);
EXPECT_CALL(clientStream->readCb, readDataAvailable_(_))
.WillOnce(Invoke([&clientStream, &promise](auto len) mutable {
promise.setValue(std::string(
reinterpret_cast<char*>(clientStream->buf.data()), len));
}));
std::string msg = "yo yo!";
EXPECT_CALL(clientStream->writeCb, writeSuccess_()).WillOnce(Return());
clientStream->transport->write(
&clientStream->writeCb, msg.data(), msg.size());
clientStream->transport->shutdownWrite();
EXPECT_EQ(
std::move(future).via(&clientEvb_).getVia(&clientEvb_), "echo yo yo!");
}
TEST_F(QuicStreamAsyncTransportTest, TwoClients) {
std::list<std::unique_ptr<Stream>> clientStreams;
std::list<folly::SemiFuture<std::string>> futures;
std::string msg = "yo yo!";
for (auto i = 0; i < 2; i++) {
expectNewServerStream();
clientStreams.emplace_back(createClient());
auto& clientStream = clientStreams.back();
EXPECT_CALL(clientStream->readCb, readEOF_()).WillOnce(Return());
auto [promiseX, future] = folly::makePromiseContract<std::string>();
auto promise = std::move(promiseX);
futures.emplace_back(std::move(future));
EXPECT_CALL(clientStream->readCb, readDataAvailable_(_))
.WillOnce(Invoke(
[clientStream = clientStream.get(),
p = folly::MoveWrapper(std::move(promise))](auto len) mutable {
p->setValue(std::string(
reinterpret_cast<char*>(clientStream->buf.data()), len));
}));
EXPECT_CALL(clientStream->writeCb, writeSuccess_()).WillOnce(Return());
clientStream->transport->write(
&clientStream->writeCb, msg.data(), msg.size());
clientStream->transport->shutdownWrite();
}
for (auto& future : futures) {
EXPECT_EQ(
std::move(future).via(&clientEvb_).getVia(&clientEvb_), "echo yo yo!");
}
}
TEST_F(QuicStreamAsyncTransportTest, DelayedSetReadCB) {
expectNewServerStream();
auto clientStream = createClient(/*setReadCB=*/false);
auto [promiseX, future] = folly::makePromiseContract<std::string>();
auto promise = std::move(promiseX);
EXPECT_CALL(clientStream->readCb, readDataAvailable_(_))
.WillOnce(Invoke([&clientStream, &promise](auto len) mutable {
promise.setValue(std::string(
reinterpret_cast<char*>(clientStream->buf.data()), len));
}));
std::string msg = "yo yo!";
EXPECT_CALL(clientStream->writeCb, writeSuccess_()).WillOnce(Return());
clientStream->transport->write(
&clientStream->writeCb, msg.data(), msg.size());
clientEvb_.runAfterDelay(
[&clientStream] {
EXPECT_CALL(clientStream->readCb, readEOF_()).WillOnce(Return());
clientStream->transport->setReadCB(&clientStream->readCb);
clientStream->transport->shutdownWrite();
},
750);
EXPECT_EQ(
std::move(future).via(&clientEvb_).getVia(&clientEvb_), "echo yo yo!");
}
TEST_F(QuicStreamAsyncTransportTest, close) {
auto clientStream = createClient(/*setReadCB=*/false);
EXPECT_TRUE(client_->good());
clientStream->transport->close();
clientStream->transport.reset();
EXPECT_TRUE(client_->good());
clientEvb_.loopOnce();
}
TEST_F(QuicStreamAsyncTransportTest, closeNow) {
auto clientStream = createClient(/*setReadCB=*/false);
EXPECT_TRUE(client_->good());
clientStream->transport->closeNow();
clientStream->transport.reset();
// The quic socket is still good
EXPECT_TRUE(client_->good());
clientEvb_.loopOnce();
}
} // namespace quic::test