|  | // 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 <array> | 
|  | #include <cstdint> | 
|  | #include <optional> | 
|  | #include <string> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/base/attributes.h" | 
|  | #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/quiche_status_utils.h" | 
|  | #include "quiche/common/structured_headers.h" | 
|  |  | 
|  | namespace webtransport { | 
|  |  | 
|  | namespace { | 
|  | using ::quiche::structured_headers::Dictionary; | 
|  | using ::quiche::structured_headers::DictionaryMember; | 
|  | using ::quiche::structured_headers::Item; | 
|  | using ::quiche::structured_headers::ItemTypeToString; | 
|  | using ::quiche::structured_headers::List; | 
|  | using ::quiche::structured_headers::ParameterizedItem; | 
|  | using ::quiche::structured_headers::ParameterizedMember; | 
|  |  | 
|  | absl::Status CheckItemType(const ParameterizedMember& member, | 
|  | Item::ItemType expected_type) { | 
|  | if (member.member_is_inner_list || member.member.size() != 1) { | 
|  | return absl::InvalidArgumentError(absl::StrCat( | 
|  | "Expected all members to be of type", ItemTypeToString(expected_type), | 
|  | ", found a nested list instead")); | 
|  | } | 
|  | if (member.member[0].item.Type() != expected_type) { | 
|  | return absl::InvalidArgumentError(absl::StrCat( | 
|  | "Expected all members to be of type ", ItemTypeToString(expected_type), | 
|  | ", found ", ItemTypeToString(member.member[0].item.Type()), | 
|  | " instead")); | 
|  | } | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | ABSL_CONST_INIT std::array kInitHeaderFields{ | 
|  | std::make_pair("u", &WebTransportInitHeader::initial_unidi_limit), | 
|  | std::make_pair("bl", &WebTransportInitHeader::initial_incoming_bidi_limit), | 
|  | std::make_pair("br", &WebTransportInitHeader::initial_outgoing_bidi_limit), | 
|  | }; | 
|  | }  // namespace | 
|  |  | 
|  | 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) { | 
|  | QUICHE_RETURN_IF_ERROR(CheckItemType(member, Item::kTokenType)); | 
|  | result.push_back(std::move(member.member[0].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, ", "); | 
|  | } | 
|  |  | 
|  | absl::StatusOr<WebTransportInitHeader> ParseInitHeader( | 
|  | absl::string_view header) { | 
|  | std::optional<Dictionary> parsed = | 
|  | quiche::structured_headers::ParseDictionary(header); | 
|  | if (!parsed.has_value()) { | 
|  | return absl::InvalidArgumentError( | 
|  | "Failed to parse WebTransport-Init header as an sf-dictionary"); | 
|  | } | 
|  | WebTransportInitHeader output; | 
|  | for (const auto& [field_name_a, field_value] : *parsed) { | 
|  | for (const auto& [field_name_b, field_accessor] : kInitHeaderFields) { | 
|  | if (field_name_a != field_name_b) { | 
|  | continue; | 
|  | } | 
|  | QUICHE_RETURN_IF_ERROR(CheckItemType(field_value, Item::kIntegerType)); | 
|  | int64_t value = field_value.member[0].item.GetInteger(); | 
|  | if (value < 0) { | 
|  | return absl::InvalidArgumentError( | 
|  | absl::StrCat("Received negative value for ", field_name_a)); | 
|  | } | 
|  | output.*field_accessor = value; | 
|  | } | 
|  | } | 
|  | return output; | 
|  | } | 
|  |  | 
|  | absl::StatusOr<std::string> SerializeInitHeader( | 
|  | const WebTransportInitHeader& header) { | 
|  | std::vector<DictionaryMember> members; | 
|  | members.reserve(kInitHeaderFields.size()); | 
|  | for (const auto& [field_name, field_accessor] : kInitHeaderFields) { | 
|  | Item item(static_cast<int64_t>(header.*field_accessor)); | 
|  | members.push_back(std::make_pair( | 
|  | field_name, ParameterizedMember({ParameterizedItem(item, {})}, false, | 
|  | /*parameters=*/{}))); | 
|  | } | 
|  | std::optional<std::string> result = | 
|  | quiche::structured_headers::SerializeDictionary( | 
|  | Dictionary(std::move(members))); | 
|  | if (!result.has_value()) { | 
|  | return absl::InternalError("Failed to serialize the dictionary"); | 
|  | } | 
|  | return *std::move(result); | 
|  | } | 
|  |  | 
|  | }  // namespace webtransport |