// Copyright 2008 Google Inc. // Author: Lincoln Smith // // 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. // // Implements a Decoder for the format described in // RFC 3284 - The VCDIFF Generic Differencing and Compression Data Format. // The RFC text can be found at http://www.faqs.org/rfcs/rfc3284.html // // The RFC describes the possibility of using a secondary compressor // to further reduce the size of each section of the VCDIFF output. // That feature is not supported in this implementation of the encoder // and decoder. // No secondary compressor types have been publicly registered with // the IANA at http://www.iana.org/assignments/vcdiff-comp-ids // in the more than five years since the registry was created, so there // is no standard set of compressor IDs which would be generated by other // encoders or accepted by other decoders. #include <config.h> #include "google/vcdecoder.h" #include <stddef.h> // size_t, ptrdiff_t #include <stdint.h> // int32_t #include <string.h> // memcpy, memset #include <memory> // auto_ptr #include <string> #include "addrcache.h" #include "checksum.h" #include "codetable.h" #include "decodetable.h" #include "headerparser.h" #include "logging.h" #include "google/output_string.h" #include "varint_bigendian.h" #include "vcdiff_defs.h" namespace open_vcdiff { // This class is used to parse delta file windows as described // in RFC sections 4.2 and 4.3. Its methods are not thread-safe. // // Here is the window format copied from the RFC: // // Window1 // Win_Indicator - byte // [Source segment size] - integer // [Source segment position] - integer // The delta encoding of the target window // Length of the delta encoding - integer // The delta encoding // Size of the target window - integer // Delta_Indicator - byte // Length of data for ADDs and RUNs - integer // Length of instructions and sizes - integer // Length of addresses for COPYs - integer // Data section for ADDs and RUNs - array of bytes // Instructions and sizes section - array of bytes // Addresses section for COPYs - array of bytes // Window2 // ... // // Sample usage: // // VCDiffDeltaFileWindow delta_window_; // delta_window_.Init(parent); // ParseableChunk parseable_chunk(input_buffer, // input_size, // leftover_unencoded_bytes); // switch (delta_window_.DecodeWindows(&parseable_chunk)) { // case RESULT_END_OF_DATA: // <Read more input and retry DecodeWindows later.> // case RESULT_ERROR: // <Handle error case. An error log message has already been generated.> // } // // DecodeWindows consumes as many windows from the input as it can. It only // needs to be placed within a loop if the loop is used to obtain more input // (delta file) data. // class VCDiffDeltaFileWindow { public: VCDiffDeltaFileWindow(); ~VCDiffDeltaFileWindow(); // Init() should be called immediately after constructing the // VCDiffDeltaFileWindow(). It must be called before DecodeWindows() can be // invoked, or an error will occur. void Init(VCDiffStreamingDecoderImpl* parent); // Resets the pointers to the data sections in the current window. void Reset(); bool UseCodeTable(const VCDiffCodeTableData& code_table_data, unsigned char max_mode) { return reader_.UseCodeTable(code_table_data, max_mode); } // Decodes as many delta windows as possible using the input data from // *parseable_chunk. Appends the decoded target windows to // parent_->decoded_target(). Returns RESULT_SUCCESS on success, or // RESULT_END_OF_DATA if the end of input was reached before the entire window // could be decoded and more input is expected (only possible if // IsInterleaved() is true), or RESULT_ERROR if an error occurred during // decoding. In the RESULT_ERROR case, the value of parseable_chunk->pointer_ // is undefined; otherwise, parseable_chunk->Advance() is called to point to // the input data position just after the data that has been decoded. // // If planned_target_file_size is not set to kUnlimitedBytes, then the decoder // expects *exactly* this number of target bytes to be decoded from one or // more delta file windows. If this number is met exactly after finishing a // delta window, this function will return RESULT_SUCCESS without processing // any more bytes from data_pointer. If this number is exceeded while // decoding a window, but was not met before starting that window, // then RESULT_ERROR will be returned. // VCDiffResult DecodeWindows(ParseableChunk* parseable_chunk); bool FoundWindowHeader() const { return found_header_; } bool MoreDataExpected() const { // When parsing an interleaved-format delta file, // every time DecodeBody() exits, interleaved_bytes_expected_ // will be decremented by the number of bytes parsed. If it // reaches zero, then there is no more data expected because // the size of the interleaved section (given in the window // header) has been reached. return IsInterleaved() && (interleaved_bytes_expected_ > 0); } size_t target_window_start_pos() const { return target_window_start_pos_; } void set_target_window_start_pos(size_t new_start_pos) { target_window_start_pos_ = new_start_pos; } // Returns the number of bytes remaining to be decoded in the target window. // If not in the process of decoding a window, returns 0. size_t TargetBytesRemaining(); private: // Reads the header of the window section as described in RFC sections 4.2 and // 4.3, up to and including the value "Length of addresses for COPYs". If the // entire header is found, this function sets up the DeltaWindowSections // instructions_and_sizes_, data_for_add_and_run_, and addresses_for_copy_ so // that the decoder can begin decoding the opcodes in these sections. Returns // RESULT_ERROR if an error occurred, or RESULT_END_OF_DATA if the end of // available data was reached before the entire header could be read. (The // latter may be an error condition if there is no more data available.) // Otherwise, returns RESULT_SUCCESS and advances parseable_chunk past the // parsed header. // VCDiffResult ReadHeader(ParseableChunk* parseable_chunk); // After the window header has been parsed as far as the Delta_Indicator, // this function is called to parse the following delta window header fields: // // Length of data for ADDs and RUNs - integer (VarintBE format) // Length of instructions and sizes - integer (VarintBE format) // Length of addresses for COPYs - integer (VarintBE format) // // If has_checksum_ is true, it also looks for the following element: // // Adler32 checksum - unsigned 32-bit integer (VarintBE format) // // It sets up the DeltaWindowSections instructions_and_sizes_, // data_for_add_and_run_, and addresses_for_copy_. If the interleaved format // is being used, all three sections will include the entire window body; if // the standard format is used, three non-overlapping window sections will be // defined. Returns RESULT_ERROR if an error occurred, or RESULT_END_OF_DATA // if standard format is being used and there is not enough input data to read // the entire window body. Otherwise, returns RESULT_SUCCESS. VCDiffResult SetUpWindowSections(VCDiffHeaderParser* header_parser); // Decodes the body of the window section as described in RFC sections 4.3, // including the sections "Data section for ADDs and RUNs", "Instructions // and sizes section", and "Addresses section for COPYs". These sections // must already have been set up by ReadWindowHeader(). Returns a // non-negative value on success, or RESULT_END_OF_DATA if the end of input // was reached before the entire window could be decoded (only possible if // IsInterleaved() is true), or RESULT_ERROR if an error occurred during // decoding. Appends as much of the decoded target window as possible to // parent->decoded_target(). // int DecodeBody(ParseableChunk* parseable_chunk); // Returns the number of bytes already decoded into the target window. size_t TargetBytesDecoded(); // Decodes a single ADD instruction, updating parent_->decoded_target_. VCDiffResult DecodeAdd(size_t size); // Decodes a single RUN instruction, updating parent_->decoded_target_. VCDiffResult DecodeRun(size_t size); // Decodes a single COPY instruction, updating parent_->decoded_target_. VCDiffResult DecodeCopy(size_t size, unsigned char mode); // When using the interleaved format, this function is called both on parsing // the header and on resuming after a RESULT_END_OF_DATA was returned from a // previous call to DecodeBody(). It sets up all three section pointers to // reference the same interleaved stream of instructions, sizes, addresses, // and data. These pointers must be reset every time that work resumes on a // delta window, because the input data string may have been changed or // resized since DecodeBody() last returned. void UpdateInterleavedSectionPointers(const char* data_pos, const char* data_end) { const ptrdiff_t available_data = data_end - data_pos; // Don't read past the end of currently-available data if (available_data > interleaved_bytes_expected_) { instructions_and_sizes_.Init(data_pos, interleaved_bytes_expected_); } else { instructions_and_sizes_.Init(data_pos, available_data); } data_for_add_and_run_.Init(&instructions_and_sizes_); addresses_for_copy_.Init(&instructions_and_sizes_); } // If true, the interleaved format described in AllowInterleaved() is used // for the current delta file. Only valid after ReadWindowHeader() has been // called and returned a positive number (i.e., the whole header was parsed), // but before the window has finished decoding. // bool IsInterleaved() const { // If the sections are interleaved, both addresses_for_copy_ and // data_for_add_and_run_ should point at instructions_and_sizes_. return !addresses_for_copy_.IsOwned(); } // Executes a single COPY or ADD instruction, appending data to // parent_->decoded_target(). void CopyBytes(const char* data, size_t size); // Executes a single RUN instruction, appending data to // parent_->decoded_target(). void RunByte(unsigned char byte, size_t size); // Advance *parseable_chunk to point to the current position in the // instructions/sizes section. If interleaved format is used, then // decrement the number of expected bytes in the instructions/sizes section // by the number of instruction/size bytes parsed. void UpdateInstructionPointer(ParseableChunk* parseable_chunk); // The parent object which was passed to Init(). VCDiffStreamingDecoderImpl* parent_; // This value will be true if VCDiffDeltaFileWindow::ReadDeltaWindowHeader() // has been called and succeeded in parsing the delta window header, but the // entire window has not yet been decoded. bool found_header_; // Contents and length of the current source window. source_segment_ptr_ // will be non-NULL if (a) the window section header for the current window // has been read, but the window has not yet finished decoding; or // (b) the window did not specify a source segment. const char* source_segment_ptr_; size_t source_segment_length_; // The delta encoding window sections as defined in RFC section 4.3. // The pointer for each section will be incremented as data is consumed and // decoded from that section. If the interleaved format is used, // data_for_add_and_run_ and addresses_for_copy_ will both point to // instructions_and_sizes_; otherwise, they will be separate data sections. // DeltaWindowSection instructions_and_sizes_; DeltaWindowSection data_for_add_and_run_; DeltaWindowSection addresses_for_copy_; // The expected bytes left to decode in instructions_and_sizes_. Only used // for the interleaved format. int interleaved_bytes_expected_; // The expected length of the target window once it has been decoded. size_t target_window_length_; // The index in decoded_target at which the first byte of the current // target window was/will be written. size_t target_window_start_pos_; // If has_checksum_ is true, then expected_checksum_ contains an Adler32 // checksum of the target window data. This is an extension included in the // VCDIFF 'S' (SDCH) format, but is not part of the RFC 3284 draft standard. bool has_checksum_; VCDChecksum expected_checksum_; VCDiffCodeTableReader reader_; // Making these private avoids implicit copy constructor & assignment operator VCDiffDeltaFileWindow(const VCDiffDeltaFileWindow&); // NOLINT void operator=(const VCDiffDeltaFileWindow&); }; // *** Inline methods for VCDiffDeltaFileWindow inline VCDiffDeltaFileWindow::VCDiffDeltaFileWindow() : parent_(NULL) { Reset(); } inline VCDiffDeltaFileWindow::~VCDiffDeltaFileWindow() { } inline void VCDiffDeltaFileWindow::Init(VCDiffStreamingDecoderImpl* parent) { parent_ = parent; } class VCDiffStreamingDecoderImpl { public: typedef std::string string; // The default maximum target file size (and target window size) if // SetMaximumTargetFileSize() is not called. static const size_t kDefaultMaximumTargetFileSize = 67108864U; // 64 MB // The largest value that can be passed to SetMaximumTargetWindowSize(). // Using a larger value will result in an error. static const size_t kTargetSizeLimit = 2147483647U; // INT32_MAX // A constant that is the default value for planned_target_file_size_, // indicating that the decoder does not have an expected length // for the target data. static const size_t kUnlimitedBytes = static_cast<size_t>(-3); VCDiffStreamingDecoderImpl(); ~VCDiffStreamingDecoderImpl(); // Resets all member variables to their initial states. void Reset(); // These functions are identical to their counterparts // in VCDiffStreamingDecoder. // void StartDecoding(const char* dictionary_ptr, size_t dictionary_size); bool DecodeChunk(const char* data, size_t len, OutputStringInterface* output_string); bool FinishDecoding(); // If true, the version of VCDIFF used in the current delta file allows // for the interleaved format, in which instructions, addresses and data // are all sent interleaved in the instructions section of each window // rather than being sent in separate sections. This is not part of // the VCDIFF draft standard, so we've defined a special version code // 'S' which implies that this feature is available. Even if interleaving // is supported, it is not mandatory; interleaved format will be implied // if the address and data sections are both zero-length. // bool AllowInterleaved() const { return vcdiff_version_code_ == 'S'; } // If true, the version of VCDIFF used in the current delta file allows // each delta window to contain an Adler32 checksum of the target window data. // If the bit 0x08 (VCD_CHECKSUM) is set in the Win_Indicator flags, then // this checksum will appear as a variable-length integer, just after the // "length of addresses for COPYs" value and before the window data sections. // It is possible for some windows in a delta file to use the checksum feature // and for others not to use it (and leave the flag bit set to 0.) // Just as with AllowInterleaved(), this extension is not part of the draft // standard and is only available when the version code 'S' is specified. // bool AllowChecksum() const { return vcdiff_version_code_ == 'S'; } bool SetMaximumTargetFileSize(size_t new_maximum_target_file_size) { maximum_target_file_size_ = new_maximum_target_file_size; return true; } bool SetMaximumTargetWindowSize(size_t new_maximum_target_window_size) { if (new_maximum_target_window_size > kTargetSizeLimit) { LOG(ERROR) << "Specified maximum target window size " << new_maximum_target_window_size << " exceeds limit of " << kTargetSizeLimit << " bytes" << LOG_ENDL; return false; } maximum_target_window_size_ = new_maximum_target_window_size; return true; } // See description of planned_target_file_size_, below. bool HasPlannedTargetFileSize() const { return planned_target_file_size_ != kUnlimitedBytes; } void SetPlannedTargetFileSize(size_t planned_target_file_size) { planned_target_file_size_ = planned_target_file_size; } void AddToTotalTargetWindowSize(size_t window_size) { total_of_target_window_sizes_ += window_size; } // Checks to see whether the decoded target data has reached its planned size. bool ReachedPlannedTargetFileSize() const { if (!HasPlannedTargetFileSize()) { return false; } // The planned target file size should not have been exceeded. // TargetWindowWouldExceedSizeLimits() ensures that the advertised size of // each target window would not make the target file exceed that limit, and // DecodeBody() will return RESULT_ERROR if the actual decoded output ever // exceeds the advertised target window size. if (total_of_target_window_sizes_ > planned_target_file_size_) { LOG(DFATAL) << "Internal error: Decoded data size " << total_of_target_window_sizes_ << " exceeds planned target file size " << planned_target_file_size_ << LOG_ENDL; return true; } return total_of_target_window_sizes_ == planned_target_file_size_; } // Checks to see whether adding a new target window of the specified size // would exceed the planned target file size, the maximum target file size, // or the maximum target window size. If so, logs an error and returns true; // otherwise, returns false. bool TargetWindowWouldExceedSizeLimits(size_t window_size) const; // Returns the amount of input data passed to the last DecodeChunk() // that was not consumed by the decoder. This is essential if // SetPlannedTargetFileSize() is being used, in order to preserve the // remaining input data stream once the planned target file has been decoded. size_t GetUnconsumedDataSize() const { return unparsed_bytes_.size(); } // This function will return true if the decoder has parsed a complete delta // file header plus zero or more delta file windows, with no data left over. // It will also return true if no delta data at all was decoded. If these // conditions are not met, then FinishDecoding() should not be called. bool IsDecodingComplete() const { if (!FoundFileHeader()) { // No complete delta file header has been parsed yet. DecodeChunk() // may have received some data that it hasn't yet parsed, in which case // decoding is incomplete. return unparsed_bytes_.empty(); } else if (custom_code_table_decoder_.get()) { // The decoder is in the middle of parsing a custom code table. return false; } else if (delta_window_.FoundWindowHeader()) { // The decoder is in the middle of parsing an interleaved format delta // window. return false; } else if (ReachedPlannedTargetFileSize()) { // The decoder found exactly the planned number of bytes. In this case // it is OK for unparsed_bytes_ to be non-empty; it contains the leftover // data after the end of the delta file. return true; } else { // No complete delta file window has been parsed yet. DecodeChunk() // may have received some data that it hasn't yet parsed, in which case // decoding is incomplete. return unparsed_bytes_.empty(); } } const char* dictionary_ptr() const { return dictionary_ptr_; } size_t dictionary_size() const { return dictionary_size_; } VCDiffAddressCache* addr_cache() { return addr_cache_.get(); } string* decoded_target() { return &decoded_target_; } bool allow_vcd_target() const { return allow_vcd_target_; } void SetAllowVcdTarget(bool allow_vcd_target) { if (start_decoding_was_called_) { LOG(DFATAL) << "SetAllowVcdTarget() called after StartDecoding()" << LOG_ENDL; return; } allow_vcd_target_ = allow_vcd_target; } // Removes the contents of decoded_target_ that precede the beginning of the // current window. void TruncateToBeginningOfWindow(); private: // Reads the VCDiff delta file header section as described in RFC section 4.1, // except the custom code table data. Returns RESULT_ERROR if an error // occurred, or RESULT_END_OF_DATA if the end of available data was reached // before the entire header could be read. (The latter may be an error // condition if there is no more data available.) Otherwise, advances // data->position_ past the header and returns RESULT_SUCCESS. // VCDiffResult ReadDeltaFileHeader(ParseableChunk* data); // Indicates whether or not the header has already been read. bool FoundFileHeader() const { return addr_cache_.get() != NULL; } // If ReadDeltaFileHeader() finds the VCD_CODETABLE flag set within the delta // file header, this function parses the custom cache sizes and initializes // a nested VCDiffStreamingDecoderImpl object that will be used to parse the // custom code table in ReadCustomCodeTable(). Returns RESULT_ERROR if an // error occurred, or RESULT_END_OF_DATA if the end of available data was // reached before the custom cache sizes could be read. Otherwise, returns // the number of bytes read. // int InitCustomCodeTable(const char* data_start, const char* data_end); // If a custom code table was specified in the header section that was parsed // by ReadDeltaFileHeader(), this function makes a recursive call to another // VCDiffStreamingDecoderImpl object (custom_code_table_decoder_), since the // custom code table is expected to be supplied as an embedded VCDIFF // encoding that uses the standard code table. Returns RESULT_ERROR if an // error occurs, or RESULT_END_OF_DATA if the end of available data was // reached before the entire custom code table could be read. Otherwise, // returns RESULT_SUCCESS and sets *data_ptr to the position after the encoded // custom code table. If the function returns RESULT_SUCCESS or // RESULT_END_OF_DATA, it advances data->position_ past the parsed bytes. // VCDiffResult ReadCustomCodeTable(ParseableChunk* data); // Contents and length of the source (dictionary) data. const char* dictionary_ptr_; size_t dictionary_size_; // This string will be used to store any unparsed bytes left over when // DecodeChunk() reaches the end of its input and returns RESULT_END_OF_DATA. // It will also be used to concatenate those unparsed bytes with the data // supplied to the next call to DecodeChunk(), so that they appear in // contiguous memory. string unparsed_bytes_; // The portion of the target file that has been decoded so far. This will be // used to fill the output string for DecodeChunk(), and will also be used to // execute COPY instructions that reference target data. Since the source // window can come from a range of addresses in the previously decoded target // data, the entire target file needs to be available to the decoder, not just // the current target window. string decoded_target_; // The VCDIFF version byte (also known as "header4") from the // delta file header. unsigned char vcdiff_version_code_; VCDiffDeltaFileWindow delta_window_; std::auto_ptr<VCDiffAddressCache> addr_cache_; // Will be NULL unless a custom code table has been defined. std::auto_ptr<VCDiffCodeTableData> custom_code_table_; // Used to receive the decoded custom code table. string custom_code_table_string_; // If a custom code table is specified, it will be expressed // as an embedded VCDIFF delta file which uses the default code table // as the source file (dictionary). Use a child decoder object // to decode that delta file. std::auto_ptr<VCDiffStreamingDecoderImpl> custom_code_table_decoder_; // If set, then the decoder is expecting *exactly* this number of // target bytes to be decoded from one or more delta file windows. // If this number is exceeded while decoding a window, but was not met // before starting on that window, an error will be reported. // If FinishDecoding() is called before this number is met, an error // will also be reported. This feature is used for decoding the // embedded code table data within a VCDIFF delta file; we want to // stop processing the embedded data once the entire code table has // been decoded, and treat the rest of the available data as part // of the enclosing delta file. size_t planned_target_file_size_; size_t maximum_target_file_size_; size_t maximum_target_window_size_; // Contains the sum of the decoded sizes of all target windows seen so far, // including the expected total size of the current target window in progress // (even if some of the current target window has not yet been decoded.) size_t total_of_target_window_sizes_; // This value is used to ensure the correct order of calls to the interface // functions, i.e., a single call to StartDecoding(), followed by zero or // more calls to DecodeChunk(), followed by a single call to // FinishDecoding(). bool start_decoding_was_called_; // If this value is true then the VCD_TARGET flag can be specified to allow // the source segment to be chosen from the previously-decoded target data. // (This is the default behavior.) If it is false, then specifying the // VCD_TARGET flag is considered an error, and the decoder does not need to // keep in memory any decoded target data prior to the current window. bool allow_vcd_target_; // Making these private avoids implicit copy constructor & assignment operator VCDiffStreamingDecoderImpl(const VCDiffStreamingDecoderImpl&); // NOLINT void operator=(const VCDiffStreamingDecoderImpl&); }; // *** Methods for VCDiffStreamingDecoderImpl const size_t VCDiffStreamingDecoderImpl::kDefaultMaximumTargetFileSize; const size_t VCDiffStreamingDecoderImpl::kUnlimitedBytes; VCDiffStreamingDecoderImpl::VCDiffStreamingDecoderImpl() : maximum_target_file_size_(kDefaultMaximumTargetFileSize), maximum_target_window_size_(kDefaultMaximumTargetFileSize), allow_vcd_target_(true) { delta_window_.Init(this); Reset(); } // Reset() will delete the component objects without reallocating them. VCDiffStreamingDecoderImpl::~VCDiffStreamingDecoderImpl() { Reset(); } void VCDiffStreamingDecoderImpl::Reset() { start_decoding_was_called_ = false; dictionary_ptr_ = NULL; dictionary_size_ = 0; vcdiff_version_code_ = '\0'; planned_target_file_size_ = kUnlimitedBytes; total_of_target_window_sizes_ = 0; addr_cache_.reset(); custom_code_table_.reset(); custom_code_table_decoder_.reset(); delta_window_.Reset(); } void VCDiffStreamingDecoderImpl::TruncateToBeginningOfWindow() { // Conserve the data for the current window that has been partially decoded. decoded_target_.erase(0, delta_window_.target_window_start_pos()); delta_window_.set_target_window_start_pos(0); } void VCDiffStreamingDecoderImpl::StartDecoding(const char* dictionary_ptr, size_t dictionary_size) { if (start_decoding_was_called_) { LOG(DFATAL) << "StartDecoding() called twice without FinishDecoding()" << LOG_ENDL; return; } unparsed_bytes_.clear(); decoded_target_.clear(); // delta_window_.Reset() depends on this Reset(); dictionary_ptr_ = dictionary_ptr; dictionary_size_ = dictionary_size; start_decoding_was_called_ = true; } // Reads the VCDiff delta file header section as described in RFC section 4.1: // // Header1 - byte = 0xD6 (ASCII 'V' | 0x80) // Header2 - byte = 0xC3 (ASCII 'C' | 0x80) // Header3 - byte = 0xC4 (ASCII 'D' | 0x80) // Header4 - byte // Hdr_Indicator - byte // [Secondary compressor ID] - byte // [Length of code table data] - integer // [Code table data] // // Initializes the code table and address cache objects. Returns RESULT_ERROR // if an error occurred, and RESULT_END_OF_DATA if the end of available data was // reached before the entire header could be read. (The latter may be an error // condition if there is no more data available.) Otherwise, returns // RESULT_SUCCESS, and removes the header bytes from the data string. // // It's relatively inefficient to expect this function to parse any number of // input bytes available, down to 1 byte, but it is necessary in case the input // is not a properly formatted VCDIFF delta file. If the entire input consists // of two bytes "12", then we should recognize that it does not match the // initial VCDIFF magic number "VCD" and report an error, rather than waiting // indefinitely for more input that will never arrive. // VCDiffResult VCDiffStreamingDecoderImpl::ReadDeltaFileHeader( ParseableChunk* data) { if (FoundFileHeader()) { return RESULT_SUCCESS; } size_t data_size = data->UnparsedSize(); const DeltaFileHeader* header = reinterpret_cast<const DeltaFileHeader*>(data->UnparsedData()); bool wrong_magic_number = false; switch (data_size) { // Verify only the bytes that are available. default: // Found header contents up to and including VCDIFF version vcdiff_version_code_ = header->header4; if ((vcdiff_version_code_ != 0x00) && // Draft standard VCDIFF (RFC 3284) (vcdiff_version_code_ != 'S')) { // Enhancements for SDCH protocol LOG(ERROR) << "Unrecognized VCDIFF format version" << LOG_ENDL; return RESULT_ERROR; } // fall through case 3: if (header->header3 != 0xC4) { // magic value 'D' | 0x80 wrong_magic_number = true; } // fall through case 2: if (header->header2 != 0xC3) { // magic value 'C' | 0x80 wrong_magic_number = true; } // fall through case 1: if (header->header1 != 0xD6) { // magic value 'V' | 0x80 wrong_magic_number = true; } // fall through case 0: if (wrong_magic_number) { LOG(ERROR) << "Did not find VCDIFF header bytes; " "input is not a VCDIFF delta file" << LOG_ENDL; return RESULT_ERROR; } if (data_size < sizeof(DeltaFileHeader)) return RESULT_END_OF_DATA; } // Secondary compressor not supported. if (header->hdr_indicator & VCD_DECOMPRESS) { LOG(ERROR) << "Secondary compression is not supported" << LOG_ENDL; return RESULT_ERROR; } if (header->hdr_indicator & VCD_CODETABLE) { int bytes_parsed = InitCustomCodeTable( data->UnparsedData() + sizeof(DeltaFileHeader), data->End()); switch (bytes_parsed) { case RESULT_ERROR: return RESULT_ERROR; case RESULT_END_OF_DATA: return RESULT_END_OF_DATA; default: data->Advance(sizeof(DeltaFileHeader) + bytes_parsed); } } else { addr_cache_.reset(new VCDiffAddressCache); // addr_cache_->Init() will be called // from VCDiffStreamingDecoderImpl::DecodeChunk() data->Advance(sizeof(DeltaFileHeader)); } return RESULT_SUCCESS; } int VCDiffStreamingDecoderImpl::InitCustomCodeTable(const char* data_start, const char* data_end) { // A custom code table is being specified. Parse the variable-length // cache sizes and begin parsing the encoded custom code table. int32_t near_cache_size = 0, same_cache_size = 0; VCDiffHeaderParser header_parser(data_start, data_end); if (!header_parser.ParseInt32("size of near cache", &near_cache_size)) { return header_parser.GetResult(); } if (!header_parser.ParseInt32("size of same cache", &same_cache_size)) { return header_parser.GetResult(); } custom_code_table_.reset(new struct VCDiffCodeTableData); memset(custom_code_table_.get(), 0, sizeof(struct VCDiffCodeTableData)); custom_code_table_string_.clear(); addr_cache_.reset(new VCDiffAddressCache(near_cache_size, same_cache_size)); // addr_cache_->Init() will be called // from VCDiffStreamingDecoderImpl::DecodeChunk() // If we reach this point (the start of the custom code table) // without encountering a RESULT_END_OF_DATA condition, then we won't call // ReadDeltaFileHeader() again for this delta file. // // Instantiate a recursive decoder to interpret the custom code table // as a VCDIFF encoding of the default code table. custom_code_table_decoder_.reset(new VCDiffStreamingDecoderImpl); custom_code_table_decoder_->StartDecoding( reinterpret_cast<const char*>( &VCDiffCodeTableData::kDefaultCodeTableData), sizeof(VCDiffCodeTableData::kDefaultCodeTableData)); custom_code_table_decoder_->SetPlannedTargetFileSize( sizeof(*custom_code_table_)); return static_cast<int>(header_parser.ParsedSize()); } VCDiffResult VCDiffStreamingDecoderImpl::ReadCustomCodeTable( ParseableChunk* data) { if (!custom_code_table_decoder_.get()) { return RESULT_SUCCESS; } if (!custom_code_table_.get()) { LOG(DFATAL) << "Internal error: custom_code_table_decoder_ is set," " but custom_code_table_ is NULL" << LOG_ENDL; return RESULT_ERROR; } OutputString<string> output_string(&custom_code_table_string_); if (!custom_code_table_decoder_->DecodeChunk(data->UnparsedData(), data->UnparsedSize(), &output_string)) { return RESULT_ERROR; } if (custom_code_table_string_.length() < sizeof(*custom_code_table_)) { // Skip over the consumed data. data->Finish(); return RESULT_END_OF_DATA; } if (!custom_code_table_decoder_->FinishDecoding()) { return RESULT_ERROR; } if (custom_code_table_string_.length() != sizeof(*custom_code_table_)) { LOG(DFATAL) << "Decoded custom code table size (" << custom_code_table_string_.length() << ") does not match size of a code table (" << sizeof(*custom_code_table_) << ")" << LOG_ENDL; return RESULT_ERROR; } memcpy(custom_code_table_.get(), custom_code_table_string_.data(), sizeof(*custom_code_table_)); custom_code_table_string_.clear(); // Skip over the consumed data. data->FinishExcept(custom_code_table_decoder_->GetUnconsumedDataSize()); custom_code_table_decoder_.reset(); delta_window_.UseCodeTable(*custom_code_table_, addr_cache_->LastMode()); return RESULT_SUCCESS; } namespace { class TrackNewOutputText { public: typedef std::string string; explicit TrackNewOutputText(const string& decoded_target) : decoded_target_(decoded_target), initial_decoded_target_size_(decoded_target.size()) { } void AppendNewOutputText(size_t target_bytes_remaining, OutputStringInterface* output_string) { const size_t bytes_decoded_this_chunk = decoded_target_.size() - initial_decoded_target_size_; if (bytes_decoded_this_chunk > 0) { if (target_bytes_remaining > 0) { // The decoder is midway through decoding a target window. Resize // output_string to match the expected length. The interface guarantees // not to resize the output_string more than once per target window // decoded. output_string->ReserveAdditionalBytes(bytes_decoded_this_chunk + target_bytes_remaining); } output_string->append( decoded_target_.data() + initial_decoded_target_size_, bytes_decoded_this_chunk); } } private: const string& decoded_target_; size_t initial_decoded_target_size_; }; } // anonymous namespace bool VCDiffStreamingDecoderImpl::DecodeChunk( const char* data, size_t len, OutputStringInterface* output_string) { if (!start_decoding_was_called_) { LOG(DFATAL) << "DecodeChunk() called without StartDecoding()" << LOG_ENDL; Reset(); return false; } ParseableChunk parseable_chunk(data, len); if (!unparsed_bytes_.empty()) { unparsed_bytes_.append(data, len); parseable_chunk.SetDataBuffer(unparsed_bytes_.data(), unparsed_bytes_.size()); } TrackNewOutputText output_tracker(decoded_target_); VCDiffResult result = ReadDeltaFileHeader(&parseable_chunk); if (RESULT_SUCCESS == result) { result = ReadCustomCodeTable(&parseable_chunk); } if (RESULT_SUCCESS == result) { result = delta_window_.DecodeWindows(&parseable_chunk); } if (RESULT_ERROR == result) { Reset(); // Don't allow further DecodeChunk calls return false; } unparsed_bytes_.assign(parseable_chunk.UnparsedData(), parseable_chunk.UnparsedSize()); output_tracker.AppendNewOutputText(delta_window_.TargetBytesRemaining(), output_string); if (!allow_vcd_target()) { // VCD_TARGET will never be used to reference target data beyond the start // of the current window, so throw away any earlier target data. TruncateToBeginningOfWindow(); } return true; } // Finishes decoding after all data has been received. Returns true // if decoding of the entire stream was successful. bool VCDiffStreamingDecoderImpl::FinishDecoding() { bool success = true; if (!start_decoding_was_called_) { LOG(WARNING) << "FinishDecoding() called before StartDecoding()," " or called after DecodeChunk() returned false" << LOG_ENDL; success = false; } else if (!IsDecodingComplete()) { LOG(ERROR) << "FinishDecoding() called before parsing entire" " delta file window" << LOG_ENDL; success = false; } // Reset the object state for the next decode operation Reset(); return success; } bool VCDiffStreamingDecoderImpl::TargetWindowWouldExceedSizeLimits( size_t window_size) const { if (window_size > maximum_target_window_size_) { LOG(ERROR) << "Length of target window (" << window_size << ") exceeds limit of " << maximum_target_window_size_ << " bytes" << LOG_ENDL; return true; } if (HasPlannedTargetFileSize()) { // The logical expression to check would be: // // total_of_target_window_sizes_ + window_size > planned_target_file_size_ // // but the addition might cause an integer overflow if target_bytes_to_add // is very large. So it is better to check target_bytes_to_add against // the remaining planned target bytes. size_t remaining_planned_target_file_size = planned_target_file_size_ - total_of_target_window_sizes_; if (window_size > remaining_planned_target_file_size) { LOG(ERROR) << "Length of target window (" << window_size << " bytes) plus previous windows (" << total_of_target_window_sizes_ << " bytes) would exceed planned size of " << planned_target_file_size_ << " bytes" << LOG_ENDL; return true; } } size_t remaining_maximum_target_bytes = maximum_target_file_size_ - total_of_target_window_sizes_; if (window_size > remaining_maximum_target_bytes) { LOG(ERROR) << "Length of target window (" << window_size << " bytes) plus previous windows (" << total_of_target_window_sizes_ << " bytes) would exceed maximum target file size of " << maximum_target_file_size_ << " bytes" << LOG_ENDL; return true; } return false; } // *** Methods for VCDiffDeltaFileWindow void VCDiffDeltaFileWindow::Reset() { found_header_ = false; // Mark the start of the current target window. target_window_start_pos_ = parent_ ? parent_->decoded_target()->size() : 0U; target_window_length_ = 0; source_segment_ptr_ = NULL; source_segment_length_ = 0; instructions_and_sizes_.Invalidate(); data_for_add_and_run_.Invalidate(); addresses_for_copy_.Invalidate(); interleaved_bytes_expected_ = 0; has_checksum_ = false; expected_checksum_ = 0; } VCDiffResult VCDiffDeltaFileWindow::SetUpWindowSections( VCDiffHeaderParser* header_parser) { size_t add_and_run_data_length = 0; size_t instructions_and_sizes_length = 0; size_t addresses_length = 0; if (!header_parser->ParseSectionLengths(has_checksum_, &add_and_run_data_length, &instructions_and_sizes_length, &addresses_length, &expected_checksum_)) { return header_parser->GetResult(); } if (parent_->AllowInterleaved() && (add_and_run_data_length == 0) && (addresses_length == 0)) { // The interleaved format is being used. interleaved_bytes_expected_ = static_cast<int>(instructions_and_sizes_length); UpdateInterleavedSectionPointers(header_parser->UnparsedData(), header_parser->End()); } else { // If interleaved format is not used, then the whole window contents // must be available before decoding can begin. If only part of // the current window is available, then report end of data // and re-parse the whole header when DecodeChunk() is called again. if (header_parser->UnparsedSize() < (add_and_run_data_length + instructions_and_sizes_length + addresses_length)) { return RESULT_END_OF_DATA; } data_for_add_and_run_.Init(header_parser->UnparsedData(), add_and_run_data_length); instructions_and_sizes_.Init(data_for_add_and_run_.End(), instructions_and_sizes_length); addresses_for_copy_.Init(instructions_and_sizes_.End(), addresses_length); if (addresses_for_copy_.End() != header_parser->EndOfDeltaWindow()) { LOG(ERROR) << "The end of the instructions section " "does not match the end of the delta window" << LOG_ENDL; return RESULT_ERROR; } } reader_.Init(instructions_and_sizes_.UnparsedDataAddr(), instructions_and_sizes_.End()); return RESULT_SUCCESS; } // Here are the elements of the delta window header to be parsed, // from section 4 of the RFC: // // Window1 // Win_Indicator - byte // [Source segment size] - integer // [Source segment position] - integer // The delta encoding of the target window // Length of the delta encoding - integer // The delta encoding // Size of the target window - integer // Delta_Indicator - byte // Length of data for ADDs and RUNs - integer // Length of instructions and sizes - integer // Length of addresses for COPYs - integer // Data section for ADDs and RUNs - array of bytes // Instructions and sizes section - array of bytes // Addresses section for COPYs - array of bytes // VCDiffResult VCDiffDeltaFileWindow::ReadHeader( ParseableChunk* parseable_chunk) { std::string* decoded_target = parent_->decoded_target(); VCDiffHeaderParser header_parser(parseable_chunk->UnparsedData(), parseable_chunk->End()); size_t source_segment_position = 0; unsigned char win_indicator = 0; if (!header_parser.ParseWinIndicatorAndSourceSegment( parent_->dictionary_size(), decoded_target->size(), parent_->allow_vcd_target(), &win_indicator, &source_segment_length_, &source_segment_position)) { return header_parser.GetResult(); } has_checksum_ = parent_->AllowChecksum() && (win_indicator & VCD_CHECKSUM); if (!header_parser.ParseWindowLengths(&target_window_length_)) { return header_parser.GetResult(); } if (parent_->TargetWindowWouldExceedSizeLimits(target_window_length_)) { // An error has been logged by TargetWindowWouldExceedSizeLimits(). return RESULT_ERROR; } header_parser.ParseDeltaIndicator(); VCDiffResult setup_return_code = SetUpWindowSections(&header_parser); if (RESULT_SUCCESS != setup_return_code) { return setup_return_code; } // Reserve enough space in the output string for the current target window. decoded_target->reserve(target_window_start_pos_ + target_window_length_); // Get a pointer to the start of the source segment. if (win_indicator & VCD_SOURCE) { source_segment_ptr_ = parent_->dictionary_ptr() + source_segment_position; } else if (win_indicator & VCD_TARGET) { // This assignment must happen after the reserve(). // decoded_target should not be resized again while processing this window, // so source_segment_ptr_ should remain valid. source_segment_ptr_ = decoded_target->data() + source_segment_position; } // The whole window header was found and parsed successfully. found_header_ = true; parseable_chunk->Advance(header_parser.ParsedSize()); parent_->AddToTotalTargetWindowSize(target_window_length_); return RESULT_SUCCESS; } void VCDiffDeltaFileWindow::UpdateInstructionPointer( ParseableChunk* parseable_chunk) { if (IsInterleaved()) { size_t bytes_parsed = instructions_and_sizes_.ParsedSize(); // Reduce expected instruction segment length by bytes parsed interleaved_bytes_expected_ -= static_cast<int>(bytes_parsed); parseable_chunk->Advance(bytes_parsed); } } inline size_t VCDiffDeltaFileWindow::TargetBytesDecoded() { return parent_->decoded_target()->size() - target_window_start_pos_; } size_t VCDiffDeltaFileWindow::TargetBytesRemaining() { if (target_window_length_ == 0) { // There is no window being decoded at present return 0; } else { return target_window_length_ - TargetBytesDecoded(); } } inline void VCDiffDeltaFileWindow::CopyBytes(const char* data, size_t size) { parent_->decoded_target()->append(data, size); } inline void VCDiffDeltaFileWindow::RunByte(unsigned char byte, size_t size) { parent_->decoded_target()->append(size, byte); } VCDiffResult VCDiffDeltaFileWindow::DecodeAdd(size_t size) { if (size > data_for_add_and_run_.UnparsedSize()) { return RESULT_END_OF_DATA; } // Write the next "size" data bytes CopyBytes(data_for_add_and_run_.UnparsedData(), size); data_for_add_and_run_.Advance(size); return RESULT_SUCCESS; } VCDiffResult VCDiffDeltaFileWindow::DecodeRun(size_t size) { if (data_for_add_and_run_.Empty()) { return RESULT_END_OF_DATA; } // Write "size" copies of the next data byte RunByte(*data_for_add_and_run_.UnparsedData(), size); data_for_add_and_run_.Advance(1); return RESULT_SUCCESS; } VCDiffResult VCDiffDeltaFileWindow::DecodeCopy(size_t size, unsigned char mode) { // Keep track of the number of target bytes decoded as a local variable // to avoid recalculating it each time it is needed. size_t target_bytes_decoded = TargetBytesDecoded(); const VCDAddress here_address = static_cast<VCDAddress>(source_segment_length_ + target_bytes_decoded); const VCDAddress decoded_address = parent_->addr_cache()->DecodeAddress( here_address, mode, addresses_for_copy_.UnparsedDataAddr(), addresses_for_copy_.End()); switch (decoded_address) { case RESULT_ERROR: LOG(ERROR) << "Unable to decode address for COPY" << LOG_ENDL; return RESULT_ERROR; case RESULT_END_OF_DATA: return RESULT_END_OF_DATA; default: if ((decoded_address < 0) || (decoded_address > here_address)) { LOG(DFATAL) << "Internal error: unexpected address " << decoded_address << " returned from DecodeAddress, with here_address = " << here_address << LOG_ENDL; return RESULT_ERROR; } break; } size_t address = static_cast<size_t>(decoded_address); if ((address + size) <= source_segment_length_) { // Copy all data from source segment CopyBytes(&source_segment_ptr_[address], size); return RESULT_SUCCESS; } // Copy some data from target window... if (address < source_segment_length_) { // ... plus some data from source segment const size_t partial_copy_size = source_segment_length_ - address; CopyBytes(&source_segment_ptr_[address], partial_copy_size); target_bytes_decoded += partial_copy_size; address += partial_copy_size; size -= partial_copy_size; } address -= source_segment_length_; // address is now based at start of target window const char* const target_segment_ptr = parent_->decoded_target()->data() + target_window_start_pos_; while (size > (target_bytes_decoded - address)) { // Recursive copy that extends into the yet-to-be-copied target data const size_t partial_copy_size = target_bytes_decoded - address; CopyBytes(&target_segment_ptr[address], partial_copy_size); target_bytes_decoded += partial_copy_size; address += partial_copy_size; size -= partial_copy_size; } CopyBytes(&target_segment_ptr[address], size); return RESULT_SUCCESS; } int VCDiffDeltaFileWindow::DecodeBody(ParseableChunk* parseable_chunk) { if (IsInterleaved() && (instructions_and_sizes_.UnparsedData() != parseable_chunk->UnparsedData())) { LOG(DFATAL) << "Internal error: interleaved format is used, but the" " input pointer does not point to the instructions section" << LOG_ENDL; return RESULT_ERROR; } while (TargetBytesDecoded() < target_window_length_) { int32_t decoded_size = VCD_INSTRUCTION_ERROR; unsigned char mode = 0; VCDiffInstructionType instruction = reader_.GetNextInstruction(&decoded_size, &mode); switch (instruction) { case VCD_INSTRUCTION_END_OF_DATA: UpdateInstructionPointer(parseable_chunk); return RESULT_END_OF_DATA; case VCD_INSTRUCTION_ERROR: return RESULT_ERROR; default: break; } const size_t size = static_cast<size_t>(decoded_size); // The value of "size" itself could be enormous (say, INT32_MAX) // so check it individually against the limit to protect against // overflow when adding it to something else. if ((size > target_window_length_) || ((size + TargetBytesDecoded()) > target_window_length_)) { LOG(ERROR) << VCDiffInstructionName(instruction) << " with size " << size << " plus existing " << TargetBytesDecoded() << " bytes of target data exceeds length of target" " window (" << target_window_length_ << " bytes)" << LOG_ENDL; return RESULT_ERROR; } VCDiffResult result = RESULT_SUCCESS; switch (instruction) { case VCD_ADD: result = DecodeAdd(size); break; case VCD_RUN: result = DecodeRun(size); break; case VCD_COPY: result = DecodeCopy(size, mode); break; default: LOG(DFATAL) << "Unexpected instruction type " << instruction << "in opcode stream" << LOG_ENDL; return RESULT_ERROR; } switch (result) { case RESULT_END_OF_DATA: reader_.UnGetInstruction(); UpdateInstructionPointer(parseable_chunk); return RESULT_END_OF_DATA; case RESULT_ERROR: return RESULT_ERROR; case RESULT_SUCCESS: break; } } if (TargetBytesDecoded() != target_window_length_) { LOG(ERROR) << "Decoded target window size (" << TargetBytesDecoded() << " bytes) does not match expected size (" << target_window_length_ << " bytes)" << LOG_ENDL; return RESULT_ERROR; } const char* const target_window_start = parent_->decoded_target()->data() + target_window_start_pos_; if (has_checksum_ && (ComputeAdler32(target_window_start, target_window_length_) != expected_checksum_)) { LOG(ERROR) << "Target data does not match checksum; this could mean " "that the wrong dictionary was used" << LOG_ENDL; return RESULT_ERROR; } if (!instructions_and_sizes_.Empty()) { LOG(ERROR) << "Excess instructions and sizes left over " "after decoding target window" << LOG_ENDL; return RESULT_ERROR; } if (!IsInterleaved()) { // Standard format is being used, with three separate sections for the // instructions, data, and addresses. if (!data_for_add_and_run_.Empty()) { LOG(ERROR) << "Excess ADD/RUN data left over " "after decoding target window" << LOG_ENDL; return RESULT_ERROR; } if (!addresses_for_copy_.Empty()) { LOG(ERROR) << "Excess COPY addresses left over " "after decoding target window" << LOG_ENDL; return RESULT_ERROR; } // Reached the end of the window. Update the ParseableChunk to point to the // end of the addresses section, which is the last section in the window. parseable_chunk->SetPosition(addresses_for_copy_.End()); } else { // Interleaved format is being used. UpdateInstructionPointer(parseable_chunk); } return RESULT_SUCCESS; } VCDiffResult VCDiffDeltaFileWindow::DecodeWindows( ParseableChunk* parseable_chunk) { if (!parent_) { LOG(DFATAL) << "Internal error: VCDiffDeltaFileWindow::DecodeWindows() " "called before VCDiffDeltaFileWindow::Init()" << LOG_ENDL; return RESULT_ERROR; } while (!parseable_chunk->Empty()) { if (!found_header_) { switch (ReadHeader(parseable_chunk)) { case RESULT_END_OF_DATA: return RESULT_END_OF_DATA; case RESULT_ERROR: return RESULT_ERROR; default: // Reset address cache between windows (RFC section 5.1) if (!parent_->addr_cache()->Init()) { LOG(DFATAL) << "Error initializing address cache" << LOG_ENDL; return RESULT_ERROR; } } } else { // We are resuming a window that was partially decoded before a // RESULT_END_OF_DATA was returned. This can only happen on the first // loop iteration, and only if the interleaved format is enabled and used. if (!IsInterleaved()) { LOG(DFATAL) << "Internal error: Resumed decoding of a delta file window" " when interleaved format is not being used" << LOG_ENDL; return RESULT_ERROR; } UpdateInterleavedSectionPointers(parseable_chunk->UnparsedData(), parseable_chunk->End()); reader_.UpdatePointers(instructions_and_sizes_.UnparsedDataAddr(), instructions_and_sizes_.End()); } switch (DecodeBody(parseable_chunk)) { case RESULT_END_OF_DATA: if (MoreDataExpected()) { return RESULT_END_OF_DATA; } else { LOG(ERROR) << "End of data reached while decoding VCDIFF delta file" << LOG_ENDL; // fall through to RESULT_ERROR case } case RESULT_ERROR: return RESULT_ERROR; default: break; // DecodeBody succeeded } // Get ready to read a new delta window Reset(); if (parent_->ReachedPlannedTargetFileSize()) { // Found exactly the length we expected. Stop decoding. return RESULT_SUCCESS; } } return RESULT_SUCCESS; } // *** Methods for VCDiffStreamingDecoder VCDiffStreamingDecoder::VCDiffStreamingDecoder() : impl_(new VCDiffStreamingDecoderImpl) { } VCDiffStreamingDecoder::~VCDiffStreamingDecoder() { delete impl_; } void VCDiffStreamingDecoder::StartDecoding(const char* source, size_t len) { impl_->StartDecoding(source, len); } bool VCDiffStreamingDecoder::DecodeChunkToInterface( const char* data, size_t len, OutputStringInterface* output_string) { return impl_->DecodeChunk(data, len, output_string); } bool VCDiffStreamingDecoder::FinishDecoding() { return impl_->FinishDecoding(); } bool VCDiffStreamingDecoder::SetMaximumTargetFileSize( size_t new_maximum_target_file_size) { return impl_->SetMaximumTargetFileSize(new_maximum_target_file_size); } bool VCDiffStreamingDecoder::SetMaximumTargetWindowSize( size_t new_maximum_target_window_size) { return impl_->SetMaximumTargetWindowSize(new_maximum_target_window_size); } void VCDiffStreamingDecoder::SetAllowVcdTarget(bool allow_vcd_target) { impl_->SetAllowVcdTarget(allow_vcd_target); } bool VCDiffDecoder::DecodeToInterface(const char* dictionary_ptr, size_t dictionary_size, const string& encoding, OutputStringInterface* target) { target->clear(); decoder_.StartDecoding(dictionary_ptr, dictionary_size); if (!decoder_.DecodeChunkToInterface(encoding.data(), encoding.size(), target)) { return false; } return decoder_.FinishDecoding(); } } // namespace open_vcdiff