blob: cf6e123d833db6e6d84cad049188f811e08fa7e1 [file] [log] [blame]
// Copyright (c) 2012 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 "quic/core/crypto/crypto_framer.h"
#include <string>
#include <utility>
#include "absl/base/attributes.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "quic/core/crypto/crypto_protocol.h"
#include "quic/core/quic_data_reader.h"
#include "quic/core/quic_data_writer.h"
#include "quic/core/quic_packets.h"
#include "quic/platform/api/quic_logging.h"
#include "common/quiche_endian.h"
namespace quic {
namespace {
const size_t kQuicTagSize = sizeof(QuicTag);
const size_t kCryptoEndOffsetSize = sizeof(uint32_t);
const size_t kNumEntriesSize = sizeof(uint16_t);
// OneShotVisitor is a framer visitor that records a single handshake message.
class OneShotVisitor : public CryptoFramerVisitorInterface {
public:
OneShotVisitor() : error_(false) {}
void OnError(CryptoFramer* /*framer*/) override { error_ = true; }
void OnHandshakeMessage(const CryptoHandshakeMessage& message) override {
out_ = std::make_unique<CryptoHandshakeMessage>(message);
}
bool error() const { return error_; }
std::unique_ptr<CryptoHandshakeMessage> release() { return std::move(out_); }
private:
std::unique_ptr<CryptoHandshakeMessage> out_;
bool error_;
};
} // namespace
CryptoFramer::CryptoFramer()
: visitor_(nullptr),
error_detail_(""),
num_entries_(0),
values_len_(0),
process_truncated_messages_(false) {
Clear();
}
CryptoFramer::~CryptoFramer() {}
// static
std::unique_ptr<CryptoHandshakeMessage> CryptoFramer::ParseMessage(
absl::string_view in) {
OneShotVisitor visitor;
CryptoFramer framer;
framer.set_visitor(&visitor);
if (!framer.ProcessInput(in) || visitor.error() ||
framer.InputBytesRemaining()) {
return nullptr;
}
return visitor.release();
}
QuicErrorCode CryptoFramer::error() const {
return error_;
}
const std::string& CryptoFramer::error_detail() const {
return error_detail_;
}
bool CryptoFramer::ProcessInput(absl::string_view input,
EncryptionLevel /*level*/) {
return ProcessInput(input);
}
bool CryptoFramer::ProcessInput(absl::string_view input) {
QUICHE_DCHECK_EQ(QUIC_NO_ERROR, error_);
if (error_ != QUIC_NO_ERROR) {
return false;
}
error_ = Process(input);
if (error_ != QUIC_NO_ERROR) {
QUICHE_DCHECK(!error_detail_.empty());
visitor_->OnError(this);
return false;
}
return true;
}
size_t CryptoFramer::InputBytesRemaining() const {
return buffer_.length();
}
bool CryptoFramer::HasTag(QuicTag tag) const {
if (state_ != STATE_READING_VALUES) {
return false;
}
for (const auto& it : tags_and_lengths_) {
if (it.first == tag) {
return true;
}
}
return false;
}
void CryptoFramer::ForceHandshake() {
QuicDataReader reader(buffer_.data(), buffer_.length(),
quiche::HOST_BYTE_ORDER);
for (const std::pair<QuicTag, size_t>& item : tags_and_lengths_) {
absl::string_view value;
if (reader.BytesRemaining() < item.second) {
break;
}
reader.ReadStringPiece(&value, item.second);
message_.SetStringPiece(item.first, value);
}
visitor_->OnHandshakeMessage(message_);
}
// static
std::unique_ptr<QuicData> CryptoFramer::ConstructHandshakeMessage(
const CryptoHandshakeMessage& message) {
size_t num_entries = message.tag_value_map().size();
size_t pad_length = 0;
bool need_pad_tag = false;
bool need_pad_value = false;
size_t len = message.size();
if (len < message.minimum_size()) {
need_pad_tag = true;
need_pad_value = true;
num_entries++;
size_t delta = message.minimum_size() - len;
const size_t overhead = kQuicTagSize + kCryptoEndOffsetSize;
if (delta > overhead) {
pad_length = delta - overhead;
}
len += overhead + pad_length;
}
if (num_entries > kMaxEntries) {
return nullptr;
}
std::unique_ptr<char[]> buffer(new char[len]);
QuicDataWriter writer(len, buffer.get(), quiche::HOST_BYTE_ORDER);
if (!writer.WriteTag(message.tag())) {
QUICHE_DCHECK(false) << "Failed to write message tag.";
return nullptr;
}
if (!writer.WriteUInt16(static_cast<uint16_t>(num_entries))) {
QUICHE_DCHECK(false) << "Failed to write size.";
return nullptr;
}
if (!writer.WriteUInt16(0)) {
QUICHE_DCHECK(false) << "Failed to write padding.";
return nullptr;
}
uint32_t end_offset = 0;
// Tags and offsets
for (auto it = message.tag_value_map().begin();
it != message.tag_value_map().end(); ++it) {
if (it->first == kPAD && need_pad_tag) {
// Existing PAD tags are only checked when padding needs to be added
// because parts of the code may need to reserialize received messages
// and those messages may, legitimately include padding.
QUICHE_DCHECK(false)
<< "Message needed padding but already contained a PAD tag";
return nullptr;
}
if (it->first > kPAD && need_pad_tag) {
need_pad_tag = false;
if (!WritePadTag(&writer, pad_length, &end_offset)) {
return nullptr;
}
}
if (!writer.WriteTag(it->first)) {
QUICHE_DCHECK(false) << "Failed to write tag.";
return nullptr;
}
end_offset += it->second.length();
if (!writer.WriteUInt32(end_offset)) {
QUICHE_DCHECK(false) << "Failed to write end offset.";
return nullptr;
}
}
if (need_pad_tag) {
if (!WritePadTag(&writer, pad_length, &end_offset)) {
return nullptr;
}
}
// Values
for (auto it = message.tag_value_map().begin();
it != message.tag_value_map().end(); ++it) {
if (it->first > kPAD && need_pad_value) {
need_pad_value = false;
if (!writer.WriteRepeatedByte('-', pad_length)) {
QUICHE_DCHECK(false) << "Failed to write padding.";
return nullptr;
}
}
if (!writer.WriteBytes(it->second.data(), it->second.length())) {
QUICHE_DCHECK(false) << "Failed to write value.";
return nullptr;
}
}
if (need_pad_value) {
if (!writer.WriteRepeatedByte('-', pad_length)) {
QUICHE_DCHECK(false) << "Failed to write padding.";
return nullptr;
}
}
return std::make_unique<QuicData>(buffer.release(), len, true);
}
void CryptoFramer::Clear() {
message_.Clear();
tags_and_lengths_.clear();
error_ = QUIC_NO_ERROR;
error_detail_ = "";
state_ = STATE_READING_TAG;
}
QuicErrorCode CryptoFramer::Process(absl::string_view input) {
// Add this data to the buffer.
buffer_.append(input.data(), input.length());
QuicDataReader reader(buffer_.data(), buffer_.length(),
quiche::HOST_BYTE_ORDER);
switch (state_) {
case STATE_READING_TAG:
if (reader.BytesRemaining() < kQuicTagSize) {
break;
}
QuicTag message_tag;
reader.ReadTag(&message_tag);
message_.set_tag(message_tag);
state_ = STATE_READING_NUM_ENTRIES;
ABSL_FALLTHROUGH_INTENDED;
case STATE_READING_NUM_ENTRIES:
if (reader.BytesRemaining() < kNumEntriesSize + sizeof(uint16_t)) {
break;
}
reader.ReadUInt16(&num_entries_);
if (num_entries_ > kMaxEntries) {
error_detail_ = absl::StrCat(num_entries_, " entries");
return QUIC_CRYPTO_TOO_MANY_ENTRIES;
}
uint16_t padding;
reader.ReadUInt16(&padding);
tags_and_lengths_.reserve(num_entries_);
state_ = STATE_READING_TAGS_AND_LENGTHS;
values_len_ = 0;
ABSL_FALLTHROUGH_INTENDED;
case STATE_READING_TAGS_AND_LENGTHS: {
if (reader.BytesRemaining() <
num_entries_ * (kQuicTagSize + kCryptoEndOffsetSize)) {
break;
}
uint32_t last_end_offset = 0;
for (unsigned i = 0; i < num_entries_; ++i) {
QuicTag tag;
reader.ReadTag(&tag);
if (i > 0 && tag <= tags_and_lengths_[i - 1].first) {
if (tag == tags_and_lengths_[i - 1].first) {
error_detail_ = absl::StrCat("Duplicate tag:", tag);
return QUIC_CRYPTO_DUPLICATE_TAG;
}
error_detail_ = absl::StrCat("Tag ", tag, " out of order");
return QUIC_CRYPTO_TAGS_OUT_OF_ORDER;
}
uint32_t end_offset;
reader.ReadUInt32(&end_offset);
if (end_offset < last_end_offset) {
error_detail_ =
absl::StrCat("End offset: ", end_offset, " vs ", last_end_offset);
return QUIC_CRYPTO_TAGS_OUT_OF_ORDER;
}
tags_and_lengths_.push_back(std::make_pair(
tag, static_cast<size_t>(end_offset - last_end_offset)));
last_end_offset = end_offset;
}
values_len_ = last_end_offset;
state_ = STATE_READING_VALUES;
ABSL_FALLTHROUGH_INTENDED;
}
case STATE_READING_VALUES:
if (reader.BytesRemaining() < values_len_) {
if (!process_truncated_messages_) {
break;
}
QUIC_LOG(ERROR) << "Trunacted message. Missing "
<< values_len_ - reader.BytesRemaining() << " bytes.";
}
for (const std::pair<QuicTag, size_t>& item : tags_and_lengths_) {
absl::string_view value;
if (!reader.ReadStringPiece(&value, item.second)) {
QUICHE_DCHECK(process_truncated_messages_);
// Store an empty value.
message_.SetStringPiece(item.first, "");
continue;
}
message_.SetStringPiece(item.first, value);
}
visitor_->OnHandshakeMessage(message_);
Clear();
state_ = STATE_READING_TAG;
break;
}
// Save any remaining data.
buffer_ = std::string(reader.PeekRemainingPayload());
return QUIC_NO_ERROR;
}
// static
bool CryptoFramer::WritePadTag(QuicDataWriter* writer,
size_t pad_length,
uint32_t* end_offset) {
if (!writer->WriteTag(kPAD)) {
QUICHE_DCHECK(false) << "Failed to write tag.";
return false;
}
*end_offset += pad_length;
if (!writer->WriteUInt32(*end_offset)) {
QUICHE_DCHECK(false) << "Failed to write end offset.";
return false;
}
return true;
}
} // namespace quic