xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/web_transport/web_transport_headers.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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