blob: fc46536e40a5f7b23c0134f521ea0473c264159e [file] [log] [blame]
// 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 ParameterizedItem& item,
Item::ItemType expected_type) {
if (item.item.Type() != expected_type) {
return absl::InvalidArgumentError(absl::StrCat(
"Expected all members to be of type ", ItemTypeToString(expected_type),
", found ", ItemTypeToString(item.item.Type()), " instead"));
}
return absl::OkStatus();
}
absl::Status CheckMemberType(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"));
}
return CheckItemType(member.member[0], expected_type);
}
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(CheckMemberType(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<std::string> ParseSubprotocolResponseHeader(
absl::string_view value) {
std::optional<ParameterizedItem> parsed =
quiche::structured_headers::ParseItem(value);
if (!parsed.has_value()) {
return absl::InvalidArgumentError("Failed to parse sf-item");
}
QUICHE_RETURN_IF_ERROR(CheckItemType(*parsed, Item::kTokenType));
return std::move(parsed->item).TakeString();
}
absl::StatusOr<std::string> SerializeSubprotocolResponseHeader(
absl::string_view subprotocol) {
if (!quiche::structured_headers::IsValidToken(subprotocol)) {
return absl::InvalidArgumentError("Invalid token value supplied");
}
return std::string(subprotocol);
}
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(CheckMemberType(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