// 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 #include #include #include #include #include #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(segment.data()); *length = static_cast(segment.size()); byte_count_ += static_cast(segment.size()); return true; } void BackUp(int length) override { ABSL_CHECK(length <= static_cast(segment_.size())); size_t backup = segment_.size() - static_cast(length); back_up_.push_back(segment_.substr(backup)); segment_ = segment_.substr(0, backup); byte_count_ -= static_cast(length); } int64_t ByteCount() const override { return byte_count_; } private: ChunkedString data_; absl::string_view segment_; std::vector back_up_; int64_t byte_count_ = 0; }; class ZeroCopyStreamByteSinkTest : public testing::Test { protected: std::array 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