| // Copyright 2023 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "quiche/quic/tools/devious_baton.h" | |
| #include <cstdint> | |
| #include <functional> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/functional/bind_front.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/string_view.h" | |
| #include "quiche/quic/core/crypto/quic_random.h" | |
| #include "quiche/quic/core/quic_types.h" | |
| #include "quiche/common/quiche_data_reader.h" | |
| #include "quiche/common/wire_serialization.h" | |
| #include "quiche/web_transport/complete_buffer_visitor.h" | |
| #include "quiche/web_transport/web_transport.h" | |
| namespace quic { | |
| namespace { | |
| constexpr QuicByteCount kMaxPaddingSize = 64; | |
| constexpr char kPaddingData[kMaxPaddingSize] = {0}; | |
| absl::StatusOr<DeviousBatonValue> Parse(absl::string_view message) { | |
| quiche::QuicheDataReader reader(message); | |
| uint64_t padding_size; | |
| if (!reader.ReadVarInt62(&padding_size)) { | |
| return absl::InvalidArgumentError("Failed to read the padding size"); | |
| } | |
| if (!reader.Seek(padding_size)) { | |
| return absl::InvalidArgumentError("Failed to skip padding"); | |
| } | |
| DeviousBatonValue value; | |
| if (!reader.ReadUInt8(&value)) { | |
| return absl::InvalidArgumentError("Failed to read the baton"); | |
| } | |
| if (!reader.IsDoneReading()) { | |
| return absl::InvalidArgumentError("Trailing data after the baton"); | |
| } | |
| return value; | |
| } | |
| std::string Serialize(DeviousBatonValue value) { | |
| // Randomize padding size for extra deviousness. | |
| QuicByteCount padding_size = | |
| QuicRandom::GetInstance()->InsecureRandUint64() % kMaxPaddingSize; | |
| absl::string_view padding(kPaddingData, padding_size); | |
| absl::StatusOr<std::string> result = quiche::SerializeIntoString( | |
| quiche::WireStringWithLengthPrefix<quiche::WireVarInt62>(padding), | |
| quiche::WireUint8(value)); | |
| QUICHE_DCHECK(result.ok()); | |
| return *std::move(result); | |
| } | |
| class IncomingBidiBatonVisitor : public webtransport::CompleteBufferVisitor { | |
| public: | |
| IncomingBidiBatonVisitor(webtransport::Session& session, | |
| webtransport::Stream& stream) | |
| : CompleteBufferVisitor( | |
| &stream, absl::bind_front( | |
| &IncomingBidiBatonVisitor::OnAllDataReceived, this)), | |
| session_(&session) {} | |
| private: | |
| void OnAllDataReceived(std::string data) { | |
| absl::StatusOr<DeviousBatonValue> value = Parse(data); | |
| if (!value.ok()) { | |
| session_->CloseSession(kDeviousBatonErrorBruh, | |
| absl::StrCat("Failed to parse incoming baton: ", | |
| value.status().message())); | |
| return; | |
| } | |
| DeviousBatonValue next_value = 1 + *value; | |
| if (next_value != 0) { | |
| SetOutgoingData(Serialize(*value + 1)); | |
| } | |
| } | |
| webtransport::Session* session_; | |
| }; | |
| } // namespace | |
| void DeviousBatonSessionVisitor::OnSessionReady() { | |
| if (!is_server_) { | |
| return; | |
| } | |
| for (int i = 0; i < count_; ++i) { | |
| webtransport::Stream* stream = session_->OpenOutgoingUnidirectionalStream(); | |
| if (stream == nullptr) { | |
| session_->CloseSession( | |
| kDeviousBatonErrorDaYamn, | |
| "Insufficient flow control when opening initial baton streams"); | |
| return; | |
| } | |
| stream->SetVisitor(std::make_unique<webtransport::CompleteBufferVisitor>( | |
| stream, Serialize(initial_value_))); | |
| stream->visitor()->OnCanWrite(); | |
| } | |
| } | |
| void DeviousBatonSessionVisitor::OnSessionClosed( | |
| webtransport::SessionErrorCode error_code, | |
| const std::string& error_message) { | |
| QUICHE_LOG(INFO) << "Devious Baton session closed with error " << error_code | |
| << " (message: " << error_message << ")"; | |
| } | |
| void DeviousBatonSessionVisitor::OnIncomingBidirectionalStreamAvailable() { | |
| while (true) { | |
| webtransport::Stream* stream = | |
| session_->AcceptIncomingBidirectionalStream(); | |
| if (stream == nullptr) { | |
| return; | |
| } | |
| stream->SetVisitor( | |
| std::make_unique<IncomingBidiBatonVisitor>(*session_, *stream)); | |
| stream->visitor()->OnCanRead(); | |
| } | |
| } | |
| void DeviousBatonSessionVisitor::OnIncomingUnidirectionalStreamAvailable() { | |
| while (true) { | |
| webtransport::Stream* stream = | |
| session_->AcceptIncomingUnidirectionalStream(); | |
| if (stream == nullptr) { | |
| return; | |
| } | |
| stream->SetVisitor(std::make_unique<webtransport::CompleteBufferVisitor>( | |
| stream, CreateResponseCallback( | |
| &DeviousBatonSessionVisitor::SendBidirectionalBaton))); | |
| stream->visitor()->OnCanRead(); | |
| } | |
| } | |
| void DeviousBatonSessionVisitor::OnDatagramReceived( | |
| absl::string_view datagram) { | |
| // TODO(vasilvv): implement datagram behavior. | |
| } | |
| void DeviousBatonSessionVisitor::OnCanCreateNewOutgoingBidirectionalStream() { | |
| while (!outgoing_bidi_batons_.empty()) { | |
| webtransport::Stream* stream = session_->OpenOutgoingBidirectionalStream(); | |
| if (stream == nullptr) { | |
| return; | |
| } | |
| stream->SetVisitor(std::make_unique<webtransport::CompleteBufferVisitor>( | |
| stream, Serialize(outgoing_bidi_batons_.front()), | |
| CreateResponseCallback( | |
| &DeviousBatonSessionVisitor::SendUnidirectionalBaton))); | |
| outgoing_bidi_batons_.pop_front(); | |
| stream->visitor()->OnCanWrite(); | |
| } | |
| } | |
| void DeviousBatonSessionVisitor::OnCanCreateNewOutgoingUnidirectionalStream() { | |
| while (!outgoing_unidi_batons_.empty()) { | |
| webtransport::Stream* stream = session_->OpenOutgoingUnidirectionalStream(); | |
| if (stream == nullptr) { | |
| return; | |
| } | |
| stream->SetVisitor(std::make_unique<webtransport::CompleteBufferVisitor>( | |
| stream, Serialize(outgoing_unidi_batons_.front()))); | |
| outgoing_unidi_batons_.pop_front(); | |
| stream->visitor()->OnCanWrite(); | |
| } | |
| } | |
| quiche::SingleUseCallback<void(std::string)> | |
| DeviousBatonSessionVisitor::CreateResponseCallback(SendFunction send_function) { | |
| return [this, send_function](std::string data) { | |
| absl::StatusOr<DeviousBatonValue> value = Parse(data); | |
| if (!value.ok()) { | |
| session_->CloseSession(kDeviousBatonErrorBruh, | |
| absl::StrCat("Failed to parse incoming baton: ", | |
| value.status().message())); | |
| return; | |
| } | |
| DeviousBatonValue new_value = 1 + *value; | |
| if (new_value != 0) { | |
| std::invoke(send_function, this, *value); | |
| } | |
| }; | |
| } | |
| } // namespace quic |