[chaotic-good] Revamp wire format (#37765)

Update the chaotic-good wire format with some learnings from the past year, and set up things for the next round of changes we'd like to make:

* Instead of a composite FRAGMENT frame, split out CLIENT_INITIAL_METADATA, CLIENT_END_OF_STREAM, MESSAGE, SERVER_INITIAL_METADATA, SERVER_TRAILING_METADATA as separate frame types - this eliminates a ton of complexity in the transport, and corresponds to how we used the wire format in practice anyway.
* Switch the frame payload for metadata, settings to be protobuf instead of HPACK - this eliminates the ordering requirements on interpreting these frames between streams, which I expect to open up some flexibility with head of line avoidance in the future. It's a heck of a lot easier to read and reason about the code. It's also easier to predict the size of the frame at encode time, which lets us treat metadata and payloads more uniformly in the protocol.
* Add a connection id field to our header, in preparation for allowing multiple data connections
* Allow payloads to be shipped on the control channel ('connection id 0') and use this for sending small messages

Closes #37765

COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/37765 from ctiller:tiefling 7b57f72367
PiperOrigin-RevId: 695766541
pull/37866/head
Craig Tiller 2 weeks ago committed by Copybara-Service
parent 8342a109ae
commit 630d790fe1
  1. 552
      CMakeLists.txt
  2. 458
      build_autogenerated.yaml
  3. 66
      src/core/BUILD
  4. 51
      src/core/ext/transport/chaotic_good/chaotic_good_frame.proto
  5. 109
      src/core/ext/transport/chaotic_good/chaotic_good_transport.h
  6. 87
      src/core/ext/transport/chaotic_good/client/chaotic_good_connector.cc
  7. 5
      src/core/ext/transport/chaotic_good/client/chaotic_good_connector.h
  8. 202
      src/core/ext/transport/chaotic_good/client_transport.cc
  9. 16
      src/core/ext/transport/chaotic_good/client_transport.h
  10. 554
      src/core/ext/transport/chaotic_good/frame.cc
  11. 172
      src/core/ext/transport/chaotic_good/frame.h
  12. 40
      src/core/ext/transport/chaotic_good/frame_header.cc
  13. 60
      src/core/ext/transport/chaotic_good/frame_header.h
  14. 75
      src/core/ext/transport/chaotic_good/server/chaotic_good_server.cc
  15. 4
      src/core/ext/transport/chaotic_good/server/chaotic_good_server.h
  16. 302
      src/core/ext/transport/chaotic_good/server_transport.cc
  17. 30
      src/core/ext/transport/chaotic_good/server_transport.h
  18. 79
      src/core/ext/transport/chaotic_good/settings_metadata.cc
  19. 45
      src/core/ext/transport/chaotic_good/settings_metadata.h
  20. 70
      src/core/ext/transport/chaotic_good_legacy/server_transport.cc
  21. 50
      src/core/lib/promise/detail/promise_variant.h
  22. 23
      src/core/lib/promise/match_promise.h
  23. 7
      src/core/lib/promise/status_flag.h
  24. 55
      src/core/lib/promise/switch.h
  25. 1
      src/core/lib/slice/slice_buffer.cc
  26. 1
      test/core/end2end/fuzzers/BUILD
  27. 41
      test/core/end2end/fuzzers/fuzzer_input.proto
  28. 114
      test/core/end2end/fuzzers/network_input.cc
  29. 2
      test/core/end2end/tests/no_logging.cc
  30. 18
      test/core/promise/switch_test.cc
  31. 6
      test/core/transport/benchmarks/bm_chaotic_good.cc
  32. 5
      test/core/transport/chaotic_good/BUILD
  33. 8
      test/core/transport/chaotic_good/client_transport_error_test.cc
  34. 108
      test/core/transport/chaotic_good/client_transport_test.cc
  35. 86
      test/core/transport/chaotic_good/frame_fuzzer.cc
  36. 5
      test/core/transport/chaotic_good/frame_fuzzer.proto
  37. 19
      test/core/transport/chaotic_good/frame_header_fuzzer.cc
  38. 67
      test/core/transport/chaotic_good/frame_header_test.cc
  39. 34
      test/core/transport/chaotic_good/frame_test.cc
  40. 57
      test/core/transport/chaotic_good/server_transport_test.cc
  41. 46
      test/core/transport/chaotic_good/transport_test.cc
  42. 17
      test/core/transport/chaotic_good/transport_test.h
  43. 6
      test/core/transport/test_suite/chaotic_good_fixture.cc
  44. 1
      tools/distrib/fix_build_deps.py

552
CMakeLists.txt generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -15,7 +15,9 @@
load(
"//bazel:grpc_build_system.bzl",
"grpc_cc_library",
"grpc_cc_proto_library",
"grpc_generate_one_off_internal_targets",
"grpc_internal_proto_library",
"grpc_upb_proto_library",
"grpc_upb_proto_reflection_library",
)
@ -696,6 +698,17 @@ grpc_cc_library(
],
)
grpc_cc_library(
name = "promise_variant",
external_deps = [
"absl/types:variant",
],
language = "c++",
public_hdrs = ["lib/promise/detail/promise_variant.h"],
deps = [
],
)
grpc_cc_library(
name = "match_promise",
external_deps = [
@ -709,6 +722,7 @@ grpc_cc_library(
"poll",
"promise_factory",
"promise_like",
"promise_variant",
"//:gpr_platform",
],
)
@ -830,6 +844,8 @@ grpc_cc_library(
deps = [
"if",
"promise_factory",
"promise_variant",
"//:gpr",
"//:gpr_platform",
],
)
@ -7797,6 +7813,17 @@ grpc_cc_library(
],
)
grpc_internal_proto_library(
name = "chaotic_good_frame_proto",
srcs = ["ext/transport/chaotic_good/chaotic_good_frame.proto"],
has_services = False,
)
grpc_cc_proto_library(
name = "chaotic_good_frame_cc_proto",
deps = ["chaotic_good_frame_proto"],
)
grpc_cc_library(
name = "chaotic_good_frame",
srcs = [
@ -7815,38 +7842,19 @@ grpc_cc_library(
deps = [
"arena",
"bitset",
"chaotic_good_frame_cc_proto",
"chaotic_good_frame_header",
"context",
"match",
"message",
"metadata",
"metadata_batch",
"no_destruct",
"slice",
"slice_buffer",
"status_helper",
"//:gpr",
"//:gpr_platform",
"//:grpc_base",
"//:hpack_encoder",
"//:hpack_parser",
],
)
grpc_cc_library(
name = "chaotic_good_settings_metadata",
srcs = [
"ext/transport/chaotic_good/settings_metadata.cc",
],
hdrs = [
"ext/transport/chaotic_good/settings_metadata.h",
],
external_deps = [
"absl/status",
"absl/types:optional",
],
deps = [
"arena",
"metadata_batch",
"//:gpr",
],
)
@ -8087,6 +8095,7 @@ grpc_cc_library(
external_deps = [
"absl/log:log",
"absl/random",
"absl/strings",
],
language = "c++",
deps = [
@ -8099,7 +8108,6 @@ grpc_cc_library(
"try_seq",
"//:gpr_platform",
"//:grpc_trace",
"//:hpack_encoder",
"//:promise",
],
)
@ -8150,14 +8158,13 @@ grpc_cc_library(
"resource_quota",
"slice",
"slice_buffer",
"switch",
"try_join",
"try_seq",
"//:exec_ctx",
"//:gpr",
"//:gpr_platform",
"//:grpc_base",
"//:hpack_encoder",
"//:hpack_parser",
"//:ref_counted_ptr",
],
)
@ -8217,8 +8224,6 @@ grpc_cc_library(
"//:gpr",
"//:gpr_platform",
"//:grpc_base",
"//:hpack_encoder",
"//:hpack_parser",
"//:ref_counted_ptr",
],
)
@ -8714,7 +8719,6 @@ grpc_cc_library(
"chaotic_good_frame_header",
"chaotic_good_legacy_server",
"chaotic_good_server_transport",
"chaotic_good_settings_metadata",
"closure",
"context",
"error",
@ -8747,8 +8751,6 @@ grpc_cc_library(
"//:gpr_platform",
"//:grpc_base",
"//:handshaker",
"//:hpack_encoder",
"//:hpack_parser",
"//:iomgr",
"//:orphanable",
"//:ref_counted_ptr",
@ -8847,9 +8849,9 @@ grpc_cc_library(
"channel_args_endpoint_config",
"chaotic_good_client_transport",
"chaotic_good_frame",
"chaotic_good_frame_cc_proto",
"chaotic_good_frame_header",
"chaotic_good_legacy_connector",
"chaotic_good_settings_metadata",
"closure",
"context",
"error",
@ -8884,8 +8886,6 @@ grpc_cc_library(
"//:grpc_base",
"//:grpc_client_channel",
"//:handshaker",
"//:hpack_encoder",
"//:hpack_parser",
"//:iomgr",
"//:ref_counted_ptr",
],

@ -0,0 +1,51 @@
// Copyright 2024 The gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
package chaotic_good_frame;
message Settings {
// Connection id
// - sent server->client on the control channel to specify the
// data channel connection id
// - sent client->server on the data channel to complete the
// connection
bytes connection_id = 1;
// Flag true if this is a data channel (and not a control channel)
bool data_channel = 2;
// Requested alignment for the data channel
// Client and server each send this with their preferences
uint32 alignment = 3;
}
message UnknownMetadata {
string key = 1;
bytes value = 2;
}
message ClientMetadata {
optional string path = 1;
optional string authority = 2;
optional uint64 timeout_ms = 3;
repeated UnknownMetadata unknown_metadata = 100;
}
message ServerMetadata {
optional uint32 status = 1;
optional bytes message = 2;
repeated UnknownMetadata unknown_metadata = 100;
}

@ -22,9 +22,9 @@
#include "absl/log/log.h"
#include "absl/random/random.h"
#include "absl/strings/escaping.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/event_engine/tcp_socket_utils.h"
#include "src/core/lib/promise/if.h"
@ -38,13 +38,17 @@ namespace chaotic_good {
class ChaoticGoodTransport : public RefCounted<ChaoticGoodTransport> {
public:
struct Options {
uint32_t encode_alignment = 64;
uint32_t decode_alignment = 64;
uint32_t inlined_payload_size_threshold = 8 * 1024;
};
ChaoticGoodTransport(PromiseEndpoint control_endpoint,
PromiseEndpoint data_endpoint, HPackParser hpack_parser,
HPackCompressor hpack_encoder)
PromiseEndpoint data_endpoint, Options options)
: control_endpoint_(std::move(control_endpoint)),
data_endpoint_(std::move(data_endpoint)),
encoder_(std::move(hpack_encoder)),
parser_(std::move(hpack_parser)) {
options_(options) {
// Enable RxMemoryAlignment and RPC receive coalescing after the transport
// setup is complete. At this point all the settings frames should have
// been read.
@ -52,17 +56,31 @@ class ChaoticGoodTransport : public RefCounted<ChaoticGoodTransport> {
}
auto WriteFrame(const FrameInterface& frame) {
bool saw_encoding_errors = false;
auto buffers = frame.Serialize(&encoder_, saw_encoding_errors);
SliceBuffer control;
SliceBuffer data;
FrameHeader header = frame.MakeHeader();
if (header.payload_length > options_.inlined_payload_size_threshold) {
header.payload_connection_id = 1;
header.Serialize(control.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(data);
const size_t padding = header.Padding(options_.encode_alignment);
if (padding != 0) {
auto slice = MutableSlice::CreateUninitialized(padding);
memset(slice.data(), 0, padding);
data.AppendIndexed(Slice(std::move(slice)));
}
} else {
header.Serialize(control.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(control);
}
// ignore encoding errors: they will be logged separately already
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: WriteFrame to:"
<< ResolvedAddressToString(control_endpoint_.GetPeerAddress())
.value_or("<<unknown peer address>>")
<< " " << frame.ToString();
return TryJoin<absl::StatusOr>(
control_endpoint_.Write(std::move(buffers.control)),
data_endpoint_.Write(std::move(buffers.data)));
return TryJoin<absl::StatusOr>(control_endpoint_.Write(std::move(control)),
data_endpoint_.Write(std::move(data)));
}
// Read frame header and payloads for control and data portions of one frame.
@ -81,59 +99,46 @@ class ChaoticGoodTransport : public RefCounted<ChaoticGoodTransport> {
<< " "
<< (frame_header.ok() ? frame_header->ToString()
: frame_header.status().ToString());
// Read header and trailers from control endpoint.
// Read message padding and message from data endpoint.
return If(
frame_header.ok(),
[this, &frame_header] {
const uint32_t message_padding = frame_header->message_padding;
const uint32_t message_length = frame_header->message_length;
return Map(
TryJoin<absl::StatusOr>(
control_endpoint_.Read(frame_header->GetFrameLength()),
data_endpoint_.Read(message_length + message_padding)),
[frame_header = *frame_header, message_padding](
absl::StatusOr<std::tuple<SliceBuffer, SliceBuffer>>
buffers)
-> absl::StatusOr<std::tuple<FrameHeader, BufferPair>> {
if (!buffers.ok()) return buffers.status();
SliceBuffer data_read = std::move(std::get<1>(*buffers));
if (message_padding > 0) {
data_read.RemoveLastNBytesNoInline(message_padding);
}
return std::tuple<FrameHeader, BufferPair>(
frame_header,
BufferPair{std::move(std::get<0>(*buffers)),
std::move(data_read)});
});
},
[&frame_header]() {
return
[status = frame_header.status()]() mutable
-> absl::StatusOr<std::tuple<FrameHeader, BufferPair>> {
return std::move(status);
};
});
return frame_header;
},
[this](FrameHeader frame_header) {
current_frame_header_ = frame_header;
auto con = frame_header.payload_connection_id == 0
? &control_endpoint_
: &data_endpoint_;
return con->Read(frame_header.payload_length +
frame_header.Padding(options_.decode_alignment));
},
[this](SliceBuffer payload)
-> absl::StatusOr<std::tuple<FrameHeader, SliceBuffer>> {
payload.RemoveLastNBytesNoInline(
current_frame_header_.Padding(options_.decode_alignment));
return std::tuple<FrameHeader, SliceBuffer>(current_frame_header_,
std::move(payload));
});
}
absl::Status DeserializeFrame(FrameHeader header, BufferPair buffers,
Arena* arena, FrameInterface& frame,
FrameLimits limits) {
auto s = frame.Deserialize(&parser_, header, bitgen_, arena,
std::move(buffers), limits);
template <typename T>
absl::StatusOr<T> DeserializeFrame(const FrameHeader& header,
SliceBuffer payload) {
T frame;
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: Deserialize " << header << " with payload "
<< absl::CEscape(payload.JoinIntoString());
CHECK_EQ(header.payload_length, payload.Length());
auto s = frame.Deserialize(header, std::move(payload));
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: DeserializeFrame "
<< (s.ok() ? frame.ToString() : s.ToString());
return s;
if (s.ok()) return std::move(frame);
return std::move(s);
}
private:
PromiseEndpoint control_endpoint_;
PromiseEndpoint data_endpoint_;
HPackCompressor encoder_;
HPackParser parser_;
absl::BitGen bitgen_;
FrameHeader current_frame_header_;
Options options_;
};
} // namespace chaotic_good

@ -28,10 +28,10 @@
#include "absl/status/statusor.h"
#include "src/core/client_channel/client_channel_factory.h"
#include "src/core/client_channel/client_channel_filter.h"
#include "src/core/ext/transport/chaotic_good/chaotic_good_frame.pb.h"
#include "src/core/ext/transport/chaotic_good/client_transport.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chaotic_good/settings_metadata.h"
#include "src/core/ext/transport/chaotic_good_legacy/client/chaotic_good_connector.h"
#include "src/core/handshaker/handshaker.h"
#include "src/core/lib/channel/channel_args.h"
@ -91,38 +91,38 @@ ChaoticGoodConnector::~ChaoticGoodConnector() {
auto ChaoticGoodConnector::DataEndpointReadSettingsFrame(
RefCountedPtr<ChaoticGoodConnector> self) {
return TrySeq(
self->data_endpoint_.ReadSlice(FrameHeader::kFrameHeaderSize),
[self](Slice slice) mutable {
// Read setting frame;
// Parse frame header
auto frame_header_ =
FrameHeader::Parse(reinterpret_cast<const uint8_t*>(
GRPC_SLICE_START_PTR(slice.c_slice())));
return If(
frame_header_.ok(),
[frame_header_ = *frame_header_, self]() {
auto frame_header_length = frame_header_.GetFrameLength();
return TrySeq(self->data_endpoint_.Read(frame_header_length),
return TrySeq(self->data_endpoint_.ReadSlice(FrameHeader::kFrameHeaderSize),
[self](Slice slice) mutable {
// Read setting frame;
// Parse frame header
auto frame_header_ =
FrameHeader::Parse(reinterpret_cast<const uint8_t*>(
GRPC_SLICE_START_PTR(slice.c_slice())));
return If(
frame_header_.ok(),
[frame_header_ = *frame_header_, self]() {
auto frame_header_length = frame_header_.payload_length;
return TrySeq(
self->data_endpoint_.Read(frame_header_length),
[]() { return absl::OkStatus(); });
},
[status = frame_header_.status()]() { return status; });
});
},
[status = frame_header_.status()]() { return status; });
});
}
auto ChaoticGoodConnector::DataEndpointWriteSettingsFrame(
RefCountedPtr<ChaoticGoodConnector> self) {
// Serialize setting frame.
SettingsFrame frame;
// frame.header set connectiion_type: control
frame.headers = SettingsMetadata{SettingsMetadata::ConnectionType::kData,
self->connection_id_, kDataAlignmentBytes}
.ToMetadataBatch();
bool saw_encoding_errors = false;
auto write_buffer =
frame.Serialize(&self->hpack_compressor_, saw_encoding_errors);
frame.settings.set_data_channel(true);
frame.settings.set_connection_id(self->connection_id_);
frame.settings.set_alignment(kDataAlignmentBytes);
SliceBuffer write_buffer;
frame.MakeHeader().Serialize(
write_buffer.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(write_buffer);
// ignore encoding errors: they will be logged separately already
return self->data_endpoint_.Write(std::move(write_buffer.control));
return self->data_endpoint_.Write(std::move(write_buffer));
}
auto ChaoticGoodConnector::WaitForDataEndpointSetup(
@ -187,29 +187,18 @@ auto ChaoticGoodConnector::ControlEndpointReadSettingsFrame(
return If(
frame_header.ok(),
TrySeq(
self->control_endpoint_.Read(frame_header->GetFrameLength()),
self->control_endpoint_.Read(frame_header->payload_length),
[frame_header = *frame_header, self](SliceBuffer buffer) {
// Deserialize setting frame.
SettingsFrame frame;
BufferPair buffer_pair{std::move(buffer), SliceBuffer()};
auto status = frame.Deserialize(
&self->hpack_parser_, frame_header,
absl::BitGenRef(self->bitgen_), GetContext<Arena>(),
std::move(buffer_pair), FrameLimits{});
auto status =
frame.Deserialize(frame_header, std::move(buffer));
if (!status.ok()) return status;
if (frame.headers == nullptr) {
return absl::UnavailableError("no settings headers");
}
auto settings_metadata =
SettingsMetadata::FromMetadataBatch(*frame.headers);
if (!settings_metadata.ok()) {
return settings_metadata.status();
}
if (!settings_metadata->connection_id.has_value()) {
if (frame.settings.connection_id().empty()) {
return absl::UnavailableError(
"no connection id in settings frame");
}
self->connection_id_ = *settings_metadata->connection_id;
self->connection_id_ = frame.settings.connection_id();
return absl::OkStatus();
},
WaitForDataEndpointSetup(self)),
@ -222,14 +211,13 @@ auto ChaoticGoodConnector::ControlEndpointWriteSettingsFrame(
// Serialize setting frame.
SettingsFrame frame;
// frame.header set connectiion_type: control
frame.headers = SettingsMetadata{SettingsMetadata::ConnectionType::kControl,
absl::nullopt, absl::nullopt}
.ToMetadataBatch();
bool saw_encoding_errors = false;
auto write_buffer =
frame.Serialize(&self->hpack_compressor_, saw_encoding_errors);
frame.settings.set_data_channel(false);
SliceBuffer write_buffer;
frame.MakeHeader().Serialize(
write_buffer.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(write_buffer);
// ignore encoding errors: they will be logged separately already
return self->control_endpoint_.Write(std::move(write_buffer.control));
return self->control_endpoint_.Write(std::move(write_buffer));
}
void ChaoticGoodConnector::Connect(const Args& args, Result* result,
@ -331,8 +319,7 @@ void ChaoticGoodConnector::OnHandshakeDone(
self->result_->transport = new ChaoticGoodClientTransport(
std::move(self->control_endpoint_),
std::move(self->data_endpoint_), self->args_.channel_args,
self->event_engine_, std::move(self->hpack_parser_),
std::move(self->hpack_compressor_));
self->event_engine_);
self->result_->channel_args = self->args_.channel_args;
ExecCtx::Run(DEBUG_LOCATION, std::exchange(self->notify_, nullptr),
status);

@ -25,8 +25,6 @@
#include "absl/random/random.h"
#include "absl/status/statusor.h"
#include "src/core/client_channel/connector.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
#include "src/core/handshaker/handshaker.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/event_engine/channel_args_endpoint_config.h"
@ -93,9 +91,6 @@ class ChaoticGoodConnector : public SubchannelConnector {
const std::shared_ptr<grpc_event_engine::experimental::EventEngine>
event_engine_;
RefCountedPtr<HandshakeManager> handshake_mgr_;
HPackCompressor hpack_compressor_;
HPackParser hpack_parser_;
absl::BitGen bitgen_;
InterActivityLatch<void> data_endpoint_ready_;
std::string connection_id_;
};

@ -34,13 +34,13 @@
#include "src/core/ext/transport/chaotic_good/chaotic_good_transport.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/lib/event_engine/event_engine_context.h"
#include "src/core/lib/event_engine/extensions/tcp_trace.h"
#include "src/core/lib/event_engine/query_extensions.h"
#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/promise/loop.h"
#include "src/core/lib/promise/map.h"
#include "src/core/lib/promise/switch.h"
#include "src/core/lib/promise/try_seq.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/resource_quota/resource_quota.h"
@ -91,36 +91,59 @@ absl::optional<CallHandler> ChaoticGoodClientTransport::LookupStream(
return it->second;
}
auto ChaoticGoodClientTransport::PushFrameIntoCall(ServerFragmentFrame frame,
auto ChaoticGoodClientTransport::PushFrameIntoCall(
ServerInitialMetadataFrame frame, CallHandler call_handler) {
auto headers = ServerMetadataGrpcFromProto(frame.headers);
if (!headers.ok()) {
LOG_EVERY_N_SEC(INFO, 10) << "Encode headers failed: " << headers.status();
return Immediate(StatusFlag(Failure{}));
}
return Immediate(call_handler.PushServerInitialMetadata(std::move(*headers)));
}
auto ChaoticGoodClientTransport::PushFrameIntoCall(MessageFrame frame,
CallHandler call_handler) {
const bool has_headers = frame.headers != nullptr;
auto push = TrySeq(
If(
has_headers,
[call_handler, headers = std::move(frame.headers)]() mutable {
return call_handler.PushServerInitialMetadata(std::move(headers));
},
[]() -> StatusFlag { return Success{}; }),
[call_handler, message = std::move(frame.message)]() mutable {
return If(
message.has_value(),
[&call_handler, &message]() mutable {
return call_handler.PushMessage(std::move(message->message));
},
[]() -> StatusFlag { return Success{}; });
},
[call_handler,
trailers = std::move(frame.trailers)]() mutable -> StatusFlag {
if (trailers != nullptr) {
call_handler.PushServerTrailingMetadata(std::move(trailers));
}
return Success{};
});
// Wrap the actual sequence with something that owns the call handler so that
// its lifetime extends until the push completes.
return call_handler.PushMessage(std::move(frame.message));
}
auto ChaoticGoodClientTransport::PushFrameIntoCall(
ServerTrailingMetadataFrame frame, CallHandler call_handler) {
auto trailers = ServerMetadataGrpcFromProto(frame.trailers);
if (!trailers.ok()) {
call_handler.PushServerTrailingMetadata(
CancelledServerMetadataFromStatus(trailers.status()));
} else {
call_handler.PushServerTrailingMetadata(std::move(*trailers));
}
return Immediate(Success{});
}
template <typename T>
auto ChaoticGoodClientTransport::DispatchFrame(ChaoticGoodTransport* transport,
const FrameHeader& header,
SliceBuffer payload) {
return GRPC_LATENT_SEE_PROMISE(
"PushFrameIntoCall",
([call_handler, push = std::move(push)]() mutable { return push(); }));
"ChaoticGoodClientTransport::DispatchFrame",
TrySeq(
[transport, header, payload = std::move(payload)]() mutable {
return transport->DeserializeFrame<T>(header, std::move(payload));
},
[this](T frame) {
absl::optional<CallHandler> call_handler =
LookupStream(frame.stream_id);
return If(
call_handler.has_value(),
[this, &call_handler, &frame]() {
return call_handler->SpawnWaitable(
"push-frame", [this, call_handler = *call_handler,
frame = std::move(frame)]() mutable {
return Map(call_handler.CancelIfFails(PushFrameIntoCall(
std::move(frame), call_handler)),
[](StatusFlag) { return absl::OkStatus(); });
});
},
[]() { return absl::OkStatus(); });
}));
}
auto ChaoticGoodClientTransport::TransportReadLoop(
@ -128,54 +151,29 @@ auto ChaoticGoodClientTransport::TransportReadLoop(
return Loop([this, transport = std::move(transport)] {
return TrySeq(
transport->ReadFrameBytes(),
[](std::tuple<FrameHeader, BufferPair> frame_bytes)
-> absl::StatusOr<std::tuple<FrameHeader, BufferPair>> {
const auto& frame_header = std::get<0>(frame_bytes);
if (frame_header.type != FrameType::kFragment) {
return absl::InternalError(
absl::StrCat("Expected fragment frame, got ",
static_cast<int>(frame_header.type)));
}
return frame_bytes;
},
[this, transport = transport.get()](
std::tuple<FrameHeader, BufferPair> frame_bytes) {
const auto& frame_header = std::get<0>(frame_bytes);
auto& buffers = std::get<1>(frame_bytes);
absl::optional<CallHandler> call_handler =
LookupStream(frame_header.stream_id);
ServerFragmentFrame frame;
absl::Status deserialize_status;
const FrameLimits frame_limits{1024 * 1024 * 1024,
aligned_bytes_ - 1};
if (call_handler.has_value()) {
deserialize_status = transport->DeserializeFrame(
frame_header, std::move(buffers), call_handler->arena(), frame,
frame_limits);
} else {
// Stream not found, skip the frame.
deserialize_status = transport->DeserializeFrame(
frame_header, std::move(buffers),
SimpleArenaAllocator()->MakeArena().get(), frame, frame_limits);
}
return If(
deserialize_status.ok() && call_handler.has_value(),
[this, &frame, &call_handler]() {
return call_handler->SpawnWaitable(
"push-frame", [this, call_handler = *call_handler,
frame = std::move(frame)]() mutable {
return Map(call_handler.CancelIfFails(PushFrameIntoCall(
std::move(frame), call_handler)),
[](StatusFlag) { return absl::OkStatus(); });
});
},
[&deserialize_status]() {
// Stream not found, nothing to do.
return [deserialize_status =
std::move(deserialize_status)]() mutable {
return std::move(deserialize_status);
};
});
std::tuple<FrameHeader, SliceBuffer> frame_bytes) {
const auto& header = std::get<0>(frame_bytes);
SliceBuffer& payload = std::get<1>(frame_bytes);
return Switch(
header.type,
Case<FrameType, FrameType::kServerInitialMetadata>([&, this]() {
return DispatchFrame<ServerInitialMetadataFrame>(
transport, header, std::move(payload));
}),
Case<FrameType, FrameType::kServerTrailingMetadata>([&, this]() {
return DispatchFrame<ServerTrailingMetadataFrame>(
transport, header, std::move(payload));
}),
Case<FrameType, FrameType::kMessage>([&, this]() {
return DispatchFrame<MessageFrame>(transport, header,
std::move(payload));
}),
Default([&]() {
LOG_EVERY_N_SEC(INFO, 10)
<< "Bad frame type: " << header.ToString();
return absl::OkStatus();
}));
},
[]() -> LoopCtl<absl::Status> { return Continue{}; });
});
@ -195,8 +193,7 @@ auto ChaoticGoodClientTransport::OnTransportActivityDone(
ChaoticGoodClientTransport::ChaoticGoodClientTransport(
PromiseEndpoint control_endpoint, PromiseEndpoint data_endpoint,
const ChannelArgs& args,
std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine,
HPackParser hpack_parser, HPackCompressor hpack_encoder)
std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine)
: allocator_(args.GetObject<ResourceQuota>()
->memory_quota()
->CreateMemoryAllocator("chaotic-good")),
@ -210,9 +207,12 @@ ChaoticGoodClientTransport::ChaoticGoodClientTransport(
epte->InitializeAndReturnTcpTracer();
}
}
ChaoticGoodTransport::Options options;
options.inlined_payload_size_threshold =
args.GetInt("grpc.chaotic_good.inlined_payload_size_threshold")
.value_or(options.inlined_payload_size_threshold);
auto transport = MakeRefCounted<ChaoticGoodTransport>(
std::move(control_endpoint), std::move(data_endpoint),
std::move(hpack_parser), std::move(hpack_encoder));
std::move(control_endpoint), std::move(data_endpoint), options);
auto party_arena = SimpleArenaAllocator(0)->MakeArena();
party_arena->SetContext<grpc_event_engine::experimental::EventEngine>(
event_engine.get());
@ -276,19 +276,19 @@ absl::Status BooleanSuccessToTransportError(bool success) {
auto ChaoticGoodClientTransport::CallOutboundLoop(uint32_t stream_id,
CallHandler call_handler) {
auto send_fragment = [stream_id,
outgoing_frames = outgoing_frames_.MakeSender()](
ClientFragmentFrame frame) mutable {
outgoing_frames =
outgoing_frames_.MakeSender()](auto frame) mutable {
frame.stream_id = stream_id;
return Map(outgoing_frames.Send(std::move(frame)),
BooleanSuccessToTransportError);
};
auto send_fragment_acked = [stream_id,
outgoing_frames = outgoing_frames_.MakeSender()](
ClientFragmentFrame frame) mutable {
frame.stream_id = stream_id;
return Map(outgoing_frames.SendAcked(std::move(frame)),
BooleanSuccessToTransportError);
};
auto send_fragment_acked =
[stream_id,
outgoing_frames = outgoing_frames_.MakeSender()](auto frame) mutable {
frame.stream_id = stream_id;
return Map(outgoing_frames.SendAcked(std::move(frame)),
BooleanSuccessToTransportError);
};
return GRPC_LATENT_SEE_PROMISE(
"CallOutboundLoop",
TrySeq(
@ -298,31 +298,19 @@ auto ChaoticGoodClientTransport::CallOutboundLoop(uint32_t stream_id,
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: Sending initial metadata: "
<< md->DebugString();
ClientFragmentFrame frame;
frame.headers = std::move(md);
ClientInitialMetadataFrame frame;
frame.headers = ClientMetadataProtoFromGrpc(*md);
return send_fragment(std::move(frame));
},
// Continuously send client frame with client to server messages.
ForEach(OutgoingMessages(call_handler),
[send_fragment_acked, aligned_bytes = aligned_bytes_](
MessageHandle message) mutable {
ClientFragmentFrame frame;
// Construct frame header (flags, header_length and
// trailer_length will be added in serialization).
const uint32_t message_length =
message->payload()->Length();
const uint32_t padding =
message_length % aligned_bytes == 0
? 0
: aligned_bytes - message_length % aligned_bytes;
CHECK_EQ((message_length + padding) % aligned_bytes, 0u);
frame.message = FragmentMessage(std::move(message), padding,
message_length);
[send_fragment_acked](MessageHandle message) mutable {
MessageFrame frame;
frame.message = std::move(message);
return send_fragment_acked(std::move(frame));
}),
[send_fragment]() mutable {
ClientFragmentFrame frame;
frame.end_of_stream = true;
ClientEndOfStream frame;
return send_fragment(std::move(frame));
}));
}

@ -39,8 +39,6 @@
#include "src/core/ext/transport/chaotic_good/chaotic_good_transport.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
#include "src/core/lib/promise/activity.h"
#include "src/core/lib/promise/context.h"
#include "src/core/lib/promise/for_each.h"
@ -69,8 +67,7 @@ class ChaoticGoodClientTransport final : public ClientTransport {
PromiseEndpoint control_endpoint, PromiseEndpoint data_endpoint,
const ChannelArgs& channel_args,
std::shared_ptr<grpc_event_engine::experimental::EventEngine>
event_engine,
HPackParser hpack_parser, HPackCompressor hpack_encoder);
event_engine);
~ChaoticGoodClientTransport() override;
FilterStackTransport* filter_stack_transport() override { return nullptr; }
@ -93,16 +90,21 @@ class ChaoticGoodClientTransport final : public ClientTransport {
auto CallOutboundLoop(uint32_t stream_id, CallHandler call_handler);
auto OnTransportActivityDone(absl::string_view what);
auto TransportWriteLoop(RefCountedPtr<ChaoticGoodTransport> transport);
template <typename T>
auto DispatchFrame(ChaoticGoodTransport* transport, const FrameHeader& header,
SliceBuffer payload);
auto TransportReadLoop(RefCountedPtr<ChaoticGoodTransport> transport);
// Push one frame into a call
auto PushFrameIntoCall(ServerFragmentFrame frame, CallHandler call_handler);
auto PushFrameIntoCall(ServerInitialMetadataFrame frame,
CallHandler call_handler);
auto PushFrameIntoCall(MessageFrame frame, CallHandler call_handler);
auto PushFrameIntoCall(ServerTrailingMetadataFrame frame,
CallHandler call_handler);
grpc_event_engine::experimental::MemoryAllocator allocator_;
// Max buffer is set to 4, so that for stream writes each time it will queue
// at most 2 frames.
MpscReceiver<ClientFrame> outgoing_frames_;
// Assigned aligned bytes from setting frame.
size_t aligned_bytes_ = 64;
Mutex mu_;
uint32_t next_stream_id_ ABSL_GUARDED_BY(mu_) = 1;
// Map of stream incoming server frames, key is stream_id.

@ -20,11 +20,13 @@
#include <cstdint>
#include <limits>
#include <type_traits>
#include <utility>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "src/core/ext/transport/chaotic_good/chaotic_good_frame.pb.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/lib/promise/context.h"
#include "src/core/lib/resource_quota/arena.h"
@ -38,262 +40,255 @@ namespace grpc_core {
namespace chaotic_good {
namespace {
const uint8_t kZeros[64] = {};
absl::Status ReadProto(SliceBuffer payload,
google::protobuf::MessageLite& msg) {
auto payload_slice = payload.JoinIntoSlice();
const bool ok =
msg.ParseFromArray(payload_slice.data(), payload_slice.length());
return ok ? absl::OkStatus() : absl::InternalError("Protobuf parse error");
}
namespace {
const NoDestruct<Slice> kZeroSlice{[] {
// Frame header size is fixed to 24 bytes.
auto slice = GRPC_SLICE_MALLOC(FrameHeader::kFrameHeaderSize);
memset(GRPC_SLICE_START_PTR(slice), 0, FrameHeader::kFrameHeaderSize);
return slice;
}()};
class FrameSerializer {
public:
explicit FrameSerializer(FrameType frame_type, uint32_t stream_id) {
output_.control.AppendIndexed(kZeroSlice->Copy());
header_.type = frame_type;
header_.stream_id = stream_id;
header_.flags.SetAll(false);
void WriteProto(const google::protobuf::MessageLite& msg, SliceBuffer& output) {
auto length = msg.ByteSizeLong();
auto slice = MutableSlice::CreateUninitialized(length);
CHECK(msg.SerializeToArray(slice.data(), length));
output.AppendIndexed(Slice(std::move(slice)));
}
uint32_t ProtoPayloadSize(const google::protobuf::MessageLite& msg) {
auto length = msg.ByteSizeLong();
CHECK_LE(length, std::numeric_limits<uint32_t>::max());
return static_cast<uint32_t>(length);
}
struct ClientMetadataEncoder {
void Encode(HttpPathMetadata,
const typename HttpPathMetadata::ValueType& value) {
out.set_path(value.as_string_view());
}
// If called, must be called before AddTrailers, Finish.
SliceBuffer& AddHeaders() {
header_.flags.set(0);
return output_.control;
void Encode(HttpAuthorityMetadata,
const typename HttpAuthorityMetadata::ValueType& value) {
out.set_authority(value.as_string_view());
}
void AddMessage(const FragmentMessage& msg) {
header_.flags.set(1);
header_.message_length = msg.length;
header_.message_padding = msg.padding;
output_.data = msg.message->payload()->Copy();
if (msg.padding != 0) {
output_.data.Append(Slice::FromStaticBuffer(kZeros, msg.padding));
void Encode(GrpcTimeoutMetadata,
const typename GrpcTimeoutMetadata::ValueType& value) {
auto now = Timestamp::Now();
if (now > value) {
out.set_timeout_ms(0);
} else {
out.set_timeout_ms((value - now).millis());
}
}
// If called, must be called before Finish.
SliceBuffer& AddTrailers() {
header_.flags.set(2);
header_.header_length =
output_.control.Length() - FrameHeader::kFrameHeaderSize;
return output_.control;
template <typename Which>
void Encode(Which, const typename Which::ValueType& value) {
EncodeWithWarning(Slice::FromExternalString(Which::key()),
Slice(Which::Encode(value)));
}
BufferPair Finish() {
// Calculate frame header_length or trailer_length if available.
if (header_.flags.is_set(2)) {
// Header length is already known in AddTrailers().
header_.trailer_length = output_.control.Length() -
header_.header_length -
FrameHeader::kFrameHeaderSize;
} else {
if (header_.flags.is_set(0)) {
// Calculate frame header length in Finish() since AddTrailers() isn't
// called.
header_.header_length =
output_.control.Length() - FrameHeader::kFrameHeaderSize;
}
}
header_.Serialize(
GRPC_SLICE_START_PTR(output_.control.c_slice_buffer()->slices[0]));
return std::move(output_);
void EncodeWithWarning(const Slice& key, const Slice& value) {
LOG_EVERY_N_SEC(INFO, 10) << "encoding known key " << key.as_string_view()
<< " with unknown encoding";
Encode(key, value);
}
private:
FrameHeader header_;
BufferPair output_;
void Encode(const Slice& key, const Slice& value) {
auto* unk = out.add_unknown_metadata();
unk->set_key(key.as_string_view());
unk->set_value(value.as_string_view());
}
chaotic_good_frame::ClientMetadata out;
};
class FrameDeserializer {
public:
FrameDeserializer(const FrameHeader& header, BufferPair& input)
: header_(header), input_(input) {}
const FrameHeader& header() const { return header_; }
// If called, must be called before ReceiveTrailers, Finish.
absl::StatusOr<SliceBuffer> ReceiveHeaders() {
return Take(header_.header_length);
struct ServerMetadataEncoder {
void Encode(GrpcStatusMetadata, grpc_status_code code) {
out.set_status(code);
}
// If called, must be called before Finish.
absl::StatusOr<SliceBuffer> ReceiveTrailers() {
return Take(header_.trailer_length);
void Encode(GrpcMessageMetadata, const Slice& value) {
out.set_message(value.as_string_view());
}
// Return message length to get payload size in data plane.
uint32_t GetMessageLength() const { return header_.message_length; }
// Return message padding to get padding size in data plane.
uint32_t GetMessagePadding() const { return header_.message_padding; }
template <typename Which>
void Encode(Which, const typename Which::ValueType& value) {
EncodeWithWarning(Slice::FromExternalString(Which::key()),
Slice(Which::Encode(value)));
}
absl::Status Finish() { return absl::OkStatus(); }
void EncodeWithWarning(const Slice& key, const Slice& value) {
LOG_EVERY_N_SEC(INFO, 10) << "encoding known key " << key.as_string_view()
<< " with unknown encoding";
Encode(key, value);
}
private:
absl::StatusOr<SliceBuffer> Take(uint32_t length) {
if (length == 0) return SliceBuffer{};
if (input_.control.Length() < length) {
return absl::InvalidArgumentError(
"Frame too short (insufficient payload)");
}
SliceBuffer out;
input_.control.MoveFirstNBytesIntoSliceBuffer(length, out);
return std::move(out);
void Encode(const Slice& key, const Slice& value) {
auto* unk = out.add_unknown_metadata();
unk->set_key(key.as_string_view());
unk->set_value(value.as_string_view());
}
FrameHeader header_;
BufferPair& input_;
chaotic_good_frame::ServerMetadata out;
};
template <typename Metadata>
absl::StatusOr<Arena::PoolPtr<Metadata>> ReadMetadata(
HPackParser* parser, absl::StatusOr<SliceBuffer> maybe_slices,
uint32_t stream_id, bool is_header, bool is_client, absl::BitGenRef bitsrc,
Arena* arena) {
if (!maybe_slices.ok()) return maybe_slices.status();
auto& slices = *maybe_slices;
CHECK_NE(arena, nullptr);
Arena::PoolPtr<Metadata> metadata = Arena::MakePooledForOverwrite<Metadata>();
parser->BeginFrame(
metadata.get(), std::numeric_limits<uint32_t>::max(),
std::numeric_limits<uint32_t>::max(),
is_header ? HPackParser::Boundary::EndOfHeaders
: HPackParser::Boundary::EndOfStream,
HPackParser::Priority::None,
HPackParser::LogInfo{stream_id,
is_header ? HPackParser::LogInfo::Type::kHeaders
: HPackParser::LogInfo::Type::kTrailers,
is_client});
for (size_t i = 0; i < slices.Count(); i++) {
GRPC_RETURN_IF_ERROR(parser->Parse(slices.c_slice_at(i),
i == slices.Count() - 1, bitsrc,
/*call_tracer=*/nullptr));
}
parser->FinishFrame();
return std::move(metadata);
template <typename T, typename M>
absl::StatusOr<T> ReadUnknownFields(const M& msg, T md) {
absl::Status error = absl::OkStatus();
for (const auto& unk : msg.unknown_metadata()) {
md->Append(unk.key(), Slice::FromCopiedString(unk.value()),
[&error](absl::string_view error_msg, const Slice&) {
if (!error.ok()) return;
error = absl::InternalError(error_msg);
});
}
if (!error.ok()) return error;
return std::move(md);
}
} // namespace
absl::Status FrameLimits::ValidateMessage(const FrameHeader& header) {
if (header.message_length > max_message_size) {
return absl::InvalidArgumentError(
absl::StrCat("Message length ", header.message_length,
" exceeds maximum allowed ", max_message_size));
}
if (header.message_padding > max_padding) {
return absl::InvalidArgumentError(
absl::StrCat("Message padding ", header.message_padding,
" exceeds maximum allowed ", max_padding));
}
return absl::OkStatus();
chaotic_good_frame::ClientMetadata ClientMetadataProtoFromGrpc(
const ClientMetadata& md) {
ClientMetadataEncoder e;
md.Encode(&e);
return std::move(e.out);
}
absl::Status SettingsFrame::Deserialize(HPackParser* parser,
const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits) {
if (header.type != FrameType::kSettings) {
return absl::InvalidArgumentError("Expected settings frame");
absl::StatusOr<ClientMetadataHandle> ClientMetadataGrpcFromProto(
chaotic_good_frame::ClientMetadata& metadata) {
auto md = Arena::MakePooled<ClientMetadata>();
md->Set(GrpcStatusFromWire(), true);
if (metadata.has_path()) {
md->Set(HttpPathMetadata(), Slice::FromCopiedString(metadata.path()));
}
if (header.flags.is_set(1) || header.flags.is_set(2)) {
return absl::InvalidArgumentError("Unexpected flags");
if (metadata.has_authority()) {
md->Set(HttpAuthorityMetadata(),
Slice::FromCopiedString(metadata.authority()));
}
if (buffers.data.Length() != 0) {
return absl::InvalidArgumentError("Unexpected data");
if (metadata.has_timeout_ms()) {
md->Set(GrpcTimeoutMetadata(),
Timestamp::Now() + Duration::Milliseconds(metadata.timeout_ms()));
}
FrameDeserializer deserializer(header, buffers);
if (header.flags.is_set(0)) {
auto r = ReadMetadata<ClientMetadata>(parser, deserializer.ReceiveHeaders(),
header.stream_id, true, true, bitsrc,
arena);
if (!r.ok()) return r.status();
if (r.value() != nullptr) {
headers = std::move(r.value());
}
} else if (header.header_length != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero header length", header.header_length));
return ReadUnknownFields(metadata, std::move(md));
}
chaotic_good_frame::ServerMetadata ServerMetadataProtoFromGrpc(
const ServerMetadata& md) {
ServerMetadataEncoder e;
md.Encode(&e);
return std::move(e.out);
}
absl::StatusOr<ServerMetadataHandle> ServerMetadataGrpcFromProto(
chaotic_good_frame::ServerMetadata& metadata) {
auto md = Arena::MakePooled<ServerMetadata>();
md->Set(GrpcStatusFromWire(), true);
if (metadata.has_status()) {
md->Set(GrpcStatusMetadata(),
static_cast<grpc_status_code>(metadata.status()));
}
return deserializer.Finish();
if (metadata.has_message()) {
md->Set(GrpcMessageMetadata(), Slice::FromCopiedString(metadata.message()));
}
return ReadUnknownFields(metadata, std::move(md));
}
BufferPair SettingsFrame::Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const {
FrameSerializer serializer(FrameType::kSettings, 0);
if (headers.get() != nullptr) {
saw_encoding_errors |=
!encoder->EncodeRawHeaders(*headers.get(), serializer.AddHeaders());
absl::Status SettingsFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.type, FrameType::kSettings);
if (header.stream_id != 0) {
return absl::InternalError("Expected stream id 0");
}
return serializer.Finish();
return ReadProto(std::move(payload), settings);
}
std::string SettingsFrame::ToString() const { return "SettingsFrame{}"; }
FrameHeader SettingsFrame::MakeHeader() const {
return FrameHeader{FrameType::kSettings, 0, 0, ProtoPayloadSize(settings)};
}
absl::Status ClientFragmentFrame::Deserialize(HPackParser* parser,
const FrameHeader& header,
absl::BitGenRef bitsrc,
Arena* arena, BufferPair buffers,
FrameLimits limits) {
void SettingsFrame::SerializePayload(SliceBuffer& payload) const {
WriteProto(settings, payload);
}
std::string SettingsFrame::ToString() const {
return settings.ShortDebugString();
}
absl::Status ClientInitialMetadataFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.type, FrameType::kClientInitialMetadata);
if (header.stream_id == 0) {
return absl::InvalidArgumentError("Expected non-zero stream id");
return absl::InternalError("Expected non-zero stream id");
}
stream_id = header.stream_id;
if (header.type != FrameType::kFragment) {
return absl::InvalidArgumentError("Expected fragment frame");
}
FrameDeserializer deserializer(header, buffers);
if (header.flags.is_set(0)) {
auto r = ReadMetadata<ClientMetadata>(parser, deserializer.ReceiveHeaders(),
header.stream_id, true, true, bitsrc,
arena);
if (!r.ok()) return r.status();
if (r.value() != nullptr) {
headers = std::move(r.value());
}
} else if (header.header_length != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero header length", header.header_length));
}
if (header.flags.is_set(1)) {
auto r = limits.ValidateMessage(header);
if (!r.ok()) return r;
message =
FragmentMessage{Arena::MakePooled<Message>(std::move(buffers.data), 0),
header.message_padding, header.message_length};
} else if (buffers.data.Length() != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero message length ", buffers.data.Length()));
}
if (header.flags.is_set(2)) {
if (header.trailer_length != 0) {
return absl::InvalidArgumentError(
absl::StrCat("Unexpected trailer length ", header.trailer_length));
}
end_of_stream = true;
} else {
end_of_stream = false;
}
return deserializer.Finish();
return ReadProto(std::move(payload), headers);
}
FrameHeader ClientInitialMetadataFrame::MakeHeader() const {
return FrameHeader{FrameType::kClientInitialMetadata, 0, stream_id,
ProtoPayloadSize(headers)};
}
BufferPair ClientFragmentFrame::Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const {
void ClientInitialMetadataFrame::SerializePayload(SliceBuffer& payload) const {
CHECK_NE(stream_id, 0u);
FrameSerializer serializer(FrameType::kFragment, stream_id);
if (headers.get() != nullptr) {
saw_encoding_errors |=
!encoder->EncodeRawHeaders(*headers.get(), serializer.AddHeaders());
WriteProto(headers, payload);
}
std::string ClientInitialMetadataFrame::ToString() const {
return absl::StrCat("ClientInitialMetadataFrame{stream_id=", stream_id,
", headers=", headers.ShortDebugString(), "}");
}
absl::Status ClientEndOfStream::Deserialize(const FrameHeader& header,
SliceBuffer) {
CHECK_EQ(header.type, FrameType::kClientEndOfStream);
if (header.stream_id == 0) {
return absl::InternalError("Expected non-zero stream id");
}
if (message.has_value()) {
serializer.AddMessage(message.value());
if (header.payload_length != 0) {
return absl::InternalError(
"Expected zero payload length on ClientEndOfStream");
}
if (end_of_stream) {
serializer.AddTrailers();
stream_id = header.stream_id;
return absl::OkStatus();
}
FrameHeader ClientEndOfStream::MakeHeader() const {
return FrameHeader{FrameType::kClientEndOfStream, 0, stream_id, 0};
}
void ClientEndOfStream::SerializePayload(SliceBuffer&) const {}
std::string ClientEndOfStream::ToString() const { return "ClientEndOfStream"; }
absl::Status MessageFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.type, FrameType::kMessage);
if (header.stream_id == 0) {
return absl::InternalError("Expected non-zero stream id");
}
return serializer.Finish();
stream_id = header.stream_id;
message = Arena::MakePooled<Message>(std::move(payload), 0);
return absl::OkStatus();
}
std::string FragmentMessage::ToString() const {
std::string out =
absl::StrCat("FragmentMessage{length=", length, ", padding=", padding);
FrameHeader MessageFrame::MakeHeader() const {
auto length = message->payload()->Length();
CHECK_LE(length, std::numeric_limits<uint32_t>::max());
return FrameHeader{FrameType::kMessage, 0, stream_id,
static_cast<uint32_t>(length)};
}
void MessageFrame::SerializePayload(SliceBuffer& payload) const {
CHECK_NE(stream_id, 0u);
payload.Append(*message->payload());
}
std::string MessageFrame::ToString() const {
std::string out = absl::StrCat("MessageFrame{stream_id=", stream_id);
if (message.get() != nullptr) {
absl::StrAppend(&out, ", message=", message->DebugString().c_str());
}
@ -301,114 +296,83 @@ std::string FragmentMessage::ToString() const {
return out;
}
std::string ClientFragmentFrame::ToString() const {
return absl::StrCat(
"ClientFragmentFrame{stream_id=", stream_id, ", headers=",
headers.get() != nullptr ? headers->DebugString().c_str() : "nullptr",
", message=", message.has_value() ? message->ToString().c_str() : "none",
", end_of_stream=", end_of_stream, "}");
absl::Status ServerInitialMetadataFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.type, FrameType::kServerInitialMetadata);
if (header.stream_id == 0) {
return absl::InternalError("Expected non-zero stream id");
}
stream_id = header.stream_id;
return ReadProto(std::move(payload), headers);
}
FrameHeader ServerInitialMetadataFrame::MakeHeader() const {
return FrameHeader{FrameType::kServerInitialMetadata, 0, stream_id,
ProtoPayloadSize(headers)};
}
absl::Status ServerFragmentFrame::Deserialize(HPackParser* parser,
const FrameHeader& header,
absl::BitGenRef bitsrc,
Arena* arena, BufferPair buffers,
FrameLimits limits) {
void ServerInitialMetadataFrame::SerializePayload(SliceBuffer& payload) const {
CHECK_NE(stream_id, 0u);
WriteProto(headers, payload);
}
std::string ServerInitialMetadataFrame::ToString() const {
return absl::StrCat("ServerInitialMetadataFrame{stream_id=", stream_id,
", headers=", headers.ShortDebugString(), "}");
}
absl::Status ServerTrailingMetadataFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.type, FrameType::kServerTrailingMetadata);
if (header.stream_id == 0) {
return absl::InvalidArgumentError("Expected non-zero stream id");
return absl::InternalError("Expected non-zero stream id");
}
stream_id = header.stream_id;
FrameDeserializer deserializer(header, buffers);
if (header.flags.is_set(0)) {
auto r = ReadMetadata<ServerMetadata>(parser, deserializer.ReceiveHeaders(),
header.stream_id, true, false, bitsrc,
arena);
if (!r.ok()) return r.status();
if (r.value() != nullptr) {
headers = std::move(r.value());
}
} else if (header.header_length != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero header length", header.header_length));
}
if (header.flags.is_set(1)) {
auto r = limits.ValidateMessage(header);
if (!r.ok()) return r;
message.emplace(Arena::MakePooled<Message>(std::move(buffers.data), 0),
header.message_padding, header.message_length);
} else if (buffers.data.Length() != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero message length", buffers.data.Length()));
}
if (header.flags.is_set(2)) {
auto r = ReadMetadata<ServerMetadata>(
parser, deserializer.ReceiveTrailers(), header.stream_id, false, false,
bitsrc, arena);
if (!r.ok()) return r.status();
if (r.value() != nullptr) {
trailers = std::move(r.value());
}
} else if (header.trailer_length != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero trailer length", header.trailer_length));
}
return deserializer.Finish();
return ReadProto(std::move(payload), trailers);
}
BufferPair ServerFragmentFrame::Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const {
FrameHeader ServerTrailingMetadataFrame::MakeHeader() const {
return FrameHeader{FrameType::kServerTrailingMetadata, 0, stream_id,
ProtoPayloadSize(trailers)};
}
void ServerTrailingMetadataFrame::SerializePayload(SliceBuffer& payload) const {
CHECK_NE(stream_id, 0u);
FrameSerializer serializer(FrameType::kFragment, stream_id);
if (headers.get() != nullptr) {
saw_encoding_errors |=
!encoder->EncodeRawHeaders(*headers.get(), serializer.AddHeaders());
}
if (message.has_value()) {
serializer.AddMessage(message.value());
}
if (trailers.get() != nullptr) {
saw_encoding_errors |=
!encoder->EncodeRawHeaders(*trailers.get(), serializer.AddTrailers());
}
return serializer.Finish();
WriteProto(trailers, payload);
}
std::string ServerFragmentFrame::ToString() const {
return absl::StrCat(
"ServerFragmentFrame{stream_id=", stream_id, ", headers=",
headers.get() != nullptr ? headers->DebugString().c_str() : "nullptr",
", message=", message.has_value() ? message->ToString().c_str() : "none",
", trailers=",
trailers.get() != nullptr ? trailers->DebugString().c_str() : "nullptr",
"}");
std::string ServerTrailingMetadataFrame::ToString() const {
return absl::StrCat("ServerTrailingMetadataFrame{stream_id=", stream_id,
", trailers=", trailers.ShortDebugString(), "}");
}
absl::Status CancelFrame::Deserialize(HPackParser*, const FrameHeader& header,
absl::BitGenRef, Arena*,
BufferPair buffers, FrameLimits) {
if (header.type != FrameType::kCancel) {
return absl::InvalidArgumentError("Expected cancel frame");
}
if (header.flags.any()) {
return absl::InvalidArgumentError("Unexpected flags");
}
absl::Status CancelFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
// Ensure the frame type is Cancel
CHECK_EQ(header.type, FrameType::kCancel);
// Ensure the stream_id is non-zero
if (header.stream_id == 0) {
return absl::InvalidArgumentError("Expected non-zero stream id");
return absl::InternalError("Expected non-zero stream id");
}
if (buffers.data.Length() != 0) {
return absl::InvalidArgumentError("Unexpected data");
// Ensure there is no payload
if (payload.Length() != 0) {
return absl::InternalError("Unexpected payload for Cancel frame");
}
FrameDeserializer deserializer(header, buffers);
// Set the stream_id
stream_id = header.stream_id;
return deserializer.Finish();
return absl::OkStatus();
}
BufferPair CancelFrame::Serialize(HPackCompressor*, bool&) const {
CHECK_NE(stream_id, 0u);
FrameSerializer serializer(FrameType::kCancel, stream_id);
return serializer.Finish();
FrameHeader CancelFrame::MakeHeader() const {
return FrameHeader{FrameType::kCancel, 0, stream_id, 0};
}
void CancelFrame::SerializePayload(SliceBuffer&) const {}
std::string CancelFrame::ToString() const {
return absl::StrCat("CancelFrame{stream_id=", stream_id, "}");
}

@ -18,19 +18,17 @@
#include <grpc/support/port_platform.h>
#include <cstdint>
#include <memory>
#include <string>
#include "absl/random/bit_gen_ref.h"
#include "absl/status/status.h"
#include "absl/types/variant.h"
#include "src/core/ext/transport/chaotic_good/chaotic_good_frame.pb.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/slice/slice_buffer.h"
#include "src/core/lib/transport/metadata_batch.h"
#include "src/core/lib/transport/transport.h"
#include "src/core/lib/transport/message.h"
#include "src/core/lib/transport/metadata.h"
#include "src/core/util/match.h"
namespace grpc_core {
@ -41,21 +39,12 @@ struct BufferPair {
SliceBuffer data;
};
struct FrameLimits {
size_t max_message_size = 1024 * 1024 * 1024;
size_t max_padding = 63;
absl::Status ValidateMessage(const FrameHeader& header);
};
class FrameInterface {
public:
virtual absl::Status Deserialize(HPackParser* parser,
const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits limits) = 0;
virtual BufferPair Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const = 0;
virtual absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) = 0;
virtual FrameHeader MakeHeader() const = 0;
virtual void SerializePayload(SliceBuffer& payload) const = 0;
virtual std::string ToString() const = 0;
template <typename Sink>
@ -64,16 +53,6 @@ class FrameInterface {
}
protected:
static bool EqVal(const grpc_metadata_batch& a,
const grpc_metadata_batch& b) {
return a.DebugString() == b.DebugString();
}
template <typename T>
static bool EqHdl(const Arena::PoolPtr<T>& a, const Arena::PoolPtr<T>& b) {
if (a == nullptr && b == nullptr) return true;
if (a == nullptr || b == nullptr) return false;
return EqVal(*a, *b);
}
~FrameInterface() = default;
};
@ -81,111 +60,118 @@ inline std::ostream& operator<<(std::ostream& os, const FrameInterface& frame) {
return os << frame.ToString();
}
chaotic_good_frame::ClientMetadata ClientMetadataProtoFromGrpc(
const ClientMetadata& md);
absl::StatusOr<ClientMetadataHandle> ClientMetadataGrpcFromProto(
chaotic_good_frame::ClientMetadata& metadata);
chaotic_good_frame::ServerMetadata ServerMetadataProtoFromGrpc(
const ServerMetadata& md);
absl::StatusOr<ServerMetadataHandle> ServerMetadataGrpcFromProto(
chaotic_good_frame::ServerMetadata& metadata);
struct SettingsFrame final : public FrameInterface {
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits limits) override;
BufferPair Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const override;
ClientMetadataHandle headers;
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
bool operator==(const SettingsFrame&) const { return true; }
chaotic_good_frame::Settings settings;
};
struct FragmentMessage {
FragmentMessage(MessageHandle message, uint32_t padding, uint32_t length)
: message(std::move(message)), padding(padding), length(length) {}
MessageHandle message;
uint32_t padding;
uint32_t length;
struct ClientInitialMetadataFrame final : public FrameInterface {
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
std::string ToString() const;
uint32_t stream_id;
chaotic_good_frame::ClientMetadata headers;
};
static bool EqVal(const Message& a, const Message& b) {
return a.payload()->JoinIntoString() == b.payload()->JoinIntoString() &&
a.flags() == b.flags();
}
struct MessageFrame final : public FrameInterface {
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
bool operator==(const FragmentMessage& other) const {
if (length != other.length) return false;
if (message == nullptr && other.message == nullptr) return true;
if (message == nullptr || other.message == nullptr) return false;
return EqVal(*message, *other.message);
}
uint32_t stream_id;
MessageHandle message;
};
struct ClientFragmentFrame final : public FrameInterface {
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits limits) override;
BufferPair Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const override;
struct ClientEndOfStream final : public FrameInterface {
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
uint32_t stream_id;
ClientMetadataHandle headers;
absl::optional<FragmentMessage> message;
bool end_of_stream = false;
bool operator==(const ClientFragmentFrame& other) const {
return stream_id == other.stream_id && EqHdl(headers, other.headers) &&
message == other.message && end_of_stream == other.end_of_stream;
}
};
struct ServerFragmentFrame final : public FrameInterface {
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits limits) override;
BufferPair Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const override;
struct ServerInitialMetadataFrame final : public FrameInterface {
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
uint32_t stream_id;
ServerMetadataHandle headers;
absl::optional<FragmentMessage> message;
ServerMetadataHandle trailers;
chaotic_good_frame::ServerMetadata headers;
};
bool operator==(const ServerFragmentFrame& other) const {
return stream_id == other.stream_id && EqHdl(headers, other.headers) &&
message == other.message && EqHdl(trailers, other.trailers);
}
struct ServerTrailingMetadataFrame final : public FrameInterface {
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
uint32_t stream_id;
chaotic_good_frame::ServerMetadata trailers;
};
struct CancelFrame final : public FrameInterface {
CancelFrame() = default;
explicit CancelFrame(uint32_t stream_id) : stream_id(stream_id) {}
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits limits) override;
BufferPair Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const override;
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
uint32_t stream_id;
bool operator==(const CancelFrame& other) const {
return stream_id == other.stream_id;
}
};
using ClientFrame = absl::variant<ClientFragmentFrame, CancelFrame>;
using ServerFrame = absl::variant<ServerFragmentFrame>;
using ClientFrame = absl::variant<ClientInitialMetadataFrame, MessageFrame,
ClientEndOfStream, CancelFrame>;
using ServerFrame = absl::variant<ServerInitialMetadataFrame, MessageFrame,
ServerTrailingMetadataFrame>;
inline FrameInterface& GetFrameInterface(ClientFrame& frame) {
return MatchMutable(
&frame,
[](ClientFragmentFrame* frame) -> FrameInterface& { return *frame; },
[](ClientInitialMetadataFrame* frame) -> FrameInterface& {
return *frame;
},
[](MessageFrame* frame) -> FrameInterface& { return *frame; },
[](ClientEndOfStream* frame) -> FrameInterface& { return *frame; },
[](CancelFrame* frame) -> FrameInterface& { return *frame; });
}
inline FrameInterface& GetFrameInterface(ServerFrame& frame) {
return MatchMutable(
&frame,
[](ServerFragmentFrame* frame) -> FrameInterface& { return *frame; });
[](ServerInitialMetadataFrame* frame) -> FrameInterface& {
return *frame;
},
[](MessageFrame* frame) -> FrameInterface& { return *frame; },
[](ServerTrailingMetadataFrame* frame) -> FrameInterface& {
return *frame;
});
}
} // namespace chaotic_good

@ -42,44 +42,32 @@ uint32_t ReadLittleEndianUint32(const uint8_t* data) {
// Serializes a frame header into a buffer of 24 bytes.
void FrameHeader::Serialize(uint8_t* data) const {
WriteLittleEndianUint32(
static_cast<uint32_t>(type) | (flags.ToInt<uint32_t>() << 8), data);
WriteLittleEndianUint32((static_cast<uint32_t>(type) << 16) |
static_cast<uint32_t>(payload_connection_id),
data);
WriteLittleEndianUint32(stream_id, data + 4);
WriteLittleEndianUint32(header_length, data + 8);
WriteLittleEndianUint32(message_length, data + 12);
WriteLittleEndianUint32(message_padding, data + 16);
WriteLittleEndianUint32(trailer_length, data + 20);
WriteLittleEndianUint32(payload_length, data + 8);
}
// Parses a frame header from a buffer of 24 bytes. All 24 bytes are consumed.
absl::StatusOr<FrameHeader> FrameHeader::Parse(const uint8_t* data) {
FrameHeader header;
const uint32_t type_and_flags = ReadLittleEndianUint32(data);
header.type = static_cast<FrameType>(type_and_flags & 0xff);
const uint32_t flags = type_and_flags >> 8;
if (flags > 7) return absl::InvalidArgumentError("Invalid flags");
header.flags = BitSet<3>::FromInt(flags);
const uint32_t type_and_conn_id = ReadLittleEndianUint32(data);
if (type_and_conn_id & 0xff000000u) {
return absl::InternalError("Non-zero reserved byte received");
}
header.type = static_cast<FrameType>(type_and_conn_id >> 16);
header.payload_connection_id = type_and_conn_id & 0xffff;
header.stream_id = ReadLittleEndianUint32(data + 4);
header.header_length = ReadLittleEndianUint32(data + 8);
header.message_length = ReadLittleEndianUint32(data + 12);
header.message_padding = ReadLittleEndianUint32(data + 16);
header.trailer_length = ReadLittleEndianUint32(data + 20);
header.payload_length = ReadLittleEndianUint32(data + 8);
return header;
}
uint32_t FrameHeader::GetFrameLength() const {
// In chaotic-good transport design, message and message padding are sent
// through different channel. So not included in the frame length calculation.
uint32_t frame_length = header_length + trailer_length;
return frame_length;
}
std::string FrameHeader::ToString() const {
return absl::StrFormat(
"[type=0x%02x, flags=0x%02x, stream_id=%d, header_length=%d, "
"message_length=%d, message_padding=%d, trailer_length=%d]",
static_cast<uint8_t>(type), flags.ToInt<uint8_t>(), stream_id,
header_length, message_length, message_padding, trailer_length);
"[type=0x%02x, conn=0x%04x, stream_id=%d, payload_length=%d]",
static_cast<uint8_t>(type), payload_connection_id, stream_id,
payload_length);
}
} // namespace chaotic_good

@ -26,18 +26,31 @@
namespace grpc_core {
namespace chaotic_good {
// Remember to add new frame types to frame_fuzzer.cc
enum class FrameType : uint8_t {
kSettings = 0x00,
kFragment = 0x80,
kCancel = 0x81,
kClientInitialMetadata = 0x80,
kClientEndOfStream = 0x81,
kServerInitialMetadata = 0x91,
kServerTrailingMetadata = 0x92,
kMessage = 0xa0,
kCancel = 0xff,
};
inline std::ostream& operator<<(std::ostream& out, FrameType type) {
switch (type) {
case FrameType::kSettings:
return out << "Settings";
case FrameType::kFragment:
return out << "Fragment";
case FrameType::kClientInitialMetadata:
return out << "ClientInitialMetadata";
case FrameType::kClientEndOfStream:
return out << "ClientEndOfStream";
case FrameType::kMessage:
return out << "Message";
case FrameType::kServerInitialMetadata:
return out << "ServerInitialMetadata";
case FrameType::kServerTrailingMetadata:
return out << "ServerTrailingMetadata";
case FrameType::kCancel:
return out << "Cancel";
default:
@ -47,33 +60,40 @@ inline std::ostream& operator<<(std::ostream& out, FrameType type) {
struct FrameHeader {
FrameType type = FrameType::kCancel;
BitSet<3> flags;
uint16_t payload_connection_id = 0;
uint32_t stream_id = 0;
uint32_t header_length = 0;
uint32_t message_length = 0;
uint32_t message_padding = 0;
uint32_t trailer_length = 0;
uint32_t payload_length = 0;
// Parses a frame header from a buffer of 24 bytes. All 24 bytes are consumed.
// Parses a frame header from a buffer of 12 bytes. All 12 bytes are consumed.
static absl::StatusOr<FrameHeader> Parse(const uint8_t* data);
// Serializes a frame header into a buffer of 24 bytes.
// Serializes a frame header into a buffer of 12 bytes.
void Serialize(uint8_t* data) const;
// Compute frame sizes from the header.
uint32_t GetFrameLength() const;
// Report contents as a string
std::string ToString() const;
// Required padding to maintain alignment.
uint32_t Padding(uint32_t alignment) const {
if (payload_connection_id == 0) {
return 0;
}
if (payload_length % alignment == 0) {
return 0;
}
return alignment - (payload_length % alignment);
}
bool operator==(const FrameHeader& h) const {
return type == h.type && flags == h.flags && stream_id == h.stream_id &&
header_length == h.header_length &&
message_length == h.message_length &&
message_padding == h.message_padding &&
trailer_length == h.trailer_length;
return type == h.type && stream_id == h.stream_id &&
payload_connection_id == h.payload_connection_id &&
payload_length == h.payload_length;
}
// Frame header size is fixed to 24 bytes.
static constexpr size_t kFrameHeaderSize = 24;
// Frame header size is fixed to 12 bytes.
enum { kFrameHeaderSize = 12 };
};
inline std::ostream& operator<<(std::ostream& out, const FrameHeader& h) {
return out << h.ToString();
}
} // namespace chaotic_good
} // namespace grpc_core

@ -34,7 +34,6 @@
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chaotic_good/server_transport.h"
#include "src/core/ext/transport/chaotic_good/settings_metadata.h"
#include "src/core/ext/transport/chaotic_good_legacy/server/chaotic_good_server.h"
#include "src/core/handshaker/handshaker.h"
#include "src/core/lib/channel/channel_args.h"
@ -225,51 +224,39 @@ auto ChaoticGoodServerListener::ActiveConnection::HandshakingState::
// Parse frame header
auto frame_header = FrameHeader::Parse(reinterpret_cast<const uint8_t*>(
GRPC_SLICE_START_PTR(slice.c_slice())));
if (frame_header.ok() && frame_header->type != FrameType::kSettings) {
frame_header = absl::InternalError("Not a settings frame");
}
return If(
frame_header.ok(),
[self, &frame_header]() {
return TrySeq(
self->connection_->endpoint_.Read(
frame_header->GetFrameLength()),
frame_header->payload_length),
[frame_header = *frame_header,
self](SliceBuffer buffer) -> absl::StatusOr<bool> {
// Read Setting frame.
SettingsFrame frame;
// Deserialize frame from read buffer.
BufferPair buffer_pair{std::move(buffer), SliceBuffer()};
auto status = frame.Deserialize(
&self->connection_->hpack_parser_, frame_header,
absl::BitGenRef(self->connection_->bitgen_),
GetContext<Arena>(), std::move(buffer_pair),
FrameLimits{});
auto status =
frame.Deserialize(frame_header, std::move(buffer));
if (!status.ok()) return status;
if (frame.headers == nullptr) {
return absl::UnavailableError("no settings headers");
}
auto settings_metadata =
SettingsMetadata::FromMetadataBatch(*frame.headers);
if (!settings_metadata.ok()) {
return settings_metadata.status();
}
const bool is_control_endpoint =
settings_metadata->connection_type ==
SettingsMetadata::ConnectionType::kControl;
if (!is_control_endpoint) {
if (!settings_metadata->connection_id.has_value()) {
if (frame.settings.data_channel()) {
if (frame.settings.connection_id().empty()) {
return absl::UnavailableError(
"no connection id in data endpoint settings frame");
}
if (!settings_metadata->alignment.has_value()) {
if (frame.settings.alignment() == 0) {
return absl::UnavailableError(
"no alignment in data endpoint settings frame");
}
// Get connection-id and data-alignment for data endpoint.
self->connection_->connection_id_ =
*settings_metadata->connection_id;
frame.settings.connection_id();
self->connection_->data_alignment_ =
*settings_metadata->alignment;
frame.settings.alignment();
}
return is_control_endpoint;
return !frame.settings.data_channel();
});
},
[&frame_header]() {
@ -309,9 +296,7 @@ auto ChaoticGoodServerListener::ActiveConnection::HandshakingState::
new ChaoticGoodServerTransport(
self->connection_->args(),
std::move(self->connection_->endpoint_), std::move(ret),
self->connection_->listener_->event_engine_,
std::move(self->connection_->hpack_parser_),
std::move(self->connection_->hpack_compressor_)),
self->connection_->listener_->event_engine_),
nullptr, self->connection_->args(), nullptr);
}),
// Set timeout for waiting data endpoint connect.
@ -331,33 +316,31 @@ auto ChaoticGoodServerListener::ActiveConnection::HandshakingState::
ControlEndpointWriteSettingsFrame(RefCountedPtr<HandshakingState> self) {
self->connection_->NewConnectionID();
SettingsFrame frame;
frame.headers =
SettingsMetadata{absl::nullopt, self->connection_->connection_id_,
absl::nullopt}
.ToMetadataBatch();
bool saw_encoding_errors = false;
auto write_buffer = frame.Serialize(&self->connection_->hpack_compressor_,
saw_encoding_errors);
frame.settings.set_data_channel(false);
frame.settings.set_connection_id(self->connection_->connection_id_);
SliceBuffer write_buffer;
frame.MakeHeader().Serialize(
write_buffer.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(write_buffer);
// ignore encoding errors: they will be logged separately already
return TrySeq(
self->connection_->endpoint_.Write(std::move(write_buffer.control)),
WaitForDataEndpointSetup(self));
return TrySeq(self->connection_->endpoint_.Write(std::move(write_buffer)),
WaitForDataEndpointSetup(self));
}
auto ChaoticGoodServerListener::ActiveConnection::HandshakingState::
DataEndpointWriteSettingsFrame(RefCountedPtr<HandshakingState> self) {
// Send data endpoint setting frame
SettingsFrame frame;
frame.headers =
SettingsMetadata{absl::nullopt, self->connection_->connection_id_,
self->connection_->data_alignment_}
.ToMetadataBatch();
bool saw_encoding_errors = false;
auto write_buffer = frame.Serialize(&self->connection_->hpack_compressor_,
saw_encoding_errors);
frame.settings.set_data_channel(true);
frame.settings.set_connection_id(self->connection_->connection_id_);
frame.settings.set_alignment(self->connection_->data_alignment_);
SliceBuffer write_buffer;
frame.MakeHeader().Serialize(
write_buffer.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(write_buffer);
// ignore encoding errors: they will be logged separately already
return TrySeq(
self->connection_->endpoint_.Write(std::move(write_buffer.control)),
self->connection_->endpoint_.Write(std::move(write_buffer)),
[self]() mutable {
MutexLock lock(&self->connection_->listener_->mu_);
// Set endpoint to latch

@ -29,8 +29,6 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "src/core/channelz/channelz.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
#include "src/core/handshaker/handshaker.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/iomgr/closure.h"
@ -119,8 +117,6 @@ class ChaoticGoodServerListener final : public Server::ListenerInterface {
ActivityPtr receive_settings_activity_ ABSL_GUARDED_BY(mu_);
bool orphaned_ ABSL_GUARDED_BY(mu_) = false;
PromiseEndpoint endpoint_;
HPackCompressor hpack_compressor_;
HPackParser hpack_parser_;
absl::BitGen bitgen_;
std::string connection_id_;
int32_t data_alignment_;

@ -32,7 +32,6 @@
#include "src/core/ext/transport/chaotic_good/chaotic_good_transport.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/lib/event_engine/event_engine_context.h"
#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/promise/activity.h"
@ -70,57 +69,44 @@ auto ChaoticGoodServerTransport::TransportWriteLoop(
});
}
auto ChaoticGoodServerTransport::PushFragmentIntoCall(
CallInitiator call_initiator, ClientFragmentFrame frame) {
DCHECK(frame.headers == nullptr);
auto ChaoticGoodServerTransport::PushFrameIntoCall(CallInitiator call_initiator,
MessageFrame frame) {
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: PushFragmentIntoCall: frame=" << frame.ToString();
return Seq(If(
frame.message.has_value(),
[&call_initiator, &frame]() mutable {
return call_initiator.PushMessage(
std::move(frame.message->message));
},
[]() -> StatusFlag { return Success{}; }),
[call_initiator, end_of_stream = frame.end_of_stream](
StatusFlag status) mutable -> StatusFlag {
if (!status.ok() && GRPC_TRACE_FLAG_ENABLED(chaotic_good)) {
LOG(INFO) << "CHAOTIC_GOOD: Failed PushFragmentIntoCall";
}
if (end_of_stream || !status.ok()) {
call_initiator.FinishSends();
// Note that we cannot remove from the stream map yet, as we
// may yet receive a cancellation.
}
return Success{};
});
<< "CHAOTIC_GOOD: PushFrameIntoCall: frame=" << frame.ToString();
return call_initiator.PushMessage(std::move(frame.message));
}
auto ChaoticGoodServerTransport::MaybePushFragmentIntoCall(
absl::optional<CallInitiator> call_initiator, absl::Status error,
ClientFragmentFrame frame) {
return If(
call_initiator.has_value() && error.ok(),
[this, &call_initiator, &frame]() {
return Map(
call_initiator->SpawnWaitable(
"push-fragment",
[call_initiator, frame = std::move(frame), this]() mutable {
return call_initiator->CancelIfFails(
PushFragmentIntoCall(*call_initiator, std::move(frame)));
}),
[](StatusFlag status) { return StatusCast<absl::Status>(status); });
auto ChaoticGoodServerTransport::PushFrameIntoCall(CallInitiator call_initiator,
ClientEndOfStream) {
call_initiator.FinishSends();
// Note that we cannot remove from the stream map yet, as we
// may yet receive a cancellation.
return Immediate(Success{});
}
template <typename T>
auto ChaoticGoodServerTransport::DispatchFrame(ChaoticGoodTransport& transport,
const FrameHeader& header,
SliceBuffer payload) {
return TrySeq(
[&transport, header, payload = std::move(payload)]() mutable {
return transport.DeserializeFrame<T>(header, std::move(payload));
},
[&error, &frame]() {
// EOF frames may arrive after the call_initiator's OnDone callback
// has been invoked. In that case, the call_initiator would have
// already been removed from the stream_map and hence the EOF frame
// cannot be pushed into the call. No need to log such frames.
if (!frame.end_of_stream) {
LOG(INFO) << "CHAOTIC_GOOD: Cannot pass frame to stream. Error:"
<< error.ToString() << " Frame:" << frame.ToString();
}
return Immediate(std::move(error));
[this](T frame) {
absl::optional<CallInitiator> call_initiator =
LookupStream(frame.stream_id);
return If(
call_initiator.has_value(),
[this, &call_initiator, &frame]() {
return call_initiator->SpawnWaitable(
"push-frame", [this, call_initiator = *call_initiator,
frame = std::move(frame)]() mutable {
return Map(call_initiator.CancelIfFails(PushFrameIntoCall(
call_initiator, std::move(frame))),
[](StatusFlag) { return absl::OkStatus(); });
});
},
[]() { return absl::OkStatus(); });
});
}
@ -133,11 +119,9 @@ auto BooleanSuccessToTransportErrorCapturingInitiator(CallInitiator initiator) {
}
} // namespace
auto ChaoticGoodServerTransport::SendFragment(
ServerFragmentFrame frame, MpscSender<ServerFrame> outgoing_frames,
auto ChaoticGoodServerTransport::SendFrame(
ServerFrame frame, MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator) {
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: SendFragment: frame=" << frame.ToString();
// Capture the call_initiator to ensure the underlying call spine is alive
// until the outgoing_frames.Send promise completes.
return Map(outgoing_frames.Send(std::move(frame)),
@ -145,11 +129,9 @@ auto ChaoticGoodServerTransport::SendFragment(
std::move(call_initiator)));
}
auto ChaoticGoodServerTransport::SendFragmentAcked(
ServerFragmentFrame frame, MpscSender<ServerFrame> outgoing_frames,
auto ChaoticGoodServerTransport::SendFrameAcked(
ServerFrame frame, MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator) {
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: SendFragmentAcked: frame=" << frame.ToString();
// Capture the call_initiator to ensure the underlying call spine is alive
// until the outgoing_frames.Send promise completes.
return Map(outgoing_frames.SendAcked(std::move(frame)),
@ -160,30 +142,16 @@ auto ChaoticGoodServerTransport::SendFragmentAcked(
auto ChaoticGoodServerTransport::SendCallBody(
uint32_t stream_id, MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator) {
// Continuously send client frame with client to server
// messages.
return ForEach(
OutgoingMessages(call_initiator),
// Capture the call_initiator to ensure the underlying call
// spine is alive until the SendFragment promise completes.
[stream_id, outgoing_frames, call_initiator,
aligned_bytes = aligned_bytes_](MessageHandle message) mutable {
ServerFragmentFrame frame;
// Construct frame header (flags, header_length
// and trailer_length will be added in
// serialization).
const uint32_t message_length = message->payload()->Length();
const uint32_t padding =
message_length % aligned_bytes == 0
? 0
: aligned_bytes - (message_length % aligned_bytes);
CHECK_EQ((message_length + padding) % aligned_bytes, 0u);
frame.message =
FragmentMessage(std::move(message), padding, message_length);
frame.stream_id = stream_id;
return SendFragmentAcked(std::move(frame), outgoing_frames,
call_initiator);
});
// Continuously send client frame with client to server messages.
return ForEach(OutgoingMessages(call_initiator),
[this, stream_id, outgoing_frames = std::move(outgoing_frames),
call_initiator](MessageHandle message) mutable {
MessageFrame frame;
frame.message = std::move(message);
frame.stream_id = stream_id;
return SendFrameAcked(std::move(frame), outgoing_frames,
call_initiator);
});
}
auto ChaoticGoodServerTransport::SendCallInitialMetadataAndBody(
@ -200,12 +168,11 @@ auto ChaoticGoodServerTransport::SendCallInitialMetadataAndBody(
return If(
md.has_value(),
[&md, stream_id, &outgoing_frames, &call_initiator, this]() {
ServerFragmentFrame frame;
frame.headers = std::move(*md);
ServerInitialMetadataFrame frame;
frame.headers = ServerMetadataProtoFromGrpc(**md);
frame.stream_id = stream_id;
return TrySeq(
SendFragment(std::move(frame), outgoing_frames,
call_initiator),
SendFrame(std::move(frame), outgoing_frames, call_initiator),
SendCallBody(stream_id, outgoing_frames, call_initiator));
},
[]() { return absl::OkStatus(); });
@ -228,62 +195,48 @@ auto ChaoticGoodServerTransport::CallOutboundLoop(
call_initiator.PullServerTrailingMetadata(),
// Capture the call_initiator to ensure the underlying call_spine
// is alive until the SendFragment promise completes.
[stream_id, outgoing_frames,
[this, stream_id, outgoing_frames,
call_initiator](ServerMetadataHandle md) mutable {
ServerFragmentFrame frame;
frame.trailers = std::move(md);
ServerTrailingMetadataFrame frame;
frame.trailers = ServerMetadataProtoFromGrpc(*md);
frame.stream_id = stream_id;
return SendFragment(std::move(frame), outgoing_frames,
call_initiator);
return SendFrame(std::move(frame), outgoing_frames, call_initiator);
}));
}
auto ChaoticGoodServerTransport::DeserializeAndPushFragmentToNewCall(
FrameHeader frame_header, BufferPair buffers,
ChaoticGoodTransport& transport) {
ClientFragmentFrame fragment_frame;
absl::Status ChaoticGoodServerTransport::NewStream(
ChaoticGoodTransport& transport, const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.payload_length, payload.Length());
auto client_initial_metadata_frame =
transport.DeserializeFrame<ClientInitialMetadataFrame>(
header, std::move(payload));
if (!client_initial_metadata_frame.ok()) {
return client_initial_metadata_frame.status();
}
auto md = ClientMetadataGrpcFromProto(client_initial_metadata_frame->headers);
if (!md.ok()) {
return md.status();
}
RefCountedPtr<Arena> arena(call_arena_allocator_->MakeArena());
arena->SetContext<grpc_event_engine::experimental::EventEngine>(
event_engine_.get());
absl::Status status = transport.DeserializeFrame(
frame_header, std::move(buffers), arena.get(), fragment_frame,
FrameLimits{1024 * 1024 * 1024, aligned_bytes_ - 1});
absl::optional<CallInitiator> call_initiator;
if (status.ok()) {
auto call =
MakeCallPair(std::move(fragment_frame.headers), std::move(arena));
call_initiator.emplace(std::move(call.initiator));
auto add_result = NewStream(frame_header.stream_id, *call_initiator);
if (add_result.ok()) {
call_initiator->SpawnGuarded(
"server-write", [this, stream_id = frame_header.stream_id,
call_initiator = *call_initiator,
call_handler = std::move(call.handler)]() mutable {
call_destination_->StartCall(std::move(call_handler));
return CallOutboundLoop(stream_id, call_initiator);
});
} else {
call_initiator.reset();
status = add_result;
}
auto call = MakeCallPair(std::move(*md), std::move(arena));
call_initiator.emplace(std::move(call.initiator));
const auto stream_id = client_initial_metadata_frame->stream_id;
auto add_result = NewStream(stream_id, *call_initiator);
if (!add_result.ok()) {
call_initiator.reset();
return add_result;
}
return MaybePushFragmentIntoCall(std::move(call_initiator), std::move(status),
std::move(fragment_frame));
}
auto ChaoticGoodServerTransport::DeserializeAndPushFragmentToExistingCall(
FrameHeader frame_header, BufferPair buffers,
ChaoticGoodTransport& transport) {
absl::optional<CallInitiator> call_initiator =
LookupStream(frame_header.stream_id);
Arena* arena = nullptr;
if (call_initiator.has_value()) arena = call_initiator->arena();
ClientFragmentFrame fragment_frame;
absl::Status status = transport.DeserializeFrame(
frame_header, std::move(buffers), arena, fragment_frame,
FrameLimits{1024 * 1024 * 1024, aligned_bytes_ - 1});
return MaybePushFragmentIntoCall(std::move(call_initiator), std::move(status),
std::move(fragment_frame));
call_initiator->SpawnGuarded(
"server-write", [this, stream_id, call_initiator = *call_initiator,
call_handler = std::move(call.handler)]() mutable {
call_destination_->StartCall(std::move(call_handler));
return CallOutboundLoop(stream_id, call_initiator);
});
return absl::OkStatus();
}
auto ChaoticGoodServerTransport::ReadOneFrame(ChaoticGoodTransport& transport) {
@ -292,51 +245,46 @@ auto ChaoticGoodServerTransport::ReadOneFrame(ChaoticGoodTransport& transport) {
TrySeq(
transport.ReadFrameBytes(),
[this, transport = &transport](
std::tuple<FrameHeader, BufferPair> frame_bytes) {
const auto& frame_header = std::get<0>(frame_bytes);
auto& buffers = std::get<1>(frame_bytes);
std::tuple<FrameHeader, SliceBuffer> frame_bytes) {
const auto& header = std::get<0>(frame_bytes);
SliceBuffer& payload = std::get<1>(frame_bytes);
CHECK_EQ(header.payload_length, payload.Length());
return Switch(
frame_header.type,
Case(FrameType::kSettings,
[]() -> absl::Status {
return absl::InternalError("Unexpected settings frame");
}),
Case(FrameType::kFragment,
[this, &frame_header, &buffers, transport]() {
return If(
frame_header.flags.is_set(0),
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToNewCall(
frame_header, std::move(buffers), *transport);
},
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToExistingCall(
frame_header, std::move(buffers), *transport);
});
}),
Case(FrameType::kCancel,
[this, &frame_header]() {
absl::optional<CallInitiator> call_initiator =
ExtractStream(frame_header.stream_id);
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "Cancel stream " << frame_header.stream_id
<< (call_initiator.has_value() ? " (active)"
: " (not found)");
return If(
call_initiator.has_value(),
[&call_initiator]() {
auto c = std::move(*call_initiator);
return c.SpawnWaitable("cancel", [c]() mutable {
c.Cancel();
return absl::OkStatus();
});
},
[]() -> absl::Status { return absl::OkStatus(); });
}),
Default([frame_header]() {
header.type,
Case<FrameType, FrameType::kClientInitialMetadata>([&, this]() {
return Immediate(
NewStream(*transport, header, std::move(payload)));
}),
Case<FrameType, FrameType::kMessage>([&, this]() {
return DispatchFrame<MessageFrame>(*transport, header,
std::move(payload));
}),
Case<FrameType, FrameType::kClientEndOfStream>([&, this]() {
return DispatchFrame<ClientEndOfStream>(*transport, header,
std::move(payload));
}),
Case<FrameType, FrameType::kCancel>([&, this]() {
absl::optional<CallInitiator> call_initiator =
ExtractStream(header.stream_id);
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "Cancel stream " << header.stream_id
<< (call_initiator.has_value() ? " (active)"
: " (not found)");
return If(
call_initiator.has_value(),
[&call_initiator]() {
auto c = std::move(*call_initiator);
return c.SpawnWaitable("cancel", [c]() mutable {
c.Cancel();
return absl::OkStatus();
});
},
[]() -> absl::Status { return absl::OkStatus(); });
}),
Default([&]() {
return absl::InternalError(
absl::StrCat("Unexpected frame type: ",
static_cast<uint8_t>(frame_header.type)));
static_cast<uint8_t>(header.type)));
}));
},
[]() -> LoopCtl<absl::Status> { return Continue{}; }));
@ -364,8 +312,7 @@ auto ChaoticGoodServerTransport::OnTransportActivityDone(
ChaoticGoodServerTransport::ChaoticGoodServerTransport(
const ChannelArgs& args, PromiseEndpoint control_endpoint,
PromiseEndpoint data_endpoint,
std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine,
HPackParser hpack_parser, HPackCompressor hpack_encoder)
std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine)
: call_arena_allocator_(MakeRefCounted<CallArenaAllocator>(
args.GetObject<ResourceQuota>()
->memory_quota()
@ -373,9 +320,12 @@ ChaoticGoodServerTransport::ChaoticGoodServerTransport(
1024)),
event_engine_(event_engine),
outgoing_frames_(4) {
ChaoticGoodTransport::Options options;
options.inlined_payload_size_threshold =
args.GetInt("grpc.chaotic_good.inlined_payload_size_threshold")
.value_or(options.inlined_payload_size_threshold);
auto transport = MakeRefCounted<ChaoticGoodTransport>(
std::move(control_endpoint), std::move(data_endpoint),
std::move(hpack_parser), std::move(hpack_encoder));
std::move(control_endpoint), std::move(data_endpoint), options);
auto party_arena = SimpleArenaAllocator(0)->MakeArena();
party_arena->SetContext<grpc_event_engine::experimental::EventEngine>(
event_engine.get());

@ -45,8 +45,6 @@
#include "src/core/ext/transport/chaotic_good/chaotic_good_transport.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
#include "src/core/lib/event_engine/default_event_engine.h" // IWYU pragma: keep
#include "src/core/lib/promise/activity.h"
#include "src/core/lib/promise/context.h"
@ -81,8 +79,7 @@ class ChaoticGoodServerTransport final : public ServerTransport {
const ChannelArgs& args, PromiseEndpoint control_endpoint,
PromiseEndpoint data_endpoint,
std::shared_ptr<grpc_event_engine::experimental::EventEngine>
event_engine,
HPackParser hpack_parser, HPackCompressor hpack_encoder);
event_engine);
FilterStackTransport* filter_stack_transport() override { return nullptr; }
ClientTransport* client_transport() override { return nullptr; }
@ -108,12 +105,6 @@ class ChaoticGoodServerTransport final : public ServerTransport {
CallInitiator call_initiator);
auto SendCallBody(uint32_t stream_id, MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator);
static auto SendFragment(ServerFragmentFrame frame,
MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator);
static auto SendFragmentAcked(ServerFragmentFrame frame,
MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator);
auto CallOutboundLoop(uint32_t stream_id, CallInitiator call_initiator);
auto OnTransportActivityDone(absl::string_view activity);
auto TransportReadLoop(RefCountedPtr<ChaoticGoodTransport> transport);
@ -130,10 +121,19 @@ class ChaoticGoodServerTransport final : public ServerTransport {
auto DeserializeAndPushFragmentToExistingCall(
FrameHeader frame_header, BufferPair buffers,
ChaoticGoodTransport& transport);
auto MaybePushFragmentIntoCall(absl::optional<CallInitiator> call_initiator,
absl::Status error, ClientFragmentFrame frame);
auto PushFragmentIntoCall(CallInitiator call_initiator,
ClientFragmentFrame frame);
absl::Status NewStream(ChaoticGoodTransport& transport,
const FrameHeader& header,
SliceBuffer initial_metadata_payload);
template <typename T>
auto DispatchFrame(ChaoticGoodTransport& transport, const FrameHeader& header,
SliceBuffer payload);
auto PushFrameIntoCall(CallInitiator call_initiator, MessageFrame frame);
auto PushFrameIntoCall(CallInitiator call_initiator, ClientEndOfStream frame);
auto SendFrame(ServerFrame frame, MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator);
auto SendFrameAcked(ServerFrame frame,
MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator);
RefCountedPtr<UnstartedCallDestination> call_destination_;
const RefCountedPtr<CallArenaAllocator> call_arena_allocator_;
@ -141,8 +141,6 @@ class ChaoticGoodServerTransport final : public ServerTransport {
event_engine_;
InterActivityLatch<void> got_acceptor_;
MpscReceiver<ServerFrame> outgoing_frames_;
// Assigned aligned bytes from setting frame.
size_t aligned_bytes_ = 64;
Mutex mu_;
// Map of stream incoming server frames, key is stream_id.
StreamMap stream_map_ ABSL_GUARDED_BY(mu_);

@ -1,79 +0,0 @@
// Copyright 2024 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/core/ext/transport/chaotic_good/settings_metadata.h"
#include <grpc/support/port_platform.h>
#include "absl/status/status.h"
#include "src/core/util/crash.h"
namespace grpc_core {
namespace chaotic_good {
Arena::PoolPtr<grpc_metadata_batch> SettingsMetadata::ToMetadataBatch() {
auto md = Arena::MakePooledForOverwrite<grpc_metadata_batch>();
auto add = [&md](absl::string_view key, std::string value) {
md->Append(key, Slice::FromCopiedString(value),
[key, value](absl::string_view error, const Slice&) {
Crash(absl::StrCat("Failed to add metadata '", key, "' = '",
value, "': ", error));
});
};
if (connection_type.has_value()) {
add("chaotic-good-connection-type",
connection_type.value() == ConnectionType::kControl ? "control"
: "data");
}
if (connection_id.has_value()) {
add("chaotic-good-connection-id", connection_id.value());
}
if (alignment.has_value()) {
add("chaotic-good-alignment", absl::StrCat(alignment.value()));
}
return md;
}
absl::StatusOr<SettingsMetadata> SettingsMetadata::FromMetadataBatch(
const grpc_metadata_batch& batch) {
SettingsMetadata md;
std::string buffer;
auto v = batch.GetStringValue("chaotic-good-connection-type", &buffer);
if (v.has_value()) {
if (*v == "control") {
md.connection_type = ConnectionType::kControl;
} else if (*v == "data") {
md.connection_type = ConnectionType::kData;
} else {
return absl::UnavailableError(
absl::StrCat("Invalid connection type: ", *v));
}
}
v = batch.GetStringValue("chaotic-good-connection-id", &buffer);
if (v.has_value()) {
md.connection_id = std::string(*v);
}
v = batch.GetStringValue("chaotic-good-alignment", &buffer);
if (v.has_value()) {
uint32_t alignment;
if (!absl::SimpleAtoi(*v, &alignment)) {
return absl::UnavailableError(absl::StrCat("Invalid alignment: ", *v));
}
md.alignment = alignment;
}
return md;
}
} // namespace chaotic_good
} // namespace grpc_core

@ -1,45 +0,0 @@
// Copyright 2024 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef GRPC_SRC_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_SETTINGS_METADATA_H
#define GRPC_SRC_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_SETTINGS_METADATA_H
#include <grpc/support/port_platform.h>
#include "absl/types/optional.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/transport/metadata_batch.h"
namespace grpc_core {
namespace chaotic_good {
// Captures metadata sent in a chaotic good settings frame.
struct SettingsMetadata {
enum class ConnectionType {
kControl,
kData,
};
absl::optional<ConnectionType> connection_type;
absl::optional<std::string> connection_id;
absl::optional<uint32_t> alignment;
Arena::PoolPtr<grpc_metadata_batch> ToMetadataBatch();
static absl::StatusOr<SettingsMetadata> FromMetadataBatch(
const grpc_metadata_batch& batch);
};
} // namespace chaotic_good
} // namespace grpc_core
#endif // GRPC_SRC_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_SETTINGS_METADATA_H

@ -297,42 +297,40 @@ auto ChaoticGoodServerTransport::ReadOneFrame(ChaoticGoodTransport& transport) {
auto& buffers = std::get<1>(frame_bytes);
return Switch(
frame_header.type,
Case(FrameType::kSettings,
[]() -> absl::Status {
return absl::InternalError("Unexpected settings frame");
}),
Case(FrameType::kFragment,
[this, &frame_header, &buffers, transport]() {
return If(
frame_header.flags.is_set(0),
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToNewCall(
frame_header, std::move(buffers), *transport);
},
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToExistingCall(
frame_header, std::move(buffers), *transport);
});
}),
Case(FrameType::kCancel,
[this, &frame_header]() {
absl::optional<CallInitiator> call_initiator =
ExtractStream(frame_header.stream_id);
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "Cancel stream " << frame_header.stream_id
<< (call_initiator.has_value() ? " (active)"
: " (not found)");
return If(
call_initiator.has_value(),
[&call_initiator]() {
auto c = std::move(*call_initiator);
return c.SpawnWaitable("cancel", [c]() mutable {
c.Cancel();
return absl::OkStatus();
});
},
[]() -> absl::Status { return absl::OkStatus(); });
}),
Case<FrameType, FrameType::kSettings>([]() -> absl::Status {
return absl::InternalError("Unexpected settings frame");
}),
Case<FrameType, FrameType::kFragment>(
[this, &frame_header, &buffers, transport]() {
return If(
frame_header.flags.is_set(0),
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToNewCall(
frame_header, std::move(buffers), *transport);
},
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToExistingCall(
frame_header, std::move(buffers), *transport);
});
}),
Case<FrameType, FrameType::kCancel>([this, &frame_header]() {
absl::optional<CallInitiator> call_initiator =
ExtractStream(frame_header.stream_id);
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "Cancel stream " << frame_header.stream_id
<< (call_initiator.has_value() ? " (active)"
: " (not found)");
return If(
call_initiator.has_value(),
[&call_initiator]() {
auto c = std::move(*call_initiator);
return c.SpawnWaitable("cancel", [c]() mutable {
c.Cancel();
return absl::OkStatus();
});
},
[]() -> absl::Status { return absl::OkStatus(); });
}),
Default([frame_header]() {
return absl::InternalError(
absl::StrCat("Unexpected frame type: ",

@ -0,0 +1,50 @@
// Copyright 2024 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef GRPC_SRC_CORE_LIB_PROMISE_DETAIL_PROMISE_VARIANT_H
#define GRPC_SRC_CORE_LIB_PROMISE_DETAIL_PROMISE_VARIANT_H
#include "absl/types/variant.h"
namespace grpc_core {
namespace promise_detail {
// Visitor function for PromiseVariant - calls the poll operator on the inner
// type
class PollVisitor {
public:
template <typename T>
auto operator()(T& x) {
return x();
}
};
// Helper type - given a variant V, provides the poll operator (which simply
// visits the inner type on the variant with PollVisitor)
template <typename V>
class PromiseVariant {
public:
explicit PromiseVariant(V variant) : variant_(std::move(variant)) {}
auto operator()() { return absl::visit(PollVisitor(), variant_); }
private:
V variant_;
};
} // namespace promise_detail
} // namespace grpc_core
#endif // GRPC_SRC_CORE_LIB_PROMISE_DETAIL_PROMISE_VARIANT_H

@ -18,6 +18,7 @@
#include "absl/types/variant.h"
#include "src/core/lib/promise/detail/promise_factory.h"
#include "src/core/lib/promise/detail/promise_like.h"
#include "src/core/lib/promise/detail/promise_variant.h"
#include "src/core/util/overload.h"
namespace grpc_core {
@ -56,28 +57,6 @@ struct ConstructPromiseVariantVisitor {
}
};
// Visitor function for PromiseVariant - calls the poll operator on the inner
// type
class PollVisitor {
public:
template <typename T>
auto operator()(T& x) {
return x();
}
};
// Helper type - given a variant V, provides the poll operator (which simply
// visits the inner type on the variant with PollVisitor)
template <typename V>
class PromiseVariant {
public:
explicit PromiseVariant(V variant) : variant_(std::move(variant)) {}
auto operator()() { return absl::visit(PollVisitor(), variant_); }
private:
V variant_;
};
} // namespace promise_detail
// Match for promises

@ -55,6 +55,13 @@ struct StatusCastImpl<absl::Status, Success> {
}
};
template <>
struct StatusCastImpl<absl::Status, Success&> {
GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static absl::Status Cast(Success) {
return absl::OkStatus();
}
};
template <>
struct StatusCastImpl<absl::Status, const Success&> {
GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static absl::Status Cast(Success) {

@ -21,26 +21,54 @@
#include <utility>
#include "src/core/lib/promise/detail/promise_factory.h"
#include "src/core/lib/promise/detail/promise_variant.h"
#include "src/core/lib/promise/if.h"
#include "src/core/util/crash.h"
namespace grpc_core {
namespace promise_detail {
template <typename D, typename F>
template <typename D, D discriminator, typename F>
struct Case {
D discriminator;
F factory;
using Factory = OncePromiseFactory<void, F>;
explicit Case(F f) : factory(std::move(f)) {}
Factory factory;
static bool Matches(D value) { return value == discriminator; }
};
template <typename F>
struct Default {
F factory;
using Factory = OncePromiseFactory<void, F>;
explicit Default(F f) : factory(std::move(f)) {}
Factory factory;
};
template <typename Promise, typename D, typename F>
Promise ConstructSwitchPromise(D, Default<F>& def) {
return def.factory.Make();
}
template <typename Promise, typename D, typename Case, typename... OtherCases>
Promise ConstructSwitchPromise(D discriminator, Case& c, OtherCases&... cs) {
if (Case::Matches(discriminator)) return c.factory.Make();
return ConstructSwitchPromise<Promise>(discriminator, cs...);
}
template <typename D, typename... Cases>
auto SwitchImpl(D discriminator, Cases&... cases) {
using Promise = absl::variant<typename Cases::Factory::Promise...>;
return PromiseVariant<Promise>(
ConstructSwitchPromise<Promise>(discriminator, cases...));
}
} // namespace promise_detail
template <typename D, typename PromiseFactory>
auto Case(D discriminator, PromiseFactory f) {
return promise_detail::Case<D, PromiseFactory>{discriminator, std::move(f)};
// TODO(ctiller): when we have C++17, make this
// template <auto D, typename PromiseFactory>.
// (this way we don't need to list the type on /every/ case)
template <typename D, D discriminator, typename PromiseFactory>
auto Case(PromiseFactory f) {
return promise_detail::Case<D, discriminator, PromiseFactory>{std::move(f)};
}
template <typename PromiseFactory>
@ -55,16 +83,9 @@ auto Default(PromiseFactory f) {
// resolves to 43.
// TODO(ctiller): consider writing a code-generator like we do for seq/join
// so that this lowers into a C switch statement.
template <typename D, typename F>
auto Switch(D, promise_detail::Default<F> def) {
return promise_detail::OncePromiseFactory<void, F>(std::move(def.factory))
.Make();
}
template <typename D, typename F, typename... Others>
auto Switch(D discriminator, promise_detail::Case<D, F> c, Others... others) {
return If(discriminator == c.discriminator, std::move(c.factory),
Switch(discriminator, std::move(others)...));
template <typename D, typename... C>
auto Switch(D discriminator, C... cases) {
return promise_detail::SwitchImpl(discriminator, cases...);
}
} // namespace grpc_core

@ -441,6 +441,7 @@ void grpc_slice_buffer_copy_first_into_buffer(grpc_slice_buffer* src, size_t n,
template <bool allow_inline>
void grpc_slice_buffer_trim_end_impl(grpc_slice_buffer* sb, size_t n,
grpc_slice_buffer* garbage) {
if (n == 0) return;
CHECK(n <= sb->length);
sb->length -= n;
for (;;) {

@ -87,6 +87,7 @@ grpc_internal_proto_library(
srcs = ["fuzzer_input.proto"],
deps = [
"api_fuzzer_proto",
"//src/core:chaotic_good_frame_proto",
"//test/core/event_engine/fuzzing_event_engine:fuzzing_event_engine_proto",
"//test/core/test_util:fuzz_config_vars_proto",
"//test/core/test_util:fuzzing_channel_args_proto",

@ -20,6 +20,7 @@ import "test/core/end2end/fuzzers/api_fuzzer.proto";
import "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.proto";
import "test/core/test_util/fuzz_config_vars.proto";
import "test/core/test_util/fuzzing_channel_args.proto";
import "src/core/ext/transport/chaotic_good/chaotic_good_frame.proto";
message Empty{};
@ -145,37 +146,37 @@ message ChaoticGoodServerFragment {
}
}
message ChaoticGoodMessageData {
uint32 length = 1;
uint32 padding = 2;
message ChaoticGoodPayloadOtherConnection {
uint32 connection_id = 1;
uint32 length = 2;
}
message ChaoticGoodFrame {
enum FrameType {
SETTINGS = 0;
FRAGMENT = 1;
CANCEL = 2;
CLIENT_INITIAL_METADATA = 1;
MESSAGE = 2;
CLIENT_END_OF_STREAM = 3;
SERVER_INITIAL_METADATA = 4;
SERVER_TRAILING_METADATA = 5;
CANCEL = 6;
};
uint32 stream_id = 1;
FrameType type = 2;
oneof headers {
Empty headers_none = 11;
bytes headers_raw_bytes = 12;
SimpleHeaders headers_simple_header = 13;
}
oneof data {
Empty data_none = 21;
ChaoticGoodMessageData data_sized = 23;
oneof frame_type {
FrameType known_type = 2;
uint32 unknown_type = 3;
}
oneof trailers {
Empty trailers_none = 31;
bytes trailers_raw_bytes = 32;
SimpleHeaders trailers_simple_header = 33;
oneof payload {
ChaoticGoodPayloadOtherConnection payload_other_connection_id = 10;
Empty payload_none = 11;
bytes payload_raw_bytes = 12;
uint32 payload_empty_of_length = 13;
chaotic_good_frame.Settings settings = 14;
chaotic_good_frame.ClientMetadata client_metadata = 15;
chaotic_good_frame.ServerMetadata server_metadata = 16;
}
}
message ChaoticGoodSettings {}
message FakeTransportFrame {
enum MessageString {
CLIENT_INIT = 0;

@ -198,69 +198,83 @@ SliceBuffer ChaoticGoodFrame(const fuzzer_input::ChaoticGoodFrame& frame) {
chaotic_good::FrameHeader h;
SliceBuffer suffix;
h.stream_id = frame.stream_id();
switch (frame.type()) {
case fuzzer_input::ChaoticGoodFrame::SETTINGS:
h.type = chaotic_good::FrameType::kSettings;
break;
case fuzzer_input::ChaoticGoodFrame::FRAGMENT:
h.type = chaotic_good::FrameType::kFragment;
switch (frame.frame_type_case()) {
case fuzzer_input::ChaoticGoodFrame::kKnownType:
switch (frame.known_type()) {
case fuzzer_input::ChaoticGoodFrame::SETTINGS:
h.type = chaotic_good::FrameType::kSettings;
break;
case fuzzer_input::ChaoticGoodFrame::CLIENT_INITIAL_METADATA:
h.type = chaotic_good::FrameType::kClientInitialMetadata;
break;
case fuzzer_input::ChaoticGoodFrame::MESSAGE:
h.type = chaotic_good::FrameType::kMessage;
break;
case fuzzer_input::ChaoticGoodFrame::CLIENT_END_OF_STREAM:
h.type = chaotic_good::FrameType::kClientEndOfStream;
break;
case fuzzer_input::ChaoticGoodFrame::SERVER_INITIAL_METADATA:
h.type = chaotic_good::FrameType::kServerInitialMetadata;
break;
case fuzzer_input::ChaoticGoodFrame::SERVER_TRAILING_METADATA:
h.type = chaotic_good::FrameType::kServerTrailingMetadata;
break;
case fuzzer_input::ChaoticGoodFrame::CANCEL:
h.type = chaotic_good::FrameType::kCancel;
break;
default:
break;
}
break;
case fuzzer_input::ChaoticGoodFrame::CANCEL:
h.type = chaotic_good::FrameType::kCancel;
case fuzzer_input::ChaoticGoodFrame::kUnknownType:
h.type = static_cast<chaotic_good::FrameType>(frame.unknown_type());
break;
default:
case fuzzer_input::ChaoticGoodFrame::FRAME_TYPE_NOT_SET:
h.type = chaotic_good::FrameType::kMessage;
break;
}
switch (frame.headers_case()) {
case fuzzer_input::ChaoticGoodFrame::kHeadersNone:
case fuzzer_input::ChaoticGoodFrame::HEADERS_NOT_SET:
h.stream_id = frame.stream_id();
h.payload_connection_id = 0;
h.payload_length = 0;
auto proto_payload = [&](auto payload) {
std::string temp = payload.SerializeAsString();
h.payload_length = temp.length();
suffix.Append(Slice::FromCopiedString(temp));
};
switch (frame.payload_case()) {
case fuzzer_input::ChaoticGoodFrame::kPayloadNone:
case fuzzer_input::ChaoticGoodFrame::PAYLOAD_NOT_SET:
break;
case fuzzer_input::ChaoticGoodFrame::kHeadersRawBytes:
if (frame.headers_raw_bytes().empty()) break;
h.header_length = frame.headers_raw_bytes().size();
h.flags.Set(0, true);
suffix.Append(Slice::FromCopiedString(frame.headers_raw_bytes()));
case fuzzer_input::ChaoticGoodFrame::kPayloadRawBytes:
if (frame.payload_raw_bytes().empty()) break;
h.payload_length = frame.payload_raw_bytes().length();
suffix.Append(Slice::FromCopiedString(frame.payload_raw_bytes()));
break;
case fuzzer_input::ChaoticGoodFrame::kHeadersSimpleHeader: {
SliceBuffer append =
SliceBufferFromSimpleHeaders(frame.headers_simple_header());
if (append.Length() == 0) break;
h.header_length = append.Length();
h.flags.Set(0, true);
suffix.Append(append.JoinIntoSlice());
} break;
}
switch (frame.data_case()) {
case fuzzer_input::ChaoticGoodFrame::kDataNone:
case fuzzer_input::ChaoticGoodFrame::DATA_NOT_SET:
case fuzzer_input::ChaoticGoodFrame::kPayloadEmptyOfLength:
h.payload_length = frame.payload_empty_of_length();
suffix.Append(Slice::FromCopiedString(
std::string(frame.payload_empty_of_length(), 'a')));
break;
case fuzzer_input::ChaoticGoodFrame::kDataSized:
h.flags.Set(1, true);
h.message_length = frame.data_sized().length();
h.message_padding = frame.data_sized().padding();
case fuzzer_input::ChaoticGoodFrame::kPayloadOtherConnectionId:
h.payload_connection_id =
frame.payload_other_connection_id().connection_id();
h.payload_length = frame.payload_other_connection_id().length();
break;
}
switch (frame.trailers_case()) {
case fuzzer_input::ChaoticGoodFrame::kTrailersNone:
case fuzzer_input::ChaoticGoodFrame::TRAILERS_NOT_SET:
case fuzzer_input::ChaoticGoodFrame::kSettings:
proto_payload(frame.settings());
break;
case fuzzer_input::ChaoticGoodFrame::kTrailersRawBytes:
h.trailer_length = frame.trailers_raw_bytes().size();
h.flags.Set(2, true);
suffix.Append(Slice::FromCopiedString(frame.trailers_raw_bytes()));
case fuzzer_input::ChaoticGoodFrame::kClientMetadata:
proto_payload(frame.client_metadata());
break;
case fuzzer_input::ChaoticGoodFrame::kServerMetadata:
proto_payload(frame.server_metadata());
break;
case fuzzer_input::ChaoticGoodFrame::kTrailersSimpleHeader: {
SliceBuffer append =
SliceBufferFromSimpleHeaders(frame.trailers_simple_header());
h.trailer_length = append.Length();
h.flags.Set(2, true);
suffix.Append(append.JoinIntoSlice());
} break;
}
uint8_t bytes[24];
uint8_t bytes[chaotic_good::FrameHeader::kFrameHeaderSize];
h.Serialize(bytes);
SliceBuffer out;
out.Append(Slice::FromCopiedBuffer(bytes, 24));
out.Append(Slice::FromCopiedBuffer(
bytes, chaotic_good::FrameHeader::kFrameHeaderSize));
out.Append(suffix);
return out;
}

@ -121,7 +121,7 @@ class VerifyLogNoiseLogSink : public absl::LogSink {
// If we reach here means we have log noise. log_noise_absent_ will make the
// test fail.
log_noise_absent_ = false;
LOG(ERROR) << "Unwanted log at location : " << entry.source_filename()
LOG(ERROR) << "🛑 Unwanted log at location : " << entry.source_filename()
<< ":" << entry.source_line() << " " << entry.text_message();
}

@ -24,8 +24,9 @@ TEST(SwitchTest, JustDefault) {
TEST(SwitchTest, ThreeCases) {
auto test_switch = [](int d) {
return Switch(d, Case(1, [] { return 25; }), Case(2, [] { return 95; }),
Case(3, [] { return 68; }), Default([] { return 52; }));
return Switch(d, Case<int, 1>([] { return 25; }),
Case<int, 2>([] { return 95; }),
Case<int, 3>([] { return 68; }), Default([] { return 52; }));
};
EXPECT_EQ(test_switch(0)(), Poll<int>(52));
EXPECT_EQ(test_switch(1)(), Poll<int>(25));
@ -36,9 +37,10 @@ TEST(SwitchTest, ThreeCases) {
TEST(SwitchTest, Pending) {
auto test_switch = [](int d) {
return Switch(d, Case(42, []() -> Poll<int> { return Pending{}; }),
Case(1, [] { return 25; }), Case(2, [] { return 95; }),
Case(3, [] { return 68; }), Default([] { return 52; }));
return Switch(d, Case<int, 42>([]() -> Poll<int> { return Pending{}; }),
Case<int, 1>([] { return 25; }),
Case<int, 2>([] { return 95; }),
Case<int, 3>([] { return 68; }), Default([] { return 52; }));
};
EXPECT_EQ(test_switch(0)(), Poll<int>(52));
EXPECT_EQ(test_switch(1)(), Poll<int>(25));
@ -52,9 +54,9 @@ TEST(SwitchTest, ThreeCasesFromEnum) {
enum class X : uint8_t { A, B, C };
auto test_switch = [](X d) {
return Switch(d, Case(X::A, [] { return 25; }),
Case(X::B, [] { return 95; }), Case(X::C, [] { return 68; }),
Default([] { return 52; }));
return Switch(d, Case<X, X::A>([] { return 25; }),
Case<X, X::B>([] { return 95; }),
Case<X, X::C>([] { return 68; }), Default([] { return 52; }));
};
EXPECT_EQ(test_switch(X::A)(), Poll<int>(25));
EXPECT_EQ(test_switch(X::B)(), Poll<int>(95));

@ -41,13 +41,11 @@ class ChaoticGoodTraits {
auto client = MakeOrphanable<chaotic_good::ChaoticGoodClientTransport>(
PromiseEndpoint(std::move(control.client), SliceBuffer()),
PromiseEndpoint(std::move(data.client), SliceBuffer()), channel_args,
grpc_event_engine::experimental::GetDefaultEventEngine(), HPackParser(),
HPackCompressor());
grpc_event_engine::experimental::GetDefaultEventEngine());
auto server = MakeOrphanable<chaotic_good::ChaoticGoodServerTransport>(
channel_args, PromiseEndpoint(std::move(control.server), SliceBuffer()),
PromiseEndpoint(std::move(data.server), SliceBuffer()),
grpc_event_engine::experimental::GetDefaultEventEngine(), HPackParser(),
HPackCompressor());
grpc_event_engine::experimental::GetDefaultEventEngine());
return {std::move(client), std::move(server)};
}

@ -52,7 +52,10 @@ grpc_fuzzer(
name = "frame_header_fuzzer",
srcs = ["frame_header_fuzzer.cc"],
corpus = "frame_header_fuzzer_corpus",
external_deps = ["absl/status:statusor"],
external_deps = [
"absl/status:statusor",
"absl/strings",
],
language = "C++",
tags = ["no_windows"],
deps = [

@ -187,7 +187,7 @@ TEST_F(ClientTransportTest, AddOneStreamWithWriteFailed) {
auto transport = MakeOrphanable<ChaoticGoodClientTransport>(
std::move(control_endpoint.promise_endpoint),
std::move(data_endpoint.promise_endpoint), MakeChannelArgs(),
event_engine(), HPackParser(), HPackCompressor());
event_engine());
auto call = MakeCall(TestInitialMetadata());
transport->StartCall(call.handler.StartCall());
call.initiator.SpawnGuarded("test-send",
@ -231,7 +231,7 @@ TEST_F(ClientTransportTest, AddOneStreamWithReadFailed) {
auto transport = MakeOrphanable<ChaoticGoodClientTransport>(
std::move(control_endpoint.promise_endpoint),
std::move(data_endpoint.promise_endpoint), MakeChannelArgs(),
event_engine(), HPackParser(), HPackCompressor());
event_engine());
auto call = MakeCall(TestInitialMetadata());
transport->StartCall(call.handler.StartCall());
call.initiator.SpawnGuarded("test-send",
@ -283,7 +283,7 @@ TEST_F(ClientTransportTest, AddMultipleStreamWithWriteFailed) {
auto transport = MakeOrphanable<ChaoticGoodClientTransport>(
std::move(control_endpoint.promise_endpoint),
std::move(data_endpoint.promise_endpoint), MakeChannelArgs(),
event_engine(), HPackParser(), HPackCompressor());
event_engine());
auto call1 = MakeCall(TestInitialMetadata());
transport->StartCall(call1.handler.StartCall());
auto call2 = MakeCall(TestInitialMetadata());
@ -351,7 +351,7 @@ TEST_F(ClientTransportTest, AddMultipleStreamWithReadFailed) {
auto transport = MakeOrphanable<ChaoticGoodClientTransport>(
std::move(control_endpoint.promise_endpoint),
std::move(data_endpoint.promise_endpoint), MakeChannelArgs(),
event_engine(), HPackParser(), HPackCompressor());
event_engine());
auto call1 = MakeCall(TestInitialMetadata());
transport->StartCall(call1.handler.StartCall());
auto call2 = MakeCall(TestInitialMetadata());

@ -36,6 +36,7 @@
#include "absl/types/optional.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/core/ext/transport/chaotic_good/chaotic_good_frame.pb.h"
#include "src/core/lib/config/core_configuration.h"
#include "src/core/lib/promise/if.h"
#include "src/core/lib/promise/loop.h"
@ -56,16 +57,6 @@ namespace grpc_core {
namespace chaotic_good {
namespace testing {
// Encoded string of header ":path: /demo.Service/Step".
const uint8_t kPathDemoServiceStep[] = {
0x40, 0x05, 0x3a, 0x70, 0x61, 0x74, 0x68, 0x12, 0x2f,
0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x2f, 0x53, 0x74, 0x65, 0x70};
// Encoded string of trailer "grpc-status: 0".
const uint8_t kGrpcStatus0[] = {0x10, 0x0b, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x01, 0x30};
ClientMetadataHandle TestInitialMetadata() {
auto md = Arena::MakePooledForOverwrite<ClientMetadata>();
md->Set(HttpPathMetadata(), Slice::FromStaticString("/demo.Service/Step"));
@ -103,37 +94,46 @@ ChannelArgs MakeChannelArgs() {
TEST_F(TransportTest, AddOneStream) {
MockPromiseEndpoint control_endpoint(1000);
MockPromiseEndpoint data_endpoint(1001);
static const std::string many_as(1024 * 1024, 'a');
const auto server_initial_metadata =
EncodeProto<chaotic_good_frame::ServerMetadata>("message: 'hello'");
const auto server_trailing_metadata =
EncodeProto<chaotic_good_frame::ServerMetadata>("status: 0");
const auto client_initial_metadata =
EncodeProto<chaotic_good_frame::ClientMetadata>(
"path: '/demo.Service/Step'");
control_endpoint.ExpectRead(
{SerializedFrameHeader(FrameType::kFragment, 7, 1, 26, 8, 56, 15),
EventEngineSlice::FromCopiedBuffer(kPathDemoServiceStep,
sizeof(kPathDemoServiceStep)),
EventEngineSlice::FromCopiedBuffer(kGrpcStatus0, sizeof(kGrpcStatus0))},
{SerializedFrameHeader(FrameType::kServerInitialMetadata, 0, 1,
server_initial_metadata.length()),
server_initial_metadata.Copy(),
SerializedFrameHeader(FrameType::kMessage, 1, 1, many_as.length()),
SerializedFrameHeader(FrameType::kServerTrailingMetadata, 0, 1,
server_trailing_metadata.length()),
server_trailing_metadata.Copy()},
event_engine().get());
data_endpoint.ExpectRead(
{EventEngineSlice::FromCopiedString("12345678"), Zeros(56)}, nullptr);
{EventEngineSlice::FromCopiedString(many_as), Zeros(56)}, nullptr);
EXPECT_CALL(*control_endpoint.endpoint, Read)
.InSequence(control_endpoint.read_sequence)
.WillOnce(Return(false));
auto transport = MakeOrphanable<ChaoticGoodClientTransport>(
std::move(control_endpoint.promise_endpoint),
std::move(data_endpoint.promise_endpoint), MakeChannelArgs(),
event_engine(), HPackParser(), HPackCompressor());
event_engine());
auto call = MakeCall(TestInitialMetadata());
StrictMock<MockFunction<void()>> on_done;
EXPECT_CALL(on_done, Call());
control_endpoint.ExpectWrite(
{SerializedFrameHeader(FrameType::kFragment, 1, 1,
sizeof(kPathDemoServiceStep), 0, 0, 0),
EventEngineSlice::FromCopiedBuffer(kPathDemoServiceStep,
sizeof(kPathDemoServiceStep))},
{SerializedFrameHeader(FrameType::kClientInitialMetadata, 0, 1,
client_initial_metadata.length()),
client_initial_metadata.Copy()},
nullptr);
control_endpoint.ExpectWrite(
{SerializedFrameHeader(FrameType::kFragment, 2, 1, 0, 1, 63, 0)},
{SerializedFrameHeader(FrameType::kMessage, 0, 1, 1),
EventEngineSlice::FromCopiedString("0")},
nullptr);
data_endpoint.ExpectWrite(
{EventEngineSlice::FromCopiedString("0"), Zeros(63)}, nullptr);
control_endpoint.ExpectWrite(
{SerializedFrameHeader(FrameType::kFragment, 4, 1, 0, 0, 0, 0)}, nullptr);
{SerializedFrameHeader(FrameType::kClientEndOfStream, 0, 1, 0)}, nullptr);
transport->StartCall(call.handler.StartCall());
call.initiator.SpawnGuarded("test-send",
[initiator = call.initiator]() mutable {
@ -148,16 +148,16 @@ TEST_F(TransportTest, AddOneStream) {
EXPECT_TRUE(md.value().has_value());
EXPECT_EQ(md.value()
.value()
->get_pointer(HttpPathMetadata())
->get_pointer(GrpcMessageMetadata())
->as_string_view(),
"/demo.Service/Step");
"hello");
return Empty{};
},
[initiator]() mutable { return initiator.PullMessage(); },
[](ServerToClientNextMessage msg) {
EXPECT_TRUE(msg.ok());
EXPECT_TRUE(msg.has_value());
EXPECT_EQ(msg.value().payload()->JoinIntoString(), "12345678");
EXPECT_EQ(msg.value().payload()->JoinIntoString(), many_as);
return Empty{};
},
[initiator]() mutable { return initiator.PullMessage(); },
@ -183,47 +183,50 @@ TEST_F(TransportTest, AddOneStream) {
TEST_F(TransportTest, AddOneStreamMultipleMessages) {
MockPromiseEndpoint control_endpoint(1000);
MockPromiseEndpoint data_endpoint(1001);
const auto server_initial_metadata =
EncodeProto<chaotic_good_frame::ServerMetadata>("");
const auto server_trailing_metadata =
EncodeProto<chaotic_good_frame::ServerMetadata>("status: 0");
const auto client_initial_metadata =
EncodeProto<chaotic_good_frame::ClientMetadata>(
"path: '/demo.Service/Step'");
control_endpoint.ExpectRead(
{SerializedFrameHeader(FrameType::kFragment, 3, 1, 26, 8, 56, 0),
EventEngineSlice::FromCopiedBuffer(kPathDemoServiceStep,
sizeof(kPathDemoServiceStep))},
event_engine().get());
control_endpoint.ExpectRead(
{SerializedFrameHeader(FrameType::kFragment, 6, 1, 0, 8, 56, 15),
EventEngineSlice::FromCopiedBuffer(kGrpcStatus0, sizeof(kGrpcStatus0))},
{SerializedFrameHeader(FrameType::kServerInitialMetadata, 0, 1,
server_initial_metadata.length()),
server_initial_metadata.Copy(),
SerializedFrameHeader(FrameType::kMessage, 0, 1, 8),
EventEngineSlice::FromCopiedString("12345678"),
SerializedFrameHeader(FrameType::kMessage, 0, 1, 8),
EventEngineSlice::FromCopiedString("87654321"),
SerializedFrameHeader(FrameType::kServerTrailingMetadata, 0, 1,
server_trailing_metadata.length()),
server_trailing_metadata.Copy()},
event_engine().get());
data_endpoint.ExpectRead(
{EventEngineSlice::FromCopiedString("12345678"), Zeros(56)}, nullptr);
data_endpoint.ExpectRead(
{EventEngineSlice::FromCopiedString("87654321"), Zeros(56)}, nullptr);
EXPECT_CALL(*control_endpoint.endpoint, Read)
.InSequence(control_endpoint.read_sequence)
.WillOnce(Return(false));
auto transport = MakeOrphanable<ChaoticGoodClientTransport>(
std::move(control_endpoint.promise_endpoint),
std::move(data_endpoint.promise_endpoint), MakeChannelArgs(),
event_engine(), HPackParser(), HPackCompressor());
event_engine());
auto call = MakeCall(TestInitialMetadata());
StrictMock<MockFunction<void()>> on_done;
EXPECT_CALL(on_done, Call());
control_endpoint.ExpectWrite(
{SerializedFrameHeader(FrameType::kFragment, 1, 1,
sizeof(kPathDemoServiceStep), 0, 0, 0),
EventEngineSlice::FromCopiedBuffer(kPathDemoServiceStep,
sizeof(kPathDemoServiceStep))},
{SerializedFrameHeader(FrameType::kClientInitialMetadata, 0, 1,
client_initial_metadata.length()),
client_initial_metadata.Copy()},
nullptr);
control_endpoint.ExpectWrite(
{SerializedFrameHeader(FrameType::kFragment, 2, 1, 0, 1, 63, 0)},
{SerializedFrameHeader(FrameType::kMessage, 0, 1, 1),
EventEngineSlice::FromCopiedString("0")},
nullptr);
data_endpoint.ExpectWrite(
{EventEngineSlice::FromCopiedString("0"), Zeros(63)}, nullptr);
control_endpoint.ExpectWrite(
{SerializedFrameHeader(FrameType::kFragment, 2, 1, 0, 1, 63, 0)},
{SerializedFrameHeader(FrameType::kMessage, 0, 1, 1),
EventEngineSlice::FromCopiedString("1")},
nullptr);
data_endpoint.ExpectWrite(
{EventEngineSlice::FromCopiedString("1"), Zeros(63)}, nullptr);
control_endpoint.ExpectWrite(
{SerializedFrameHeader(FrameType::kFragment, 4, 1, 0, 0, 0, 0)}, nullptr);
{SerializedFrameHeader(FrameType::kClientEndOfStream, 0, 1, 0)}, nullptr);
transport->StartCall(call.handler.StartCall());
call.initiator.SpawnGuarded("test-send",
[initiator = call.initiator]() mutable {
@ -236,11 +239,6 @@ TEST_F(TransportTest, AddOneStreamMultipleMessages) {
[](ValueOrFailure<absl::optional<ServerMetadataHandle>> md) {
EXPECT_TRUE(md.ok());
EXPECT_TRUE(md.value().has_value());
EXPECT_EQ(md.value()
.value()
->get_pointer(HttpPathMetadata())
->as_string_view(),
"/demo.Service/Step");
return Empty{};
},
initiator.PullMessage(),

@ -25,8 +25,6 @@
#include "absl/status/statusor.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/resource_quota/memory_quota.h"
@ -48,82 +46,62 @@ struct DeterministicBitGen : public std::numeric_limits<uint64_t> {
uint64_t operator()() { return 42; }
};
FrameLimits FuzzerFrameLimits() { return FrameLimits{1024 * 1024 * 1024, 63}; }
template <typename T>
void AssertRoundTrips(const T& input, FrameType expected_frame_type) {
HPackCompressor hpack_compressor;
bool saw_encoding_errors = false;
auto serialized = input.Serialize(&hpack_compressor, saw_encoding_errors);
CHECK(serialized.control.Length() >=
24); // Initial output buffer size is 64 byte.
uint8_t header_bytes[24];
serialized.control.MoveFirstNBytesIntoBuffer(24, header_bytes);
auto header = FrameHeader::Parse(header_bytes);
if (!header.ok()) {
if (!squelch) {
LOG(ERROR) << "Failed to parse header: " << header.status().ToString();
}
Crash("Failed to parse header");
}
CHECK_EQ(header->type, expected_frame_type);
FrameHeader hdr = input.MakeHeader();
CHECK_EQ(hdr.type, expected_frame_type);
CHECK_EQ(hdr.payload_connection_id, 0);
SliceBuffer payload;
input.SerializePayload(payload);
CHECK_GE(hdr.payload_length, payload.Length());
T output;
HPackParser hpack_parser;
DeterministicBitGen bitgen;
auto deser = output.Deserialize(&hpack_parser, header.value(),
absl::BitGenRef(bitgen), GetContext<Arena>(),
std::move(serialized), FuzzerFrameLimits());
auto deser = output.Deserialize(hdr, std::move(payload));
CHECK_OK(deser);
if (!saw_encoding_errors) CHECK_EQ(input, output);
CHECK_EQ(input.ToString(), output.ToString());
}
template <typename T>
void FinishParseAndChecks(const FrameHeader& header, BufferPair buffers) {
void FinishParseAndChecks(const FrameHeader& header, SliceBuffer payload) {
T parsed;
ExecCtx exec_ctx; // Initialized to get this_cpu() info in global_stat().
HPackParser hpack_parser;
DeterministicBitGen bitgen;
auto deser = parsed.Deserialize(&hpack_parser, header,
absl::BitGenRef(bitgen), GetContext<Arena>(),
std::move(buffers), FuzzerFrameLimits());
auto deser = parsed.Deserialize(header, std::move(payload));
if (!deser.ok()) return;
LOG(INFO) << "Read frame: " << parsed.ToString();
AssertRoundTrips(parsed, header.type);
}
void Run(const frame_fuzzer::Test& test) {
const uint8_t* control_data =
reinterpret_cast<const uint8_t*>(test.control().data());
size_t control_size = test.control().size();
if (test.control().size() < 24) return;
auto r = FrameHeader::Parse(control_data);
if (test.header().size() != FrameHeader::kFrameHeaderSize) return;
auto r = FrameHeader::Parse(
reinterpret_cast<const uint8_t*>(test.header().data()));
if (!r.ok()) return;
if (test.data().size() != r->message_length) return;
LOG(INFO) << "Read frame header: " << r->ToString();
control_data += 24;
control_size -= 24;
if (test.payload().size() != r->payload_length) return;
auto arena = SimpleArenaAllocator()->MakeArena();
TestContext<Arena> ctx(arena.get());
BufferPair buffers{
SliceBuffer(Slice::FromCopiedBuffer(control_data, control_size)),
SliceBuffer(
Slice::FromCopiedBuffer(test.data().data(), test.data().size())),
};
SliceBuffer payload(
Slice::FromCopiedBuffer(test.payload().data(), test.payload().size()));
switch (r->type) {
default:
return; // We don't know how to parse this frame type.
case FrameType::kSettings:
FinishParseAndChecks<SettingsFrame>(*r, std::move(buffers));
FinishParseAndChecks<SettingsFrame>(*r, std::move(payload));
break;
case FrameType::kClientInitialMetadata:
FinishParseAndChecks<ClientInitialMetadataFrame>(*r, std::move(payload));
break;
case FrameType::kClientEndOfStream:
FinishParseAndChecks<ClientEndOfStream>(*r, std::move(payload));
break;
case FrameType::kServerInitialMetadata:
FinishParseAndChecks<ServerInitialMetadataFrame>(*r, std::move(payload));
break;
case FrameType::kServerTrailingMetadata:
FinishParseAndChecks<ServerTrailingMetadataFrame>(*r, std::move(payload));
break;
case FrameType::kFragment:
if (test.is_server()) {
FinishParseAndChecks<ServerFragmentFrame>(*r, std::move(buffers));
} else {
FinishParseAndChecks<ClientFragmentFrame>(*r, std::move(buffers));
}
case FrameType::kMessage:
FinishParseAndChecks<MessageFrame>(*r, std::move(payload));
break;
case FrameType::kCancel:
FinishParseAndChecks<CancelFrame>(*r, std::move(buffers));
FinishParseAndChecks<CancelFrame>(*r, std::move(payload));
break;
}
}

@ -17,7 +17,6 @@ syntax = "proto3";
package frame_fuzzer;
message Test {
bool is_server = 1;
bytes control = 2;
bytes data = 3;
bytes header = 1;
bytes payload = 2;
}

@ -17,17 +17,28 @@
#include <string.h>
#include "absl/status/statusor.h"
#include "absl/strings/escaping.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
bool squelch = false;
using grpc_core::chaotic_good::FrameHeader;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size != 24) return 0;
auto r = grpc_core::chaotic_good::FrameHeader::Parse(data);
if (size != FrameHeader::kFrameHeaderSize) return 0;
auto r = FrameHeader::Parse(data);
if (!r.ok()) return 0;
uint8_t reserialized[24];
uint8_t reserialized[FrameHeader::kFrameHeaderSize];
r->Serialize(reserialized);
// If it parses, we insist that the bytes reserialize to the same thing.
if (memcmp(data, reserialized, 24) != 0) abort();
if (memcmp(data, reserialized, FrameHeader::kFrameHeaderSize) != 0) {
auto esc = [](const void* s) {
return absl::CEscape(absl::string_view(static_cast<const char*>(s),
FrameHeader::kFrameHeaderSize));
};
fprintf(stderr, "Failed:\nin: %s\nout: %s\nser: %s\n", esc(data).c_str(),
esc(reserialized).c_str(), r->ToString().c_str());
abort();
}
return 0;
}

@ -25,71 +25,36 @@ namespace chaotic_good {
namespace {
std::vector<uint8_t> Serialize(FrameHeader h) {
uint8_t buffer[24];
uint8_t buffer[FrameHeader::kFrameHeaderSize];
h.Serialize(buffer);
return std::vector<uint8_t>(buffer, buffer + 24);
return std::vector<uint8_t>(buffer, buffer + FrameHeader::kFrameHeaderSize);
}
absl::StatusOr<FrameHeader> Deserialize(std::vector<uint8_t> data) {
if (data.size() != 24) return absl::InvalidArgumentError("bad length");
if (data.size() != FrameHeader::kFrameHeaderSize) {
return absl::InvalidArgumentError("bad length");
}
return FrameHeader::Parse(data.data());
}
TEST(FrameHeaderTest, SimpleSerialize) {
EXPECT_EQ(Serialize(FrameHeader{FrameType::kCancel, BitSet<3>::FromInt(0),
0x01020304, 0x05060708, 0x090a0b0c,
0x00000034, 0x0d0e0f10}),
std::vector<uint8_t>({
0x81, 0, 0, 0, // type, flags
0x04, 0x03, 0x02, 0x01, // stream_id
0x08, 0x07, 0x06, 0x05, // header_length
0x0c, 0x0b, 0x0a, 0x09, // message_length
0x34, 0x00, 0x00, 0x00, // message_padding
0x10, 0x0f, 0x0e, 0x0d // trailer_length
}));
EXPECT_EQ(
Serialize(FrameHeader{FrameType::kCancel, 1, 0x01020304, 0x05060708}),
std::vector<uint8_t>({
1, 0, 0xff, 0, // type, payload_connection_id
0x04, 0x03, 0x02, 0x01, // stream_id
0x08, 0x07, 0x06, 0x05, // payload_length
}));
}
TEST(FrameHeaderTest, SimpleDeserialize) {
EXPECT_EQ(Deserialize(std::vector<uint8_t>({
0x81, 0, 0, 0, // type, flags
1, 0, 0xff, 0, // type, payload_connection_id
0x04, 0x03, 0x02, 0x01, // stream_id
0x08, 0x07, 0x06, 0x05, // header_length
0x0c, 0x0b, 0x0a, 0x09, // message_length
0x34, 0x00, 0x00, 0x00, // message_padding
0x10, 0x0f, 0x0e, 0x0d // trailer_length
0x08, 0x07, 0x06, 0x05, // payload_length
})),
absl::StatusOr<FrameHeader>(FrameHeader{
FrameType::kCancel, BitSet<3>::FromInt(0), 0x01020304,
0x05060708, 0x090a0b0c, 0x00000034, 0x0d0e0f10}));
EXPECT_EQ(Deserialize(std::vector<uint8_t>({
0x81, 88, 88, 88, // type, flags
0x04, 0x03, 0x02, 0x01, // stream_id
0x08, 0x07, 0x06, 0x05, // header_length
0x0c, 0x0b, 0x0a, 0x09, // message_length
0x34, 0x00, 0x00, 0x00, // message_padding
0x10, 0x0f, 0x0e, 0x0d // trailer_length
}))
.status(),
absl::InvalidArgumentError("Invalid flags"));
}
TEST(FrameHeaderTest, GetFrameLength) {
EXPECT_EQ(
(FrameHeader{FrameType::kFragment, BitSet<3>::FromInt(5), 1, 0, 0, 0, 0})
.GetFrameLength(),
0);
EXPECT_EQ(
(FrameHeader{FrameType::kFragment, BitSet<3>::FromInt(5), 1, 14, 0, 0, 0})
.GetFrameLength(),
14);
EXPECT_EQ((FrameHeader{FrameType::kFragment, BitSet<3>::FromInt(5), 1, 0, 14,
50, 0})
.GetFrameLength(),
0);
EXPECT_EQ(
(FrameHeader{FrameType::kFragment, BitSet<3>::FromInt(5), 1, 0, 0, 0, 14})
.GetFrameLength(),
14);
absl::StatusOr<FrameHeader>(
FrameHeader{FrameType::kCancel, 1, 0x01020304, 0x05060708}));
}
} // namespace

@ -28,34 +28,20 @@ namespace grpc_core {
namespace chaotic_good {
namespace {
FrameLimits TestFrameLimits() { return FrameLimits{1024 * 1024 * 1024, 63}; }
template <typename T>
void AssertRoundTrips(const T& input, FrameType expected_frame_type) {
HPackCompressor hpack_compressor;
bool saw_encoding_errors = false;
auto serialized = input.Serialize(&hpack_compressor, saw_encoding_errors);
CHECK_GE(serialized.control.Length(),
24); // Initial output buffer size is 64 byte.
uint8_t header_bytes[24];
serialized.control.MoveFirstNBytesIntoBuffer(24, header_bytes);
auto header = FrameHeader::Parse(header_bytes);
if (!header.ok()) {
Crash("Failed to parse header");
}
CHECK_EQ(header->type, expected_frame_type);
const auto hdr = input.MakeHeader();
EXPECT_EQ(hdr.type, expected_frame_type);
// Frames should always set connection id 0, though the transport may adjust
// it.
EXPECT_EQ(hdr.payload_connection_id, 0);
SliceBuffer output_buffer;
input.SerializePayload(output_buffer);
EXPECT_EQ(hdr.payload_length, output_buffer.Length());
T output;
HPackParser hpack_parser;
absl::BitGen bitgen;
MemoryAllocator allocator = MakeResourceQuota("test-quota")
->memory_quota()
->CreateMemoryAllocator("test-allocator");
RefCountedPtr<Arena> arena = SimpleArenaAllocator()->MakeArena();
auto deser =
output.Deserialize(&hpack_parser, header.value(), absl::BitGenRef(bitgen),
arena.get(), std::move(serialized), TestFrameLimits());
auto deser = output.Deserialize(hdr, std::move(output_buffer));
CHECK_OK(deser);
if (!saw_encoding_errors) CHECK_EQ(output, input);
CHECK_EQ(output.ToString(), input.ToString());
}
TEST(FrameTest, SettingsFrameRoundTrips) {

@ -59,19 +59,9 @@ namespace grpc_core {
namespace chaotic_good {
namespace testing {
// Encoded string of header ":path: /demo.Service/Step".
const uint8_t kPathDemoServiceStep[] = {
0x40, 0x05, 0x3a, 0x70, 0x61, 0x74, 0x68, 0x12, 0x2f,
0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x2f, 0x53, 0x74, 0x65, 0x70};
// Encoded string of trailer "grpc-status: 0".
const uint8_t kGrpcStatus0[] = {0x40, 0x0b, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x01, 0x30};
ServerMetadataHandle TestInitialMetadata() {
auto md = Arena::MakePooledForOverwrite<ServerMetadata>();
md->Set(HttpPathMetadata(), Slice::FromStaticString("/demo.Service/Step"));
md->Set(GrpcMessageMetadata(), Slice::FromStaticString("hello"));
return md;
}
@ -99,24 +89,35 @@ TEST_F(TransportTest, ReadAndWriteOneMessage) {
.channel_args_preconditioning()
.PreconditionChannelArgs(nullptr),
std::move(control_endpoint.promise_endpoint),
std::move(data_endpoint.promise_endpoint), event_engine(), HPackParser(),
HPackCompressor());
std::move(data_endpoint.promise_endpoint), event_engine());
const auto server_initial_metadata =
EncodeProto<chaotic_good_frame::ServerMetadata>("message: 'hello'");
const auto server_trailing_metadata =
EncodeProto<chaotic_good_frame::ServerMetadata>("status: 0");
const auto client_initial_metadata =
EncodeProto<chaotic_good_frame::ClientMetadata>(
"path: '/demo.Service/Step'");
// Once we set the acceptor, expect to read some frames.
// We'll return a new request with a payload of "12345678".
control_endpoint.ExpectRead(
{SerializedFrameHeader(FrameType::kFragment, 7, 1, 26, 8, 56, 0),
EventEngineSlice::FromCopiedBuffer(kPathDemoServiceStep,
sizeof(kPathDemoServiceStep))},
{SerializedFrameHeader(FrameType::kClientInitialMetadata, 0, 1,
client_initial_metadata.length()),
client_initial_metadata.Copy(),
SerializedFrameHeader(FrameType::kMessage, 0, 1, 8),
EventEngineSlice::FromCopiedString("12345678"),
SerializedFrameHeader(FrameType::kClientEndOfStream, 0, 1, 0)},
event_engine().get());
data_endpoint.ExpectRead(
{EventEngineSlice::FromCopiedString("12345678"), Zeros(56)}, nullptr);
// Once that's read we'll create a new call
StrictMock<MockFunction<void()>> on_done;
auto control_address =
grpc_event_engine::experimental::URIToResolvedAddress("ipv4:1.2.3.4:5678")
.value();
EXPECT_CALL(*control_endpoint.endpoint, GetPeerAddress)
.WillRepeatedly([&control_address]() { return control_address; });
.WillRepeatedly(
[&control_address]() -> const grpc_event_engine::experimental::
EventEngine::ResolvedAddress& {
return control_address;
});
EXPECT_CALL(*call_destination, StartCall(_))
.WillOnce(WithArgs<0>([&on_done](
UnstartedCallHandler unstarted_call_handler) {
@ -169,20 +170,18 @@ TEST_F(TransportTest, ReadAndWriteOneMessage) {
.InSequence(control_endpoint.read_sequence)
.WillOnce(Return(false));
control_endpoint.ExpectWrite(
{SerializedFrameHeader(FrameType::kFragment, 1, 1,
sizeof(kPathDemoServiceStep), 0, 0, 0),
EventEngineSlice::FromCopiedBuffer(kPathDemoServiceStep,
sizeof(kPathDemoServiceStep))},
{SerializedFrameHeader(FrameType::kServerInitialMetadata, 0, 1,
server_initial_metadata.length()),
server_initial_metadata.Copy()},
nullptr);
control_endpoint.ExpectWrite(
{SerializedFrameHeader(FrameType::kFragment, 2, 1, 0, 8, 56, 0)},
{SerializedFrameHeader(FrameType::kMessage, 0, 1, 8),
EventEngineSlice::FromCopiedString("87654321")},
nullptr);
data_endpoint.ExpectWrite(
{EventEngineSlice::FromCopiedString("87654321"), Zeros(56)}, nullptr);
control_endpoint.ExpectWrite(
{SerializedFrameHeader(FrameType::kFragment, 4, 1, 0, 0, 0,
sizeof(kGrpcStatus0)),
EventEngineSlice::FromCopiedBuffer(kGrpcStatus0, sizeof(kGrpcStatus0))},
{SerializedFrameHeader(FrameType::kServerTrailingMetadata, 0, 1,
server_trailing_metadata.length()),
server_trailing_metadata.Copy()},
nullptr);
// Wait until ClientTransport's internal activities to finish.
event_engine()->TickUntilIdle();

@ -19,34 +19,24 @@ namespace chaotic_good {
namespace testing {
grpc_event_engine::experimental::Slice SerializedFrameHeader(
FrameType type, uint8_t flags, uint32_t stream_id, uint32_t header_length,
uint32_t message_length, uint32_t message_padding,
uint32_t trailer_length) {
uint8_t buffer[24] = {static_cast<uint8_t>(type),
flags,
0,
0,
static_cast<uint8_t>(stream_id),
static_cast<uint8_t>(stream_id >> 8),
static_cast<uint8_t>(stream_id >> 16),
static_cast<uint8_t>(stream_id >> 24),
static_cast<uint8_t>(header_length),
static_cast<uint8_t>(header_length >> 8),
static_cast<uint8_t>(header_length >> 16),
static_cast<uint8_t>(header_length >> 24),
static_cast<uint8_t>(message_length),
static_cast<uint8_t>(message_length >> 8),
static_cast<uint8_t>(message_length >> 16),
static_cast<uint8_t>(message_length >> 24),
static_cast<uint8_t>(message_padding),
static_cast<uint8_t>(message_padding >> 8),
static_cast<uint8_t>(message_padding >> 16),
static_cast<uint8_t>(message_padding >> 24),
static_cast<uint8_t>(trailer_length),
static_cast<uint8_t>(trailer_length >> 8),
static_cast<uint8_t>(trailer_length >> 16),
static_cast<uint8_t>(trailer_length >> 24)};
return grpc_event_engine::experimental::Slice::FromCopiedBuffer(buffer, 24);
FrameType type, uint16_t payload_connection_id, uint32_t stream_id,
uint32_t payload_length) {
uint8_t buffer[FrameHeader::kFrameHeaderSize] = {
static_cast<uint8_t>(payload_connection_id),
static_cast<uint8_t>(payload_connection_id >> 16),
static_cast<uint8_t>(type),
0,
static_cast<uint8_t>(stream_id),
static_cast<uint8_t>(stream_id >> 8),
static_cast<uint8_t>(stream_id >> 16),
static_cast<uint8_t>(stream_id >> 24),
static_cast<uint8_t>(payload_length),
static_cast<uint8_t>(payload_length >> 8),
static_cast<uint8_t>(payload_length >> 16),
static_cast<uint8_t>(payload_length >> 24),
};
return grpc_event_engine::experimental::Slice::FromCopiedBuffer(
buffer, sizeof(buffer));
}
grpc_event_engine::experimental::Slice Zeros(uint32_t length) {

@ -15,6 +15,8 @@
#ifndef GRPC_TEST_CORE_TRANSPORT_CHAOTIC_GOOD_TRANSPORT_TEST_H
#define GRPC_TEST_CORE_TRANSPORT_CHAOTIC_GOOD_TRANSPORT_TEST_H
#include <google/protobuf/text_format.h>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
@ -22,6 +24,8 @@
#include "src/core/lib/iomgr/timer_manager.h"
#include "src/core/lib/resource_quota/memory_quota.h"
#include "src/core/lib/resource_quota/resource_quota.h"
#include "src/core/lib/transport/call_arena_allocator.h"
#include "src/core/lib/transport/call_spine.h"
#include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h"
#include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h"
@ -71,11 +75,20 @@ class TransportTest : public ::testing::Test {
};
grpc_event_engine::experimental::Slice SerializedFrameHeader(
FrameType type, uint8_t flags, uint32_t stream_id, uint32_t header_length,
uint32_t message_length, uint32_t message_padding, uint32_t trailer_length);
FrameType type, uint16_t payload_connection_id, uint32_t stream_id,
uint32_t payload_length);
grpc_event_engine::experimental::Slice Zeros(uint32_t length);
template <typename T>
grpc_event_engine::experimental::Slice EncodeProto(const std::string& fields) {
T msg;
CHECK(google::protobuf::TextFormat::ParseFromString(fields, &msg));
std::string out;
CHECK(msg.SerializeToString(&out));
return grpc_event_engine::experimental::Slice::FromCopiedString(out);
}
} // namespace testing
} // namespace chaotic_good
} // namespace grpc_core

@ -104,13 +104,11 @@ TRANSPORT_FIXTURE(ChaoticGood) {
auto client_transport =
MakeOrphanable<chaotic_good::ChaoticGoodClientTransport>(
std::move(control_endpoints.client), std::move(data_endpoints.client),
ChannelArgs().SetObject(resource_quota), event_engine, HPackParser(),
HPackCompressor());
ChannelArgs().SetObject(resource_quota), event_engine);
auto server_transport =
MakeOrphanable<chaotic_good::ChaoticGoodServerTransport>(
channel_args, std::move(control_endpoints.server),
std::move(data_endpoints.server), event_engine, HPackParser(),
HPackCompressor());
std::move(data_endpoints.server), event_engine);
return ClientAndServerTransportPair{std::move(client_transport),
std::move(server_transport)};
}

@ -458,6 +458,7 @@ for dirname in [
"platform": lambda name, **kwargs: None,
"grpc_clang_cl_settings": lambda **kwargs: None,
"grpc_benchmark_args": lambda **kwargs: [],
"LARGE_MACHINE": 1,
},
{},
)

Loading…
Cancel
Save