Skip to content

Commit

Permalink
Add functions to handle WebTransport subprotocol negotiation headers.
Browse files Browse the repository at this point in the history
Based on ietf-wg-webtrans/draft-ietf-webtrans-http3#144

PiperOrigin-RevId: 583029408
  • Loading branch information
vasilvv authored and copybara-github committed Nov 16, 2023
1 parent 8cbf649 commit a20f97d
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 9 deletions.
3 changes: 3 additions & 0 deletions build/source_list.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ quiche_core_hdrs = [
"web_transport/complete_buffer_visitor.h",
"web_transport/encapsulated/encapsulated_web_transport.h",
"web_transport/web_transport.h",
"web_transport/web_transport_headers.h",
]
quiche_core_srcs = [
"common/capsule.cc",
Expand Down Expand Up @@ -682,6 +683,7 @@ quiche_core_srcs = [
"spdy/core/spdy_protocol.cc",
"web_transport/complete_buffer_visitor.cc",
"web_transport/encapsulated/encapsulated_web_transport.cc",
"web_transport/web_transport_headers.cc",
]
quiche_tool_support_hdrs = [
"common/platform/api/quiche_command_line_flags.h",
Expand Down Expand Up @@ -1307,6 +1309,7 @@ quiche_tests_srcs = [
"spdy/core/spdy_prefixed_buffer_reader_test.cc",
"spdy/core/spdy_protocol_test.cc",
"web_transport/encapsulated/encapsulated_web_transport_test.cc",
"web_transport/web_transport_headers_test.cc",
]
io_tests_hdrs = [
]
Expand Down
3 changes: 3 additions & 0 deletions build/source_list.gni
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ quiche_core_hdrs = [
"src/quiche/web_transport/complete_buffer_visitor.h",
"src/quiche/web_transport/encapsulated/encapsulated_web_transport.h",
"src/quiche/web_transport/web_transport.h",
"src/quiche/web_transport/web_transport_headers.h",
]
quiche_core_srcs = [
"src/quiche/common/capsule.cc",
Expand Down Expand Up @@ -682,6 +683,7 @@ quiche_core_srcs = [
"src/quiche/spdy/core/spdy_protocol.cc",
"src/quiche/web_transport/complete_buffer_visitor.cc",
"src/quiche/web_transport/encapsulated/encapsulated_web_transport.cc",
"src/quiche/web_transport/web_transport_headers.cc",
]
quiche_tool_support_hdrs = [
"src/quiche/common/platform/api/quiche_command_line_flags.h",
Expand Down Expand Up @@ -1308,6 +1310,7 @@ quiche_tests_srcs = [
"src/quiche/spdy/core/spdy_prefixed_buffer_reader_test.cc",
"src/quiche/spdy/core/spdy_protocol_test.cc",
"src/quiche/web_transport/encapsulated/encapsulated_web_transport_test.cc",
"src/quiche/web_transport/web_transport_headers_test.cc",
]
io_tests_hdrs = [

Expand Down
9 changes: 6 additions & 3 deletions build/source_list.json
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,8 @@
"quiche/spdy/core/zero_copy_output_buffer.h",
"quiche/web_transport/complete_buffer_visitor.h",
"quiche/web_transport/encapsulated/encapsulated_web_transport.h",
"quiche/web_transport/web_transport.h"
"quiche/web_transport/web_transport.h",
"quiche/web_transport/web_transport_headers.h"
],
"quiche_core_srcs": [
"quiche/common/capsule.cc",
Expand Down Expand Up @@ -680,7 +681,8 @@
"quiche/spdy/core/spdy_prefixed_buffer_reader.cc",
"quiche/spdy/core/spdy_protocol.cc",
"quiche/web_transport/complete_buffer_visitor.cc",
"quiche/web_transport/encapsulated/encapsulated_web_transport.cc"
"quiche/web_transport/encapsulated/encapsulated_web_transport.cc",
"quiche/web_transport/web_transport_headers.cc"
],
"quiche_tool_support_hdrs": [
"quiche/common/platform/api/quiche_command_line_flags.h",
Expand Down Expand Up @@ -1306,7 +1308,8 @@
"quiche/spdy/core/spdy_pinnable_buffer_piece_test.cc",
"quiche/spdy/core/spdy_prefixed_buffer_reader_test.cc",
"quiche/spdy/core/spdy_protocol_test.cc",
"quiche/web_transport/encapsulated/encapsulated_web_transport_test.cc"
"quiche/web_transport/encapsulated/encapsulated_web_transport_test.cc",
"quiche/web_transport/web_transport_headers_test.cc"
],
"io_tests_hdrs": [

Expand Down
43 changes: 38 additions & 5 deletions quiche/common/structured_headers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
#include "quiche/common/structured_headers.h"

#include <cmath>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "absl/algorithm/container.h"
#include "absl/container/flat_hash_set.h"
Expand Down Expand Up @@ -574,12 +578,9 @@ class StructuredHeaderSerializer {
}
if (value.is_token()) {
// Serializes a Token ([RFC8941] 4.1.7).
if (value.GetString().empty() ||
!(absl::ascii_isalpha(value.GetString().front()) ||
value.GetString().front() == '*'))
return false;
if (value.GetString().find_first_not_of(kTokenChars) != std::string::npos)
if (!IsValidToken(value.GetString())) {
return false;
}
output_ << value.GetString();
return true;
}
Expand Down Expand Up @@ -720,6 +721,38 @@ class StructuredHeaderSerializer {

} // namespace

absl::string_view ItemTypeToString(Item::ItemType type) {
switch (type) {
case Item::kNullType:
return "null";
case Item::kIntegerType:
return "integer";
case Item::kDecimalType:
return "decimal";
case Item::kStringType:
return "string";
case Item::kTokenType:
return "token";
case Item::kByteSequenceType:
return "byte sequence";
case Item::kBooleanType:
return "boolean";
}
return "[invalid type]";
}

bool IsValidToken(absl::string_view str) {
// Validate Token value per [RFC8941] 4.1.7.
if (str.empty() ||
!(absl::ascii_isalpha(str.front()) || str.front() == '*')) {
return false;
}
if (str.find_first_not_of(kTokenChars) != std::string::npos) {
return false;
}
return true;
}

Item::Item() {}
Item::Item(std::string value, Item::ItemType type) {
switch (type) {
Expand Down
9 changes: 8 additions & 1 deletion quiche/common/structured_headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
#ifndef QUICHE_COMMON_STRUCTURED_HEADERS_H_
#define QUICHE_COMMON_STRUCTURED_HEADERS_H_

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <map>
#include <optional>
#include <string>
Expand Down Expand Up @@ -141,6 +142,12 @@ class QUICHE_EXPORT Item {
value_;
};

// Returns a human-readable representation of an ItemType.
QUICHE_EXPORT absl::string_view ItemTypeToString(Item::ItemType type);

// Returns `true` if the string is a valid Token value.
QUICHE_EXPORT bool IsValidToken(absl::string_view str);

// Holds a ParameterizedIdentifier (draft 9 only). The contained Item must be a
// Token, and there may be any number of parameters. Parameter ordering is not
// significant.
Expand Down
65 changes: 65 additions & 0 deletions quiche/web_transport/web_transport_headers.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "quiche/web_transport/web_transport_headers.h"

#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "quiche/common/structured_headers.h"

namespace webtransport {

using ::quiche::structured_headers::ItemTypeToString;
using ::quiche::structured_headers::List;
using ::quiche::structured_headers::ParameterizedItem;
using ::quiche::structured_headers::ParameterizedMember;

absl::StatusOr<std::vector<std::string>> ParseSubprotocolRequestHeader(
absl::string_view value) {
std::optional<List> parsed = quiche::structured_headers::ParseList(value);
if (!parsed.has_value()) {
return absl::InvalidArgumentError(
"Failed to parse the header as an sf-list");
}

std::vector<std::string> result;
result.reserve(parsed->size());
for (ParameterizedMember& member : *parsed) {
if (member.member_is_inner_list || member.member.size() != 1) {
return absl::InvalidArgumentError(
"Expected all members to be tokens, found a nested list instead");
}
ParameterizedItem& item = member.member[0];
if (!item.item.is_token()) {
return absl::InvalidArgumentError(
absl::StrCat("Expected all members to be tokens, found ",
ItemTypeToString(item.item.Type()), " instead"));
}
result.push_back(std::move(item).item.TakeString());
}
return result;
}

absl::StatusOr<std::string> SerializeSubprotocolRequestHeader(
absl::Span<const std::string> subprotocols) {
// Serialize tokens manually via a simple StrJoin call; this lets us provide
// better error messages, and is probably more efficient too.
for (const std::string& token : subprotocols) {
if (!quiche::structured_headers::IsValidToken(token)) {
return absl::InvalidArgumentError(absl::StrCat("Invalid token: ", token));
}
}
return absl::StrJoin(subprotocols, ", ");
}

} // namespace webtransport
30 changes: 30 additions & 0 deletions quiche/web_transport/web_transport_headers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef QUICHE_WEB_TRANSPORT_WEB_TRANSPORT_HEADERS_H_
#define QUICHE_WEB_TRANSPORT_WEB_TRANSPORT_HEADERS_H_

#include <string>
#include <vector>

#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "quiche/common/platform/api/quiche_export.h"

namespace webtransport {

inline constexpr absl::string_view kSubprotocolRequestHeader =
"WebTransport-Subprotocols-Available";
inline constexpr absl::string_view kSubprotocolResponseHeader =
"WebTransport-Subprotocol";

QUICHE_EXPORT absl::StatusOr<std::vector<std::string>>
ParseSubprotocolRequestHeader(absl::string_view value);
QUICHE_EXPORT absl::StatusOr<std::string> SerializeSubprotocolRequestHeader(
absl::Span<const std::string> subprotocols);

} // namespace webtransport

#endif // QUICHE_WEB_TRANSPORT_WEB_TRANSPORT_HEADERS_H_
61 changes: 61 additions & 0 deletions quiche/web_transport/web_transport_headers_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "quiche/web_transport/web_transport_headers.h"

#include "absl/status/status.h"
#include "quiche/common/platform/api/quiche_test.h"
#include "quiche/common/test_tools/quiche_test_utils.h"

namespace webtransport {
namespace {

using ::quiche::test::IsOkAndHolds;
using ::quiche::test::StatusIs;
using ::testing::ElementsAre;
using ::testing::HasSubstr;

TEST(WebTransportHeaders, ParseSubprotocolRequestHeader) {
EXPECT_THAT(ParseSubprotocolRequestHeader("test"),
IsOkAndHolds(ElementsAre("test")));
EXPECT_THAT(ParseSubprotocolRequestHeader("moqt-draft01, moqt-draft02"),
IsOkAndHolds(ElementsAre("moqt-draft01", "moqt-draft02")));
EXPECT_THAT(ParseSubprotocolRequestHeader("moqt-draft01; a=b, moqt-draft02"),
IsOkAndHolds(ElementsAre("moqt-draft01", "moqt-draft02")));
EXPECT_THAT(ParseSubprotocolRequestHeader("moqt-draft01, moqt-draft02; a=b"),
IsOkAndHolds(ElementsAre("moqt-draft01", "moqt-draft02")));
EXPECT_THAT(ParseSubprotocolRequestHeader("\"test\""),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("found string instead")));
EXPECT_THAT(ParseSubprotocolRequestHeader("42"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("found integer instead")));
EXPECT_THAT(ParseSubprotocolRequestHeader("a, (b)"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("found a nested list instead")));
EXPECT_THAT(ParseSubprotocolRequestHeader("a, (b c)"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("found a nested list instead")));
EXPECT_THAT(ParseSubprotocolRequestHeader("foo, ?1, bar"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("found boolean instead")));
EXPECT_THAT(ParseSubprotocolRequestHeader("(a"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("parse the header as an sf-list")));
}

TEST(WebTransportHeaders, SerializeSubprotocolRequestHeader) {
EXPECT_THAT(SerializeSubprotocolRequestHeader({"test"}),
IsOkAndHolds("test"));
EXPECT_THAT(SerializeSubprotocolRequestHeader({"foo", "bar"}),
IsOkAndHolds("foo, bar"));
EXPECT_THAT(SerializeSubprotocolRequestHeader({"moqt-draft01", "a/b/c"}),
IsOkAndHolds("moqt-draft01, a/b/c"));
EXPECT_THAT(
SerializeSubprotocolRequestHeader({"abcd", "0123", "efgh"}),
StatusIs(absl::StatusCode::kInvalidArgument, "Invalid token: 0123"));
}

} // namespace
} // namespace webtransport

0 comments on commit a20f97d

Please sign in to comment.