1 /* 2 * Copyright (C) 2010, Google Inc. 3 * and other copyright owners as documented in the project's IP log. 4 * 5 * This program and the accompanying materials are made available 6 * under the terms of the Eclipse Distribution License v1.0 which 7 * accompanies this distribution, is reproduced below, and is 8 * available at http://www.eclipse.org/org/documents/edl-v10.php 9 * 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or 13 * without modification, are permitted provided that the following 14 * conditions are met: 15 * 16 * - Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 19 * - Redistributions in binary form must reproduce the above 20 * copyright notice, this list of conditions and the following 21 * disclaimer in the documentation and/or other materials provided 22 * with the distribution. 23 * 24 * - Neither the name of the Eclipse Foundation, Inc. nor the 25 * names of its contributors may be used to endorse or promote 26 * products derived from this software without specific prior 27 * written permission. 28 * 29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 */ 43 44 package org.eclipse.jgit.internal.storage.pack; 45 46 import java.io.IOException; 47 import java.io.OutputStream; 48 49 import org.eclipse.jgit.lib.Constants; 50 51 /** Encodes an instruction stream for {@link BinaryDelta}. */ 52 public class DeltaEncoder { 53 /** 54 * Maximum number of bytes to be copied in pack v2 format. 55 * <p> 56 * Historical limitations have this at 64k, even though current delta 57 * decoders recognize larger copy instructions. 58 */ 59 private static final int MAX_V2_COPY = 0x10000; 60 61 /* 62 * Maximum number of bytes to be copied in pack v3 format. 63 * 64 * Current delta decoders can recognize a copy instruction with a count that 65 * is this large, but the historical limitation of {@link MAX_V2_COPY} is 66 * still used. 67 */ 68 // private static final int MAX_V3_COPY = (0xff << 16) | (0xff << 8) | 0xff; 69 70 /** Maximum number of bytes used by a copy instruction. */ 71 private static final int MAX_COPY_CMD_SIZE = 8; 72 73 /** Maximum length that an an insert command can encode at once. */ 74 private static final int MAX_INSERT_DATA_SIZE = 127; 75 76 private final OutputStream out; 77 78 private final byte[] buf = new byte[MAX_COPY_CMD_SIZE * 4]; 79 80 private final int limit; 81 82 private int size; 83 84 /** 85 * Create an encoder with no upper bound on the instruction stream size. 86 * 87 * @param out 88 * buffer to store the instructions written. 89 * @param baseSize 90 * size of the base object, in bytes. 91 * @param resultSize 92 * size of the resulting object, after applying this instruction 93 * stream to the base object, in bytes. 94 * @throws IOException 95 * the output buffer cannot store the instruction stream's 96 * header with the size fields. 97 */ 98 public DeltaEncoder(OutputStream out, long baseSize, long resultSize) 99 throws IOException { 100 this(out, baseSize, resultSize, 0); 101 } 102 103 /** 104 * Create an encoder with an upper limit on the instruction size. 105 * 106 * @param out 107 * buffer to store the instructions written. 108 * @param baseSize 109 * size of the base object, in bytes. 110 * @param resultSize 111 * size of the resulting object, after applying this instruction 112 * stream to the base object, in bytes. 113 * @param limit 114 * maximum number of bytes to write to the out buffer declaring 115 * the stream is over limit and should be discarded. May be 0 to 116 * specify an infinite limit. 117 * @throws IOException 118 * the output buffer cannot store the instruction stream's 119 * header with the size fields. 120 */ 121 public DeltaEncoder(OutputStream out, long baseSize, long resultSize, 122 int limit) throws IOException { 123 this.out = out; 124 this.limit = limit; 125 writeVarint(baseSize); 126 writeVarint(resultSize); 127 } 128 129 private void writeVarint(long sz) throws IOException { 130 int p = 0; 131 while (sz >= 0x80) { 132 buf[p++] = (byte) (0x80 | (((int) sz) & 0x7f)); 133 sz >>>= 7; 134 } 135 buf[p++] = (byte) (((int) sz) & 0x7f); 136 size += p; 137 if (limit == 0 || size < limit) 138 out.write(buf, 0, p); 139 } 140 141 /** @return current size of the delta stream, in bytes. */ 142 public int getSize() { 143 return size; 144 } 145 146 /** 147 * Insert a literal string of text, in UTF-8 encoding. 148 * 149 * @param text 150 * the string to insert. 151 * @return true if the insert fits within the limit; false if the insert 152 * would cause the instruction stream to exceed the limit. 153 * @throws IOException 154 * the instruction buffer can't store the instructions. 155 */ 156 public boolean insert(String text) throws IOException { 157 return insert(Constants.encode(text)); 158 } 159 160 /** 161 * Insert a literal binary sequence. 162 * 163 * @param text 164 * the binary to insert. 165 * @return true if the insert fits within the limit; false if the insert 166 * would cause the instruction stream to exceed the limit. 167 * @throws IOException 168 * the instruction buffer can't store the instructions. 169 */ 170 public boolean insert(byte[] text) throws IOException { 171 return insert(text, 0, text.length); 172 } 173 174 /** 175 * Insert a literal binary sequence. 176 * 177 * @param text 178 * the binary to insert. 179 * @param off 180 * offset within {@code text} to start copying from. 181 * @param cnt 182 * number of bytes to insert. 183 * @return true if the insert fits within the limit; false if the insert 184 * would cause the instruction stream to exceed the limit. 185 * @throws IOException 186 * the instruction buffer can't store the instructions. 187 */ 188 public boolean insert(byte[] text, int off, int cnt) 189 throws IOException { 190 if (cnt <= 0) 191 return true; 192 if (limit != 0) { 193 int hdrs = cnt / MAX_INSERT_DATA_SIZE; 194 if (cnt % MAX_INSERT_DATA_SIZE != 0) 195 hdrs++; 196 if (limit < size + hdrs + cnt) 197 return false; 198 } 199 do { 200 int n = Math.min(MAX_INSERT_DATA_SIZE, cnt); 201 out.write((byte) n); 202 out.write(text, off, n); 203 off += n; 204 cnt -= n; 205 size += 1 + n; 206 } while (0 < cnt); 207 return true; 208 } 209 210 /** 211 * Create a copy instruction to copy from the base object. 212 * 213 * @param offset 214 * position in the base object to copy from. This is absolute, 215 * from the beginning of the base. 216 * @param cnt 217 * number of bytes to copy. 218 * @return true if the copy fits within the limit; false if the copy 219 * would cause the instruction stream to exceed the limit. 220 * @throws IOException 221 * the instruction buffer cannot store the instructions. 222 */ 223 public boolean copy(long offset, int cnt) throws IOException { 224 if (cnt == 0) 225 return true; 226 227 int p = 0; 228 229 // We cannot encode more than MAX_V2_COPY bytes in a single 230 // command, so encode that much and start a new command. 231 // This limit is imposed by the pack file format rules. 232 // 233 while (MAX_V2_COPY < cnt) { 234 p = encodeCopy(p, offset, MAX_V2_COPY); 235 offset += MAX_V2_COPY; 236 cnt -= MAX_V2_COPY; 237 238 if (buf.length < p + MAX_COPY_CMD_SIZE) { 239 if (limit != 0 && limit < size + p) 240 return false; 241 out.write(buf, 0, p); 242 size += p; 243 p = 0; 244 } 245 } 246 247 p = encodeCopy(p, offset, cnt); 248 if (limit != 0 && limit < size + p) 249 return false; 250 out.write(buf, 0, p); 251 size += p; 252 return true; 253 } 254 255 private int encodeCopy(int p, long offset, int cnt) { 256 int cmd = 0x80; 257 final int cmdPtr = p++; // save room for the command 258 byte b; 259 260 if ((b = (byte) (offset & 0xff)) != 0) { 261 cmd |= 0x01; 262 buf[p++] = b; 263 } 264 if ((b = (byte) ((offset >>> 8) & 0xff)) != 0) { 265 cmd |= 0x02; 266 buf[p++] = b; 267 } 268 if ((b = (byte) ((offset >>> 16) & 0xff)) != 0) { 269 cmd |= 0x04; 270 buf[p++] = b; 271 } 272 if ((b = (byte) ((offset >>> 24) & 0xff)) != 0) { 273 cmd |= 0x08; 274 buf[p++] = b; 275 } 276 277 if (cnt != MAX_V2_COPY) { 278 if ((b = (byte) (cnt & 0xff)) != 0) { 279 cmd |= 0x10; 280 buf[p++] = b; 281 } 282 if ((b = (byte) ((cnt >>> 8) & 0xff)) != 0) { 283 cmd |= 0x20; 284 buf[p++] = b; 285 } 286 if ((b = (byte) ((cnt >>> 16) & 0xff)) != 0) { 287 cmd |= 0x40; 288 buf[p++] = b; 289 } 290 } 291 292 buf[cmdPtr] = (byte) cmd; 293 return p; 294 } 295 }