// 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/include/puffin/huffer.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "puffin/src/bit_writer.h"
#include "puffin/src/huffman_table.h"
#include "puffin/src/include/puffin/common.h"
#include "puffin/src/include/puffin/stream.h"
#include "puffin/src/puff_data.h"
#include "puffin/src/puff_reader.h"
#include "puffin/src/set_errors.h"
namespace puffin {
using std::string;
Huffer::Huffer() : dyn_ht_(new HuffmanTable()), fix_ht_(new HuffmanTable()) {}
Huffer::~Huffer() {}
bool Huffer::HuffDeflate(PuffReaderInterface* pr,
BitWriterInterface* bw,
Error* error) const {
*error = Error::kSuccess;
PuffData pd;
HuffmanTable* cur_ht = nullptr;
// If no bytes left for PuffReader to read, bail out.
while (pr->BytesLeft() != 0) {
TEST_AND_RETURN_FALSE(pr->GetNext(&pd, error));
// The first data should be a metadata.
TEST_AND_RETURN_FALSE_SET_ERROR(pd.type == PuffData::Type::kBlockMetadata,
Error::kInvalidInput);
auto header = pd.block_metadata[0];
auto final_bit = (header & 0x80) >> 7;
auto type = (header & 0x60) >> 5;
auto skipped_bits = header & 0x1F;
DVLOG(2) << "Write block type: "
<< BlockTypeToString(static_cast<BlockType>(type));
TEST_AND_RETURN_FALSE_SET_ERROR(bw->WriteBits(1, final_bit),
Error::kInsufficientInput);
TEST_AND_RETURN_FALSE_SET_ERROR(bw->WriteBits(2, type),
Error::kInsufficientInput);
switch (static_cast<BlockType>(type)) {
case BlockType::kUncompressed:
bw->WriteBoundaryBits(skipped_bits);
TEST_AND_RETURN_FALSE(pr->GetNext(&pd, error));
TEST_AND_RETURN_FALSE_SET_ERROR(pd.type != PuffData::Type::kLiteral,
Error::kInvalidInput);
if (pd.type == PuffData::Type::kLiterals) {
TEST_AND_RETURN_FALSE_SET_ERROR(bw->WriteBits(16, pd.length),
Error::kInsufficientOutput);
TEST_AND_RETURN_FALSE_SET_ERROR(bw->WriteBits(16, ~pd.length),
Error::kInsufficientOutput);
TEST_AND_RETURN_FALSE_SET_ERROR(bw->WriteBytes(pd.length, pd.read_fn),
Error::kInsufficientOutput);
// Reading end of block, but don't write anything.
TEST_AND_RETURN_FALSE(pr->GetNext(&pd, error));
TEST_AND_RETURN_FALSE_SET_ERROR(
pd.type == PuffData::Type::kEndOfBlock, Error::kInvalidInput);
} else if (pd.type == PuffData::Type::kEndOfBlock) {
TEST_AND_RETURN_FALSE_SET_ERROR(bw->WriteBits(16, 0),
Error::kInsufficientOutput);
TEST_AND_RETURN_FALSE_SET_ERROR(bw->WriteBits(16, ~0),
Error::kInsufficientOutput);
} else {
LOG(ERROR) << "Uncompressed block did not end properly!";
*error = Error::kInvalidInput;
return false;
}
// We have to read a new block.
continue;
case BlockType::kFixed:
fix_ht_->BuildFixedHuffmanTable();
cur_ht = fix_ht_.get();
break;
case BlockType::kDynamic:
cur_ht = dyn_ht_.get();
TEST_AND_RETURN_FALSE(dyn_ht_->BuildDynamicHuffmanTable(
&pd.block_metadata[1], pd.length - 1, bw, error));
break;
default:
LOG(ERROR) << "Invalid block compression type: "
<< static_cast<int>(type);
*error = Error::kInvalidInput;
return false;
}
// We read literal or distrance/lengths until and end of block or end of
// stream is reached.
bool block_ended = false;
while (!block_ended) {
TEST_AND_RETURN_FALSE(pr->GetNext(&pd, error));
switch (pd.type) {
case PuffData::Type::kLiteral:
case PuffData::Type::kLiterals: {
auto write_literal = [&cur_ht, &bw, &error](uint8_t literal) {
uint16_t literal_huffman;
size_t nbits;
TEST_AND_RETURN_FALSE_SET_ERROR(
cur_ht->LitLenHuffman(literal, &literal_huffman, &nbits),
Error::kInvalidInput);
TEST_AND_RETURN_FALSE_SET_ERROR(
bw->WriteBits(nbits, literal_huffman),
Error::kInsufficientOutput);
return true;
};
if (pd.type == PuffData::Type::kLiteral) {
TEST_AND_RETURN_FALSE(write_literal(pd.byte));
} else {
auto len = pd.length;
while (len-- > 0) {
uint8_t literal;
pd.read_fn(&literal, 1);
TEST_AND_RETURN_FALSE(write_literal(literal));
}
}
break;
}
case PuffData::Type::kLenDist: {
auto len = pd.length;
auto dist = pd.distance;
TEST_AND_RETURN_FALSE_SET_ERROR(len >= 3 && len <= 258,
Error::kInvalidInput);
// Using a binary search here instead of the linear search may be (but
// not necessarily) faster. Needs experiment to validate.
size_t index = 0;
while (len > kLengthBases[index]) {
index++;
}
if (len < kLengthBases[index]) {
index--;
}
auto extra_bits_len = kLengthExtraBits[index];
uint16_t length_huffman;
size_t nbits;
TEST_AND_RETURN_FALSE_SET_ERROR(
cur_ht->LitLenHuffman(index + 257, &length_huffman, &nbits),
Error::kInvalidInput);
TEST_AND_RETURN_FALSE_SET_ERROR(bw->WriteBits(nbits, length_huffman),
Error::kInsufficientInput);
if (extra_bits_len > 0) {
TEST_AND_RETURN_FALSE_SET_ERROR(
bw->WriteBits(extra_bits_len, len - kLengthBases[index]),
Error::kInsufficientInput);
}
// Same as above (binary search).
index = 0;
while (dist > kDistanceBases[index]) {
index++;
}
if (dist < kDistanceBases[index]) {
index--;
}
extra_bits_len = kDistanceExtraBits[index];
uint16_t distance_huffman;
TEST_AND_RETURN_FALSE_SET_ERROR(
cur_ht->DistanceHuffman(index, &distance_huffman, &nbits),
Error::kInvalidInput);
TEST_AND_RETURN_FALSE_SET_ERROR(
bw->WriteBits(nbits, distance_huffman),
Error::kInsufficientInput);
if (extra_bits_len > 0) {
TEST_AND_RETURN_FALSE_SET_ERROR(
bw->WriteBits(extra_bits_len, dist - kDistanceBases[index]),
Error::kInsufficientInput);
}
break;
}
case PuffData::Type::kEndOfBlock: {
uint16_t eos_huffman;
size_t nbits;
TEST_AND_RETURN_FALSE_SET_ERROR(
cur_ht->LitLenHuffman(256, &eos_huffman, &nbits),
Error::kInvalidInput);
TEST_AND_RETURN_FALSE_SET_ERROR(bw->WriteBits(nbits, eos_huffman),
Error::kInsufficientInput);
block_ended = true;
break;
}
case PuffData::Type::kBlockMetadata:
LOG(ERROR) << "Not expecing a metadata!";
*error = Error::kInvalidInput;
return false;
default:
LOG(ERROR) << "Invalid block data type!";
*error = Error::kInvalidInput;
return false;
}
}
}
TEST_AND_RETURN_FALSE_SET_ERROR(bw->Flush(), Error::kInsufficientOutput);
return true;
}
} // namespace puffin