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