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