1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.internal.storage.reftable;
12
13 import static java.nio.charset.StandardCharsets.UTF_8;
14 import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.compare;
15 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE;
16 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
17 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
18 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
19 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_DATA;
20 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_NONE;
21 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
22 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
23 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_1ID;
24 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_2ID;
25 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_NONE;
26 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_SYMREF;
27 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_TYPE_MASK;
28 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.reverseUpdateIndex;
29 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
30 import static org.eclipse.jgit.lib.Ref.Storage.NEW;
31 import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
32
33 import java.io.IOException;
34 import java.nio.ByteBuffer;
35 import java.util.Arrays;
36 import java.util.zip.DataFormatException;
37 import java.util.zip.Inflater;
38
39 import org.eclipse.jgit.annotations.Nullable;
40 import org.eclipse.jgit.internal.JGitText;
41 import org.eclipse.jgit.internal.storage.io.BlockSource;
42 import org.eclipse.jgit.lib.CheckoutEntry;
43 import org.eclipse.jgit.lib.InflaterCache;
44 import org.eclipse.jgit.lib.ObjectId;
45 import org.eclipse.jgit.lib.ObjectIdRef;
46 import org.eclipse.jgit.lib.PersonIdent;
47 import org.eclipse.jgit.lib.Ref;
48 import org.eclipse.jgit.lib.ReflogEntry;
49 import org.eclipse.jgit.lib.SymbolicRef;
50 import org.eclipse.jgit.util.LongList;
51 import org.eclipse.jgit.util.NB;
52 import org.eclipse.jgit.util.RawParseUtils;
53
54
55
56
57
58
59 class BlockReader {
60 private byte blockType;
61 private long endPosition;
62 private boolean truncated;
63
64 private byte[] buf;
65 private int bufLen;
66 private int ptr;
67
68 private int keysStart;
69 private int keysEnd;
70
71 private int restartCnt;
72 private int restartTbl;
73
74 private byte[] nameBuf = new byte[256];
75 private int nameLen;
76 private int valueType;
77
78 byte type() {
79 return blockType;
80 }
81
82 boolean truncated() {
83 return truncated;
84 }
85
86 long endPosition() {
87 return endPosition;
88 }
89
90 boolean next() {
91 return ptr < keysEnd;
92 }
93
94 void parseKey() {
95 int pfx = readVarint32();
96 valueType = readVarint32();
97 int sfx = valueType >>> 3;
98 if (pfx + sfx > nameBuf.length) {
99 int n = Math.max(pfx + sfx, nameBuf.length * 2);
100 nameBuf = Arrays.copyOf(nameBuf, n);
101 }
102 System.arraycopy(buf, ptr, nameBuf, pfx, sfx);
103 ptr += sfx;
104 nameLen = pfx + sfx;
105 }
106
107 String name() {
108 int len = nameLen;
109 if (blockType == LOG_BLOCK_TYPE) {
110 len -= 9;
111 }
112 return RawParseUtils.decode(UTF_8, nameBuf, 0, len);
113 }
114
115
116
117 boolean match(byte[] match, boolean matchIsPrefix) {
118 int len = nameLen;
119 if (blockType == LOG_BLOCK_TYPE) {
120 len -= 9;
121 }
122 if (matchIsPrefix) {
123 return len >= match.length
124 && compare(
125 match, 0, match.length,
126 nameBuf, 0, match.length) == 0;
127 }
128 return compare(match, 0, match.length, nameBuf, 0, len) == 0;
129 }
130
131 long readPositionFromIndex() throws IOException {
132 if (blockType != INDEX_BLOCK_TYPE) {
133 throw invalidBlock();
134 }
135
136 readVarint32();
137 int n = readVarint32() >>> 3;
138 ptr += n;
139 return readVarint64();
140 }
141
142 long readUpdateIndexDelta() {
143 return readVarint64();
144 }
145
146 Ref readRef(long minUpdateIndex) throws IOException {
147 long updateIndex = minUpdateIndex + readUpdateIndexDelta();
148 String name = RawParseUtils.decode(UTF_8, nameBuf, 0, nameLen);
149 switch (valueType & VALUE_TYPE_MASK) {
150 case VALUE_NONE:
151 return newRef(name, updateIndex);
152
153 case VALUE_1ID:
154 return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId(),
155 updateIndex);
156
157 case VALUE_2ID: {
158 ObjectId id1 = readValueId();
159 ObjectId id2 = readValueId();
160 return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2,
161 updateIndex);
162 }
163
164 case VALUE_SYMREF: {
165 String val = readValueString();
166 return new SymbolicRef(name, newRef(val, updateIndex), updateIndex);
167 }
168
169 default:
170 throw invalidBlock();
171 }
172 }
173
174 @Nullable
175 LongList readBlockPositionList() {
176 int n = valueType & VALUE_TYPE_MASK;
177 if (n == 0) {
178 n = readVarint32();
179 if (n == 0) {
180 return null;
181 }
182 }
183
184 LongList b = new LongList(n);
185 b.add(readVarint64());
186 for (int j = 1; j < n; j++) {
187 long prior = b.get(j - 1);
188 b.add(prior + readVarint64());
189 }
190 return b;
191 }
192
193 long readLogUpdateIndex() {
194 return reverseUpdateIndex(NB.decodeUInt64(nameBuf, nameLen - 8));
195 }
196
197 @Nullable
198 ReflogEntry readLogEntry() {
199 if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) {
200 return null;
201 }
202
203 ObjectId oldId = readValueId();
204 ObjectId newId = readValueId();
205 PersonIdent who = readPersonIdent();
206 String msg = readValueString();
207
208 return new ReflogEntry() {
209 @Override
210 public ObjectId getOldId() {
211 return oldId;
212 }
213
214 @Override
215 public ObjectId getNewId() {
216 return newId;
217 }
218
219 @Override
220 public PersonIdent getWho() {
221 return who;
222 }
223
224 @Override
225 public String getComment() {
226 return msg;
227 }
228
229 @Override
230 public CheckoutEntry parseCheckout() {
231 return null;
232 }
233 };
234 }
235
236 private ObjectId readValueId() {
237 ObjectId id = ObjectId.fromRaw(buf, ptr);
238 ptr += OBJECT_ID_LENGTH;
239 return id;
240 }
241
242 private String readValueString() {
243 int len = readVarint32();
244 int end = ptr + len;
245 String s = RawParseUtils.decode(UTF_8, buf, ptr, end);
246 ptr = end;
247 return s;
248 }
249
250 private PersonIdent readPersonIdent() {
251 String name = readValueString();
252 String email = readValueString();
253 long ms = readVarint64() * 1000;
254 int tz = readInt16();
255 return new PersonIdent(name, email, ms, tz);
256 }
257
258 void readBlock(BlockSource src, long pos, int fileBlockSize)
259 throws IOException {
260 readBlockIntoBuf(src, pos, fileBlockSize);
261 parseBlockStart(src, pos, fileBlockSize);
262 }
263
264 private void readBlockIntoBuf(BlockSource src, long pos, int size)
265 throws IOException {
266 ByteBuffer b = src.read(pos, size);
267 bufLen = b.position();
268 if (bufLen <= 0) {
269 throw invalidBlock();
270 }
271 if (b.hasArray() && b.arrayOffset() == 0) {
272 buf = b.array();
273 } else {
274 buf = new byte[bufLen];
275 b.flip();
276 b.get(buf);
277 }
278 endPosition = pos + bufLen;
279 }
280
281 private void parseBlockStart(BlockSource src, long pos, int fileBlockSize)
282 throws IOException {
283 ptr = 0;
284 if (pos == 0) {
285 if (bufLen == FILE_HEADER_LEN) {
286 setupEmptyFileBlock();
287 return;
288 }
289 ptr += FILE_HEADER_LEN;
290 }
291
292 int typeAndSize = NB.decodeInt32(buf, ptr);
293 ptr += 4;
294
295 blockType = (byte) (typeAndSize >>> 24);
296 int blockLen = decodeBlockLen(typeAndSize);
297 if (blockType == LOG_BLOCK_TYPE) {
298
299 long deflatedSize = inflateBuf(src, pos, blockLen, fileBlockSize);
300 endPosition = pos + 4 + deflatedSize;
301 }
302 if (bufLen < blockLen) {
303 if (blockType != INDEX_BLOCK_TYPE) {
304 throw invalidBlock();
305 }
306
307
308
309
310 truncated = true;
311 } else if (bufLen > blockLen) {
312 bufLen = blockLen;
313 }
314
315 if (blockType != FILE_BLOCK_TYPE) {
316 restartCnt = NB.decodeUInt16(buf, bufLen - 2);
317 restartTbl = bufLen - (restartCnt * 3 + 2);
318 keysStart = ptr;
319 keysEnd = restartTbl;
320 } else {
321 keysStart = ptr;
322 keysEnd = ptr;
323 }
324 }
325
326 static int decodeBlockLen(int typeAndSize) {
327 return typeAndSize & 0xffffff;
328 }
329
330 private long inflateBuf(BlockSource src, long pos, int blockLen,
331 int fileBlockSize) throws IOException {
332 byte[] dst = new byte[blockLen];
333 System.arraycopy(buf, 0, dst, 0, 4);
334
335 long deflatedSize = 0;
336 Inflater inf = InflaterCache.get();
337 try {
338 inf.setInput(buf, ptr, bufLen - ptr);
339 for (int o = 4;;) {
340 int n = inf.inflate(dst, o, dst.length - o);
341 o += n;
342 if (inf.finished()) {
343 deflatedSize = inf.getBytesRead();
344 break;
345 } else if (n <= 0 && inf.needsInput()) {
346 long p = pos + 4 + inf.getBytesRead();
347 readBlockIntoBuf(src, p, fileBlockSize);
348 inf.setInput(buf, 0, bufLen);
349 } else if (n <= 0) {
350 throw invalidBlock();
351 }
352 }
353 } catch (DataFormatException e) {
354 throw invalidBlock(e);
355 } finally {
356 InflaterCache.release(inf);
357 }
358
359 buf = dst;
360 bufLen = dst.length;
361 return deflatedSize;
362 }
363
364 private void setupEmptyFileBlock() {
365
366 blockType = FILE_BLOCK_TYPE;
367 ptr = FILE_HEADER_LEN;
368 restartCnt = 0;
369 restartTbl = bufLen;
370 keysStart = bufLen;
371 keysEnd = bufLen;
372 }
373
374 void verifyIndex() throws IOException {
375 if (blockType != INDEX_BLOCK_TYPE || truncated) {
376 throw invalidBlock();
377 }
378 }
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401 int seekKey(byte[] key) {
402 int low = 0;
403 int end = restartCnt;
404 for (;;) {
405 int mid = (low + end) >>> 1;
406 int p = NB.decodeUInt24(buf, restartTbl + mid * 3);
407 ptr = p + 1;
408 int n = readVarint32() >>> 3;
409 int cmp = compare(key, 0, key.length, buf, ptr, n);
410 if (cmp < 0) {
411 end = mid;
412 } else if (cmp == 0) {
413 ptr = p;
414 return 0;
415 } else {
416 low = mid + 1;
417 }
418 if (low >= end) {
419 return scanToKey(key, p, low, cmp);
420 }
421 }
422 }
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443 private int scanToKey(byte[] key, int rPtr, int rIdx, int rCmp) {
444 if (rCmp < 0) {
445 if (rIdx == 0) {
446 ptr = keysStart;
447 return -1;
448 }
449 ptr = NB.decodeUInt24(buf, restartTbl + (rIdx - 1) * 3);
450 } else {
451 ptr = rPtr;
452 }
453
454 int cmp;
455 do {
456 int savePtr = ptr;
457 parseKey();
458 cmp = compare(key, 0, key.length, nameBuf, 0, nameLen);
459 if (cmp <= 0) {
460
461
462 ptr = savePtr;
463 return cmp < 0 && savePtr == keysStart ? -1 : 0;
464 }
465 skipValue();
466 } while (ptr < keysEnd);
467 return cmp;
468 }
469
470 void skipValue() {
471 switch (blockType) {
472 case REF_BLOCK_TYPE:
473 readVarint64();
474 switch (valueType & VALUE_TYPE_MASK) {
475 case VALUE_NONE:
476 return;
477 case VALUE_1ID:
478 ptr += OBJECT_ID_LENGTH;
479 return;
480 case VALUE_2ID:
481 ptr += 2 * OBJECT_ID_LENGTH;
482 return;
483 case VALUE_SYMREF:
484 skipString();
485 return;
486 }
487 break;
488
489 case OBJ_BLOCK_TYPE: {
490 int n = valueType & VALUE_TYPE_MASK;
491 if (n == 0) {
492 n = readVarint32();
493 }
494 while (n-- > 0) {
495 readVarint32();
496 }
497 return;
498 }
499
500 case INDEX_BLOCK_TYPE:
501 readVarint32();
502 return;
503
504 case LOG_BLOCK_TYPE:
505 if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) {
506 return;
507 } else if ((valueType & VALUE_TYPE_MASK) == LOG_DATA) {
508 ptr += 2 * OBJECT_ID_LENGTH;
509 skipString();
510 skipString();
511 readVarint64();
512 ptr += 2;
513 skipString();
514 return;
515 }
516 }
517
518 throw new IllegalStateException();
519 }
520
521 private void skipString() {
522 int n = readVarint32();
523 ptr += n;
524 }
525
526 private short readInt16() {
527 short result =(short) NB.decodeUInt16(buf, ptr);
528 ptr += 2;
529 return result;
530 }
531
532 private int readVarint32() {
533 byte c = buf[ptr++];
534 int val = c & 0x7f;
535 while ((c & 0x80) != 0) {
536 c = buf[ptr++];
537 val++;
538 val <<= 7;
539 val |= (c & 0x7f);
540 }
541 return val;
542 }
543
544 private long readVarint64() {
545 byte c = buf[ptr++];
546 long val = c & 0x7f;
547 while ((c & 0x80) != 0) {
548 c = buf[ptr++];
549 val++;
550 val <<= 7;
551 val |= (c & 0x7f);
552 }
553 return val;
554 }
555
556 private static Ref newRef(String name, long updateIndex) {
557 return new ObjectIdRef.Unpeeled(NEW, name, null, updateIndex);
558 }
559
560 private static IOException invalidBlock() {
561 return invalidBlock(null);
562 }
563
564 private static IOException invalidBlock(Throwable cause) {
565 return new IOException(JGitText.get().invalidReftableBlock, cause);
566 }
567 }