View Javadoc
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 }