blob: 08bde5b4bb2bfa57337e84734accb8ea497fb196 [file] [log] [blame]
// Copyright (c) 2021 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_COMMON_CAPSULE_H_
#define QUICHE_COMMON_CAPSULE_H_
#include <cstdint>
#include <string>
#include <vector>
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/quiche_buffer_allocator.h"
#include "quiche/common/quiche_ip_address.h"
#include "quiche/web_transport/web_transport.h"
namespace quiche {
enum class CapsuleType : uint64_t {
// Casing in this enum matches the IETF specifications.
DATAGRAM = 0x00, // RFC 9297.
LEGACY_DATAGRAM = 0xff37a0, // draft-ietf-masque-h3-datagram-04.
LEGACY_DATAGRAM_WITHOUT_CONTEXT =
0xff37a5, // draft-ietf-masque-h3-datagram-05 to -08.
// <https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/>
CLOSE_WEBTRANSPORT_SESSION = 0x2843,
// draft-ietf-masque-connect-ip-03.
ADDRESS_ASSIGN = 0x1ECA6A00,
ADDRESS_REQUEST = 0x1ECA6A01,
ROUTE_ADVERTISEMENT = 0x1ECA6A02,
// <https://ietf-wg-webtrans.github.io/draft-webtransport-http2/draft-ietf-webtrans-http2.html#name-webtransport-capsules>
WT_RESET_STREAM = 0x190b4d39,
WT_STOP_SENDING = 0x190b4d3a,
WT_STREAM = 0x190b4d3b,
WT_STREAM_WITH_FIN = 0x190b4d3c,
// Should be removed as a result of
// <https://github.com/ietf-wg-webtrans/draft-webtransport-http2/issues/27>.
// WT_MAX_DATA = 0x190b4d3d,
WT_MAX_STREAM_DATA = 0x190b4d3e,
WT_MAX_STREAMS_BIDI = 0x190b4d3f,
WT_MAX_STREAMS_UNIDI = 0x190b4d40,
// TODO(b/264263113): implement those.
// PADDING = 0x190b4d38,
// WT_DATA_BLOCKED = 0x190b4d41,
// WT_STREAM_DATA_BLOCKED = 0x190b4d42,
// WT_STREAMS_BLOCKED_BIDI = 0x190b4d43,
// WT_STREAMS_BLOCKED_UNIDI = 0x190b4d44,
};
QUICHE_EXPORT std::string CapsuleTypeToString(CapsuleType capsule_type);
QUICHE_EXPORT std::ostream& operator<<(std::ostream& os,
const CapsuleType& capsule_type);
// General.
struct QUICHE_EXPORT DatagramCapsule {
absl::string_view http_datagram_payload;
std::string ToString() const;
CapsuleType capsule_type() const { return CapsuleType::DATAGRAM; }
bool operator==(const DatagramCapsule& other) const {
return http_datagram_payload == other.http_datagram_payload;
}
};
struct QUICHE_EXPORT LegacyDatagramCapsule {
absl::string_view http_datagram_payload;
std::string ToString() const;
CapsuleType capsule_type() const { return CapsuleType::LEGACY_DATAGRAM; }
bool operator==(const LegacyDatagramCapsule& other) const {
return http_datagram_payload == other.http_datagram_payload;
}
};
struct QUICHE_EXPORT LegacyDatagramWithoutContextCapsule {
absl::string_view http_datagram_payload;
std::string ToString() const;
CapsuleType capsule_type() const {
return CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT;
}
bool operator==(const LegacyDatagramWithoutContextCapsule& other) const {
return http_datagram_payload == other.http_datagram_payload;
}
};
// WebTransport over HTTP/3.
struct QUICHE_EXPORT CloseWebTransportSessionCapsule {
webtransport::SessionErrorCode error_code;
absl::string_view error_message;
std::string ToString() const;
CapsuleType capsule_type() const {
return CapsuleType::CLOSE_WEBTRANSPORT_SESSION;
}
bool operator==(const CloseWebTransportSessionCapsule& other) const {
return error_code == other.error_code &&
error_message == other.error_message;
}
};
// MASQUE CONNECT-IP.
struct QUICHE_EXPORT PrefixWithId {
uint64_t request_id;
quiche::QuicheIpPrefix ip_prefix;
bool operator==(const PrefixWithId& other) const;
};
struct QUICHE_EXPORT IpAddressRange {
quiche::QuicheIpAddress start_ip_address;
quiche::QuicheIpAddress end_ip_address;
uint8_t ip_protocol;
bool operator==(const IpAddressRange& other) const;
};
struct QUICHE_EXPORT AddressAssignCapsule {
std::vector<PrefixWithId> assigned_addresses;
bool operator==(const AddressAssignCapsule& other) const;
std::string ToString() const;
CapsuleType capsule_type() const { return CapsuleType::ADDRESS_ASSIGN; }
};
struct QUICHE_EXPORT AddressRequestCapsule {
std::vector<PrefixWithId> requested_addresses;
bool operator==(const AddressRequestCapsule& other) const;
std::string ToString() const;
CapsuleType capsule_type() const { return CapsuleType::ADDRESS_REQUEST; }
};
struct QUICHE_EXPORT RouteAdvertisementCapsule {
std::vector<IpAddressRange> ip_address_ranges;
bool operator==(const RouteAdvertisementCapsule& other) const;
std::string ToString() const;
CapsuleType capsule_type() const { return CapsuleType::ROUTE_ADVERTISEMENT; }
};
struct QUICHE_EXPORT UnknownCapsule {
uint64_t type;
absl::string_view payload;
std::string ToString() const;
CapsuleType capsule_type() const { return static_cast<CapsuleType>(type); }
bool operator==(const UnknownCapsule& other) const {
return type == other.type && payload == other.payload;
}
};
// WebTransport over HTTP/2.
struct QUICHE_EXPORT WebTransportStreamDataCapsule {
webtransport::StreamId stream_id;
absl::string_view data;
bool fin;
bool operator==(const WebTransportStreamDataCapsule& other) const;
std::string ToString() const;
CapsuleType capsule_type() const {
return fin ? CapsuleType::WT_STREAM_WITH_FIN : CapsuleType::WT_STREAM;
}
};
struct QUICHE_EXPORT WebTransportResetStreamCapsule {
webtransport::StreamId stream_id;
uint64_t error_code;
bool operator==(const WebTransportResetStreamCapsule& other) const;
std::string ToString() const;
CapsuleType capsule_type() const { return CapsuleType::WT_RESET_STREAM; }
};
struct QUICHE_EXPORT WebTransportStopSendingCapsule {
webtransport::StreamId stream_id;
uint64_t error_code;
bool operator==(const WebTransportStopSendingCapsule& other) const;
std::string ToString() const;
CapsuleType capsule_type() const { return CapsuleType::WT_STOP_SENDING; }
};
struct QUICHE_EXPORT WebTransportMaxStreamDataCapsule {
webtransport::StreamId stream_id;
uint64_t max_stream_data;
bool operator==(const WebTransportMaxStreamDataCapsule& other) const;
std::string ToString() const;
CapsuleType capsule_type() const { return CapsuleType::WT_MAX_STREAM_DATA; }
};
struct QUICHE_EXPORT WebTransportMaxStreamsCapsule {
webtransport::StreamType stream_type;
uint64_t max_stream_count;
bool operator==(const WebTransportMaxStreamsCapsule& other) const;
std::string ToString() const;
CapsuleType capsule_type() const {
return stream_type == webtransport::StreamType::kBidirectional
? CapsuleType::WT_MAX_STREAMS_BIDI
: CapsuleType::WT_MAX_STREAMS_UNIDI;
}
};
// Capsule from RFC 9297.
// IMPORTANT NOTE: Capsule does not own any of the absl::string_view memory it
// points to. Strings saved into a capsule must outlive the capsule object. Any
// code that sees a capsule in a callback needs to either process it immediately
// or perform its own deep copy.
class QUICHE_EXPORT Capsule {
public:
static Capsule Datagram(
absl::string_view http_datagram_payload = absl::string_view());
static Capsule LegacyDatagram(
absl::string_view http_datagram_payload = absl::string_view());
static Capsule LegacyDatagramWithoutContext(
absl::string_view http_datagram_payload = absl::string_view());
static Capsule CloseWebTransportSession(
webtransport::SessionErrorCode error_code = 0,
absl::string_view error_message = "");
static Capsule AddressRequest();
static Capsule AddressAssign();
static Capsule RouteAdvertisement();
static Capsule Unknown(
uint64_t capsule_type,
absl::string_view unknown_capsule_data = absl::string_view());
template <typename CapsuleStruct>
explicit Capsule(CapsuleStruct capsule) : capsule_(std::move(capsule)) {}
bool operator==(const Capsule& other) const;
// Human-readable information string for debugging purposes.
std::string ToString() const;
friend QUICHE_EXPORT std::ostream& operator<<(std::ostream& os,
const Capsule& capsule);
CapsuleType capsule_type() const {
return absl::visit(
[](const auto& capsule) { return capsule.capsule_type(); }, capsule_);
}
DatagramCapsule& datagram_capsule() {
return absl::get<DatagramCapsule>(capsule_);
}
const DatagramCapsule& datagram_capsule() const {
return absl::get<DatagramCapsule>(capsule_);
}
LegacyDatagramCapsule& legacy_datagram_capsule() {
return absl::get<LegacyDatagramCapsule>(capsule_);
}
const LegacyDatagramCapsule& legacy_datagram_capsule() const {
return absl::get<LegacyDatagramCapsule>(capsule_);
}
LegacyDatagramWithoutContextCapsule&
legacy_datagram_without_context_capsule() {
return absl::get<LegacyDatagramWithoutContextCapsule>(capsule_);
}
const LegacyDatagramWithoutContextCapsule&
legacy_datagram_without_context_capsule() const {
return absl::get<LegacyDatagramWithoutContextCapsule>(capsule_);
}
CloseWebTransportSessionCapsule& close_web_transport_session_capsule() {
return absl::get<CloseWebTransportSessionCapsule>(capsule_);
}
const CloseWebTransportSessionCapsule& close_web_transport_session_capsule()
const {
return absl::get<CloseWebTransportSessionCapsule>(capsule_);
}
AddressRequestCapsule& address_request_capsule() {
return absl::get<AddressRequestCapsule>(capsule_);
}
const AddressRequestCapsule& address_request_capsule() const {
return absl::get<AddressRequestCapsule>(capsule_);
}
AddressAssignCapsule& address_assign_capsule() {
return absl::get<AddressAssignCapsule>(capsule_);
}
const AddressAssignCapsule& address_assign_capsule() const {
return absl::get<AddressAssignCapsule>(capsule_);
}
RouteAdvertisementCapsule& route_advertisement_capsule() {
return absl::get<RouteAdvertisementCapsule>(capsule_);
}
const RouteAdvertisementCapsule& route_advertisement_capsule() const {
return absl::get<RouteAdvertisementCapsule>(capsule_);
}
WebTransportStreamDataCapsule& web_transport_stream_data() {
return absl::get<WebTransportStreamDataCapsule>(capsule_);
}
const WebTransportStreamDataCapsule& web_transport_stream_data() const {
return absl::get<WebTransportStreamDataCapsule>(capsule_);
}
WebTransportResetStreamCapsule& web_transport_reset_stream() {
return absl::get<WebTransportResetStreamCapsule>(capsule_);
}
const WebTransportResetStreamCapsule& web_transport_reset_stream() const {
return absl::get<WebTransportResetStreamCapsule>(capsule_);
}
WebTransportStopSendingCapsule& web_transport_stop_sending() {
return absl::get<WebTransportStopSendingCapsule>(capsule_);
}
const WebTransportStopSendingCapsule& web_transport_stop_sending() const {
return absl::get<WebTransportStopSendingCapsule>(capsule_);
}
WebTransportMaxStreamDataCapsule& web_transport_max_stream_data() {
return absl::get<WebTransportMaxStreamDataCapsule>(capsule_);
}
const WebTransportMaxStreamDataCapsule& web_transport_max_stream_data()
const {
return absl::get<WebTransportMaxStreamDataCapsule>(capsule_);
}
WebTransportMaxStreamsCapsule& web_transport_max_streams() {
return absl::get<WebTransportMaxStreamsCapsule>(capsule_);
}
const WebTransportMaxStreamsCapsule& web_transport_max_streams() const {
return absl::get<WebTransportMaxStreamsCapsule>(capsule_);
}
UnknownCapsule& unknown_capsule() {
return absl::get<UnknownCapsule>(capsule_);
}
const UnknownCapsule& unknown_capsule() const {
return absl::get<UnknownCapsule>(capsule_);
}
private:
absl::variant<DatagramCapsule, LegacyDatagramCapsule,
LegacyDatagramWithoutContextCapsule,
CloseWebTransportSessionCapsule, AddressRequestCapsule,
AddressAssignCapsule, RouteAdvertisementCapsule,
WebTransportStreamDataCapsule, WebTransportResetStreamCapsule,
WebTransportStopSendingCapsule, WebTransportMaxStreamsCapsule,
WebTransportMaxStreamDataCapsule, UnknownCapsule>
capsule_;
};
namespace test {
class CapsuleParserPeer;
} // namespace test
class QUICHE_EXPORT CapsuleParser {
public:
class QUICHE_EXPORT Visitor {
public:
virtual ~Visitor() {}
// Called when a capsule has been successfully parsed. The return value
// indicates whether the contents of the capsule are valid: if false is
// returned, the parse operation will be considered failed and
// OnCapsuleParseFailure will be called. Note that since Capsule does not
// own the memory backing its string_views, that memory is only valid until
// this callback returns. Visitors that wish to access the capsule later
// MUST make a deep copy before this returns.
virtual bool OnCapsule(const Capsule& capsule) = 0;
virtual void OnCapsuleParseFailure(absl::string_view error_message) = 0;
};
// |visitor| must be non-null, and must outlive CapsuleParser.
explicit CapsuleParser(Visitor* visitor);
// Ingests a capsule fragment (any fragment of bytes from the capsule data
// stream) and parses and complete capsules it encounters. Returns false if a
// parsing error occurred.
bool IngestCapsuleFragment(absl::string_view capsule_fragment);
void ErrorIfThereIsRemainingBufferedData();
friend class test::CapsuleParserPeer;
private:
// Attempts to parse a single capsule from |buffered_data_|. If a full capsule
// is not available, returns 0. If a parsing error occurs, returns an error.
// Otherwise, returns the number of bytes in the parsed capsule.
absl::StatusOr<size_t> AttemptParseCapsule();
void ReportParseFailure(absl::string_view error_message);
// Whether a parsing error has occurred.
bool parsing_error_occurred_ = false;
// Visitor which will receive callbacks, unowned.
Visitor* visitor_;
std::string buffered_data_;
};
// Serializes |capsule| into a newly allocated buffer.
QUICHE_EXPORT quiche::QuicheBuffer SerializeCapsule(
const Capsule& capsule, quiche::QuicheBufferAllocator* allocator);
} // namespace quiche
#endif // QUICHE_COMMON_CAPSULE_H_