228 lines
7.2 KiB
C++
228 lines
7.2 KiB
C++
|
// Protocol Buffers - Google's data interchange format
|
||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||
|
// https://developers.google.com/protocol-buffers/
|
||
|
//
|
||
|
// Redistribution and use in source and binary forms, with or without
|
||
|
// modification, are permitted provided that the following conditions are
|
||
|
// met:
|
||
|
//
|
||
|
// * Redistributions of source code must retain the above copyright
|
||
|
// notice, this list of conditions and the following disclaimer.
|
||
|
// * Redistributions in binary form must reproduce the above
|
||
|
// copyright notice, this list of conditions and the following disclaimer
|
||
|
// in the documentation and/or other materials provided with the
|
||
|
// distribution.
|
||
|
// * Neither the name of Google Inc. nor the names of its
|
||
|
// contributors may be used to endorse or promote products derived from
|
||
|
// this software without specific prior written permission.
|
||
|
//
|
||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
||
|
#include "google/protobuf/io/zero_copy_sink.h"
|
||
|
|
||
|
#include <algorithm>
|
||
|
#include <array>
|
||
|
#include <cstdint>
|
||
|
#include <string>
|
||
|
#include <vector>
|
||
|
|
||
|
#include <gtest/gtest.h>
|
||
|
#include "absl/log/absl_check.h"
|
||
|
#include "absl/strings/escaping.h"
|
||
|
#include "absl/strings/str_cat.h"
|
||
|
#include "absl/strings/string_view.h"
|
||
|
|
||
|
namespace google {
|
||
|
namespace protobuf {
|
||
|
namespace io {
|
||
|
namespace zc_sink_internal {
|
||
|
namespace {
|
||
|
|
||
|
class ChunkedString {
|
||
|
public:
|
||
|
explicit ChunkedString(absl::string_view data, size_t skipped_patterns)
|
||
|
: data_(data), skipped_patterns_(skipped_patterns) {}
|
||
|
|
||
|
// Returns the next chunk; empty if out of chunks.
|
||
|
absl::string_view NextChunk() {
|
||
|
if (pattern_bit_idx_ == data_.size()) {
|
||
|
return "";
|
||
|
}
|
||
|
size_t start = pattern_bit_idx_;
|
||
|
do {
|
||
|
++pattern_bit_idx_;
|
||
|
} while (pattern_bit_idx_ < data_.size() &&
|
||
|
(pattern_ >> pattern_bit_idx_ & 1) == 0);
|
||
|
size_t end = pattern_bit_idx_;
|
||
|
return data_.substr(start, end - start);
|
||
|
}
|
||
|
|
||
|
// Resets the stream and runs the next pattern of splits.
|
||
|
bool NextPattern() {
|
||
|
pattern_ += skipped_patterns_;
|
||
|
if (pattern_ >= (1 << data_.size())) {
|
||
|
return false;
|
||
|
}
|
||
|
pattern_bit_idx_ = 0;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// prints out the pattern as a sequence of quoted strings.
|
||
|
std::string PatternAsQuotedString() {
|
||
|
std::string out;
|
||
|
size_t start = 0;
|
||
|
for (size_t i = 0; i <= data_.size(); ++i) {
|
||
|
if (i == data_.size() || (pattern_ >> start & 1) != 0) {
|
||
|
if (!out.empty()) {
|
||
|
absl::StrAppend(&out, " ");
|
||
|
}
|
||
|
absl::StrAppend(
|
||
|
&out, "\"",
|
||
|
absl::CHexEscape(std::string{data_.substr(start, i - start)}),
|
||
|
"\"");
|
||
|
start = i;
|
||
|
}
|
||
|
}
|
||
|
return out;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
absl::string_view data_;
|
||
|
size_t skipped_patterns_;
|
||
|
// pattern_ is a bitset indicating at which indices we insert a seam.
|
||
|
uint64_t pattern_ = 0;
|
||
|
size_t pattern_bit_idx_ = 0;
|
||
|
};
|
||
|
|
||
|
class PatternedOutputStream : public io::ZeroCopyOutputStream {
|
||
|
public:
|
||
|
explicit PatternedOutputStream(ChunkedString data) : data_(data) {}
|
||
|
|
||
|
bool Next(void** buffer, int* length) override {
|
||
|
absl::string_view segment;
|
||
|
if (!back_up_.empty()) {
|
||
|
segment = back_up_.back();
|
||
|
back_up_.pop_back();
|
||
|
} else {
|
||
|
segment_ = data_.NextChunk();
|
||
|
segment = segment_;
|
||
|
}
|
||
|
|
||
|
if (segment_.empty()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// TODO(b/234159981): This is only ever constructed in test code, and only
|
||
|
// from non-const bytes, so this is a valid cast. We need to do this since
|
||
|
// OSS proto does not yet have absl::Span; once we take a full Abseil
|
||
|
// dependency we should use that here instead.
|
||
|
*buffer = const_cast<char*>(segment.data());
|
||
|
*length = static_cast<int>(segment.size());
|
||
|
byte_count_ += static_cast<int64_t>(segment.size());
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void BackUp(int length) override {
|
||
|
ABSL_CHECK(length <= static_cast<int>(segment_.size()));
|
||
|
|
||
|
size_t backup = segment_.size() - static_cast<size_t>(length);
|
||
|
back_up_.push_back(segment_.substr(backup));
|
||
|
segment_ = segment_.substr(0, backup);
|
||
|
byte_count_ -= static_cast<int64_t>(length);
|
||
|
}
|
||
|
|
||
|
int64_t ByteCount() const override { return byte_count_; }
|
||
|
|
||
|
private:
|
||
|
ChunkedString data_;
|
||
|
absl::string_view segment_;
|
||
|
|
||
|
std::vector<absl::string_view> back_up_;
|
||
|
int64_t byte_count_ = 0;
|
||
|
};
|
||
|
|
||
|
class ZeroCopyStreamByteSinkTest : public testing::Test {
|
||
|
protected:
|
||
|
std::array<char, 10> output_{};
|
||
|
absl::string_view output_view_{output_.data(), output_.size()};
|
||
|
ChunkedString output_chunks_{output_view_, 7};
|
||
|
};
|
||
|
|
||
|
TEST_F(ZeroCopyStreamByteSinkTest, WriteExact) {
|
||
|
do {
|
||
|
SCOPED_TRACE(output_chunks_.PatternAsQuotedString());
|
||
|
ChunkedString input("0123456789", 1);
|
||
|
|
||
|
do {
|
||
|
SCOPED_TRACE(input.PatternAsQuotedString());
|
||
|
output_ = {};
|
||
|
PatternedOutputStream output_stream(output_chunks_);
|
||
|
ZeroCopyStreamByteSink byte_sink(&output_stream);
|
||
|
SCOPED_TRACE(input.PatternAsQuotedString());
|
||
|
absl::string_view chunk;
|
||
|
while (!(chunk = input.NextChunk()).empty()) {
|
||
|
byte_sink.Append(chunk.data(), chunk.size());
|
||
|
}
|
||
|
} while (input.NextPattern());
|
||
|
|
||
|
ASSERT_EQ(output_view_, "0123456789");
|
||
|
} while (output_chunks_.NextPattern());
|
||
|
}
|
||
|
|
||
|
TEST_F(ZeroCopyStreamByteSinkTest, WriteShort) {
|
||
|
do {
|
||
|
SCOPED_TRACE(output_chunks_.PatternAsQuotedString());
|
||
|
ChunkedString input("012345678", 1);
|
||
|
|
||
|
do {
|
||
|
SCOPED_TRACE(input.PatternAsQuotedString());
|
||
|
output_ = {};
|
||
|
PatternedOutputStream output_stream(output_chunks_);
|
||
|
ZeroCopyStreamByteSink byte_sink(&output_stream);
|
||
|
SCOPED_TRACE(input.PatternAsQuotedString());
|
||
|
absl::string_view chunk;
|
||
|
while (!(chunk = input.NextChunk()).empty()) {
|
||
|
byte_sink.Append(chunk.data(), chunk.size());
|
||
|
}
|
||
|
} while (input.NextPattern());
|
||
|
|
||
|
ASSERT_EQ(output_view_, absl::string_view("012345678\0", 10));
|
||
|
} while (output_chunks_.NextPattern());
|
||
|
}
|
||
|
|
||
|
TEST_F(ZeroCopyStreamByteSinkTest, WriteLong) {
|
||
|
do {
|
||
|
SCOPED_TRACE(output_chunks_.PatternAsQuotedString());
|
||
|
ChunkedString input("0123456789A", 1);
|
||
|
|
||
|
do {
|
||
|
SCOPED_TRACE(input.PatternAsQuotedString());
|
||
|
output_ = {};
|
||
|
PatternedOutputStream output_stream(output_chunks_);
|
||
|
ZeroCopyStreamByteSink byte_sink(&output_stream);
|
||
|
SCOPED_TRACE(input.PatternAsQuotedString());
|
||
|
absl::string_view chunk;
|
||
|
while (!(chunk = input.NextChunk()).empty()) {
|
||
|
byte_sink.Append(chunk.data(), chunk.size());
|
||
|
}
|
||
|
} while (input.NextPattern());
|
||
|
|
||
|
ASSERT_EQ(output_view_, "0123456789");
|
||
|
} while (output_chunks_.NextPattern());
|
||
|
}
|
||
|
} // namespace
|
||
|
} // namespace zc_sink_internal
|
||
|
} // namespace io
|
||
|
} // namespace protobuf
|
||
|
} // namespace google
|