blob: 99318877a59103400481e3ddeb86ffd6b92c47ec [file] [log] [blame] [edit]
// Copyright 2025 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/masque/masque_h2_connection.h"
#include <algorithm>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "quiche/http2/adapter/http2_protocol.h"
#include "quiche/http2/adapter/http2_util.h"
#include "quiche/http2/adapter/http2_visitor_interface.h"
#include "quiche/http2/adapter/oghttp2_adapter.h"
#include "openssl/base.h"
#include "openssl/err.h"
#include "openssl/ssl.h"
#include "quiche/common/http/http_header_block.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/quiche_text_utils.h"
using http2::adapter::Header;
using http2::adapter::Http2KnownSettingsId;
namespace quic {
MasqueH2Connection::MasqueH2Connection(SSL *ssl, bool is_server,
Visitor *visitor)
: ssl_(ssl), is_server_(is_server), visitor_(visitor) {}
void MasqueH2Connection::OnTransportReadable() {
while (TryRead()) {
}
}
MasqueH2Connection::~MasqueH2Connection() {}
void MasqueH2Connection::Abort() {
if (aborted_) {
return;
}
aborted_ = true;
QUICHE_LOG(ERROR) << "Aborting connection";
visitor_->OnConnectionFinished(this);
}
void MasqueH2Connection::StartH2() {
http2::adapter::OgHttp2Adapter::Options options;
std::vector<Http2Setting> settings;
if (is_server_) {
options.perspective = http2::adapter::Perspective::kServer;
settings.push_back(
Http2Setting{Http2KnownSettingsId::ENABLE_CONNECT_PROTOCOL, 1});
} else {
options.perspective = http2::adapter::Perspective::kClient;
}
settings.push_back(
Http2Setting{Http2KnownSettingsId::HEADER_TABLE_SIZE, 4096});
settings.push_back(Http2Setting{Http2KnownSettingsId::ENABLE_PUSH, 0});
settings.push_back(
Http2Setting{Http2KnownSettingsId::MAX_CONCURRENT_STREAMS, 100});
settings.push_back(
Http2Setting{Http2KnownSettingsId::INITIAL_WINDOW_SIZE, 65535});
settings.push_back(Http2Setting{Http2KnownSettingsId::MAX_FRAME_SIZE, 16384});
settings.push_back(
Http2Setting{Http2KnownSettingsId::MAX_HEADER_LIST_SIZE, 65535});
h2_adapter_ = http2::adapter::OgHttp2Adapter::Create(*this, options);
h2_adapter_->SubmitSettings(settings);
visitor_->OnConnectionReady(this);
}
bool MasqueH2Connection::TryRead() {
if (!tls_connected_) {
int ssl_handshake_ret = SSL_do_handshake(ssl_);
if (ssl_handshake_ret == 1) {
tls_connected_ = true;
StartH2();
AttemptToSend();
} else {
int ssl_err = SSL_get_error(ssl_, ssl_handshake_ret);
if (ssl_err == SSL_ERROR_WANT_READ) {
QUICHE_DVLOG(1) << "SSL_do_handshake will require another read";
return false;
}
PrintSSLError("Error while connecting", ssl_err, ssl_handshake_ret);
return false;
}
}
uint8_t buffer[kBioBufferSize] = {};
int ssl_read_ret = SSL_read(ssl_, buffer, sizeof(buffer) - 1);
if (ssl_read_ret < 0) {
int ssl_err = SSL_get_error(ssl_, ssl_read_ret);
if (ssl_err == SSL_ERROR_WANT_READ) {
return false;
}
PrintSSLError("Error while connecting", ssl_err, ssl_read_ret);
return false;
}
if (ssl_read_ret == 0) {
QUICHE_LOG(INFO) << "TLS read closed";
return false;
}
QUICHE_DVLOG(1) << "Read " << ssl_read_ret << " bytes from TLS";
QUICHE_DVLOG(2) << "Read TLS bytes:" << std::endl
<< quiche::QuicheTextUtils::HexDump(absl::string_view(
reinterpret_cast<const char *>(buffer), ssl_read_ret));
h2_adapter_->ProcessBytes(
absl::string_view(reinterpret_cast<const char *>(buffer), ssl_read_ret));
return AttemptToSend();
}
int MasqueH2Connection::WriteDataToTls(absl::string_view data) {
QUICHE_DVLOG(2) << "Writing " << data.size()
<< " app bytes to TLS:" << std::endl
<< quiche::QuicheTextUtils::HexDump(data);
int ssl_write_ret = SSL_write(ssl_, data.data(), data.size());
if (ssl_write_ret <= 0) {
int ssl_err = SSL_get_error(ssl_, ssl_write_ret);
PrintSSLError("Error while writing request to TLS", ssl_err, ssl_write_ret);
return -1;
} else {
if (ssl_write_ret == static_cast<int>(data.size())) {
QUICHE_DVLOG(1) << "Wrote " << data.size() << " bytes to TLS";
} else {
QUICHE_DVLOG(1) << "Wrote " << ssl_write_ret << " / " << data.size()
<< "bytes to TLS";
}
}
return ssl_write_ret;
}
int64_t MasqueH2Connection::OnReadyToSend(absl::string_view serialized) {
QUICHE_DVLOG(1) << "Writing " << serialized.size()
<< " bytes of h2 data to TLS";
int write_res = WriteDataToTls(serialized);
if (write_res < 0) {
return kSendError;
}
return write_res;
}
MasqueH2Connection::DataFrameHeaderInfo
MasqueH2Connection::OnReadyToSendDataForStream(Http2StreamId stream_id,
size_t max_length) {
MasqueH2Stream *stream = GetOrCreateH2Stream(stream_id);
DataFrameHeaderInfo info;
if (max_length < stream->body_to_send.size()) {
info.payload_length = max_length;
info.end_data = false;
} else {
info.payload_length = stream->body_to_send.size();
info.end_data = true;
}
info.end_stream = info.end_data;
return info;
}
bool MasqueH2Connection::SendDataFrame(Http2StreamId stream_id,
absl::string_view frame_header,
size_t payload_bytes) {
if (!WriteDataToTls(frame_header)) {
return false;
}
MasqueH2Stream *stream = GetOrCreateH2Stream(stream_id);
size_t length_to_write = std::min(payload_bytes, stream->body_to_send.size());
int length_written =
WriteDataToTls(stream->body_to_send.substr(0, length_to_write));
if (length_written < 0) {
return false;
}
if (length_written == static_cast<int>(stream->body_to_send.size())) {
stream->body_to_send.clear();
} else {
// Remove the written bytes from the start of `body_to_send`.
stream->body_to_send = stream->body_to_send.substr(length_written);
}
return true;
}
void MasqueH2Connection::OnConnectionError(ConnectionError error) {
QUICHE_LOG(ERROR) << "OnConnectionError: "
<< http2::adapter::ConnectionErrorToString(error);
Abort();
}
void MasqueH2Connection::OnSettingsStart() {}
void MasqueH2Connection::OnSetting(Http2Setting setting) {
QUICHE_LOG(INFO) << "Received "
<< http2::adapter::Http2SettingsIdToString(setting.id)
<< " = " << setting.value;
}
void MasqueH2Connection::OnSettingsEnd() {}
void MasqueH2Connection::OnSettingsAck() {}
bool MasqueH2Connection::OnBeginHeadersForStream(Http2StreamId stream_id) {
QUICHE_DVLOG(1) << "OnBeginHeadersForStream " << stream_id;
return true;
}
MasqueH2Connection::OnHeaderResult MasqueH2Connection::OnHeaderForStream(
Http2StreamId stream_id, absl::string_view key, absl::string_view value) {
QUICHE_DVLOG(2) << "Stream " << stream_id << " received header " << key
<< " = " << value;
GetOrCreateH2Stream(stream_id)->received_headers.AppendValueOrAddHeader(
key, value);
return OnHeaderResult::HEADER_OK;
}
bool MasqueH2Connection::AttemptToSend() {
if (!h2_adapter_) {
QUICHE_LOG(ERROR) << "Connection is not ready to send yet";
return false;
}
int h2_send_result = h2_adapter_->Send();
if (h2_send_result != 0) {
QUICHE_LOG(ERROR) << "h2 adapter failed to send";
Abort();
return false;
}
return true;
}
bool MasqueH2Connection::OnEndHeadersForStream(Http2StreamId stream_id) {
MasqueH2Stream *stream = GetOrCreateH2Stream(stream_id);
QUICHE_LOG(INFO) << "OnEndHeadersForStream " << stream_id
<< " headers: " << stream->received_headers.DebugString();
return true;
}
void MasqueH2Connection::SendResponse(int32_t stream_id,
const quiche::HttpHeaderBlock &headers,
const std::string &body) {
MasqueH2Stream *stream = GetOrCreateH2Stream(stream_id);
std::vector<Header> h2_headers = ConvertHeaders(headers);
stream->body_to_send = body;
if (h2_adapter_->SubmitResponse(
stream_id, h2_headers,
/*end_stream=*/stream->body_to_send.empty()) != 0) {
QUICHE_LOG(ERROR) << "Failed to submit response for stream " << stream_id;
Abort();
}
}
int32_t MasqueH2Connection::SendRequest(const quiche::HttpHeaderBlock &headers,
const std::string &body) {
if (is_server_) {
QUICHE_LOG(FATAL) << "Server cannot send requests";
}
if (!h2_adapter_) {
QUICHE_LOG(ERROR) << "Connection is not ready to send requests yet";
return -1;
}
std::vector<Header> h2_headers = ConvertHeaders(headers);
QUICHE_LOG(INFO) << "Sending request with body of length " << body.size()
<< ", headers: " << headers.DebugString();
int32_t stream_id =
h2_adapter_->SubmitRequest(h2_headers, /*end_stream=*/body.empty(),
/*user_data=*/nullptr);
if (stream_id < 0) {
QUICHE_LOG(ERROR) << "Failed to submit request";
Abort();
return -1;
}
GetOrCreateH2Stream(stream_id)->body_to_send = body;
return stream_id;
}
std::vector<Header> MasqueH2Connection::ConvertHeaders(
const quiche::HttpHeaderBlock &headers) {
std::vector<Header> h2_headers;
for (const auto &[key, value] : headers) {
h2_headers.push_back({http2::adapter::HeaderRep(std::string(key)),
http2::adapter::HeaderRep(std::string(value))});
}
return h2_headers;
}
bool MasqueH2Connection::OnBeginDataForStream(Http2StreamId stream_id,
size_t payload_length) {
QUICHE_DVLOG(1) << "OnBeginDataForStream " << stream_id
<< " payload_length: " << payload_length;
return true;
}
bool MasqueH2Connection::OnDataPaddingLength(Http2StreamId stream_id,
size_t padding_length) {
QUICHE_DVLOG(1) << "OnDataPaddingLength stream_id: " << stream_id
<< " padding_length: " << padding_length;
return true;
}
bool MasqueH2Connection::OnDataForStream(Http2StreamId stream_id,
absl::string_view data) {
QUICHE_DVLOG(1) << "OnDataForStream " << stream_id
<< " data length: " << data.size();
GetOrCreateH2Stream(stream_id)->received_body.append(data);
return true;
}
bool MasqueH2Connection::OnEndStream(Http2StreamId stream_id) {
MasqueH2Stream *stream = GetOrCreateH2Stream(stream_id);
QUICHE_LOG(INFO) << "Received END_STREAM for stream " << stream_id
<< " body length: " << stream->received_body.size()
<< std::endl
<< stream->received_body;
if (is_server_) {
visitor_->OnRequest(this, stream_id, stream->received_headers,
stream->received_body);
} else {
visitor_->OnResponse(this, stream_id, stream->received_headers,
stream->received_body);
}
return true;
}
void MasqueH2Connection::OnRstStream(Http2StreamId stream_id,
Http2ErrorCode error_code) {
QUICHE_LOG(INFO) << "Stream " << stream_id << " reset with error code "
<< Http2ErrorCodeToString(error_code);
}
bool MasqueH2Connection::OnCloseStream(Http2StreamId stream_id,
Http2ErrorCode error_code) {
QUICHE_LOG(INFO) << "Stream " << stream_id << " closed with error code "
<< Http2ErrorCodeToString(error_code);
h2_streams_.erase(stream_id);
return true;
}
void MasqueH2Connection::OnPriorityForStream(Http2StreamId stream_id,
Http2StreamId parent_stream_id,
int weight, bool exclusive) {
QUICHE_LOG(INFO) << "Stream " << stream_id << " received priority " << weight
<< (exclusive ? " exclusive" : "") << " parent "
<< parent_stream_id;
}
void MasqueH2Connection::OnPing(Http2PingId ping_id, bool is_ack) {
QUICHE_LOG(INFO) << "Received ping " << ping_id << (is_ack ? " ack" : "");
}
void MasqueH2Connection::OnPushPromiseForStream(
Http2StreamId stream_id, Http2StreamId promised_stream_id) {
QUICHE_LOG(INFO) << "Stream " << stream_id
<< " received push promise for stream "
<< promised_stream_id;
}
bool MasqueH2Connection::OnGoAway(Http2StreamId last_accepted_stream_id,
Http2ErrorCode error_code,
absl::string_view opaque_data) {
QUICHE_LOG(INFO) << "Received GOAWAY frame with last_accepted_stream_id: "
<< last_accepted_stream_id
<< " error_code: " << Http2ErrorCodeToString(error_code)
<< " opaque_data length: " << opaque_data.size();
return true;
}
void MasqueH2Connection::OnWindowUpdate(Http2StreamId stream_id,
int window_increment) {
QUICHE_LOG(INFO) << "Stream " << stream_id << " received window update "
<< window_increment;
}
int MasqueH2Connection::OnBeforeFrameSent(uint8_t frame_type,
Http2StreamId stream_id,
size_t length, uint8_t flags) {
QUICHE_DVLOG(1) << "OnBeforeFrameSent frame_type: "
<< static_cast<int>(frame_type) << " stream_id: " << stream_id
<< " length: " << length
<< " flags: " << static_cast<int>(flags);
return 0;
}
int MasqueH2Connection::OnFrameSent(uint8_t frame_type, Http2StreamId stream_id,
size_t length, uint8_t flags,
uint32_t error_code) {
QUICHE_DVLOG(1) << "OnFrameSent frame_type: " << static_cast<int>(frame_type)
<< " stream_id: " << stream_id << " length: " << length
<< " flags: " << static_cast<int>(flags)
<< " error_code: " << error_code;
return 0;
}
bool MasqueH2Connection::OnInvalidFrame(Http2StreamId stream_id,
InvalidFrameError error) {
QUICHE_LOG(INFO) << "Stream " << stream_id << " received invalid frame error "
<< http2::adapter::InvalidFrameErrorToString(error);
return true;
}
void MasqueH2Connection::OnBeginMetadataForStream(Http2StreamId stream_id,
size_t payload_length) {
QUICHE_LOG(INFO) << "Stream " << stream_id
<< " about to receive metadata of length " << payload_length;
}
bool MasqueH2Connection::OnMetadataForStream(Http2StreamId stream_id,
absl::string_view metadata) {
QUICHE_LOG(INFO) << "Stream " << stream_id << " received metadata of length "
<< metadata.size();
return true;
}
bool MasqueH2Connection::OnMetadataEndForStream(Http2StreamId stream_id) {
QUICHE_LOG(INFO) << "Stream " << stream_id << " done receiving metadata";
return true;
}
void MasqueH2Connection::OnErrorDebug(absl::string_view message) {
QUICHE_LOG(ERROR) << "OnErrorDebug: " << message;
}
MasqueH2Connection::MasqueH2Stream *MasqueH2Connection::GetOrCreateH2Stream(
Http2StreamId stream_id) {
auto it = h2_streams_.find(stream_id);
if (it != h2_streams_.end()) {
return it->second.get();
}
return h2_streams_.insert({stream_id, std::make_unique<MasqueH2Stream>()})
.first->second.get();
}
void PrintSSLError(const char *msg, int ssl_err, int ret) {
switch (ssl_err) {
case SSL_ERROR_SSL:
QUICHE_LOG(ERROR) << msg << ": "
<< ERR_reason_error_string(ERR_peek_error());
break;
case SSL_ERROR_SYSCALL:
if (ret == 0) {
QUICHE_LOG(ERROR) << msg << ": peer closed connection";
} else {
QUICHE_LOG(ERROR) << msg << ": " << strerror(errno);
}
break;
case SSL_ERROR_ZERO_RETURN:
QUICHE_LOG(ERROR) << msg << ": received close_notify";
break;
default:
QUICHE_LOG(ERROR) << msg << ": unexpected error: "
<< SSL_error_description(ssl_err);
break;
}
ERR_print_errors_fp(stderr);
}
} // namespace quic