1 /* 2 * Copyright (C) 2010, Google Inc. and others 3 * 4 * This program and the accompanying materials are made available under the 5 * terms of the Eclipse Distribution License v. 1.0 which is available at 6 * https://www.eclipse.org/org/documents/edl-v10.php. 7 * 8 * SPDX-License-Identifier: BSD-3-Clause 9 */ 10 11 package org.eclipse.jgit.internal.storage.pack; 12 13 import java.io.IOException; 14 import java.io.OutputStream; 15 16 import org.eclipse.jgit.lib.Constants; 17 18 /** 19 * Encodes an instruction stream for 20 * {@link org.eclipse.jgit.internal.storage.pack.BinaryDelta}. 21 */ 22 public class DeltaEncoder { 23 /** 24 * Maximum number of bytes to be copied in pack v2 format. 25 * <p> 26 * Historical limitations have this at 64k, even though current delta 27 * decoders recognize larger copy instructions. 28 */ 29 private static final int MAX_V2_COPY = 0x10000; 30 31 /* 32 * Maximum number of bytes to be copied in pack v3 format. 33 * 34 * Current delta decoders can recognize a copy instruction with a count that 35 * is this large, but the historical limitation of {@link MAX_V2_COPY} is 36 * still used. 37 */ 38 // private static final int MAX_V3_COPY = (0xff << 16) | (0xff << 8) | 0xff; 39 40 /** Maximum number of bytes used by a copy instruction. */ 41 private static final int MAX_COPY_CMD_SIZE = 8; 42 43 /** Maximum length that an insert command can encode at once. */ 44 private static final int MAX_INSERT_DATA_SIZE = 127; 45 46 private final OutputStream out; 47 48 private final byte[] buf = new byte[MAX_COPY_CMD_SIZE * 4]; 49 50 private final int limit; 51 52 private int size; 53 54 /** 55 * Create an encoder with no upper bound on the instruction stream size. 56 * 57 * @param out 58 * buffer to store the instructions written. 59 * @param baseSize 60 * size of the base object, in bytes. 61 * @param resultSize 62 * size of the resulting object, after applying this instruction 63 * stream to the base object, in bytes. 64 * @throws java.io.IOException 65 * the output buffer cannot store the instruction stream's 66 * header with the size fields. 67 */ 68 public DeltaEncoder(OutputStream out, long baseSize, long resultSize) 69 throws IOException { 70 this(out, baseSize, resultSize, 0); 71 } 72 73 /** 74 * Create an encoder with an upper limit on the instruction size. 75 * 76 * @param out 77 * buffer to store the instructions written. 78 * @param baseSize 79 * size of the base object, in bytes. 80 * @param resultSize 81 * size of the resulting object, after applying this instruction 82 * stream to the base object, in bytes. 83 * @param limit 84 * maximum number of bytes to write to the out buffer declaring 85 * the stream is over limit and should be discarded. May be 0 to 86 * specify an infinite limit. 87 * @throws java.io.IOException 88 * the output buffer cannot store the instruction stream's 89 * header with the size fields. 90 */ 91 public DeltaEncoder(OutputStream out, long baseSize, long resultSize, 92 int limit) throws IOException { 93 this.out = out; 94 this.limit = limit; 95 writeVarint(baseSize); 96 writeVarint(resultSize); 97 } 98 99 private void writeVarint(long sz) throws IOException { 100 int p = 0; 101 while (sz >= 0x80) { 102 buf[p++] = (byte) (0x80 | (((int) sz) & 0x7f)); 103 sz >>>= 7; 104 } 105 buf[p++] = (byte) (((int) sz) & 0x7f); 106 size += p; 107 if (limit == 0 || size < limit) 108 out.write(buf, 0, p); 109 } 110 111 /** 112 * Get current size of the delta stream, in bytes. 113 * 114 * @return current size of the delta stream, in bytes. 115 */ 116 public int getSize() { 117 return size; 118 } 119 120 /** 121 * Insert a literal string of text, in UTF-8 encoding. 122 * 123 * @param text 124 * the string to insert. 125 * @return true if the insert fits within the limit; false if the insert 126 * would cause the instruction stream to exceed the limit. 127 * @throws java.io.IOException 128 * the instruction buffer can't store the instructions. 129 */ 130 public boolean insert(String text) throws IOException { 131 return insert(Constants.encode(text)); 132 } 133 134 /** 135 * Insert a literal binary sequence. 136 * 137 * @param text 138 * the binary to insert. 139 * @return true if the insert fits within the limit; false if the insert 140 * would cause the instruction stream to exceed the limit. 141 * @throws java.io.IOException 142 * the instruction buffer can't store the instructions. 143 */ 144 public boolean insert(byte[] text) throws IOException { 145 return insert(text, 0, text.length); 146 } 147 148 /** 149 * Insert a literal binary sequence. 150 * 151 * @param text 152 * the binary to insert. 153 * @param off 154 * offset within {@code text} to start copying from. 155 * @param cnt 156 * number of bytes to insert. 157 * @return true if the insert fits within the limit; false if the insert 158 * would cause the instruction stream to exceed the limit. 159 * @throws java.io.IOException 160 * the instruction buffer can't store the instructions. 161 */ 162 public boolean insert(byte[] text, int off, int cnt) 163 throws IOException { 164 if (cnt <= 0) 165 return true; 166 if (limit != 0) { 167 int hdrs = cnt / MAX_INSERT_DATA_SIZE; 168 if (cnt % MAX_INSERT_DATA_SIZE != 0) 169 hdrs++; 170 if (limit < size + hdrs + cnt) 171 return false; 172 } 173 do { 174 int n = Math.min(MAX_INSERT_DATA_SIZE, cnt); 175 out.write((byte) n); 176 out.write(text, off, n); 177 off += n; 178 cnt -= n; 179 size += 1 + n; 180 } while (0 < cnt); 181 return true; 182 } 183 184 /** 185 * Create a copy instruction to copy from the base object. 186 * 187 * @param offset 188 * position in the base object to copy from. This is absolute, 189 * from the beginning of the base. 190 * @param cnt 191 * number of bytes to copy. 192 * @return true if the copy fits within the limit; false if the copy 193 * would cause the instruction stream to exceed the limit. 194 * @throws java.io.IOException 195 * the instruction buffer cannot store the instructions. 196 */ 197 public boolean copy(long offset, int cnt) throws IOException { 198 if (cnt == 0) 199 return true; 200 201 int p = 0; 202 203 // We cannot encode more than MAX_V2_COPY bytes in a single 204 // command, so encode that much and start a new command. 205 // This limit is imposed by the pack file format rules. 206 // 207 while (MAX_V2_COPY < cnt) { 208 p = encodeCopy(p, offset, MAX_V2_COPY); 209 offset += MAX_V2_COPY; 210 cnt -= MAX_V2_COPY; 211 212 if (buf.length < p + MAX_COPY_CMD_SIZE) { 213 if (limit != 0 && limit < size + p) 214 return false; 215 out.write(buf, 0, p); 216 size += p; 217 p = 0; 218 } 219 } 220 221 p = encodeCopy(p, offset, cnt); 222 if (limit != 0 && limit < size + p) 223 return false; 224 out.write(buf, 0, p); 225 size += p; 226 return true; 227 } 228 229 private int encodeCopy(int p, long offset, int cnt) { 230 int cmd = 0x80; 231 final int cmdPtr = p++; // save room for the command 232 byte b; 233 234 if ((b = (byte) (offset & 0xff)) != 0) { 235 cmd |= 0x01; 236 buf[p++] = b; 237 } 238 if ((b = (byte) ((offset >>> 8) & 0xff)) != 0) { 239 cmd |= 0x02; 240 buf[p++] = b; 241 } 242 if ((b = (byte) ((offset >>> 16) & 0xff)) != 0) { 243 cmd |= 0x04; 244 buf[p++] = b; 245 } 246 if ((b = (byte) ((offset >>> 24) & 0xff)) != 0) { 247 cmd |= 0x08; 248 buf[p++] = b; 249 } 250 251 if (cnt != MAX_V2_COPY) { 252 if ((b = (byte) (cnt & 0xff)) != 0) { 253 cmd |= 0x10; 254 buf[p++] = b; 255 } 256 if ((b = (byte) ((cnt >>> 8) & 0xff)) != 0) { 257 cmd |= 0x20; 258 buf[p++] = b; 259 } 260 if ((b = (byte) ((cnt >>> 16) & 0xff)) != 0) { 261 cmd |= 0x40; 262 buf[p++] = b; 263 } 264 } 265 266 buf[cmdPtr] = (byte) cmd; 267 return p; 268 } 269 }