blob: c0558ac490c8be23c7376f44a273b2b6c83c0ad2 [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 "quiche/spdy/core/spdy_frame_builder.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <new>
#include "quiche/common/platform/api/quiche_bug_tracker.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/spdy/core/spdy_protocol.h"
#include "quiche/spdy/core/zero_copy_output_buffer.h"
namespace spdy {
SpdyFrameBuilder::SpdyFrameBuilder(size_t size)
: buffer_(new char[size]), capacity_(size), length_(0), offset_(0) {}
SpdyFrameBuilder::SpdyFrameBuilder(size_t size, ZeroCopyOutputBuffer* output)
: buffer_(output == nullptr ? new char[size] : nullptr),
output_(output),
capacity_(size),
length_(0),
offset_(0) {}
SpdyFrameBuilder::~SpdyFrameBuilder() = default;
char* SpdyFrameBuilder::GetWritableBuffer(size_t length) {
if (!CanWrite(length)) {
return nullptr;
}
return buffer_.get() + offset_ + length_;
}
char* SpdyFrameBuilder::GetWritableOutput(size_t length,
size_t* actual_length) {
char* dest = nullptr;
int size = 0;
if (!CanWrite(length)) {
return nullptr;
}
output_->Next(&dest, &size);
*actual_length = std::min<size_t>(length, size);
return dest;
}
bool SpdyFrameBuilder::Seek(size_t length) {
if (!CanWrite(length)) {
return false;
}
if (output_ == nullptr) {
length_ += length;
} else {
output_->AdvanceWritePtr(length);
length_ += length;
}
return true;
}
bool SpdyFrameBuilder::BeginNewFrame(SpdyFrameType type, uint8_t flags,
SpdyStreamId stream_id) {
uint8_t raw_frame_type = SerializeFrameType(type);
QUICHE_DCHECK(IsDefinedFrameType(raw_frame_type));
QUICHE_DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
bool success = true;
if (length_ > 0) {
QUICHE_BUG(spdy_bug_73_1)
<< "SpdyFrameBuilder doesn't have a clean state when BeginNewFrame"
<< "is called. Leftover length_ is " << length_;
offset_ += length_;
length_ = 0;
}
success &= WriteUInt24(capacity_ - offset_ - kFrameHeaderSize);
success &= WriteUInt8(raw_frame_type);
success &= WriteUInt8(flags);
success &= WriteUInt32(stream_id);
QUICHE_DCHECK_EQ(kDataFrameMinimumSize, length_);
return success;
}
bool SpdyFrameBuilder::BeginNewFrame(SpdyFrameType type, uint8_t flags,
SpdyStreamId stream_id, size_t length) {
uint8_t raw_frame_type = SerializeFrameType(type);
QUICHE_DCHECK(IsDefinedFrameType(raw_frame_type));
QUICHE_DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
QUICHE_BUG_IF(spdy_bug_73_2, length > kHttp2DefaultFramePayloadLimit)
<< "Frame length " << length_ << " is longer than frame size limit.";
return BeginNewFrameInternal(raw_frame_type, flags, stream_id, length);
}
bool SpdyFrameBuilder::BeginNewUncheckedFrame(uint8_t raw_frame_type,
uint8_t flags,
SpdyStreamId stream_id,
size_t length) {
return BeginNewFrameInternal(raw_frame_type, flags, stream_id, length);
}
bool SpdyFrameBuilder::BeginNewFrameInternal(uint8_t raw_frame_type,
uint8_t flags,
SpdyStreamId stream_id,
size_t length) {
QUICHE_DCHECK_EQ(length, length & kLengthMask);
bool success = true;
offset_ += length_;
length_ = 0;
success &= WriteUInt24(length);
success &= WriteUInt8(raw_frame_type);
success &= WriteUInt8(flags);
success &= WriteUInt32(stream_id);
QUICHE_DCHECK_EQ(kDataFrameMinimumSize, length_);
return success;
}
bool SpdyFrameBuilder::WriteStringPiece32(const absl::string_view value) {
if (!WriteUInt32(value.size())) {
return false;
}
return WriteBytes(value.data(), value.size());
}
bool SpdyFrameBuilder::WriteBytes(const void* data, uint32_t data_len) {
if (!CanWrite(data_len)) {
return false;
}
if (output_ == nullptr) {
char* dest = GetWritableBuffer(data_len);
memcpy(dest, data, data_len);
Seek(data_len);
} else {
char* dest = nullptr;
size_t size = 0;
size_t total_written = 0;
const char* data_ptr = reinterpret_cast<const char*>(data);
while (data_len > 0) {
dest = GetWritableOutput(data_len, &size);
if (dest == nullptr || size == 0) {
// Unable to make progress.
return false;
}
uint32_t to_copy = std::min<uint32_t>(data_len, size);
const char* src = data_ptr + total_written;
memcpy(dest, src, to_copy);
Seek(to_copy);
data_len -= to_copy;
total_written += to_copy;
}
}
return true;
}
bool SpdyFrameBuilder::CanWrite(size_t length) const {
if (length > kLengthMask) {
QUICHE_DCHECK(false);
return false;
}
if (output_ == nullptr) {
if (offset_ + length_ + length > capacity_) {
QUICHE_DLOG(FATAL) << "Requested: " << length
<< " capacity: " << capacity_
<< " used: " << offset_ + length_;
return false;
}
} else {
if (length > output_->BytesFree()) {
return false;
}
}
return true;
}
} // namespace spdy