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 /**
52 * Encodes an instruction stream for
53 * {@link org.eclipse.jgit.internal.storage.pack.BinaryDelta}.
54 */
55 public class DeltaEncoder {
56 /**
57 * Maximum number of bytes to be copied in pack v2 format.
58 * <p>
59 * Historical limitations have this at 64k, even though current delta
60 * decoders recognize larger copy instructions.
61 */
62 private static final int MAX_V2_COPY = 0x10000;
63
64 /*
65 * Maximum number of bytes to be copied in pack v3 format.
66 *
67 * Current delta decoders can recognize a copy instruction with a count that
68 * is this large, but the historical limitation of {@link MAX_V2_COPY} is
69 * still used.
70 */
71 // private static final int MAX_V3_COPY = (0xff << 16) | (0xff << 8) | 0xff;
72
73 /** Maximum number of bytes used by a copy instruction. */
74 private static final int MAX_COPY_CMD_SIZE = 8;
75
76 /** Maximum length that an an insert command can encode at once. */
77 private static final int MAX_INSERT_DATA_SIZE = 127;
78
79 private final OutputStream out;
80
81 private final byte[] buf = new byte[MAX_COPY_CMD_SIZE * 4];
82
83 private final int limit;
84
85 private int size;
86
87 /**
88 * Create an encoder with no upper bound on the instruction stream size.
89 *
90 * @param out
91 * buffer to store the instructions written.
92 * @param baseSize
93 * size of the base object, in bytes.
94 * @param resultSize
95 * size of the resulting object, after applying this instruction
96 * stream to the base object, in bytes.
97 * @throws java.io.IOException
98 * the output buffer cannot store the instruction stream's
99 * header with the size fields.
100 */
101 public DeltaEncoder(OutputStream out, long baseSize, long resultSize)
102 throws IOException {
103 this(out, baseSize, resultSize, 0);
104 }
105
106 /**
107 * Create an encoder with an upper limit on the instruction size.
108 *
109 * @param out
110 * buffer to store the instructions written.
111 * @param baseSize
112 * size of the base object, in bytes.
113 * @param resultSize
114 * size of the resulting object, after applying this instruction
115 * stream to the base object, in bytes.
116 * @param limit
117 * maximum number of bytes to write to the out buffer declaring
118 * the stream is over limit and should be discarded. May be 0 to
119 * specify an infinite limit.
120 * @throws java.io.IOException
121 * the output buffer cannot store the instruction stream's
122 * header with the size fields.
123 */
124 public DeltaEncoder(OutputStream out, long baseSize, long resultSize,
125 int limit) throws IOException {
126 this.out = out;
127 this.limit = limit;
128 writeVarint(baseSize);
129 writeVarint(resultSize);
130 }
131
132 private void writeVarint(long sz) throws IOException {
133 int p = 0;
134 while (sz >= 0x80) {
135 buf[p++] = (byte) (0x80 | (((int) sz) & 0x7f));
136 sz >>>= 7;
137 }
138 buf[p++] = (byte) (((int) sz) & 0x7f);
139 size += p;
140 if (limit == 0 || size < limit)
141 out.write(buf, 0, p);
142 }
143
144 /**
145 * Get current size of the delta stream, in bytes.
146 *
147 * @return current size of the delta stream, in bytes.
148 */
149 public int getSize() {
150 return size;
151 }
152
153 /**
154 * Insert a literal string of text, in UTF-8 encoding.
155 *
156 * @param text
157 * the string to insert.
158 * @return true if the insert fits within the limit; false if the insert
159 * would cause the instruction stream to exceed the limit.
160 * @throws java.io.IOException
161 * the instruction buffer can't store the instructions.
162 */
163 public boolean insert(String text) throws IOException {
164 return insert(Constants.encode(text));
165 }
166
167 /**
168 * Insert a literal binary sequence.
169 *
170 * @param text
171 * the binary to insert.
172 * @return true if the insert fits within the limit; false if the insert
173 * would cause the instruction stream to exceed the limit.
174 * @throws java.io.IOException
175 * the instruction buffer can't store the instructions.
176 */
177 public boolean insert(byte[] text) throws IOException {
178 return insert(text, 0, text.length);
179 }
180
181 /**
182 * Insert a literal binary sequence.
183 *
184 * @param text
185 * the binary to insert.
186 * @param off
187 * offset within {@code text} to start copying from.
188 * @param cnt
189 * number of bytes to insert.
190 * @return true if the insert fits within the limit; false if the insert
191 * would cause the instruction stream to exceed the limit.
192 * @throws java.io.IOException
193 * the instruction buffer can't store the instructions.
194 */
195 public boolean insert(byte[] text, int off, int cnt)
196 throws IOException {
197 if (cnt <= 0)
198 return true;
199 if (limit != 0) {
200 int hdrs = cnt / MAX_INSERT_DATA_SIZE;
201 if (cnt % MAX_INSERT_DATA_SIZE != 0)
202 hdrs++;
203 if (limit < size + hdrs + cnt)
204 return false;
205 }
206 do {
207 int n = Math.min(MAX_INSERT_DATA_SIZE, cnt);
208 out.write((byte) n);
209 out.write(text, off, n);
210 off += n;
211 cnt -= n;
212 size += 1 + n;
213 } while (0 < cnt);
214 return true;
215 }
216
217 /**
218 * Create a copy instruction to copy from the base object.
219 *
220 * @param offset
221 * position in the base object to copy from. This is absolute,
222 * from the beginning of the base.
223 * @param cnt
224 * number of bytes to copy.
225 * @return true if the copy fits within the limit; false if the copy
226 * would cause the instruction stream to exceed the limit.
227 * @throws java.io.IOException
228 * the instruction buffer cannot store the instructions.
229 */
230 public boolean copy(long offset, int cnt) throws IOException {
231 if (cnt == 0)
232 return true;
233
234 int p = 0;
235
236 // We cannot encode more than MAX_V2_COPY bytes in a single
237 // command, so encode that much and start a new command.
238 // This limit is imposed by the pack file format rules.
239 //
240 while (MAX_V2_COPY < cnt) {
241 p = encodeCopy(p, offset, MAX_V2_COPY);
242 offset += MAX_V2_COPY;
243 cnt -= MAX_V2_COPY;
244
245 if (buf.length < p + MAX_COPY_CMD_SIZE) {
246 if (limit != 0 && limit < size + p)
247 return false;
248 out.write(buf, 0, p);
249 size += p;
250 p = 0;
251 }
252 }
253
254 p = encodeCopy(p, offset, cnt);
255 if (limit != 0 && limit < size + p)
256 return false;
257 out.write(buf, 0, p);
258 size += p;
259 return true;
260 }
261
262 private int encodeCopy(int p, long offset, int cnt) {
263 int cmd = 0x80;
264 final int cmdPtr = p++; // save room for the command
265 byte b;
266
267 if ((b = (byte) (offset & 0xff)) != 0) {
268 cmd |= 0x01;
269 buf[p++] = b;
270 }
271 if ((b = (byte) ((offset >>> 8) & 0xff)) != 0) {
272 cmd |= 0x02;
273 buf[p++] = b;
274 }
275 if ((b = (byte) ((offset >>> 16) & 0xff)) != 0) {
276 cmd |= 0x04;
277 buf[p++] = b;
278 }
279 if ((b = (byte) ((offset >>> 24) & 0xff)) != 0) {
280 cmd |= 0x08;
281 buf[p++] = b;
282 }
283
284 if (cnt != MAX_V2_COPY) {
285 if ((b = (byte) (cnt & 0xff)) != 0) {
286 cmd |= 0x10;
287 buf[p++] = b;
288 }
289 if ((b = (byte) ((cnt >>> 8) & 0xff)) != 0) {
290 cmd |= 0x20;
291 buf[p++] = b;
292 }
293 if ((b = (byte) ((cnt >>> 16) & 0xff)) != 0) {
294 cmd |= 0x40;
295 buf[p++] = b;
296 }
297 }
298
299 buf[cmdPtr] = (byte) cmd;
300 return p;
301 }
302 }