blob: 6a47352124157fd8faf2f95acc0cd48b1a5668a5 [file] [log] [blame]
/*
* Copyright (C) 2022, Google LLC and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
import java.io.DataInput;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.DigestInputStream;
import java.text.MessageFormat;
import java.util.Arrays;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.PackMismatchException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.Hex;
import org.eclipse.jgit.util.IO;
/**
* Reverse index for forward pack index which is parsed from a version 1 file.
* <p>
* The file format is specified at
* https://git-scm.com/docs/pack-format#_pack_rev_files_have_the_format.
*/
final class PackReverseIndexV1 implements PackReverseIndex {
static final int OID_VERSION_SHA1 = 1;
static final int OID_VERSION_SHA256 = 2;
private static final int SHA1_BYTES = OBJECT_ID_LENGTH;
private final DigestInputStream inputStream;
private final DataInput dataIn;
/**
* A lazy supplier for the corresponding PackIndex. The PackIndex is not
* needed during instantiation and parsing, only later when querying the
* reverse index. Allow lazy loading so that the parsing of the forward and
* reverse indices could happen in parallel.
*/
private final PackBitmapIndex.SupplierWithIOException<PackIndex> packIndexSupplier;
private int objectCount;
private byte[] packChecksum;
private int[] indexPositionsSortedByOffset;
private PackIndex packIndex;
PackReverseIndexV1(DigestInputStream inputStream, long objectCount,
PackBitmapIndex.SupplierWithIOException<PackIndex> packIndexSupplier)
throws IOException {
try {
this.objectCount = Math.toIntExact(objectCount);
} catch (ArithmeticException e) {
throw new IllegalArgumentException(
JGitText.get().hugeIndexesAreNotSupportedByJgitYet, e);
}
this.inputStream = inputStream;
dataIn = new SimpleDataInput(inputStream);
int oid_version = dataIn.readInt();
switch (oid_version) {
case OID_VERSION_SHA1:
// JGit Pack only supports AnyObjectId, which represents SHA1.
break;
case OID_VERSION_SHA256:
throw new IOException(MessageFormat.format(
JGitText.get().unsupportedObjectIdVersion, "SHA256")); //$NON-NLS-1$
default:
throw new IOException(MessageFormat.format(
JGitText.get().unsupportedObjectIdVersion,
String.valueOf(oid_version)));
}
indexPositionsSortedByOffset = new int[this.objectCount];
this.packIndexSupplier = packIndexSupplier;
parseBody();
parseChecksums();
}
@Override
public void verifyPackChecksum(String packFilePath)
throws PackMismatchException {
if (!Arrays.equals(packChecksum, getPackIndex().getChecksum())) {
throw new PackMismatchException(
MessageFormat.format(JGitText.get().packChecksumMismatch,
packFilePath, PackExt.INDEX.getExtension(),
Hex.toHexString(getPackIndex().getChecksum()),
PackExt.REVERSE_INDEX.getExtension(),
Hex.toHexString(packChecksum)));
}
}
private void parseBody() throws IOException {
for (int i = 0; i < objectCount; i++) {
indexPositionsSortedByOffset[i] = dataIn.readInt();
}
}
private void parseChecksums() throws IOException {
packChecksum = new byte[SHA1_BYTES];
IO.readFully(inputStream, packChecksum);
// Take digest before reading the self checksum changes it.
byte[] observedSelfChecksum = inputStream.getMessageDigest().digest();
byte[] readSelfChecksum = new byte[SHA1_BYTES];
IO.readFully(inputStream, readSelfChecksum);
if (!Arrays.equals(readSelfChecksum, observedSelfChecksum)) {
throw new CorruptObjectException(MessageFormat.format(
JGitText.get().corruptReverseIndexChecksumIncorrect,
Hex.toHexString(readSelfChecksum),
Hex.toHexString(observedSelfChecksum)));
}
}
@Override
public ObjectId findObject(long offset) {
int reversePosition = findPosition(offset);
if (reversePosition < 0) {
return null;
}
int forwardPosition = findForwardPositionByReversePosition(
reversePosition);
return getPackIndex().getObjectId(forwardPosition);
}
@Override
public long findNextOffset(long offset, long maxOffset)
throws CorruptObjectException {
int position = findPosition(offset);
if (position < 0) {
throw new CorruptObjectException(MessageFormat.format(JGitText
.get().cantFindObjectInReversePackIndexForTheSpecifiedOffset,
Long.valueOf(offset)));
}
if (position + 1 == objectCount) {
return maxOffset;
}
return findOffsetByReversePosition(position + 1);
}
@Override
public int findPosition(long offset) {
return binarySearchByOffset(offset);
}
@Override
public ObjectId findObjectByPosition(int position) {
return getPackIndex()
.getObjectId(findForwardPositionByReversePosition(position));
}
private long findOffsetByReversePosition(int position) {
return getPackIndex()
.getOffset(findForwardPositionByReversePosition(position));
}
private int findForwardPositionByReversePosition(int reversePosition) {
assert (reversePosition >= 0);
assert (reversePosition < indexPositionsSortedByOffset.length);
return indexPositionsSortedByOffset[reversePosition];
}
private int binarySearchByOffset(long wantedOffset) {
int low = 0;
int high = objectCount - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
long offsetAtMid = findOffsetByReversePosition(mid);
if (offsetAtMid == wantedOffset) {
return mid;
} else if (offsetAtMid > wantedOffset) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
private PackIndex getPackIndex() {
if (packIndex == null) {
try {
packIndex = packIndexSupplier.get();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
return packIndex;
}
}