Fix Known-Length BHTTP frame decoding
The current logic assumes the framing indicator is only one byte, but [RFC 9292](https://www.rfc-editor.org/rfc/rfc9292.html) states that the request and response framing indicators use the variable-length integer encoding from [RFC 9000](https://www.rfc-editor.org/rfc/rfc9000#section-16) and that values do not need to be encoded on the minimum number of bytes necessary. This means the logic has to also handle 2, 4, and 8-byte long values.
Protected by Tests, this change still handles the 1-byte framing indicator.
PiperOrigin-RevId: 774991715
diff --git a/quiche/binary_http/binary_http_message.cc b/quiche/binary_http/binary_http_message.cc
index cf95e34..815b30e 100644
--- a/quiche/binary_http/binary_http_message.cc
+++ b/quiche/binary_http/binary_http_message.cc
@@ -24,8 +24,8 @@
namespace quiche {
namespace {
-constexpr uint8_t kKnownLengthRequestFraming = 0;
-constexpr uint8_t kKnownLengthResponseFraming = 1;
+constexpr uint64_t kKnownLengthRequestFraming = 0;
+constexpr uint64_t kKnownLengthResponseFraming = 1;
bool ReadStringValue(quiche::QuicheDataReader& reader, std::string& data) {
absl::string_view data_view;
@@ -270,7 +270,7 @@
std::string data;
data.resize(EncodedSize());
quiche::QuicheDataWriter writer(data.size(), data.data());
- if (!writer.WriteUInt8(kKnownLengthResponseFraming)) {
+ if (!writer.WriteVarInt62(kKnownLengthResponseFraming)) {
return absl::InvalidArgumentError("Failed to write framing indicator");
}
// Informational response
@@ -293,7 +293,7 @@
}
size_t BinaryHttpResponse::EncodedSize() const {
- size_t size = sizeof(kKnownLengthResponseFraming);
+ size_t size = QuicheDataWriter::GetVarInt62Len(kKnownLengthResponseFraming);
for (const auto& informational : informational_response_control_data_) {
size += informational.EncodedSize();
}
@@ -363,8 +363,9 @@
}
size_t BinaryHttpRequest::EncodedSize() const {
- return sizeof(kKnownLengthRequestFraming) + EncodedControlDataSize() +
- EncodedKnownLengthFieldsAndBodySize() + num_padding_bytes();
+ return QuicheDataWriter::GetVarInt62Len(kKnownLengthRequestFraming) +
+ EncodedControlDataSize() + EncodedKnownLengthFieldsAndBodySize() +
+ num_padding_bytes();
}
// https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-known-length-messages
@@ -372,7 +373,7 @@
std::string data;
data.resize(EncodedSize());
quiche::QuicheDataWriter writer(data.size(), data.data());
- if (!writer.WriteUInt8(kKnownLengthRequestFraming)) {
+ if (!writer.WriteVarInt62(kKnownLengthRequestFraming)) {
return absl::InvalidArgumentError("Failed to encode framing indicator.");
}
if (const absl::Status status = EncodeControlData(writer); !status.ok()) {
@@ -390,8 +391,8 @@
absl::StatusOr<BinaryHttpRequest> BinaryHttpRequest::Create(
absl::string_view data) {
quiche::QuicheDataReader reader(data);
- uint8_t framing;
- if (!reader.ReadUInt8(&framing)) {
+ uint64_t framing;
+ if (!reader.ReadVarInt62(&framing)) {
return absl::InvalidArgumentError("Missing framing indicator.");
}
if (framing == kKnownLengthRequestFraming) {
@@ -404,8 +405,8 @@
absl::StatusOr<BinaryHttpResponse> BinaryHttpResponse::Create(
absl::string_view data) {
quiche::QuicheDataReader reader(data);
- uint8_t framing;
- if (!reader.ReadUInt8(&framing)) {
+ uint64_t framing;
+ if (!reader.ReadVarInt62(&framing)) {
return absl::InvalidArgumentError("Missing framing indicator.");
}
if (framing == kKnownLengthResponseFraming) {
diff --git a/quiche/binary_http/binary_http_message_test.cc b/quiche/binary_http/binary_http_message_test.cc
index 5841b2a..0ae8e88 100644
--- a/quiche/binary_http/binary_http_message_test.cc
+++ b/quiche/binary_http/binary_http_message_test.cc
@@ -7,6 +7,8 @@
#include <vector>
#include "absl/container/flat_hash_map.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/string_view.h"
#include "quiche/common/platform/api/quiche_test.h"
using ::testing::ContainerEq;
@@ -84,21 +86,27 @@
}
TEST(BinaryHttpRequest, DecodeGetNoBody) {
- const uint32_t words[] = {
- 0x00034745, 0x54056874, 0x74707300, 0x0a2f6865, 0x6c6c6f2e, 0x74787440,
- 0x6c0a7573, 0x65722d61, 0x67656e74, 0x34637572, 0x6c2f372e, 0x31362e33,
- 0x206c6962, 0x6375726c, 0x2f372e31, 0x362e3320, 0x4f70656e, 0x53534c2f,
- 0x302e392e, 0x376c207a, 0x6c69622f, 0x312e322e, 0x3304686f, 0x73740f77,
- 0x77772e65, 0x78616d70, 0x6c652e63, 0x6f6d0f61, 0x63636570, 0x742d6c61,
- 0x6e677561, 0x67650665, 0x6e2c206d, 0x69000000};
+ absl::string_view known_length_request =
+ "80000000" // 4-byte framing. The framing indicator is normally encoded
+ // using a single byte but we intentionally use a
+ // multiple-byte value here to test the decoding logic.
+ "03474554" // :method = GET
+ "056874747073" // :scheme = https
+ "00" // :authority = ""
+ "0A2F68656C6C6F2E747874" // :path = /hello.txt
+ "406C" // headers section length
+ "0A757365722D6167656E74" // user-agent
+ "346375726C2F372E31362E33206C69626375726C2F372E31362E33204F70656E53534C2F"
+ "302E392E376C207A6C69622F312E322E33" // curl/7.16.3 libcurl/7.16.3
+ // OpenSSL/0.9.7l zlib/1.2.3
+ "04686F7374" // host
+ "0F7777772E6578616D706C652E636F6D" // www.example.com
+ "0F6163636570742D6C616E6775616765" // accept-language
+ "06656E2C206D69" // en, mi
+ "000000"; // padding
+
std::string data;
- for (const auto& word : words) {
- data += WordToBytes(word);
- }
-
- // Remove all padding
- data.resize(data.size() - 3);
-
+ ASSERT_TRUE(absl::HexStringToBytes(known_length_request, &data));
const auto request_so = BinaryHttpRequest::Create(data);
ASSERT_TRUE(request_so.ok());
const BinaryHttpRequest request = *request_so;
@@ -409,16 +417,23 @@
}
TEST(BinaryHttpResponse, DecodeNoBody) {
+ absl::string_view known_length_request =
+ "80000001" // 4-byte framing. The framing indicator is normally encoded
+ // using a single byte but we intentionally use a
+ // multiple-byte value here to test the decoding logic.
+ "4194" // status 404
+ "0E" // field section length
+ "06736572766572" // server
+ "06417061636865" // Apache
+ "000000"; // padding
+
+ std::string data;
+ ASSERT_TRUE(absl::HexStringToBytes(known_length_request, &data));
/*
HTTP/1.1 404 Not Found
Server: Apache
*/
- const uint32_t words[] = {0x0141940e, 0x06736572, 0x76657206, 0x41706163,
- 0x68650000};
- std::string data;
- for (const auto& word : words) {
- data += WordToBytes(word);
- }
+
const auto response_so = BinaryHttpResponse::Create(data);
ASSERT_TRUE(response_so.ok());
const BinaryHttpResponse response = *response_so;