TestHttp3ConnectUDPStream.cpp (13458B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "TestCommon.h" 7 #include "gtest/gtest.h" 8 #include "Http3ConnectUDPStream.h" 9 #include "Http3Session.h" 10 #include "nsIUDPSocket.h" 11 #include "nsIIOService.h" 12 #include "nsIProtocolProxyService.h" 13 #include "nsIProtocolHandler.h" 14 #include "nsThreadUtils.h" 15 #include "nsStringStream.h" 16 #include "nsProxyInfo.h" 17 #include "nsHttpConnectionInfo.h" 18 #include "nsHttpRequestHead.h" 19 #include "nsHttpHandler.h" 20 #include "mozilla/Components.h" 21 22 using namespace mozilla; 23 using namespace mozilla::net; 24 25 static const char* kProxyHost = "proxy.org"; 26 static const char* kHost = "example.com"; 27 static const int32_t kPort = 4433; 28 static const char* kMasqueTemplate = 29 "/.well-known/masque/udp/{target_host}/{target_port}/"; 30 static const char* kPathHeader = "/.well-known/masque/udp/example.com/4433/"; 31 32 class Http3SessionStub final : public Http3SessionBase { 33 public: 34 NS_INLINE_DECL_REFCOUNTING(Http3SessionStub, override) 35 36 nsresult TryActivating(const nsACString& aMethod, const nsACString& aScheme, 37 const nsACString& aAuthorityHeader, 38 const nsACString& aPath, const nsACString& aHeaders, 39 uint64_t* aStreamId, 40 Http3StreamBase* aStream) override { 41 mPathHeader = aPath; 42 mAuthHeader = aAuthorityHeader; 43 return NS_OK; 44 } 45 46 void CloseSendingSide(uint64_t aStreamId) override {} 47 48 void SendHTTPDatagram(uint64_t aStreamId, nsTArray<uint8_t>& aData, 49 uint64_t aTrackingId) override { 50 mOutputData.AppendElements(aData); 51 } 52 53 nsresult SendRequestBody(uint64_t aStreamId, const char* buf, uint32_t count, 54 uint32_t* countRead) override { 55 return NS_OK; 56 } 57 58 nsresult ReadResponseData(uint64_t aStreamId, char* aBuf, uint32_t aCount, 59 uint32_t* aCountWritten, bool* aFin) override { 60 *aCountWritten = 0; 61 *aFin = false; 62 return NS_OK; 63 } 64 65 nsresult SendPriorityUpdateFrame(uint64_t aStreamId, uint8_t aPriorityUrgency, 66 bool aPriorityIncremental) override { 67 return NS_OK; 68 } 69 70 void ConnectSlowConsumer(Http3StreamBase* stream) override {} 71 72 void CloseWebTransportConn() override {} 73 74 void StreamHasDataToWrite(Http3StreamBase* aStream) override { 75 mReadyForWrite.AppendElement(aStream); 76 } 77 78 nsresult CloseWebTransport(uint64_t aSessionId, uint32_t aError, 79 const nsACString& aMessage) override { 80 return NS_OK; 81 } 82 83 void SendDatagram(Http3WebTransportSession* aSession, 84 nsTArray<uint8_t>& aData, uint64_t aTrackingId) override {} 85 86 uint64_t MaxDatagramSize(uint64_t aSessionId) override { return 0; } 87 88 nsresult TryActivatingWebTransportStream(uint64_t* aStreamId, 89 Http3StreamBase* aStream) override { 90 *aStreamId = 0; 91 return NS_OK; 92 } 93 94 void ResetWebTransportStream(Http3WebTransportStream* aStream, 95 uint64_t aErrorCode) override {} 96 97 void StreamStopSending(Http3WebTransportStream* aStream, 98 uint8_t aErrorCode) override {} 99 100 void SetSendOrder(Http3StreamBase* aStream, 101 Maybe<int64_t> aSendOrder) override {} 102 103 void ProcessOutput() { 104 for (const auto& stream : mReadyForWrite) { 105 (void)stream->ReadSegments(); 106 } 107 mReadyForWrite.Clear(); 108 } 109 110 void FinishTunnelSetup(nsAHttpTransaction* aTransaction) override { 111 mFinishTunnelSetupCalled = true; 112 } 113 114 bool FinishTunnelSetupCalled() const { return mFinishTunnelSetupCalled; } 115 116 nsTArray<uint8_t> TakeOutputData() { return std::move(mOutputData); } 117 118 const nsCString& PathHeader() { return mPathHeader; } 119 const nsCString& AuthHeader() { return mAuthHeader; } 120 121 private: 122 ~Http3SessionStub() = default; 123 124 nsTArray<RefPtr<Http3StreamBase>> mReadyForWrite; 125 nsTArray<uint8_t> mOutputData; 126 nsCString mPathHeader; 127 nsCString mAuthHeader; 128 bool mFinishTunnelSetupCalled = false; 129 }; 130 131 class DummyHttpTransaction : public nsAHttpTransaction { 132 public: 133 NS_DECL_THREADSAFE_ISUPPORTS 134 135 DummyHttpTransaction() { 136 nsCString buffer; 137 buffer.AssignLiteral("capsule-protocol = ?1\r\n\r\n"); 138 NS_NewCStringInputStream(getter_AddRefs(mRequestStream), buffer); 139 140 nsCOMPtr<nsIProtocolProxyService> pps; 141 pps = mozilla::components::ProtocolProxy::Service(); 142 if (pps) { 143 nsCOMPtr<nsIProxyInfo> info; 144 nsresult rv = pps->NewMASQUEProxyInfo( 145 nsCString(kProxyHost), -1, nsCString(kMasqueTemplate), ""_ns, ""_ns, 146 0, 0, nullptr, getter_AddRefs(info)); 147 if (NS_FAILED(rv)) { 148 return; 149 } 150 mConnInfo = new nsHttpConnectionInfo( 151 nsCString(kHost), kPort, ""_ns, ""_ns, 152 static_cast<nsProxyInfo*>(info.get()), OriginAttributes()); 153 } 154 } 155 156 static nsresult ReadRequestSegment(nsIInputStream* stream, void* closure, 157 const char* buf, uint32_t offset, 158 uint32_t count, uint32_t* countRead) { 159 DummyHttpTransaction* trans = (DummyHttpTransaction*)closure; 160 return trans->mReader->OnReadSegment(buf, count, countRead); 161 } 162 163 void SetConnection(nsAHttpConnection*) override {} 164 nsAHttpConnection* Connection() override { return nullptr; } 165 void GetSecurityCallbacks(nsIInterfaceRequestor**) override {} 166 void OnTransportStatus(nsITransport* transport, nsresult status, 167 int64_t progress) override {} 168 bool IsDone() override { return mIsDone; } 169 nsresult Status() override { return NS_OK; } 170 uint32_t Caps() override { return 0; } 171 [[nodiscard]] nsresult ReadSegments(nsAHttpSegmentReader* reader, 172 uint32_t count, 173 uint32_t* countRead) override { 174 mReader = reader; 175 (void)mRequestStream->ReadSegments(ReadRequestSegment, this, count, 176 countRead); 177 mReader = nullptr; 178 return NS_OK; 179 } 180 [[nodiscard]] nsresult WriteSegments(nsAHttpSegmentWriter* writer, 181 uint32_t count, 182 uint32_t* countWritten) override { 183 char buf[1024]; 184 (void)writer->OnWriteSegment(buf, 1024, countWritten); 185 mIsDone = true; 186 return NS_OK; 187 } 188 void Close(nsresult reason) override {} 189 nsHttpConnectionInfo* ConnectionInfo() override { return mConnInfo.get(); } 190 void SetProxyConnectFailed() override {} 191 nsHttpRequestHead* RequestHead() override { 192 if (mRequestHead) { 193 return mRequestHead.get(); 194 } 195 196 mRequestHead = MakeUnique<nsHttpRequestHead>(); 197 198 (void)mRequestHead->SetHeader(nsHttp::Host, "example.com"_ns); 199 return mRequestHead.get(); 200 } 201 uint32_t Http1xTransactionCount() override { return 0; } 202 [[nodiscard]] nsresult TakeSubTransactions( 203 nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) override { 204 return NS_OK; 205 } 206 207 private: 208 virtual ~DummyHttpTransaction() = default; 209 210 nsAHttpSegmentReader* mReader{nullptr}; 211 nsCOMPtr<nsIInputStream> mRequestStream; 212 UniquePtr<nsHttpRequestHead> mRequestHead; 213 bool mIsDone = false; 214 RefPtr<nsHttpConnectionInfo> mConnInfo; 215 }; 216 217 NS_IMPL_ISUPPORTS(DummyHttpTransaction, nsISupportsWeakReference) 218 219 class UDPListener final : public nsIUDPSocketSyncListener { 220 public: 221 NS_DECL_ISUPPORTS 222 223 UDPListener() = default; 224 225 NS_IMETHOD OnPacketReceived(nsIUDPSocket* aSocket) override { 226 nsTArray<uint8_t> data; 227 NetAddr addr{}; 228 (void)aSocket->RecvWithAddr(&addr, data); 229 mReceivedData.AppendElements(data); 230 return NS_OK; 231 } 232 233 NS_IMETHOD OnStopListening(nsIUDPSocket* aSocket, nsresult aStatus) override { 234 mOnStopListeningCalled = true; 235 return NS_OK; 236 } 237 238 nsTArray<uint8_t> TakeInputData() { return std::move(mReceivedData); } 239 240 bool OnStopListeningCalled() const { return mOnStopListeningCalled; } 241 242 private: 243 ~UDPListener() = default; 244 245 bool mOnStopListeningCalled = false; 246 nsTArray<uint8_t> mReceivedData; 247 }; 248 249 NS_IMPL_ISUPPORTS(UDPListener, nsIUDPSocketSyncListener) 250 251 static void InitHttpHandler() { 252 if (gHttpHandler) { 253 return; 254 } 255 256 nsresult rv; 257 nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv); 258 if (NS_FAILED(rv)) { 259 return; 260 } 261 262 nsCOMPtr<nsIProtocolHandler> handler; 263 rv = ios->GetProtocolHandler("http", getter_AddRefs(handler)); 264 if (NS_FAILED(rv)) { 265 return; 266 } 267 } 268 269 static already_AddRefed<Http3ConnectUDPStream> CreateUDPStream( 270 Http3SessionStub* aSession) { 271 RefPtr<DummyHttpTransaction> trans = new DummyHttpTransaction(); 272 RefPtr<Http3ConnectUDPStream> stream = 273 new Http3ConnectUDPStream(trans, aSession, NS_GetCurrentThread()); 274 275 NetAddr peerAddr; 276 peerAddr.InitFromString("127.0.0.1"_ns); 277 stream->SetPeerAddr(peerAddr); 278 279 aSession->StreamHasDataToWrite(stream); 280 aSession->ProcessOutput(); 281 282 // HTTP/3 200 283 static constexpr uint8_t kResponse[] = {0x48, 0x54, 0x54, 0x50, 0x2F, 0x33, 284 0x20, 0x32, 0x30, 0x30, 0x0A, 0x0A}; 285 static constexpr uint32_t kResponseLen = sizeof(kResponse) - 1; 286 nsTArray<uint8_t> response; 287 response.AppendElements(kResponse, kResponseLen); 288 289 stream->SetResponseHeaders(response, false, false); 290 (void)stream->WriteSegments(); 291 292 return stream.forget(); 293 } 294 295 namespace ConnectUdp::testing { 296 297 static void CreateTestData(uint32_t aNumBytes, nsTArray<uint8_t>& aDataOut) { 298 static constexpr const char kSampleText[] = 299 "{\"type\":\"message\",\"id\":42,\"payload\":\"The quick brown fox jumps " 300 "over the lazy dog.\"}"; 301 static constexpr uint32_t kSampleTextLen = sizeof(kSampleText) - 1; 302 303 aDataOut.SetCapacity(aNumBytes); 304 305 while (aNumBytes > 0) { 306 uint32_t chunkSize = std::min(kSampleTextLen, aNumBytes); 307 aDataOut.AppendElements(reinterpret_cast<const uint8_t*>(kSampleText), 308 chunkSize); 309 aNumBytes -= chunkSize; 310 } 311 } 312 313 static void ValidateData(nsTArray<uint8_t>& aInput, 314 nsTArray<uint8_t>& aExpectedData) { 315 ASSERT_EQ(aExpectedData.Length(), aInput.Length()); 316 for (size_t i = 0; i < aExpectedData.Length(); i++) { 317 ASSERT_EQ(aExpectedData[i], aInput[i]); 318 } 319 } 320 321 } // namespace ConnectUdp::testing 322 323 TEST(ConnectUDP, SendDataBeforeActivate) 324 { 325 InitHttpHandler(); 326 327 RefPtr<Http3SessionStub> session = new Http3SessionStub(); 328 RefPtr<Http3ConnectUDPStream> stream = 329 new Http3ConnectUDPStream(nullptr, session, NS_GetCurrentThread()); 330 nsCOMPtr<nsIUDPSocket> udp = static_cast<nsIUDPSocket*>(stream.get()); 331 ASSERT_TRUE(udp); 332 333 NetAddr addr; 334 addr.InitFromString("127.0.0.1"_ns); 335 nsTArray<uint8_t> data; 336 ConnectUdp::testing::CreateTestData(100, data); 337 uint32_t written = 0; 338 nsresult rv = 339 udp->SendWithAddress(&addr, data.Elements(), data.Length(), &written); 340 ASSERT_EQ(rv, NS_ERROR_NOT_AVAILABLE); 341 } 342 343 TEST(ConnectUDP, SendData) 344 { 345 InitHttpHandler(); 346 347 RefPtr<Http3SessionStub> session = new Http3SessionStub(); 348 RefPtr<Http3ConnectUDPStream> stream = CreateUDPStream(session); 349 350 ASSERT_TRUE(session->FinishTunnelSetupCalled()); 351 ASSERT_TRUE(session->AuthHeader().EqualsASCII(kProxyHost)); 352 ASSERT_TRUE(session->PathHeader().EqualsASCII(kPathHeader)); 353 354 nsCOMPtr<nsIUDPSocket> udp = static_cast<nsIUDPSocket*>(stream.get()); 355 ASSERT_TRUE(udp); 356 357 NetAddr peerAddr; 358 peerAddr.InitFromString("127.0.0.1"_ns); 359 nsTArray<uint8_t> data; 360 ConnectUdp::testing::CreateTestData(100, data); 361 uint32_t written = 0; 362 nsresult rv = 363 udp->SendWithAddress(&peerAddr, data.Elements(), data.Length(), &written); 364 ASSERT_EQ(rv, NS_OK); 365 366 NS_ProcessPendingEvents(nullptr); 367 368 session->ProcessOutput(); 369 370 nsTArray<uint8_t> output = session->TakeOutputData(); 371 ConnectUdp::testing::ValidateData(data, output); 372 373 data.Clear(); 374 ConnectUdp::testing::CreateTestData(200, data); 375 376 rv = 377 udp->SendWithAddress(&peerAddr, data.Elements(), data.Length(), &written); 378 ASSERT_EQ(rv, NS_OK); 379 380 NS_ProcessPendingEvents(nullptr); 381 382 session->ProcessOutput(); 383 output = session->TakeOutputData(); 384 ConnectUdp::testing::ValidateData(data, output); 385 ASSERT_EQ(stream->ByteCountSent(), 300u); 386 387 udp->Close(); 388 } 389 390 TEST(ConnectUDP, RecvData) 391 { 392 InitHttpHandler(); 393 394 RefPtr<Http3SessionStub> session = new Http3SessionStub(); 395 RefPtr<Http3ConnectUDPStream> stream = CreateUDPStream(session); 396 397 ASSERT_TRUE(session->FinishTunnelSetupCalled()); 398 ASSERT_TRUE(session->AuthHeader().EqualsASCII(kProxyHost)); 399 ASSERT_TRUE(session->PathHeader().EqualsASCII(kPathHeader)); 400 401 nsCOMPtr<nsIUDPSocket> udp = static_cast<nsIUDPSocket*>(stream.get()); 402 ASSERT_TRUE(udp); 403 404 RefPtr<UDPListener> listener = new UDPListener(); 405 udp->SyncListen(listener); 406 407 nsTArray<uint8_t> data; 408 ConnectUdp::testing::CreateTestData(100, data); 409 stream->OnDatagramReceived(std::move(data)); 410 411 nsTArray<uint8_t> input = listener->TakeInputData(); 412 ASSERT_EQ(input.Length(), 100u); 413 414 ConnectUdp::testing::CreateTestData(200, data); 415 stream->OnDatagramReceived(std::move(data)); 416 417 input = listener->TakeInputData(); 418 ASSERT_EQ(input.Length(), 200u); 419 420 ASSERT_EQ(stream->ByteCountReceived(), 300u); 421 422 udp->Close(); 423 424 ASSERT_EQ(listener->OnStopListeningCalled(), true); 425 }