blob: 620c45b454f3a5d0b27b4e6a4cfad95acb4804bc [file]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "riegeli/snappy/framed/framed_snappy_reader.h"
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include <memory>
#include <utility>
#include "absl/base/optimization.h"
#include "absl/crc/crc32c.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "riegeli/base/arithmetic.h"
#include "riegeli/base/assert.h"
#include "riegeli/base/buffer.h"
#include "riegeli/base/status.h"
#include "riegeli/base/types.h"
#include "riegeli/bytes/pullable_reader.h"
#include "riegeli/bytes/reader.h"
#include "riegeli/endian/endian_reading.h"
#include "snappy.h"
namespace riegeli {
namespace {
// https://github.com/google/snappy/blob/e9e11b84e629c3e06fbaa4f0a86de02ceb9d6992/framing_format.txt#L39
inline uint32_t MaskChecksum(uint32_t x) {
return ((x >> 15) | (x << 17)) + 0xa282ead8;
}
} // namespace
void FramedSnappyReaderBase::Initialize(Reader* src) {
RIEGELI_ASSERT_NE(src, nullptr)
<< "Failed precondition of FramedSnappyReader: null Reader pointer";
if (ABSL_PREDICT_FALSE(!src->ok()) && src->available() == 0) {
FailWithoutAnnotation(AnnotateOverSrc(src->status()));
return;
}
initial_compressed_pos_ = src->pos();
}
void FramedSnappyReaderBase::Done() {
if (ABSL_PREDICT_FALSE(truncated_)) {
Reader& src = *SrcReader();
FailWithoutAnnotation(
AnnotateOverSrc(src.AnnotateStatus(absl::InvalidArgumentError(
"Truncated FramedSnappy-compressed stream"))));
}
PullableReader::Done();
uncompressed_ = Buffer();
}
inline bool FramedSnappyReaderBase::FailInvalidStream(
absl::string_view message) {
return Fail(absl::InvalidArgumentError(
absl::StrCat("Invalid FramedSnappy-compressed stream: ", message)));
}
absl::Status FramedSnappyReaderBase::AnnotateStatusImpl(absl::Status status) {
if (is_open()) {
if (ABSL_PREDICT_FALSE(truncated_)) {
status =
Annotate(status, "reading truncated FramedSnappy-compressed stream");
}
Reader& src = *SrcReader();
status = src.AnnotateStatus(std::move(status));
}
// The status might have been annotated by `src` with the compressed position.
// Clarify that the current position is the uncompressed position instead of
// delegating to `PullableReader::AnnotateStatusImpl()`.
return AnnotateOverSrc(std::move(status));
}
absl::Status FramedSnappyReaderBase::AnnotateOverSrc(absl::Status status) {
if (is_open()) {
return Annotate(status, absl::StrCat("at uncompressed byte ", pos()));
}
return status;
}
bool FramedSnappyReaderBase::PullBehindScratch(size_t recommended_length) {
RIEGELI_ASSERT_EQ(available(), 0u)
<< "Failed precondition of PullableReader::PullBehindScratch(): "
"some data available, use Pull() instead";
RIEGELI_ASSERT(!scratch_used())
<< "Failed precondition of PullableReader::PullBehindScratch(): "
"scratch used";
if (ABSL_PREDICT_FALSE(!ok())) return false;
Reader& src = *SrcReader();
truncated_ = false;
while (src.Pull(sizeof(uint32_t))) {
const uint32_t chunk_header = ReadLittleEndian<uint32_t>(src.cursor());
const uint8_t chunk_type = static_cast<uint8_t>(chunk_header);
const size_t chunk_length = IntCast<size_t>(chunk_header >> 8);
if (ABSL_PREDICT_FALSE(!src.Pull(sizeof(uint32_t) + chunk_length))) {
set_buffer();
if (ABSL_PREDICT_FALSE(!src.ok())) {
return FailWithoutAnnotation(AnnotateOverSrc(src.status()));
}
truncated_ = true;
return false;
}
if (ABSL_PREDICT_FALSE(src.pos() == 0 &&
chunk_type != 0xff /* Stream identifier */)) {
set_buffer();
return FailInvalidStream("missing stream identifier");
}
switch (chunk_type) {
case 0x00: { // Compressed data.
if (ABSL_PREDICT_FALSE(chunk_length < sizeof(uint32_t))) {
set_buffer();
return FailInvalidStream("compressed data too short");
}
const uint32_t checksum =
ReadLittleEndian<uint32_t>(src.cursor() + sizeof(uint32_t));
const char* const compressed_data = src.cursor() + 2 * sizeof(uint32_t);
const size_t compressed_length = chunk_length - sizeof(uint32_t);
size_t uncompressed_length;
if (ABSL_PREDICT_FALSE(!snappy::GetUncompressedLength(
compressed_data, compressed_length, &uncompressed_length))) {
set_buffer();
return FailInvalidStream("invalid uncompressed length");
}
if (ABSL_PREDICT_FALSE(uncompressed_length > snappy::kBlockSize)) {
set_buffer();
return FailInvalidStream("uncompressed length too large");
}
uncompressed_.Reset(uncompressed_length);
if (ABSL_PREDICT_FALSE(!snappy::RawUncompress(
compressed_data, compressed_length, uncompressed_.data()))) {
set_buffer();
return FailInvalidStream("invalid compressed data");
}
if (ABSL_PREDICT_FALSE(
MaskChecksum(static_cast<uint32_t>(absl::ComputeCrc32c(
absl::string_view(uncompressed_.data(),
uncompressed_length)))) != checksum)) {
set_buffer();
return FailInvalidStream(
"Invalid FramedSnappy-compressed stream: wrong checksum");
}
src.move_cursor(sizeof(uint32_t) + chunk_length);
if (ABSL_PREDICT_FALSE(uncompressed_length == 0)) continue;
const Position max_length =
std::numeric_limits<Position>::max() - limit_pos();
if (ABSL_PREDICT_FALSE(uncompressed_length > max_length)) {
set_buffer(uncompressed_.data(), IntCast<size_t>(max_length));
move_limit_pos(available());
return FailOverflow();
}
set_buffer(uncompressed_.data(), uncompressed_length);
move_limit_pos(available());
return true;
}
case 0x01: { // Uncompressed data.
if (ABSL_PREDICT_FALSE(chunk_length < sizeof(uint32_t))) {
set_buffer();
return FailInvalidStream("uncompressed data too short");
}
const uint32_t checksum =
ReadLittleEndian<uint32_t>(src.cursor() + sizeof(uint32_t));
const char* const uncompressed_data =
src.cursor() + 2 * sizeof(uint32_t);
const size_t uncompressed_length = chunk_length - sizeof(uint32_t);
if (ABSL_PREDICT_FALSE(uncompressed_length > snappy::kBlockSize)) {
set_buffer();
return FailInvalidStream("uncompressed length too large");
}
if (ABSL_PREDICT_FALSE(
MaskChecksum(static_cast<uint32_t>(absl::ComputeCrc32c(
absl::string_view(uncompressed_data,
uncompressed_length)))) != checksum)) {
set_buffer();
return FailInvalidStream("wrong checksum");
}
src.move_cursor(sizeof(uint32_t) + chunk_length);
if (ABSL_PREDICT_FALSE(uncompressed_length == 0)) continue;
const Position max_length =
std::numeric_limits<Position>::max() - limit_pos();
if (ABSL_PREDICT_FALSE(uncompressed_length > max_length)) {
set_buffer(uncompressed_data, IntCast<size_t>(max_length));
move_limit_pos(available());
return FailOverflow();
}
set_buffer(uncompressed_data, uncompressed_length);
move_limit_pos(available());
return true;
}
case 0xff: // Stream identifier.
if (ABSL_PREDICT_FALSE(
absl::string_view(src.cursor() + sizeof(uint32_t),
chunk_length) !=
absl::string_view("sNaPpY", 6))) {
set_buffer();
return FailInvalidStream("invalid stream identifier");
}
src.move_cursor(sizeof(uint32_t) + chunk_length);
continue;
default:
if (ABSL_PREDICT_FALSE(chunk_type < 0x80)) {
set_buffer();
return FailInvalidStream("reserved unskippable chunk");
}
src.move_cursor(sizeof(uint32_t) + chunk_length);
continue;
}
}
set_buffer();
if (ABSL_PREDICT_FALSE(!src.ok())) {
return FailWithoutAnnotation(AnnotateOverSrc(src.status()));
}
if (ABSL_PREDICT_FALSE(src.available() > 0)) truncated_ = true;
return false;
}
bool FramedSnappyReaderBase::ToleratesReadingAhead() {
Reader* const src = SrcReader();
return src != nullptr && src->ToleratesReadingAhead();
}
bool FramedSnappyReaderBase::SupportsRewind() {
Reader* const src = SrcReader();
return src != nullptr && src->SupportsRewind();
}
bool FramedSnappyReaderBase::SeekBehindScratch(Position new_pos) {
RIEGELI_ASSERT(new_pos < start_pos() || new_pos > limit_pos())
<< "Failed precondition of PullableReader::SeekBehindScratch(): "
"position in the buffer, use Seek() instead";
RIEGELI_ASSERT(!scratch_used())
<< "Failed precondition of PullableReader::SeekBehindScratch(): "
"scratch used";
if (new_pos <= limit_pos()) {
// Seeking backwards.
if (ABSL_PREDICT_FALSE(!ok())) return false;
Reader& src = *SrcReader();
truncated_ = false;
set_buffer();
set_limit_pos(0);
if (ABSL_PREDICT_FALSE(!src.Seek(initial_compressed_pos_))) {
return FailWithoutAnnotation(
AnnotateOverSrc(src.StatusOrAnnotate(absl::DataLossError(
"FramedSnappy-compressed stream got truncated"))));
}
if (ABSL_PREDICT_FALSE(!ok())) return false;
if (new_pos == 0) return true;
}
return PullableReader::SeekBehindScratch(new_pos);
}
bool FramedSnappyReaderBase::SupportsNewReader() {
Reader* const src = SrcReader();
return src != nullptr && src->SupportsNewReader();
}
std::unique_ptr<Reader> FramedSnappyReaderBase::NewReaderImpl(
Position initial_pos) {
if (ABSL_PREDICT_FALSE(!ok())) return nullptr;
// `NewReaderImpl()` is thread-safe from this point
// if `SrcReader()->SupportsNewReader()`.
Reader& src = *SrcReader();
std::unique_ptr<Reader> compressed_reader =
src.NewReader(initial_compressed_pos_);
if (ABSL_PREDICT_FALSE(compressed_reader == nullptr)) {
FailWithoutAnnotation(AnnotateOverSrc(src.status()));
return nullptr;
}
std::unique_ptr<Reader> reader =
std::make_unique<FramedSnappyReader<std::unique_ptr<Reader>>>(
std::move(compressed_reader));
reader->Seek(initial_pos);
return reader;
}
bool RecognizeFramedSnappy(Reader& src) {
const absl::string_view kSignature(
"\xff\x06\x00\x00"
"sNaPpY",
10);
return src.Pull(kSignature.size()) &&
absl::string_view(src.cursor(), kSignature.size()) == kSignature;
}
} // namespace riegeli