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