// Copyright 2017 The Chromium OS 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 "puffin/src/puff_writer.h"
|
|
#include <algorithm>
|
#include <memory>
|
#include <string>
|
#include <vector>
|
|
#include "puffin/src/logging.h"
|
|
namespace puffin {
|
|
namespace {
|
// Writes a value to the buffer in big-endian mode. Experience showed that
|
// big-endian creates smaller payloads.
|
inline void WriteUint16ToByteArray(uint16_t value, uint8_t* buffer) {
|
*buffer = value >> 8;
|
*(buffer + 1) = value & 0x00FF;
|
}
|
|
constexpr size_t kLiteralsMaxLength = (1 << 16) + 127; // 65663
|
} // namespace
|
|
bool BufferPuffWriter::Insert(const PuffData& pd) {
|
switch (pd.type) {
|
case PuffData::Type::kLiterals:
|
if (pd.length == 0) {
|
return true;
|
}
|
FALLTHROUGH_INTENDED;
|
case PuffData::Type::kLiteral: {
|
DVLOG(2) << "Write literals length: " << pd.length;
|
size_t length = pd.type == PuffData::Type::kLiteral ? 1 : pd.length;
|
if (state_ == State::kWritingNonLiteral) {
|
len_index_ = index_;
|
index_++;
|
state_ = State::kWritingSmallLiteral;
|
}
|
if (state_ == State::kWritingSmallLiteral) {
|
if ((cur_literals_length_ + length) > 127) {
|
if (puff_buf_out_ != nullptr) {
|
// Boundary check
|
TEST_AND_RETURN_FALSE(index_ + 2 <= puff_size_);
|
|
// Shift two bytes forward to open space for length value.
|
memmove(&puff_buf_out_[len_index_ + 3],
|
&puff_buf_out_[len_index_ + 1], cur_literals_length_);
|
}
|
index_ += 2;
|
state_ = State::kWritingLargeLiteral;
|
}
|
}
|
|
if (puff_buf_out_ != nullptr) {
|
// Boundary check
|
TEST_AND_RETURN_FALSE(index_ + length <= puff_size_);
|
if (pd.type == PuffData::Type::kLiteral) {
|
puff_buf_out_[index_] = pd.byte;
|
} else {
|
TEST_AND_RETURN_FALSE(pd.read_fn(&puff_buf_out_[index_], length));
|
}
|
} else if (pd.type == PuffData::Type::kLiterals) {
|
TEST_AND_RETURN_FALSE(pd.read_fn(nullptr, length));
|
}
|
|
index_ += length;
|
cur_literals_length_ += length;
|
|
// Technically with the current structure of the puff stream, we cannot
|
// have total length of more than 65663 bytes for a series of literals. So
|
// we have to cap it at 65663 and continue afterwards.
|
if (cur_literals_length_ == kLiteralsMaxLength) {
|
TEST_AND_RETURN_FALSE(FlushLiterals());
|
}
|
break;
|
}
|
case PuffData::Type::kLenDist:
|
DVLOG(2) << "Write length: " << pd.length << " distance: " << pd.distance;
|
TEST_AND_RETURN_FALSE(FlushLiterals());
|
TEST_AND_RETURN_FALSE(pd.length <= 258 && pd.length >= 3);
|
TEST_AND_RETURN_FALSE(pd.distance <= 32768 && pd.distance >= 1);
|
if (pd.length < 130) {
|
if (puff_buf_out_ != nullptr) {
|
// Boundary check
|
TEST_AND_RETURN_FALSE(index_ + 3 <= puff_size_);
|
|
puff_buf_out_[index_++] =
|
kLenDistHeader | static_cast<uint8_t>(pd.length - 3);
|
} else {
|
index_++;
|
}
|
} else {
|
if (puff_buf_out_ != nullptr) {
|
// Boundary check
|
TEST_AND_RETURN_FALSE(index_ + 4 <= puff_size_);
|
|
puff_buf_out_[index_++] = kLenDistHeader | 127;
|
puff_buf_out_[index_++] = static_cast<uint8_t>(pd.length - 3 - 127);
|
} else {
|
index_ += 2;
|
}
|
}
|
|
if (puff_buf_out_ != nullptr) {
|
// Write the distance in the range [1..32768] zero-based.
|
WriteUint16ToByteArray(pd.distance - 1, &puff_buf_out_[index_]);
|
}
|
index_ += 2;
|
len_index_ = index_;
|
state_ = State::kWritingNonLiteral;
|
break;
|
|
case PuffData::Type::kBlockMetadata:
|
DVLOG(2) << "Write block metadata length: " << pd.length;
|
TEST_AND_RETURN_FALSE(FlushLiterals());
|
TEST_AND_RETURN_FALSE(pd.length <= sizeof(pd.block_metadata) &&
|
pd.length > 0);
|
if (puff_buf_out_ != nullptr) {
|
// Boundary check
|
TEST_AND_RETURN_FALSE(index_ + pd.length + 2 <= puff_size_);
|
|
WriteUint16ToByteArray(pd.length - 1, &puff_buf_out_[index_]);
|
}
|
index_ += 2;
|
|
if (puff_buf_out_ != nullptr) {
|
memcpy(&puff_buf_out_[index_], pd.block_metadata, pd.length);
|
}
|
index_ += pd.length;
|
len_index_ = index_;
|
state_ = State::kWritingNonLiteral;
|
break;
|
|
case PuffData::Type::kEndOfBlock:
|
DVLOG(2) << "Write end of block";
|
TEST_AND_RETURN_FALSE(FlushLiterals());
|
if (puff_buf_out_ != nullptr) {
|
// Boundary check
|
TEST_AND_RETURN_FALSE(index_ + 2 <= puff_size_);
|
|
puff_buf_out_[index_++] = kLenDistHeader | 127;
|
puff_buf_out_[index_++] = static_cast<uint8_t>(259 - 3 - 127);
|
} else {
|
index_ += 2;
|
}
|
|
len_index_ = index_;
|
state_ = State::kWritingNonLiteral;
|
break;
|
|
default:
|
LOG(ERROR) << "Invalid PuffData::Type";
|
return false;
|
}
|
return true;
|
}
|
|
bool BufferPuffWriter::FlushLiterals() {
|
if (cur_literals_length_ == 0) {
|
return true;
|
}
|
switch (state_) {
|
case State::kWritingSmallLiteral:
|
TEST_AND_RETURN_FALSE(cur_literals_length_ == (index_ - len_index_ - 1));
|
if (puff_buf_out_ != nullptr) {
|
puff_buf_out_[len_index_] =
|
kLiteralsHeader | static_cast<uint8_t>(cur_literals_length_ - 1);
|
}
|
len_index_ = index_;
|
state_ = State::kWritingNonLiteral;
|
DVLOG(2) << "Write small literals length: " << cur_literals_length_;
|
break;
|
|
case State::kWritingLargeLiteral:
|
TEST_AND_RETURN_FALSE(cur_literals_length_ == (index_ - len_index_ - 3));
|
if (puff_buf_out_ != nullptr) {
|
puff_buf_out_[len_index_++] = kLiteralsHeader | 127;
|
WriteUint16ToByteArray(
|
static_cast<uint16_t>(cur_literals_length_ - 127 - 1),
|
&puff_buf_out_[len_index_]);
|
}
|
|
len_index_ = index_;
|
state_ = State::kWritingNonLiteral;
|
DVLOG(2) << "Write large literals length: " << cur_literals_length_;
|
break;
|
|
case State::kWritingNonLiteral:
|
// Do nothing.
|
break;
|
|
default:
|
LOG(ERROR) << "Invalid State";
|
return false;
|
}
|
cur_literals_length_ = 0;
|
return true;
|
}
|
|
bool BufferPuffWriter::Flush() {
|
TEST_AND_RETURN_FALSE(FlushLiterals());
|
return true;
|
}
|
|
size_t BufferPuffWriter::Size() {
|
return index_;
|
}
|
|
} // namespace puffin
|