1 // Copyright 2023 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "quiche/web_transport/web_transport_headers.h"
6
7 #include <array>
8 #include <cstdint>
9 #include <optional>
10 #include <string>
11 #include <utility>
12 #include <vector>
13
14 #include "absl/base/attributes.h"
15 #include "absl/status/status.h"
16 #include "absl/status/statusor.h"
17 #include "absl/strings/str_cat.h"
18 #include "absl/strings/str_join.h"
19 #include "absl/strings/string_view.h"
20 #include "absl/types/span.h"
21 #include "quiche/common/quiche_status_utils.h"
22 #include "quiche/common/structured_headers.h"
23
24 namespace webtransport {
25
26 namespace {
27 using ::quiche::structured_headers::Dictionary;
28 using ::quiche::structured_headers::DictionaryMember;
29 using ::quiche::structured_headers::Item;
30 using ::quiche::structured_headers::ItemTypeToString;
31 using ::quiche::structured_headers::List;
32 using ::quiche::structured_headers::ParameterizedItem;
33 using ::quiche::structured_headers::ParameterizedMember;
34
CheckItemType(const ParameterizedItem & item,Item::ItemType expected_type)35 absl::Status CheckItemType(const ParameterizedItem& item,
36 Item::ItemType expected_type) {
37 if (item.item.Type() != expected_type) {
38 return absl::InvalidArgumentError(absl::StrCat(
39 "Expected all members to be of type ", ItemTypeToString(expected_type),
40 ", found ", ItemTypeToString(item.item.Type()), " instead"));
41 }
42 return absl::OkStatus();
43 }
CheckMemberType(const ParameterizedMember & member,Item::ItemType expected_type)44 absl::Status CheckMemberType(const ParameterizedMember& member,
45 Item::ItemType expected_type) {
46 if (member.member_is_inner_list || member.member.size() != 1) {
47 return absl::InvalidArgumentError(absl::StrCat(
48 "Expected all members to be of type", ItemTypeToString(expected_type),
49 ", found a nested list instead"));
50 }
51 return CheckItemType(member.member[0], expected_type);
52 }
53
54 ABSL_CONST_INIT std::array kInitHeaderFields{
55 std::make_pair("u", &WebTransportInitHeader::initial_unidi_limit),
56 std::make_pair("bl", &WebTransportInitHeader::initial_incoming_bidi_limit),
57 std::make_pair("br", &WebTransportInitHeader::initial_outgoing_bidi_limit),
58 };
59 } // namespace
60
ParseSubprotocolRequestHeader(absl::string_view value)61 absl::StatusOr<std::vector<std::string>> ParseSubprotocolRequestHeader(
62 absl::string_view value) {
63 std::optional<List> parsed = quiche::structured_headers::ParseList(value);
64 if (!parsed.has_value()) {
65 return absl::InvalidArgumentError(
66 "Failed to parse the header as an sf-list");
67 }
68
69 std::vector<std::string> result;
70 result.reserve(parsed->size());
71 for (ParameterizedMember& member : *parsed) {
72 QUICHE_RETURN_IF_ERROR(CheckMemberType(member, Item::kTokenType));
73 result.push_back(std::move(member.member[0].item).TakeString());
74 }
75 return result;
76 }
77
SerializeSubprotocolRequestHeader(absl::Span<const std::string> subprotocols)78 absl::StatusOr<std::string> SerializeSubprotocolRequestHeader(
79 absl::Span<const std::string> subprotocols) {
80 // Serialize tokens manually via a simple StrJoin call; this lets us provide
81 // better error messages, and is probably more efficient too.
82 for (const std::string& token : subprotocols) {
83 if (!quiche::structured_headers::IsValidToken(token)) {
84 return absl::InvalidArgumentError(absl::StrCat("Invalid token: ", token));
85 }
86 }
87 return absl::StrJoin(subprotocols, ", ");
88 }
89
ParseSubprotocolResponseHeader(absl::string_view value)90 absl::StatusOr<std::string> ParseSubprotocolResponseHeader(
91 absl::string_view value) {
92 std::optional<ParameterizedItem> parsed =
93 quiche::structured_headers::ParseItem(value);
94 if (!parsed.has_value()) {
95 return absl::InvalidArgumentError("Failed to parse sf-item");
96 }
97 QUICHE_RETURN_IF_ERROR(CheckItemType(*parsed, Item::kTokenType));
98 return std::move(parsed->item).TakeString();
99 }
100
SerializeSubprotocolResponseHeader(absl::string_view subprotocol)101 absl::StatusOr<std::string> SerializeSubprotocolResponseHeader(
102 absl::string_view subprotocol) {
103 if (!quiche::structured_headers::IsValidToken(subprotocol)) {
104 return absl::InvalidArgumentError("Invalid token value supplied");
105 }
106 return std::string(subprotocol);
107 }
108
ParseInitHeader(absl::string_view header)109 absl::StatusOr<WebTransportInitHeader> ParseInitHeader(
110 absl::string_view header) {
111 std::optional<Dictionary> parsed =
112 quiche::structured_headers::ParseDictionary(header);
113 if (!parsed.has_value()) {
114 return absl::InvalidArgumentError(
115 "Failed to parse WebTransport-Init header as an sf-dictionary");
116 }
117 WebTransportInitHeader output;
118 for (const auto& [field_name_a, field_value] : *parsed) {
119 for (const auto& [field_name_b, field_accessor] : kInitHeaderFields) {
120 if (field_name_a != field_name_b) {
121 continue;
122 }
123 QUICHE_RETURN_IF_ERROR(CheckMemberType(field_value, Item::kIntegerType));
124 int64_t value = field_value.member[0].item.GetInteger();
125 if (value < 0) {
126 return absl::InvalidArgumentError(
127 absl::StrCat("Received negative value for ", field_name_a));
128 }
129 output.*field_accessor = value;
130 }
131 }
132 return output;
133 }
134
SerializeInitHeader(const WebTransportInitHeader & header)135 absl::StatusOr<std::string> SerializeInitHeader(
136 const WebTransportInitHeader& header) {
137 std::vector<DictionaryMember> members;
138 members.reserve(kInitHeaderFields.size());
139 for (const auto& [field_name, field_accessor] : kInitHeaderFields) {
140 Item item(static_cast<int64_t>(header.*field_accessor));
141 members.push_back(std::make_pair(
142 field_name, ParameterizedMember({ParameterizedItem(item, {})}, false,
143 /*parameters=*/{})));
144 }
145 std::optional<std::string> result =
146 quiche::structured_headers::SerializeDictionary(
147 Dictionary(std::move(members)));
148 if (!result.has_value()) {
149 return absl::InternalError("Failed to serialize the dictionary");
150 }
151 return *std::move(result);
152 }
153
154 } // namespace webtransport
155