| /* |
| * Copyright (C) 2010, Google Inc. and others |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Distribution License v. 1.0 which is available at |
| * https://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| package org.eclipse.jgit.internal.storage.pack; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| |
| import org.eclipse.jgit.lib.Constants; |
| |
| /** |
| * Encodes an instruction stream for |
| * {@link org.eclipse.jgit.internal.storage.pack.BinaryDelta}. |
| */ |
| public class DeltaEncoder { |
| /** |
| * Maximum number of bytes to be copied in pack v2 format. |
| * <p> |
| * Historical limitations have this at 64k, even though current delta |
| * decoders recognize larger copy instructions. |
| */ |
| private static final int MAX_V2_COPY = 0x10000; |
| |
| /* |
| * Maximum number of bytes to be copied in pack v3 format. |
| * |
| * Current delta decoders can recognize a copy instruction with a count that |
| * is this large, but the historical limitation of {@link MAX_V2_COPY} is |
| * still used. |
| */ |
| // private static final int MAX_V3_COPY = (0xff << 16) | (0xff << 8) | 0xff; |
| |
| /** Maximum number of bytes used by a copy instruction. */ |
| private static final int MAX_COPY_CMD_SIZE = 8; |
| |
| /** Maximum length that an insert command can encode at once. */ |
| private static final int MAX_INSERT_DATA_SIZE = 127; |
| |
| private final OutputStream out; |
| |
| private final byte[] buf = new byte[MAX_COPY_CMD_SIZE * 4]; |
| |
| private final int limit; |
| |
| private int size; |
| |
| /** |
| * Create an encoder with no upper bound on the instruction stream size. |
| * |
| * @param out |
| * buffer to store the instructions written. |
| * @param baseSize |
| * size of the base object, in bytes. |
| * @param resultSize |
| * size of the resulting object, after applying this instruction |
| * stream to the base object, in bytes. |
| * @throws java.io.IOException |
| * the output buffer cannot store the instruction stream's |
| * header with the size fields. |
| */ |
| public DeltaEncoder(OutputStream out, long baseSize, long resultSize) |
| throws IOException { |
| this(out, baseSize, resultSize, 0); |
| } |
| |
| /** |
| * Create an encoder with an upper limit on the instruction size. |
| * |
| * @param out |
| * buffer to store the instructions written. |
| * @param baseSize |
| * size of the base object, in bytes. |
| * @param resultSize |
| * size of the resulting object, after applying this instruction |
| * stream to the base object, in bytes. |
| * @param limit |
| * maximum number of bytes to write to the out buffer declaring |
| * the stream is over limit and should be discarded. May be 0 to |
| * specify an infinite limit. |
| * @throws java.io.IOException |
| * the output buffer cannot store the instruction stream's |
| * header with the size fields. |
| */ |
| public DeltaEncoder(OutputStream out, long baseSize, long resultSize, |
| int limit) throws IOException { |
| this.out = out; |
| this.limit = limit; |
| writeVarint(baseSize); |
| writeVarint(resultSize); |
| } |
| |
| private void writeVarint(long sz) throws IOException { |
| int p = 0; |
| while (sz >= 0x80) { |
| buf[p++] = (byte) (0x80 | (((int) sz) & 0x7f)); |
| sz >>>= 7; |
| } |
| buf[p++] = (byte) (((int) sz) & 0x7f); |
| size += p; |
| if (limit == 0 || size < limit) |
| out.write(buf, 0, p); |
| } |
| |
| /** |
| * Get current size of the delta stream, in bytes. |
| * |
| * @return current size of the delta stream, in bytes. |
| */ |
| public int getSize() { |
| return size; |
| } |
| |
| /** |
| * Insert a literal string of text, in UTF-8 encoding. |
| * |
| * @param text |
| * the string to insert. |
| * @return true if the insert fits within the limit; false if the insert |
| * would cause the instruction stream to exceed the limit. |
| * @throws java.io.IOException |
| * the instruction buffer can't store the instructions. |
| */ |
| public boolean insert(String text) throws IOException { |
| return insert(Constants.encode(text)); |
| } |
| |
| /** |
| * Insert a literal binary sequence. |
| * |
| * @param text |
| * the binary to insert. |
| * @return true if the insert fits within the limit; false if the insert |
| * would cause the instruction stream to exceed the limit. |
| * @throws java.io.IOException |
| * the instruction buffer can't store the instructions. |
| */ |
| public boolean insert(byte[] text) throws IOException { |
| return insert(text, 0, text.length); |
| } |
| |
| /** |
| * Insert a literal binary sequence. |
| * |
| * @param text |
| * the binary to insert. |
| * @param off |
| * offset within {@code text} to start copying from. |
| * @param cnt |
| * number of bytes to insert. |
| * @return true if the insert fits within the limit; false if the insert |
| * would cause the instruction stream to exceed the limit. |
| * @throws java.io.IOException |
| * the instruction buffer can't store the instructions. |
| */ |
| public boolean insert(byte[] text, int off, int cnt) |
| throws IOException { |
| if (cnt <= 0) |
| return true; |
| if (limit != 0) { |
| int hdrs = cnt / MAX_INSERT_DATA_SIZE; |
| if (cnt % MAX_INSERT_DATA_SIZE != 0) |
| hdrs++; |
| if (limit < size + hdrs + cnt) |
| return false; |
| } |
| do { |
| int n = Math.min(MAX_INSERT_DATA_SIZE, cnt); |
| out.write((byte) n); |
| out.write(text, off, n); |
| off += n; |
| cnt -= n; |
| size += 1 + n; |
| } while (0 < cnt); |
| return true; |
| } |
| |
| /** |
| * Create a copy instruction to copy from the base object. |
| * |
| * @param offset |
| * position in the base object to copy from. This is absolute, |
| * from the beginning of the base. |
| * @param cnt |
| * number of bytes to copy. |
| * @return true if the copy fits within the limit; false if the copy |
| * would cause the instruction stream to exceed the limit. |
| * @throws java.io.IOException |
| * the instruction buffer cannot store the instructions. |
| */ |
| public boolean copy(long offset, int cnt) throws IOException { |
| if (cnt == 0) |
| return true; |
| |
| int p = 0; |
| |
| // We cannot encode more than MAX_V2_COPY bytes in a single |
| // command, so encode that much and start a new command. |
| // This limit is imposed by the pack file format rules. |
| // |
| while (MAX_V2_COPY < cnt) { |
| p = encodeCopy(p, offset, MAX_V2_COPY); |
| offset += MAX_V2_COPY; |
| cnt -= MAX_V2_COPY; |
| |
| if (buf.length < p + MAX_COPY_CMD_SIZE) { |
| if (limit != 0 && limit < size + p) |
| return false; |
| out.write(buf, 0, p); |
| size += p; |
| p = 0; |
| } |
| } |
| |
| p = encodeCopy(p, offset, cnt); |
| if (limit != 0 && limit < size + p) |
| return false; |
| out.write(buf, 0, p); |
| size += p; |
| return true; |
| } |
| |
| private int encodeCopy(int p, long offset, int cnt) { |
| int cmd = 0x80; |
| final int cmdPtr = p++; // save room for the command |
| byte b; |
| |
| if ((b = (byte) (offset & 0xff)) != 0) { |
| cmd |= 0x01; |
| buf[p++] = b; |
| } |
| if ((b = (byte) ((offset >>> 8) & 0xff)) != 0) { |
| cmd |= 0x02; |
| buf[p++] = b; |
| } |
| if ((b = (byte) ((offset >>> 16) & 0xff)) != 0) { |
| cmd |= 0x04; |
| buf[p++] = b; |
| } |
| if ((b = (byte) ((offset >>> 24) & 0xff)) != 0) { |
| cmd |= 0x08; |
| buf[p++] = b; |
| } |
| |
| if (cnt != MAX_V2_COPY) { |
| if ((b = (byte) (cnt & 0xff)) != 0) { |
| cmd |= 0x10; |
| buf[p++] = b; |
| } |
| if ((b = (byte) ((cnt >>> 8) & 0xff)) != 0) { |
| cmd |= 0x20; |
| buf[p++] = b; |
| } |
| if ((b = (byte) ((cnt >>> 16) & 0xff)) != 0) { |
| cmd |= 0x40; |
| buf[p++] = b; |
| } |
| } |
| |
| buf[cmdPtr] = (byte) cmd; |
| return p; |
| } |
| } |