blob: a06ce86a291a187043228a02812effc2fc7dbe69 [file] [log] [blame]
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "quiche/quic/tools/devious_baton.h"
#include <cstdint>
#include <functional>
#include <memory>
#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