/* * 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. *

* 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 packIndexSupplier; private int objectCount; private byte[] packChecksum; private int[] indexPositionsSortedByOffset; private PackIndex packIndex; PackReverseIndexV1(DigestInputStream inputStream, long objectCount, PackBitmapIndex.SupplierWithIOException 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; } }