mirror of https://github.com/grpc/grpc.git
[chaotic-good] Basic frame serialization/deserialization (#31257)
* [chaotic-good] initial sketch of frame serialization * Automated change: Fix sanity tests * tinkering * fix up * add tests,fuzzers * more tests * fix * fuzzers * Automated change: Fix sanity tests * fix-build * Automated change: Fix sanity tests * fix inf loop * fix refcounting bug * fixdeps * fix * fix continuations * iwyu * fix build * fix * build fixes * better split * Automated change: Fix sanity tests * Automated change: Fix sanity tests * fixes * iwyu * Automated change: Fix sanity tests Co-authored-by: ctiller <ctiller@users.noreply.github.com>pull/31629/head
parent
1914a39875
commit
8185a56322
57 changed files with 2847 additions and 349 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,307 @@ |
||||
// Copyright 2022 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 <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/transport/chaotic_good/frame.h" |
||||
|
||||
#include <string.h> |
||||
|
||||
#include <limits> |
||||
#include <utility> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
|
||||
#include <grpc/slice.h> |
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/gprpp/bitset.h" |
||||
#include "src/core/lib/gprpp/no_destruct.h" |
||||
#include "src/core/lib/gprpp/status_helper.h" |
||||
#include "src/core/lib/promise/context.h" |
||||
#include "src/core/lib/slice/slice.h" |
||||
#include "src/core/lib/slice/slice_buffer.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace chaotic_good { |
||||
|
||||
namespace { |
||||
const NoDestruct<Slice> kZeroSlice{[] { |
||||
auto slice = GRPC_SLICE_MALLOC(64); |
||||
memset(GRPC_SLICE_START_PTR(slice), 0, 64); |
||||
return slice; |
||||
}()}; |
||||
|
||||
class FrameSerializer { |
||||
public: |
||||
explicit FrameSerializer(FrameType type, uint32_t stream_id) |
||||
: header_{type, {}, stream_id, 0, 0, 0} { |
||||
output_.AppendIndexed(kZeroSlice->Copy()); |
||||
} |
||||
// If called, must be called before AddMessage, AddTrailers, Finish
|
||||
SliceBuffer& AddHeaders() { |
||||
GPR_ASSERT(last_added_ == nullptr); |
||||
header_.flags.set(0); |
||||
return Start(&header_.header_length); |
||||
} |
||||
// If called, must be called before AddTrailers, Finish
|
||||
SliceBuffer& AddMessage() { |
||||
MaybeCommitLast(); |
||||
header_.flags.set(1); |
||||
return Start(&header_.message_length); |
||||
} |
||||
// If called, must be called before Finish
|
||||
SliceBuffer& AddTrailers() { |
||||
MaybeCommitLast(); |
||||
header_.flags.set(2); |
||||
return Start(&header_.trailer_length); |
||||
} |
||||
|
||||
SliceBuffer Finish() { |
||||
MaybeCommitLast(); |
||||
header_.Serialize( |
||||
GRPC_SLICE_START_PTR(output_.c_slice_buffer()->slices[0])); |
||||
return std::move(output_); |
||||
} |
||||
|
||||
private: |
||||
SliceBuffer& Start(uint32_t* length_field) { |
||||
last_added_ = length_field; |
||||
length_at_last_added_ = output_.Length(); |
||||
return output_; |
||||
} |
||||
|
||||
void MaybeCommitLast() { |
||||
if (last_added_ == nullptr) return; |
||||
*last_added_ = output_.Length() - length_at_last_added_; |
||||
if (output_.Length() % 64 != 0) { |
||||
output_.Append(kZeroSlice->RefSubSlice(0, 64 - output_.Length() % 64)); |
||||
} |
||||
} |
||||
|
||||
FrameHeader header_; |
||||
|
||||
uint32_t* last_added_ = nullptr; |
||||
size_t length_at_last_added_; |
||||
SliceBuffer output_; |
||||
}; |
||||
|
||||
class FrameDeserializer { |
||||
public: |
||||
FrameDeserializer(const FrameHeader& header, SliceBuffer& input) |
||||
: header_(header), input_(input) {} |
||||
const FrameHeader& header() const { return header_; } |
||||
// If called, must be called before ReceiveMessage, ReceiveTrailers
|
||||
absl::StatusOr<SliceBuffer> ReceiveHeaders() { |
||||
return Take(header_.header_length); |
||||
} |
||||
// If called, must be called before ReceiveTrailers
|
||||
absl::StatusOr<SliceBuffer> ReceiveMessage() { |
||||
return Take(header_.message_length); |
||||
} |
||||
// If called, must be called before Finish
|
||||
absl::StatusOr<SliceBuffer> ReceiveTrailers() { |
||||
return Take(header_.trailer_length); |
||||
} |
||||
|
||||
absl::Status Finish() { return absl::OkStatus(); } |
||||
|
||||
private: |
||||
absl::StatusOr<SliceBuffer> Take(uint32_t length) { |
||||
if (length == 0) return SliceBuffer{}; |
||||
if (input_.Length() < length) { |
||||
return absl::InvalidArgumentError( |
||||
"Frame too short (insufficient payload)"); |
||||
} |
||||
SliceBuffer out; |
||||
input_.MoveFirstNBytesIntoSliceBuffer(length, out); |
||||
if (length % 64 != 0) { |
||||
const uint32_t padding_length = 64 - length % 64; |
||||
if (input_.Length() < padding_length) { |
||||
return absl::InvalidArgumentError( |
||||
"Frame too short (insufficient padding)"); |
||||
} |
||||
uint8_t padding[64]; |
||||
input_.MoveFirstNBytesIntoBuffer(padding_length, padding); |
||||
for (uint32_t i = 0; i < padding_length; i++) { |
||||
if (padding[i] != 0) { |
||||
return absl::InvalidArgumentError("Frame padding not zero"); |
||||
} |
||||
} |
||||
} |
||||
return std::move(out); |
||||
} |
||||
FrameHeader header_; |
||||
SliceBuffer& input_; |
||||
}; |
||||
|
||||
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) { |
||||
if (!maybe_slices.ok()) return maybe_slices.status(); |
||||
auto& slices = *maybe_slices; |
||||
Arena::PoolPtr<Metadata> metadata; |
||||
parser->BeginFrame( |
||||
metadata.get(), 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)); |
||||
} |
||||
parser->FinishFrame(); |
||||
return std::move(metadata); |
||||
} |
||||
} // namespace
|
||||
|
||||
absl::Status SettingsFrame::Deserialize(HPackParser*, const FrameHeader& header, |
||||
SliceBuffer& slice_buffer) { |
||||
if (header.type != FrameType::kSettings) { |
||||
return absl::InvalidArgumentError("Expected settings frame"); |
||||
} |
||||
if (header.flags.any()) { |
||||
return absl::InvalidArgumentError("Unexpected flags"); |
||||
} |
||||
FrameDeserializer deserializer(header, slice_buffer); |
||||
return deserializer.Finish(); |
||||
} |
||||
|
||||
SliceBuffer SettingsFrame::Serialize(HPackCompressor*) const { |
||||
FrameSerializer serializer(FrameType::kSettings, 0); |
||||
return serializer.Finish(); |
||||
} |
||||
|
||||
absl::Status ClientFragmentFrame::Deserialize(HPackParser* parser, |
||||
const FrameHeader& header, |
||||
SliceBuffer& slice_buffer) { |
||||
if (header.stream_id == 0) { |
||||
return absl::InvalidArgumentError("Expected non-zero stream id"); |
||||
} |
||||
stream_id = header.stream_id; |
||||
if (header.type != FrameType::kFragment) { |
||||
return absl::InvalidArgumentError("Expected fragment frame"); |
||||
} |
||||
FrameDeserializer deserializer(header, slice_buffer); |
||||
if (header.flags.is_set(0)) { |
||||
auto r = ReadMetadata<ClientMetadata>(parser, deserializer.ReceiveHeaders(), |
||||
header.stream_id, true, true); |
||||
if (!r.ok()) return r.status(); |
||||
} |
||||
if (header.flags.is_set(1)) { |
||||
message = GetContext<Arena>()->MakePooled<Message>(); |
||||
auto r = deserializer.ReceiveMessage(); |
||||
if (!r.ok()) return r.status(); |
||||
r->Swap(message->payload()); |
||||
} |
||||
if (header.flags.is_set(2)) { |
||||
if (header.trailer_length != 0) { |
||||
return absl::InvalidArgumentError("Unexpected trailer length"); |
||||
} |
||||
end_of_stream = true; |
||||
} else { |
||||
end_of_stream = false; |
||||
} |
||||
return deserializer.Finish(); |
||||
} |
||||
|
||||
SliceBuffer ClientFragmentFrame::Serialize(HPackCompressor* encoder) const { |
||||
GPR_ASSERT(stream_id != 0); |
||||
FrameSerializer serializer(FrameType::kFragment, stream_id); |
||||
if (headers.get() != nullptr) { |
||||
encoder->EncodeRawHeaders(*headers.get(), serializer.AddHeaders()); |
||||
} |
||||
if (message.get() != nullptr) { |
||||
serializer.AddMessage().Append(*message->payload()); |
||||
} |
||||
if (end_of_stream) { |
||||
serializer.AddTrailers(); |
||||
} |
||||
return serializer.Finish(); |
||||
} |
||||
|
||||
absl::Status ServerFragmentFrame::Deserialize(HPackParser* parser, |
||||
const FrameHeader& header, |
||||
SliceBuffer& slice_buffer) { |
||||
if (header.stream_id == 0) { |
||||
return absl::InvalidArgumentError("Expected non-zero stream id"); |
||||
} |
||||
stream_id = header.stream_id; |
||||
if (header.type != FrameType::kFragment) { |
||||
return absl::InvalidArgumentError("Expected fragment frame"); |
||||
} |
||||
FrameDeserializer deserializer(header, slice_buffer); |
||||
if (header.flags.is_set(0)) { |
||||
auto r = ReadMetadata<ServerMetadata>(parser, deserializer.ReceiveHeaders(), |
||||
header.stream_id, true, false); |
||||
if (!r.ok()) return r.status(); |
||||
} |
||||
if (header.flags.is_set(1)) { |
||||
message = GetContext<Arena>()->MakePooled<Message>(); |
||||
auto r = deserializer.ReceiveMessage(); |
||||
if (!r.ok()) return r.status(); |
||||
r->Swap(message->payload()); |
||||
} |
||||
if (header.flags.is_set(2)) { |
||||
auto r = ReadMetadata<ServerMetadata>( |
||||
parser, deserializer.ReceiveTrailers(), header.stream_id, false, false); |
||||
} |
||||
return deserializer.Finish(); |
||||
} |
||||
|
||||
SliceBuffer ServerFragmentFrame::Serialize(HPackCompressor* encoder) const { |
||||
GPR_ASSERT(stream_id != 0); |
||||
FrameSerializer serializer(FrameType::kFragment, stream_id); |
||||
if (headers.get() != nullptr) { |
||||
encoder->EncodeRawHeaders(*headers.get(), serializer.AddHeaders()); |
||||
} |
||||
if (message.get() != nullptr) { |
||||
serializer.AddMessage().Append(*message->payload()); |
||||
} |
||||
if (trailers.get() != nullptr) { |
||||
encoder->EncodeRawHeaders(*trailers.get(), serializer.AddTrailers()); |
||||
} |
||||
return serializer.Finish(); |
||||
} |
||||
|
||||
absl::Status CancelFrame::Deserialize(HPackParser*, const FrameHeader& header, |
||||
SliceBuffer& slice_buffer) { |
||||
if (header.type != FrameType::kCancel) { |
||||
return absl::InvalidArgumentError("Expected cancel frame"); |
||||
} |
||||
if (header.flags.any()) { |
||||
return absl::InvalidArgumentError("Unexpected flags"); |
||||
} |
||||
if (header.stream_id == 0) { |
||||
return absl::InvalidArgumentError("Expected non-zero stream id"); |
||||
} |
||||
FrameDeserializer deserializer(header, slice_buffer); |
||||
stream_id = header.stream_id; |
||||
return deserializer.Finish(); |
||||
} |
||||
|
||||
SliceBuffer CancelFrame::Serialize(HPackCompressor*) const { |
||||
GPR_ASSERT(stream_id != 0); |
||||
FrameSerializer serializer(FrameType::kCancel, stream_id); |
||||
return serializer.Finish(); |
||||
} |
||||
|
||||
} // namespace chaotic_good
|
||||
} // namespace grpc_core
|
@ -0,0 +1,122 @@ |
||||
// Copyright 2022 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_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_FRAME_H |
||||
#define GRPC_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_FRAME_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <cstdint> |
||||
#include <memory> |
||||
#include <string> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/types/variant.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" |
||||
|
||||
namespace grpc_core { |
||||
namespace chaotic_good { |
||||
|
||||
class FrameInterface { |
||||
public: |
||||
virtual absl::Status Deserialize(HPackParser* parser, |
||||
const FrameHeader& header, |
||||
SliceBuffer& slice_buffer) = 0; |
||||
virtual SliceBuffer Serialize(HPackCompressor* encoder) const = 0; |
||||
|
||||
protected: |
||||
static bool EqVal(const Message& a, const Message& b) { |
||||
return a.payload()->JoinIntoString() == b.payload()->JoinIntoString() && |
||||
a.flags() == b.flags(); |
||||
} |
||||
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; |
||||
}; |
||||
|
||||
struct SettingsFrame final : public FrameInterface { |
||||
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header, |
||||
SliceBuffer& slice_buffer) override; |
||||
SliceBuffer Serialize(HPackCompressor* encoder) const override; |
||||
|
||||
bool operator==(const SettingsFrame&) const { return true; } |
||||
}; |
||||
|
||||
struct ClientFragmentFrame final : public FrameInterface { |
||||
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header, |
||||
SliceBuffer& slice_buffer) override; |
||||
SliceBuffer Serialize(HPackCompressor* encoder) const override; |
||||
|
||||
uint32_t stream_id; |
||||
ClientMetadataHandle headers; |
||||
MessageHandle message; |
||||
bool end_of_stream = false; |
||||
|
||||
bool operator==(const ClientFragmentFrame& other) const { |
||||
return stream_id == other.stream_id && EqHdl(headers, other.headers) && |
||||
EqHdl(message, other.message) && |
||||
end_of_stream == other.end_of_stream; |
||||
} |
||||
}; |
||||
|
||||
struct ServerFragmentFrame final : public FrameInterface { |
||||
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header, |
||||
SliceBuffer& slice_buffer) override; |
||||
SliceBuffer Serialize(HPackCompressor* encoder) const override; |
||||
|
||||
uint32_t stream_id; |
||||
ServerMetadataHandle headers; |
||||
MessageHandle message; |
||||
ServerMetadataHandle trailers; |
||||
|
||||
bool operator==(const ServerFragmentFrame& other) const { |
||||
return stream_id == other.stream_id && EqHdl(headers, other.headers) && |
||||
EqHdl(message, other.message) && EqHdl(trailers, other.trailers); |
||||
} |
||||
}; |
||||
|
||||
struct CancelFrame final : public FrameInterface { |
||||
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header, |
||||
SliceBuffer& slice_buffer) override; |
||||
SliceBuffer Serialize(HPackCompressor* encoder) 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>; |
||||
|
||||
} // namespace chaotic_good
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_FRAME_H
|
@ -0,0 +1,87 @@ |
||||
// Copyright 2022 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 <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/transport/chaotic_good/frame_header.h" |
||||
|
||||
#include <string.h> |
||||
|
||||
#include <cstdint> |
||||
|
||||
#include "absl/status/status.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace chaotic_good { |
||||
|
||||
namespace { |
||||
void WriteLittleEndianUint32(uint32_t value, uint8_t* data) { |
||||
data[0] = static_cast<uint8_t>(value); |
||||
data[1] = static_cast<uint8_t>(value >> 8); |
||||
data[2] = static_cast<uint8_t>(value >> 16); |
||||
data[3] = static_cast<uint8_t>(value >> 24); |
||||
} |
||||
|
||||
uint32_t ReadLittleEndianUint32(const uint8_t* data) { |
||||
return static_cast<uint32_t>(data[0]) | |
||||
(static_cast<uint32_t>(data[1]) << 8) | |
||||
(static_cast<uint32_t>(data[2]) << 16) | |
||||
(static_cast<uint32_t>(data[3]) << 24); |
||||
} |
||||
} // namespace
|
||||
|
||||
void FrameHeader::Serialize(uint8_t* data) const { |
||||
WriteLittleEndianUint32( |
||||
static_cast<uint32_t>(type) | (flags.ToInt<uint32_t>() << 8), data); |
||||
WriteLittleEndianUint32(stream_id, data + 4); |
||||
WriteLittleEndianUint32(header_length, data + 8); |
||||
WriteLittleEndianUint32(message_length, data + 12); |
||||
WriteLittleEndianUint32(trailer_length, data + 16); |
||||
memset(data + 20, 0, 44); |
||||
} |
||||
|
||||
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); |
||||
header.stream_id = ReadLittleEndianUint32(data + 4); |
||||
header.header_length = ReadLittleEndianUint32(data + 8); |
||||
header.message_length = ReadLittleEndianUint32(data + 12); |
||||
header.trailer_length = ReadLittleEndianUint32(data + 16); |
||||
for (int i = 0; i < 44; i++) { |
||||
if (data[20 + i] != 0) return absl::InvalidArgumentError("Invalid padding"); |
||||
} |
||||
return header; |
||||
} |
||||
|
||||
namespace { |
||||
uint64_t RoundUp(uint64_t x) { |
||||
if (x % 64 == 0) return x; |
||||
return x + 64 - (x % 64); |
||||
} |
||||
} // namespace
|
||||
|
||||
FrameSizes FrameHeader::ComputeFrameSizes() const { |
||||
FrameSizes sizes; |
||||
sizes.message_offset = RoundUp(header_length); |
||||
sizes.trailer_offset = sizes.message_offset + RoundUp(message_length); |
||||
sizes.frame_length = sizes.trailer_offset + RoundUp(trailer_length); |
||||
return sizes; |
||||
} |
||||
|
||||
} // namespace chaotic_good
|
||||
} // namespace grpc_core
|
@ -0,0 +1,73 @@ |
||||
// Copyright 2022 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_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_FRAME_HEADER_H |
||||
#define GRPC_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_FRAME_HEADER_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <cstdint> |
||||
|
||||
#include "absl/status/statusor.h" |
||||
|
||||
#include "src/core/lib/gprpp/bitset.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace chaotic_good { |
||||
|
||||
enum class FrameType : uint8_t { |
||||
kSettings = 0x00, |
||||
kFragment = 0x80, |
||||
kCancel = 0x81, |
||||
}; |
||||
|
||||
struct FrameSizes { |
||||
uint64_t message_offset; |
||||
uint64_t trailer_offset; |
||||
uint64_t frame_length; |
||||
|
||||
bool operator==(const FrameSizes& other) const { |
||||
return message_offset == other.message_offset && |
||||
trailer_offset == other.trailer_offset && |
||||
frame_length == other.frame_length; |
||||
} |
||||
}; |
||||
|
||||
struct FrameHeader { |
||||
FrameType type; |
||||
BitSet<3> flags; |
||||
uint32_t stream_id; |
||||
uint32_t header_length; |
||||
uint32_t message_length; |
||||
uint32_t trailer_length; |
||||
|
||||
// Parses a frame header from a buffer of 64 bytes. All 64 bytes are consumed.
|
||||
static absl::StatusOr<FrameHeader> Parse(const uint8_t* data); |
||||
// Serializes a frame header into a buffer of 64 bytes.
|
||||
void Serialize(uint8_t* data) const; |
||||
// Compute frame sizes from the header.
|
||||
FrameSizes ComputeFrameSizes() const; |
||||
|
||||
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 && |
||||
trailer_length == h.trailer_length; |
||||
} |
||||
}; |
||||
|
||||
} // namespace chaotic_good
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_FRAME_HEADER_H
|
@ -0,0 +1,19 @@ |
||||
// Copyright 2022 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 <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/transport/chttp2/transport/http_trace.h" |
||||
|
||||
grpc_core::TraceFlag grpc_http_trace(false, "http"); |
@ -0,0 +1,24 @@ |
||||
// Copyright 2022 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_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HTTP_TRACE_H |
||||
#define GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HTTP_TRACE_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/debug/trace.h" |
||||
|
||||
extern grpc_core::TraceFlag grpc_http_trace; |
||||
|
||||
#endif // GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HTTP_TRACE_H
|
@ -0,0 +1,78 @@ |
||||
# Copyright 2021 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. |
||||
|
||||
load("//bazel:grpc_build_system.bzl", "grpc_cc_test", "grpc_package") |
||||
load("//test/core/util:grpc_fuzzer.bzl", "grpc_fuzzer") |
||||
|
||||
licenses(["notice"]) |
||||
|
||||
grpc_package( |
||||
name = "test/core/transport/chaotic_good", |
||||
visibility = "tests", |
||||
) |
||||
|
||||
grpc_cc_test( |
||||
name = "frame_header_test", |
||||
srcs = ["frame_header_test.cc"], |
||||
external_deps = [ |
||||
"absl/status", |
||||
"gtest", |
||||
], |
||||
deps = ["//src/core:chaotic_good_frame_header"], |
||||
) |
||||
|
||||
grpc_fuzzer( |
||||
name = "frame_header_fuzzer", |
||||
srcs = ["frame_header_fuzzer.cc"], |
||||
corpus = "frame_header_fuzzer_corpus", |
||||
external_deps = ["absl/status:statusor"], |
||||
language = "C++", |
||||
tags = ["no_windows"], |
||||
deps = ["//src/core:chaotic_good_frame_header"], |
||||
) |
||||
|
||||
grpc_cc_test( |
||||
name = "frame_test", |
||||
srcs = ["frame_test.cc"], |
||||
external_deps = [ |
||||
"absl/status", |
||||
"absl/status:statusor", |
||||
"gtest", |
||||
], |
||||
deps = ["//src/core:chaotic_good_frame"], |
||||
) |
||||
|
||||
grpc_fuzzer( |
||||
name = "frame_fuzzer", |
||||
srcs = ["frame_fuzzer.cc"], |
||||
corpus = "frame_fuzzer_corpus", |
||||
external_deps = ["absl/status:statusor"], |
||||
language = "C++", |
||||
tags = ["no_windows"], |
||||
deps = [ |
||||
"//:gpr", |
||||
"//:hpack_encoder", |
||||
"//:hpack_parser", |
||||
"//:ref_counted_ptr", |
||||
"//src/core:arena", |
||||
"//src/core:chaotic_good_frame", |
||||
"//src/core:chaotic_good_frame_header", |
||||
"//src/core:event_engine_memory_allocator", |
||||
"//src/core:memory_quota", |
||||
"//src/core:resource_quota", |
||||
"//src/core:slice", |
||||
"//src/core:slice_buffer", |
||||
"//test/core/promise:test_context", |
||||
], |
||||
) |
@ -0,0 +1,112 @@ |
||||
// Copyright 2022 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 <stddef.h> |
||||
#include <stdint.h> |
||||
|
||||
#include <memory> |
||||
|
||||
#include "absl/status/statusor.h" |
||||
|
||||
#include <grpc/event_engine/memory_allocator.h> |
||||
#include <grpc/support/log.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/gprpp/ref_counted_ptr.h" |
||||
#include "src/core/lib/resource_quota/arena.h" |
||||
#include "src/core/lib/resource_quota/memory_quota.h" |
||||
#include "src/core/lib/resource_quota/resource_quota.h" |
||||
#include "src/core/lib/slice/slice.h" |
||||
#include "src/core/lib/slice/slice_buffer.h" |
||||
#include "test/core/promise/test_context.h" |
||||
|
||||
bool squelch = false; |
||||
|
||||
namespace grpc_core { |
||||
namespace chaotic_good { |
||||
|
||||
static auto* g_memory_allocator = new MemoryAllocator( |
||||
ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator("test")); |
||||
|
||||
template <typename T> |
||||
void AssertRoundTrips(const T& input, FrameType expected_frame_type) { |
||||
HPackCompressor hpack_compressor; |
||||
auto serialized = input.Serialize(&hpack_compressor); |
||||
GPR_ASSERT(serialized.Length() >= 64); |
||||
GPR_ASSERT(serialized.Length() % 64 == 0); |
||||
uint8_t header_bytes[64]; |
||||
serialized.MoveFirstNBytesIntoBuffer(64, header_bytes); |
||||
auto header = FrameHeader::Parse(header_bytes); |
||||
GPR_ASSERT(header.ok()); |
||||
GPR_ASSERT(header->type == expected_frame_type); |
||||
T output; |
||||
HPackParser hpack_parser; |
||||
auto deser = output.Deserialize(&hpack_parser, header.value(), serialized); |
||||
GPR_ASSERT(deser.ok()); |
||||
GPR_ASSERT(output == input); |
||||
} |
||||
|
||||
template <typename T> |
||||
void FinishParseAndChecks(const FrameHeader& header, const uint8_t* data, |
||||
size_t size) { |
||||
T parsed; |
||||
HPackParser hpack_parser; |
||||
SliceBuffer serialized; |
||||
serialized.Append(Slice::FromCopiedBuffer(data, size)); |
||||
auto deser = parsed.Deserialize(&hpack_parser, header, serialized); |
||||
if (!deser.ok()) return; |
||||
AssertRoundTrips(parsed, header.type); |
||||
} |
||||
|
||||
int Run(const uint8_t* data, size_t size) { |
||||
if (size < 1) return 0; |
||||
const bool is_server = (data[0] & 1) != 0; |
||||
size--; |
||||
data++; |
||||
if (size < 64) return 0; |
||||
auto r = FrameHeader::Parse(data); |
||||
if (!r.ok()) return 0; |
||||
size -= 64; |
||||
data += 64; |
||||
auto arena = MakeScopedArena(1024, g_memory_allocator); |
||||
TestContext<Arena> ctx(arena.get()); |
||||
switch (r->type) { |
||||
default: |
||||
return 0; // We don't know how to parse this frame type.
|
||||
case FrameType::kSettings: |
||||
FinishParseAndChecks<SettingsFrame>(*r, data, size); |
||||
break; |
||||
case FrameType::kFragment: |
||||
if (is_server) { |
||||
FinishParseAndChecks<ServerFragmentFrame>(*r, data, size); |
||||
} else { |
||||
FinishParseAndChecks<ClientFragmentFrame>(*r, data, size); |
||||
} |
||||
break; |
||||
case FrameType::kCancel: |
||||
FinishParseAndChecks<CancelFrame>(*r, data, size); |
||||
break; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
} // namespace chaotic_good
|
||||
} // namespace grpc_core
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
||||
return grpc_core::chaotic_good::Run(data, size); |
||||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,34 @@ |
||||
// Copyright 2022 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 <stdint.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
|
||||
#include "absl/status/statusor.h" |
||||
|
||||
#include "src/core/ext/transport/chaotic_good/frame_header.h" |
||||
|
||||
bool squelch = false; |
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
||||
if (size != 64) return 0; |
||||
auto r = grpc_core::chaotic_good::FrameHeader::Parse(data); |
||||
if (!r.ok()) return 0; |
||||
uint8_t reserialized[64]; |
||||
r->Serialize(reserialized); |
||||
// If it parses, we insist that the bytes reserialize to the same thing.
|
||||
if (memcmp(data, reserialized, 64) != 0) abort(); |
||||
return 0; |
||||
} |
@ -0,0 +1,120 @@ |
||||
// Copyright 2022 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/frame_header.h" |
||||
|
||||
#include <algorithm> |
||||
#include <cstdint> |
||||
#include <vector> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "gtest/gtest.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace chaotic_good { |
||||
namespace { |
||||
|
||||
std::vector<uint8_t> Serialize(FrameHeader h) { |
||||
uint8_t buffer[64]; |
||||
h.Serialize(buffer); |
||||
return std::vector<uint8_t>(buffer, buffer + 64); |
||||
} |
||||
|
||||
absl::StatusOr<FrameHeader> Deserialize(std::vector<uint8_t> data) { |
||||
if (data.size() != 64) 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, 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
|
||||
0x10, 0x0f, 0x0e, 0x0d, // trailer_length
|
||||
// padding
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); |
||||
} |
||||
|
||||
TEST(FrameHeaderTest, SimpleDeserialize) { |
||||
EXPECT_EQ( |
||||
Deserialize(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
|
||||
0x10, 0x0f, 0x0e, 0x0d, // trailer_length
|
||||
// padding
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})), |
||||
absl::StatusOr<FrameHeader>( |
||||
FrameHeader{FrameType::kCancel, BitSet<3>::FromInt(0), 0x01020304, |
||||
0x05060708, 0x090a0b0c, 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
|
||||
0x10, 0x0f, 0x0e, 0x0d, // trailer_length
|
||||
// garbage padding
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0})) |
||||
.status(), |
||||
absl::InvalidArgumentError("Invalid flags")); |
||||
EXPECT_EQ(Deserialize(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
|
||||
0x10, 0x0f, 0x0e, 0x0d, // trailer_length
|
||||
// garbage padding
|
||||
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0})) |
||||
.status(), |
||||
absl::InvalidArgumentError("Invalid padding")); |
||||
} |
||||
|
||||
TEST(FrameHeaderTest, ComputeFrameSizes) { |
||||
EXPECT_EQ( |
||||
(FrameHeader{FrameType::kFragment, BitSet<3>::FromInt(7), 1, 0, 0, 0}) |
||||
.ComputeFrameSizes(), |
||||
(FrameSizes{0, 0, 0})); |
||||
EXPECT_EQ( |
||||
(FrameHeader{FrameType::kFragment, BitSet<3>::FromInt(7), 1, 14, 0, 0}) |
||||
.ComputeFrameSizes(), |
||||
(FrameSizes{64, 64, 64})); |
||||
EXPECT_EQ( |
||||
(FrameHeader{FrameType::kFragment, BitSet<3>::FromInt(7), 1, 0, 14, 0}) |
||||
.ComputeFrameSizes(), |
||||
(FrameSizes{0, 64, 64})); |
||||
EXPECT_EQ( |
||||
(FrameHeader{FrameType::kFragment, BitSet<3>::FromInt(7), 1, 0, 0, 14}) |
||||
.ComputeFrameSizes(), |
||||
(FrameSizes{0, 0, 64})); |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace chaotic_good
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
@ -0,0 +1,57 @@ |
||||
// Copyright 2022 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/frame.h" |
||||
|
||||
#include <cstdint> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "gtest/gtest.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace chaotic_good { |
||||
namespace { |
||||
|
||||
template <typename T> |
||||
void AssertRoundTrips(const T input, FrameType expected_frame_type) { |
||||
HPackCompressor hpack_compressor; |
||||
auto serialized = input.Serialize(&hpack_compressor); |
||||
EXPECT_GE(serialized.Length(), 64); |
||||
EXPECT_EQ(serialized.Length() % 64, 0); |
||||
uint8_t header_bytes[64]; |
||||
serialized.MoveFirstNBytesIntoBuffer(64, header_bytes); |
||||
auto header = FrameHeader::Parse(header_bytes); |
||||
EXPECT_TRUE(header.ok()) << header.status(); |
||||
EXPECT_EQ(header->type, expected_frame_type); |
||||
T output; |
||||
HPackParser hpack_parser; |
||||
auto deser = output.Deserialize(&hpack_parser, header.value(), serialized); |
||||
EXPECT_TRUE(deser.ok()) << deser; |
||||
EXPECT_EQ(output, input); |
||||
} |
||||
|
||||
TEST(FrameTest, SettingsFrameRoundTrips) { |
||||
AssertRoundTrips(SettingsFrame{}, FrameType::kSettings); |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace chaotic_good
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
int r = RUN_ALL_TESTS(); |
||||
return r; |
||||
} |
Loading…
Reference in new issue