Skip to content

Commit 5c9d851

Browse files
Matt Jorasfacebook-github-bot
authored andcommitted
Allow NO_ERROR in ErrorCode2HTTPErrorCode/HTTP3ErrorCode2HTTPErrorCode; add regression tests
Summary: Problem: - Inbound H2 RST_STREAM(NO_ERROR) could crash via adaptor path: HTTPTransactionAdaptorSource::onError -> HTTPException2HTTPErrorCode -> ErrorCode2HTTPErrorCode(NO_ERROR) (pre-fix had a CHECK). - NO_ERROR is valid on the wire per H2/H3; session layer also normalizes H2 NO_ERROR to CANCEL for consumers. Fix: - Allow NO_ERROR in ErrorCode2HTTPErrorCode and HTTP3ErrorCode2HTTPErrorCode (remove CHECK), preserving mapping semantics. Tests added: - H2DownstreamSessionTest.ResetStreamNoError: client sends RST_STREAM/NO_ERROR; verify source sees CANCEL and message suffix "details=received RST_STREAM from peer". - HTTPTransactionAdaptorSourceTests.IngressNoErrorAbortNoCrash: drive adaptor onError with codec status NO_ERROR; verify error surfaced and no crash. Repro (pre-fix): - Reintroducing XCHECK(ec != ErrorCode::NO_ERROR) causes adaptor test to abort, confirming the crash path. Notes: - This aligns behavior with RFCs and avoids fatal checks for legitimate peer behavior. --- > Generated by [Confucius Code Assist (CCA)](https://www.internalfb.com/wiki/Confucius/Analect/Shared_Analects/Confucius_Code_Assist_(CCA)/) [Session](https://www.internalfb.com/confucius?session_id=c0ea225c-85ea-11f0-b2a3-fd678517c893&tab=Chat), [Trace](https://www.internalfb.com/confucius?session_id=c0ea225c-85ea-11f0-b2a3-fd678517c893&tab=Trace) Reviewed By: HsiehYuho Differential Revision: D81392253 fbshipit-source-id: 9d01036e267e1c1577c4f59c0e2c40a1d5e787f0
1 parent b97a4b7 commit 5c9d851

File tree

3 files changed

+62
-4
lines changed

3 files changed

+62
-4
lines changed

proxygen/lib/http/coro/HTTPError.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,7 @@ HTTP3::ErrorCode HTTPErrorCode2HTTP3ErrorCode(HTTPErrorCode ec,
208208
// with the same meaning and return only one code
209209
// Convert an H2 error code read off the wire to an HTTPErrorCode
210210
HTTPErrorCode ErrorCode2HTTPErrorCode(ErrorCode ec) {
211-
XCHECK(ec != ErrorCode::NO_ERROR);
212-
if (ec >= ErrorCode::PROTOCOL_ERROR && ec <= ErrorCode::HTTP_1_1_REQUIRED) {
211+
if (ec >= ErrorCode::NO_ERROR && ec <= ErrorCode::HTTP_1_1_REQUIRED) {
213212
return HTTPErrorCode(ec);
214213
}
215214
// Any unknown custom error codes are mapped to PROTOCOL_ERROR here.
@@ -218,8 +217,7 @@ HTTPErrorCode ErrorCode2HTTPErrorCode(ErrorCode ec) {
218217

219218
// Convert an H3 error code read off the wire to an HTTPErrorCode
220219
HTTPErrorCode HTTP3ErrorCode2HTTPErrorCode(HTTP3::ErrorCode ec) {
221-
XCHECK(ec != HTTP3::ErrorCode::HTTP_NO_ERROR);
222-
if ((ec >= HTTP3::ErrorCode::HTTP_GENERAL_PROTOCOL_ERROR &&
220+
if ((ec >= HTTP3::ErrorCode::HTTP_NO_ERROR &&
223221
ec <= HTTP3::ErrorCode::HTTP_VERSION_FALLBACK) ||
224222
(ec >= HTTP3::ErrorCode::HTTP_QPACK_DECOMPRESSION_FAILED &&
225223
ec <= HTTP3::ErrorCode::HTTP_QPACK_DECODER_STREAM_ERROR)) {

proxygen/lib/http/coro/test/HTTPDownstreamCoroSessionTests.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,6 +1977,38 @@ TEST_P(HTTPDownstreamSessionTest, HoldContext) {
19771977
}
19781978
}
19791979

1980+
TEST_P(H2DownstreamSessionTest, ResetStreamNoError) {
1981+
// Client sends RST_STREAM with NO_ERROR while server has not finished
1982+
// consuming ingress. This used to trigger a CHECK in
1983+
// ErrorCode2HTTPErrorCode(NO_ERROR) prior to the fix.
1984+
sendRequest("/", nullptr, /*eom=*/false, /*eof=*/false);
1985+
1986+
auto handler =
1987+
addSimpleStrictHandler([this](folly::EventBase * /*evb*/,
1988+
HTTPSessionContextPtr /*ctx*/,
1989+
HTTPSourceHolder requestSource)
1990+
-> folly::coro::Task<HTTPSourceHolder> {
1991+
auto id = *requestSource.getStreamID();
1992+
co_await expectRequest(
1993+
requestSource, HTTPMethod::GET, "/", /*eom=*/false);
1994+
// Inbound RST_STREAM with NO_ERROR from the peer.
1995+
resetStream(id, ErrorCode::NO_ERROR);
1996+
// Some transports in tests expect an extra read tick to drain.
1997+
transport_->addReadEvent(writeBuf_.move(), /*eom*/ true);
1998+
// Expect the body read to surface a CANCEL error (normalized).
1999+
auto bodyEvent =
2000+
co_await co_awaitTry(readBodyEventNoSuspend(requestSource));
2001+
EXPECT_TRUE(bodyEvent.hasException());
2002+
auto err = getHTTPError(bodyEvent);
2003+
EXPECT_TRUE(isCancelled(err.code));
2004+
EXPECT_TRUE(err.msg.ends_with("details=received RST_STREAM from peer"));
2005+
co_return nullptr;
2006+
});
2007+
2008+
evb_.loop();
2009+
parseOutput();
2010+
}
2011+
19802012
// H2 only, H1 needs to test with transport write error
19812013
TEST_P(H2QDownstreamSessionTest, StopReadingDefault500Source) {
19822014
// Stop reading as soon as headers are received without returning source. This

proxygen/lib/http/coro/test/HTTPTransactionAdaptorSourceTest.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,34 @@ CO_TEST_P_X(HTTPTransactionAdaptorSourceTests, DownstreamErrors) {
408408
adaptor_->setEgressSource(std::move(response));
409409
}
410410

411+
CO_TEST_P_X(HTTPTransactionAdaptorSourceTests, IngressNoErrorAbortNoCrash) {
412+
auto ingressSource = adaptor_->getIngressSource();
413+
auto handler = std::make_shared<TestHandler>();
414+
415+
auto msg = std::make_unique<HTTPMessage>();
416+
msg->setMethod(HTTPMethod::GET);
417+
msg->setURL("https://www.facebook.com/");
418+
419+
getHandler()->onHeadersComplete(std::move(msg));
420+
getHandler()->onBody(folly::IOBuf::copyBuffer("hello world"));
421+
// Do not send EOM to simulate incomplete ingress
422+
423+
// Simulate an inbound RST_STREAM(NO_ERROR) via codec status on the exception
424+
HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,
425+
"rst_stream_no_error");
426+
ex.setCodecStatusCode(ErrorCode::NO_ERROR);
427+
EXPECT_CALL(*mockTxn_, sendAbort(_)).Times(1);
428+
getHandler()->onError(ex);
429+
430+
// The adaptor will abort the ingress source; consuming should surface an
431+
// error
432+
auto responseTry = co_await co_awaitTry(folly::coro::detachOnCancel(
433+
handler->handleRequest(&evb_, ctx_.acquireKeepAlive(), ingressSource)));
434+
EXPECT_TRUE(responseTry.hasException());
435+
436+
getHandler()->detachTransaction();
437+
}
438+
411439
INSTANTIATE_TEST_SUITE_P(HTTPTransactionAdaptorSourceTests,
412440
HTTPTransactionAdaptorSourceTests,
413441
Values(TransportType::TCP),

0 commit comments

Comments
 (0)