• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ehrmann / vcdiff-java / 26

18 Apr 2026 05:11PM UTC coverage: 83.839% (+0.2%) from 83.679%
26

push

circleci

ehrmann
Fix Coveralls integration

663 of 814 branches covered (81.45%)

1603 of 1912 relevant lines covered (83.84%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.15
/core/src/main/java/com/davidehrmann/vcdiff/engine/VCDiffStreamingDecoderImpl.java
1
// Copyright 2008-2016 Google Inc., David Ehrmann
2
// Authors: Lincoln Smith, David Ehrmann
3
//
4
// Licensed under the Apache License, Version 2.0 (the "License");
5
// you may not use this file except in compliance with the License.
6
// You may obtain a copy of the License at
7
//
8
//      http://www.apache.org/licenses/LICENSE-2.0
9
//
10
// Unless required by applicable law or agreed to in writing, software
11
// distributed under the License is distributed on an "AS IS" BASIS,
12
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
// See the License for the specific language governing permissions and
14
// limitations under the License.
15
//
16
// Implements a Decoder for the format described in
17
// RFC 3284 - The VCDIFF Generic Differencing and Compression Data Format.
18
// The RFC text can be found at http://www.faqs.org/rfcs/rfc3284.html
19
//
20
// The RFC describes the possibility of using a secondary compressor
21
// to further reduce the size of each section of the VCDIFF output.
22
// That feature is not supported in this implementation of the encoder
23
// and decoder.
24
// No secondary compressor types have been publicly registered with
25
// the IANA at http://www.iana.org/assignments/vcdiff-comp-ids
26
// in the more than five years since the registry was created, so there
27
// is no standard set of compressor IDs which would be generated by other
28

29
package com.davidehrmann.vcdiff.engine;
30

31
import com.davidehrmann.vcdiff.VCDiffStreamingDecoder;
32
import org.slf4j.Logger;
33
import org.slf4j.LoggerFactory;
34

35
import java.io.ByteArrayOutputStream;
36
import java.io.IOException;
37
import java.io.OutputStream;
38
import java.nio.ByteBuffer;
39

40
import static com.davidehrmann.vcdiff.engine.VCDiffHeaderParser.RESULT_END_OF_DATA;
41
import static com.davidehrmann.vcdiff.engine.VCDiffHeaderParser.RESULT_SUCCESS;
42
import static com.davidehrmann.vcdiff.engine.VCDiffHeaderParser.VCD_CODETABLE;
43
import static com.davidehrmann.vcdiff.engine.VCDiffHeaderParser.VCD_DECOMPRESS;
44

45
@SuppressWarnings("ALL")
46
public class VCDiffStreamingDecoderImpl implements VCDiffStreamingDecoder {
47
    private static final Logger LOGGER = LoggerFactory.getLogger(VCDiffStreamingDecoderImpl.class);
1✔
48

49
    /**
50
     * The default maximum target file size (and target window size) if
51
     * setMaximumTargetFileSize() is not called.
52
     */
53
    public static final int DEFAULT_MAXIMUM_TARGET_FILE_SIZE = 67108864;  // 64 MB
54

55
    /**
56
     * The largest value that can be passed to setMaximumTargetWindowSize().
57
     * Using a larger value will result in an error.
58
     */
59
    public static final int TARGET_SIZE_LIMIT = Integer.MAX_VALUE;
60

61
    /**
62
     * A constant that is the default value for plannedTargetFileSize,
63
     * indicating that the decoder does not have an expected length
64
     * for the target data.
65
     */
66
    public static final int UNLIMITED_BYTES = -3;
67

68
    // Contents and length of the source (dictionary) data.
69
    private ByteBuffer dictionary;
70

71
    // This string will be used to store any unparsed bytes left over when
72
    // decodeChunk() reaches the end of its input and returns RESULT_END_OF_DATA.
73
    // It will also be used to concatenate those unparsed bytes with the data
74
    // supplied to the next call to decodeChunk(), so that they appear in
75
    // contiguous memory.
76
    private ByteBuffer unparsedBytes = ByteBuffer.allocate(0);
1✔
77

78
    // The portion of the target file that has been decoded so far.  This will be
79
    // used to fill the output string for decodeChunk(), and will also be used to
80
    // execute COPY instructions that reference target data.  Since the source
81
    // window can come from a range of addresses in the previously decoded target
82
    // data, the entire target file needs to be available to the decoder, not just
83
    // the current target window.
84
    private final DecoratedByteArrayOutputStream decodedTarget = new DecoratedByteArrayOutputStream(512); //IoBuffer.allocate(512);
1✔
85

86
    // The VCDIFF version byte (also known as "header4") from the
87
    // delta file header.
88
    private byte vcdiffVersionCode;
89

90
    private VCDiffDeltaFileWindow deltaWindow;
91

92
    private VCDiffAddressCache addrCache;
93

94
    // Will be NULL unless a custom code table has been defined.
95
    private VCDiffCodeTableData custom_code_table_;
96

97
    // Used to receive the decoded custom code table.
98
    private final ByteArrayOutputStream custom_code_table_string_ = new ByteArrayOutputStream(1024);
1✔
99

100
    // If a custom code table is specified, it will be expressed
101
    // as an embedded VCDIFF delta file which uses the default code table
102
    // as the source file (dictionary).  Use a child decoder object
103
    // to decode that delta file.
104
    private VCDiffStreamingDecoderImpl custom_code_table_decoder_;
105

106
    // If set, then the decoder is expecting *exactly* this number of
107
    // target bytes to be decoded from one or more delta file windows.
108
    // If this number is exceeded while decoding a window, but was not met
109
    // before starting on that window, an error will be reported.
110
    // If finishDecoding() is called before this number is met, an error
111
    // will also be reported.  This feature is used for decoding the
112
    // embedded code table data within a VCDIFF delta file; we want to
113
    // stop processing the embedded data once the entire code table has
114
    // been decoded, and treat the rest of the available data as part
115
    // of the enclosing delta file.
116
    private int plannedTargetFileSize;
117

118
    private long maximumTargetFileSize = DEFAULT_MAXIMUM_TARGET_FILE_SIZE;
1✔
119

120
    private int maximumTargetWindowSize = DEFAULT_MAXIMUM_TARGET_FILE_SIZE;
1✔
121

122
    // Contains the sum of the decoded sizes of all target windows seen so far,
123
    // including the expected total size of the current target window in progress
124
    // (even if some of the current target window has not yet been decoded.)
125
    private long totalOfTargetWindowSizes;
126

127
    // Contains the byte position within decodedTarget of the first data that
128
    // has not yet been output by appendNewOutputText().
129
    private int decodedTargetOutputPosition;
130

131
    // This value is used to ensure the correct order of calls to the interface
132
    // functions, i.e., a single call to startDecoding(), followed by zero or
133
    // more calls to decodeChunk(), followed by a single call to
134
    // finishDecoding().
135
    private boolean startDecodingWasCalled;
136

137
    // If this value is true then the VCD_TARGET flag can be specified to allow
138
    // the source segment to be chosen from the previously-decoded target data.
139
    // (This is the default behavior.)  If it is false, then specifying the
140
    // VCD_TARGET flag is considered an error, and the decoder does not need to
141
    // keep in memory any decoded target data prior to the current window.
142
    private boolean allowVcdTarget = true;
1✔
143

144
    public VCDiffStreamingDecoderImpl() {
1✔
145
        deltaWindow = new VCDiffDeltaFileWindow(this);
1✔
146
        reset();
1✔
147
    }
1✔
148

149
    // Resets all member variables to their initial states.
150
    public void reset() {
151
        startDecodingWasCalled = false;
1✔
152
        dictionary = null;
1✔
153
        vcdiffVersionCode = 0;
1✔
154
        plannedTargetFileSize = UNLIMITED_BYTES;
1✔
155
        totalOfTargetWindowSizes = 0;
1✔
156
        addrCache = null;
1✔
157
        custom_code_table_ = null;
1✔
158
        custom_code_table_decoder_ = null;
1✔
159
        deltaWindow.Reset();
1✔
160
        decodedTargetOutputPosition = 0;
1✔
161
    }
1✔
162

163
    public void startDecoding(byte[] dictionary) {
164
        startDecoding(ByteBuffer.wrap(dictionary));
1✔
165
    }
1✔
166

167
    public void startDecoding(ByteBuffer dictionary) {
168
        if (startDecodingWasCalled) {
1!
169
            throw new IllegalStateException("startDecoding() called twice without finishDecoding()");
×
170
        }
171

172
        unparsedBytes = ByteBuffer.allocate(0);
1✔
173
        decodedTarget.reset();  // deltaWindow.reset() depends on this
1✔
174
        reset();
1✔
175
        this.dictionary = dictionary;
1✔
176
        startDecodingWasCalled = true;
1✔
177
    }
1✔
178

179
    public void decodeChunk(byte[] data, int offset, int len, OutputStream out) throws IOException {
180
        decodeChunk(ByteBuffer.wrap(data, offset, len), out);
1✔
181
    }
1✔
182

183
    public void decodeChunk(ByteBuffer data, OutputStream out) throws IOException {
184
        if (!startDecodingWasCalled) {
1!
185
            reset();
×
186
            throw new IOException("decodeChunk() called without startDecoding()");
×
187
        }
188
        // TODO: there's a lot of room for optimization here
189
        ByteBuffer parseable_chunk = ByteBuffer.allocate(unparsedBytes.remaining() + data.remaining());
1✔
190
        parseable_chunk.put(unparsedBytes);
1✔
191
        parseable_chunk.put(data);
1✔
192
        parseable_chunk.flip();
1✔
193
        unparsedBytes = parseable_chunk.duplicate();
1✔
194

195
        try {
196
            int result = readDeltaFileHeader(parseable_chunk);
1✔
197
            if (RESULT_SUCCESS == result) {
1✔
198
                result = readCustomCodeTable(parseable_chunk);
1✔
199
            }
200
            if (RESULT_SUCCESS == result) {
1✔
201
                while (parseable_chunk.hasRemaining()) {
1✔
202
                    result = deltaWindow.DecodeWindow(parseable_chunk);
1✔
203
                    if (RESULT_SUCCESS != result) {
1✔
204
                        break;
1✔
205
                    }
206
                    if (reachedPlannedTargetFileSize()) {
1✔
207
                        // Found exactly the length we expected.  Stop decoding.
208
                        break;
1✔
209
                    }
210
                    if (!allowVcdTarget()) {
1✔
211
                        // VCD_TARGET will never be used to reference target data before the
212
                        // start of the current window, so flush and clear the contents of
213
                        // decodedTarget.
214
                        flushDecodedTarget(out);
1✔
215
                    }
216
                }
217
            }
218
        } catch (IOException e) {
1✔
219
            reset();  // Don't allow further decodeChunk calls
1✔
220
            throw e;
1✔
221
        }
1✔
222

223
        unparsedBytes = parseable_chunk;
1✔
224
        appendNewOutputText(out);
1✔
225
    }
1✔
226

227
    public void decodeChunk(byte[] data, OutputStream out) throws IOException {
228
        decodeChunk(ByteBuffer.wrap(data), out);
1✔
229
    }
1✔
230

231
    public void finishDecoding() throws IOException {
232
        try {
233
            if (!startDecodingWasCalled) {
1!
234
                throw new IOException("finishDecoding() called before startDecoding(), or called after decodeChunk() returned false");
×
235
            } else if (!isDecodingComplete()) {
1✔
236
                throw new IOException("finishDecoding() called before parsing entire delta file window");
1✔
237
            }
238
        } finally {
239
            // reset the object state for the next decode operation
240
            reset();
1✔
241
        }
242
    }
1✔
243

244
    // If true, the version of VCDIFF used in the current delta file allows
245
    // for the interleaved format, in which instructions, addresses and data
246
    // are all sent interleaved in the instructions section of each window
247
    // rather than being sent in separate sections.  This is not part of
248
    // the VCDIFF draft standard, so we've defined a special version code
249
    // 'S' which implies that this feature is available.  Even if interleaving
250
    // is supported, it is not mandatory; interleaved format will be implied
251
    // if the address and data sections are both zero-length.
252
    //
253
    public boolean allowInterleaved() { return vcdiffVersionCode == 'S'; }
1✔
254

255
    // If true, the version of VCDIFF used in the current delta file allows
256
    // each delta window to contain an Adler32 checksum of the target window data.
257
    // If the bit 0x08 (VCD_CHECKSUM) is set in the Win_Indicator flags, then
258
    // this checksum will appear as a variable-length integer, just after the
259
    // "length of addresses for COPYs" value and before the window data sections.
260
    // It is possible for some windows in a delta file to use the checksum feature
261
    // and for others not to use it (and leave the flag bit set to 0.)
262
    // Just as with allowInterleaved(), this extension is not part of the draft
263
    // standard and is only available when the version code 'S' is specified.
264
    public boolean allowChecksum() { return vcdiffVersionCode == 'S'; }
1✔
265

266
    public boolean setMaximumTargetFileSize(long newMaximumTargetFileSize) {
267
        maximumTargetFileSize = newMaximumTargetFileSize;
1✔
268
        return true;
1✔
269
    }
270

271
    public boolean setMaximumTargetWindowSize(int newMaximumTargetWindowSize) {
272
        maximumTargetWindowSize = newMaximumTargetWindowSize;
1✔
273
        return true;
1✔
274
    }
275

276
    // See description of plannedTargetFileSize, below.
277
    public boolean hasPlannedTargetFileSize() {
278
        return plannedTargetFileSize != UNLIMITED_BYTES;
1✔
279
    }
280

281
    public void setPlannedTargetFileSize(int planned_target_file_size) {
282
        plannedTargetFileSize = planned_target_file_size;
1✔
283
    }
1✔
284

285
    public void addToTotalTargetWindowSize(int window_size) {
286
        totalOfTargetWindowSizes += window_size;
1✔
287
    }
1✔
288

289
    // Checks to see whether the decoded target data has reached its planned size.
290
    public boolean reachedPlannedTargetFileSize() {
291
        if (!hasPlannedTargetFileSize()) {
1✔
292
            return false;
1✔
293
        }
294
        // The planned target file size should not have been exceeded.
295
        // targetWindowWouldExceedSizeLimits() ensures that the advertised size of
296
        // each target window would not make the target file exceed that limit, and
297
        // DecodeBody() will return RESULT_ERROR if the actual decoded output ever
298
        // exceeds the advertised target window size.
299
        if (totalOfTargetWindowSizes > plannedTargetFileSize) {
1!
300
            throw new IllegalStateException(String.format(
×
301
                    "Internal error: Decoded data size %d exceeds planned target file size %d",
302
                    totalOfTargetWindowSizes, plannedTargetFileSize
×
303
            ));
304
        }
305
        return totalOfTargetWindowSizes == plannedTargetFileSize;
1!
306
    }
307

308
    // Checks to see whether adding a new target window of the specified size
309
    // would exceed the planned target file size, the maximum target file size,
310
    // or the maximum target window size.  If so, logs an error and returns true;
311
    // otherwise, returns false.
312
    public void targetWindowWouldExceedSizeLimits(int window_size) throws IOException {
313
        if (window_size > maximumTargetWindowSize) {
1✔
314
            throw new IOException(String.format(
1✔
315
                    "Length of target window (%d) exceeds limit of %d bytes",
316
                    window_size, maximumTargetWindowSize
1✔
317
            ));
318
        }
319
        if (hasPlannedTargetFileSize()) {
1✔
320
            // The logical expression to check would be:
321
            //
322
            //   totalOfTargetWindowSizes + window_size > plannedTargetFileSize
323
            //
324
            // but the addition might cause an integer overflow if target_bytes_to_add
325
            // is very large.  So it is better to check target_bytes_to_add against
326
            // the remaining planned target bytes.
327
            long remaining_planned_target_file_size = plannedTargetFileSize - totalOfTargetWindowSizes;
1✔
328
            if (window_size > remaining_planned_target_file_size) {
1!
329
                throw new IOException(String.format(
×
330
                        "Length of target window (%d bytes) plus previous windows (%d bytes) would exceed planned size of %d bytes",
331
                        window_size, totalOfTargetWindowSizes, plannedTargetFileSize
×
332
                ));
333
            }
334
        }
335
        long remaining_maximum_target_bytes = maximumTargetFileSize - totalOfTargetWindowSizes;
1✔
336
        if (window_size > remaining_maximum_target_bytes) {
1✔
337
            throw new IOException(String.format(
1✔
338
                    "Length of target window (%d bytes) plus previous windows (%d bytes) would exceed maximum target file size of %d bytes",
339
                    window_size, totalOfTargetWindowSizes, maximumTargetFileSize
1✔
340
            ));
341
        }
342
    }
1✔
343

344
    // Returns the amount of input data passed to the last decodeChunk()
345
    // that was not consumed by the decoder.  This is essential if
346
    // setPlannedTargetFileSize() is being used, in order to preserve the
347
    // remaining input data stream once the planned target file has been decoded.
348
    private int getUnconsumedDataSize() {
349
        return unparsedBytes.remaining();
1✔
350
    }
351

352
    // This function will return true if the decoder has parsed a complete delta
353
    // file header plus zero or more delta file windows, with no data left over.
354
    // It will also return true if no delta data at all was decoded.  If these
355
    // conditions are not met, then finishDecoding() should not be called.
356
    @SuppressWarnings("SimplifiableIfStatement")
357
    private boolean isDecodingComplete() {
358
        if (!FoundFileHeader()) {
1✔
359
            // No complete delta file header has been parsed yet.  decodeChunk()
360
            // may have received some data that it hasn't yet parsed, in which case
361
            // decoding is incomplete.
362
            return !unparsedBytes.hasRemaining();
1!
363
        } else if (custom_code_table_decoder_ != null) {
1✔
364
            // The decoder is in the middle of parsing a custom code table.
365
            return false;
1✔
366
        } else if (deltaWindow.FoundWindowHeader()) {
1✔
367
            // The decoder is in the middle of parsing an interleaved format delta
368
            // window.
369
            return false;
1✔
370
        } else if (reachedPlannedTargetFileSize()) {
1✔
371
            // The decoder found exactly the planned number of bytes.  In this case
372
            // it is OK for unparsedBytes to be non-empty; it contains the leftover
373
            // data after the end of the delta file.
374
            return true;
1✔
375
        } else {
376
            // No complete delta file window has been parsed yet.  decodeChunk()
377
            // may have received some data that it hasn't yet parsed, in which case
378
            // decoding is incomplete.
379
            return !unparsedBytes.hasRemaining();
1✔
380
        }
381
    }
382

383
    public ByteBuffer dictionary_ptr() { return dictionary; }
1✔
384

385
    VCDiffAddressCache addrCache() { return addrCache; }
1✔
386

387
    DecoratedByteArrayOutputStream decodedTarget() { return decodedTarget; }
1✔
388

389
    public boolean allowVcdTarget() { return allowVcdTarget; }
1✔
390

391
    public void setAllowVcdTarget(boolean allowVcdTarget) {
392
        if (startDecodingWasCalled) {
1!
393
            throw new IllegalStateException("setAllowVcdTarget() called after startDecoding()");
×
394
        }
395
        this.allowVcdTarget = allowVcdTarget;
1✔
396
    }
1✔
397

398
    // Reads the VCDiff delta file header section as described in RFC section 4.1,
399
    // except the custom code table data.  Returns RESULT_ERROR if an error
400
    // occurred, or RESULT_END_OF_DATA if the end of available data was reached
401
    // before the entire header could be read.  (The latter may be an error
402
    // condition if there is no more data available.)  Otherwise, advances
403
    // data->position_ past the header and returns RESULT_SUCCESS.
404

405
    // Reads the VCDiff delta file header section as described in RFC section 4.1:
406
    //
407
    //             Header1                                  - byte = 0xD6 (ASCII 'V' | 0x80)
408
    //             Header2                                  - byte = 0xC3 (ASCII 'C' | 0x80)
409
    //             Header3                                  - byte = 0xC4 (ASCII 'D' | 0x80)
410
    //             Header4                                  - byte
411
    //             Hdr_Indicator                            - byte
412
    //             [Secondary compressor ID]                - byte
413
    //             [Length of code table data]              - integer
414
    //             [Code table data]
415
    //
416
    // Initializes the code table and address cache objects.  Returns RESULT_ERROR
417
    // if an error occurred, and RESULT_END_OF_DATA if the end of available data was
418
    // reached before the entire header could be read.  (The latter may be an error
419
    // condition if there is no more data available.)  Otherwise, returns
420
    // RESULT_SUCCESS, and removes the header bytes from the data string.
421
    //
422
    // It's relatively inefficient to expect this function to parse any number of
423
    // input bytes available, down to 1 byte, but it is necessary in case the input
424
    // is not a properly formatted VCDIFF delta file.  If the entire input consists
425
    // of two bytes "12", then we should recognize that it does not match the
426
    // initial VCDIFF magic number "VCD" and report an error, rather than waiting
427
    // indefinitely for more input that will never arrive.
428
    private int readDeltaFileHeader(ByteBuffer data) throws IOException {
429
        if (FoundFileHeader()) {
1✔
430
            return RESULT_SUCCESS;
1✔
431
        }
432
        int data_size = data.remaining();
1✔
433

434
        ByteBuffer paddedHeaderData = ByteBuffer.allocate(DeltaFileHeader.SERIALIZED_SIZE);
1✔
435
        paddedHeaderData.put((ByteBuffer) data.slice().limit(Math.min(DeltaFileHeader.SERIALIZED_SIZE, data.remaining())));
1✔
436
        paddedHeaderData.rewind();
1✔
437

438
        final DeltaFileHeader header = new DeltaFileHeader(paddedHeaderData);
1✔
439
        boolean wrong_magic_number = false;
1✔
440
        switch (data_size) {
1!
441
            // Verify only the bytes that are available.
442
            default:
443
                // Found header contents up to and including VCDIFF version
444
                vcdiffVersionCode = header.header4;
1✔
445
                if ((vcdiffVersionCode != 0x00) &&  // Draft standard VCDIFF (RFC 3284)
1✔
446
                        (vcdiffVersionCode != 'S')) {   // Enhancements for SDCH protocol
447
                    throw new IOException("Unrecognized VCDIFF format version");
1✔
448
                }
449
                // fall through
450
            case 3:
451
                if (header.header3 != (byte) 0xC4) {  // magic value 'D' | 0x80
1✔
452
                    wrong_magic_number = true;
1✔
453
                }
454
                // fall through
455
            case 2:
456
                if (header.header2 != (byte) 0xC3) {  // magic value 'C' | 0x80
1✔
457
                    wrong_magic_number = true;
1✔
458
                }
459
                // fall through
460
            case 1:
461
                if (header.header1 != (byte) 0xD6) {  // magic value 'V' | 0x80
1✔
462
                    wrong_magic_number = true;
1✔
463
                }
464
                // fall through
465
            case 0:
466
                if (wrong_magic_number) {
1✔
467
                    throw new IOException("Did not find VCDIFF header bytes; input is not a VCDIFF delta file");
1✔
468
                }
469
                if (data_size < DeltaFileHeader.SERIALIZED_SIZE) return RESULT_END_OF_DATA;
1✔
470
        }
471

472
        int unrecognizedFlags = header.hdr_indicator & 0xff & ~(VCD_DECOMPRESS | VCD_CODETABLE);
1✔
473
        if (unrecognizedFlags != 0) {
1✔
474
            throw new IOException(String.format("Unrecognized hdr_indicator flags: %02x", unrecognizedFlags));
1✔
475
        }
476

477
        // Secondary compressor not supported.
478
        if ((header.hdr_indicator & VCD_DECOMPRESS) != 0) {
1✔
479
            throw new IOException("Secondary compression is not supported");
1✔
480
        }
481

482
        if ((header.hdr_indicator & VCD_CODETABLE) != 0) {
1✔
483
            int bytes_parsed = InitCustomCodeTable(data.array(), data.arrayOffset() + data.position() + DeltaFileHeader.SERIALIZED_SIZE,
1✔
484
                    data.remaining() - DeltaFileHeader.SERIALIZED_SIZE);
1✔
485
            if (bytes_parsed == RESULT_END_OF_DATA) {
1!
486
                return RESULT_END_OF_DATA;
×
487
            }
488
            data.position(data.position() + DeltaFileHeader.SERIALIZED_SIZE + bytes_parsed);
1✔
489
            // TODO unknown flags on hdr_indicator
490
        } else {
1✔
491
            addrCache = new VCDiffAddressCacheImpl();
1✔
492
            // addrCache->init() will be called
493
            // from VCDiffStreamingDecoderImpl::decodeChunk()
494
            data.position(data.position() + DeltaFileHeader.SERIALIZED_SIZE);
1✔
495
        }
496
        return RESULT_SUCCESS;
1✔
497
    }
498

499
    // Indicates whether or not the header has already been read.
500
    private boolean FoundFileHeader() { return addrCache != null; }
1✔
501

502
    // If readDeltaFileHeader() finds the VCD_CODETABLE flag set within the delta
503
    // file header, this function parses the custom cache sizes and initializes
504
    // a nested VCDiffStreamingDecoderImpl object that will be used to parse the
505
    // custom code table in readCustomCodeTable().  Returns RESULT_ERROR if an
506
    // error occurred, or RESULT_END_OF_DATA if the end of available data was
507
    // reached before the custom cache sizes could be read.  Otherwise, returns
508
    // the number of bytes read.
509
    //
510
    private int InitCustomCodeTable(byte[] data_start, int offset, int length) throws IOException {
511
        // A custom code table is being specified.  Parse the variable-length
512
        // cache sizes and begin parsing the encoded custom code table.
513
        Integer near_cache_size;
514
        Integer same_cache_size;
515

516
        VCDiffHeaderParser header_parser = new VCDiffHeaderParser(ByteBuffer.wrap(data_start, offset, length).slice());
1✔
517
        if ((near_cache_size = header_parser.parseInt32("size of near cache")) == null) {
1!
518
            LOGGER.warn("Failed to parse size of near cache");
×
519
            return header_parser.getResult();
×
520
        }
521
        if ((same_cache_size = header_parser.parseInt32("size of same cache")) == null) {
1!
522
            LOGGER.warn("Failed to parse size of same cache");
×
523
            return header_parser.getResult();
×
524
        }
525

526
        custom_code_table_ = new VCDiffCodeTableData();
1✔
527

528
        custom_code_table_string_.reset();
1✔
529
        addrCache = new VCDiffAddressCacheImpl(near_cache_size.shortValue(), same_cache_size.shortValue());
1✔
530

531
        // addrCache->init() will be called
532
        // from VCDiffStreamingDecoderImpl::decodeChunk()
533

534
        // If we reach this point (the start of the custom code table)
535
        // without encountering a RESULT_END_OF_DATA condition, then we won't call
536
        // readDeltaFileHeader() again for this delta file.
537
        //
538
        // Instantiate a recursive decoder to interpret the custom code table
539
        // as a VCDIFF encoding of the default code table.
540
        custom_code_table_decoder_ = new VCDiffStreamingDecoderImpl();
1✔
541

542
        byte[] codeTableBytes = VCDiffCodeTableData.kDefaultCodeTableData.getBytes();
1✔
543
        custom_code_table_decoder_.startDecoding(codeTableBytes);
1✔
544
        custom_code_table_decoder_.setPlannedTargetFileSize(codeTableBytes.length);
1✔
545

546
        return header_parser.unparsedData().position();
1✔
547
    }
548

549
    // If a custom code table was specified in the header section that was parsed
550
    // by readDeltaFileHeader(), this function makes a recursive call to another
551
    // VCDiffStreamingDecoderImpl object (custom_code_table_decoder_), since the
552
    // custom code table is expected to be supplied as an embedded VCDIFF
553
    // encoding that uses the standard code table.  Returns RESULT_ERROR if an
554
    // error occurs, or RESULT_END_OF_DATA if the end of available data was
555
    // reached before the entire custom code table could be read.  Otherwise,
556
    // returns RESULT_SUCCESS and sets *data_ptr to the position after the encoded
557
    // custom code table.  If the function returns RESULT_SUCCESS or
558
    // RESULT_END_OF_DATA, it advances data->position_ past the parsed bytes.
559
    private int readCustomCodeTable(ByteBuffer data) throws IOException {
560
        if (custom_code_table_decoder_ == null) {
1✔
561
            return RESULT_SUCCESS;
1✔
562
        }
563
        if (custom_code_table_ == null) {
1!
564
            throw new IllegalStateException("Internal error: custom_code_table_decoder_ is set, but custom_code_table_ is null");
×
565
        }
566

567
        try {
568
            custom_code_table_decoder_.decodeChunk(data.array(),
1✔
569
                    data.arrayOffset() + data.position(), data.remaining(), custom_code_table_string_);
1✔
570
        } catch (IOException cause) {
×
571
            IOException e = new IOException("Failed to write to custom_code_table_string_");
×
572
            e.initCause(cause);
×
573
            throw e;
×
574
        }
1✔
575
        if (custom_code_table_string_.size() < VCDiffCodeTableData.SERIALIZED_BYTE_SIZE) {
1✔
576
            // Skip over the consumed data.
577
            data.position(data.limit());
1✔
578
            return RESULT_END_OF_DATA;
1✔
579
        }
580

581
        custom_code_table_decoder_.finishDecoding();
1✔
582

583
        if (custom_code_table_string_.size() != VCDiffCodeTableData.SERIALIZED_BYTE_SIZE) {
1!
584
            throw new IOException(String.format(
×
585
                    "Decoded custom code table size (%d) does not match size of a code table (%d)",
586
                    custom_code_table_string_.size(), VCDiffCodeTableData.SERIALIZED_BYTE_SIZE
×
587
            ));
588
        }
589

590
        custom_code_table_ = new VCDiffCodeTableData(custom_code_table_string_.toByteArray());
1✔
591
        custom_code_table_string_.reset();
1✔
592

593
        // Skip over the consumed data.
594
        data.position(data.limit() - custom_code_table_decoder_.getUnconsumedDataSize());
1✔
595
        custom_code_table_decoder_ = null;
1✔
596
        deltaWindow.useCodeTable(custom_code_table_, addrCache.LastMode());
1✔
597
        return RESULT_SUCCESS;
1✔
598
    }
599

600
    // Called after the decoder exhausts all input data.  This function
601
    // copies from decodedTarget into out all the data that
602
    // has not yet been output.  It sets decodedTargetOutputPosition
603
    // to mark the start of the next data that needs to be output.
604
    private void appendNewOutputText(OutputStream out) throws IOException {
605
        ByteBuffer decodedTargetBuffer = decodedTarget.toByteBuffer();
1✔
606
        decodedTargetBuffer.position(decodedTargetOutputPosition);
1✔
607

608
        // TODO: optimize
609
        while (decodedTargetBuffer.hasRemaining()) {
1✔
610
            out.write(decodedTargetBuffer.get());
1✔
611
        }
612

613
        decodedTargetOutputPosition = decodedTargetBuffer.limit();
1✔
614
    }
1✔
615

616
    // Appends to out the portion of decodedTarget that has
617
    // not yet been output, then clears decodedTarget.  This function is
618
    // called after each complete target window has been decoded if
619
    // allowVcdTarget is false.  In that case, there is no need to retain
620
    // target data from any window except the current window.
621
    private void flushDecodedTarget(OutputStream out) throws IOException {
622
        out.write(
1✔
623
                decodedTarget.getBuffer(),
1✔
624
                decodedTargetOutputPosition,
625
                decodedTarget.size() - decodedTargetOutputPosition
1✔
626
        );
627

628
        decodedTarget.reset();
1✔
629
        deltaWindow.setTargetWindowStartPos(0);
1✔
630
        decodedTargetOutputPosition = 0;
1✔
631
    }
1✔
632

633
    protected static class DecoratedByteArrayOutputStream extends ByteArrayOutputStream {
634
        public DecoratedByteArrayOutputStream() {
635
            super();
×
636
        }
×
637

638
        public DecoratedByteArrayOutputStream(int size) {
639
            super(size);
1✔
640
        }
1✔
641

642
        public synchronized ByteBuffer toByteBuffer() {
643
            return ByteBuffer.wrap(buf, 0, count).asReadOnlyBuffer();
1✔
644
        }
645

646
        public byte[] getBuffer() {
647
            return buf;
1✔
648
        }
649
    }
650
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc