diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index a8a70df69..fca6eec3f 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -49,6 +49,7 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", org.eclipse.jgit.treewalk.filter;version="[4.7.0,4.8.0)", org.eclipse.jgit.util;version="[4.7.0,4.8.0)", org.eclipse.jgit.util.io;version="[4.7.0,4.8.0)", + org.eclipse.jgit.util.sha1;version="[4.7.0,4.8.0)", org.junit;version="[4.4.0,5.0.0)", org.junit.experimental.theories;version="[4.4.0,5.0.0)", org.junit.rules;version="[4.11.0,5.0.0)", diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java new file mode 100644 index 000000000..c53080455 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.util.sha1; + +import static org.junit.Assert.assertEquals; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.eclipse.jgit.lib.ObjectId; +import org.junit.Test; + +public class SHA1Test { + private static final String TEST1 = "abc"; + + private static final String TEST2a = "abcdbcdecdefdefgefghfghighijhi"; + private static final String TEST2b = "jkijkljklmklmnlmnomnopnopq"; + private static final String TEST2 = TEST2a + TEST2b; + + @Test + public void test0() throws NoSuchAlgorithmException { + ObjectId exp = ObjectId + .fromString("da39a3ee5e6b4b0d3255bfef95601890afd80709"); + + MessageDigest m = MessageDigest.getInstance("SHA-1"); + m.update(new byte[] {}); + ObjectId m1 = ObjectId.fromRaw(m.digest()); + + SHA1 s = SHA1.newInstance(); + s.update(new byte[] {}); + ObjectId s1 = ObjectId.fromRaw(s.digest()); + + assertEquals(m1, s1); + assertEquals(exp, s1); + } + + @Test + public void test1() throws NoSuchAlgorithmException { + ObjectId exp = ObjectId + .fromString("a9993e364706816aba3e25717850c26c9cd0d89d"); + + MessageDigest m = MessageDigest.getInstance("SHA-1"); + m.update(TEST1.getBytes(StandardCharsets.UTF_8)); + ObjectId m1 = ObjectId.fromRaw(m.digest()); + + SHA1 s = SHA1.newInstance(); + s.update(TEST1.getBytes(StandardCharsets.UTF_8)); + ObjectId s1 = ObjectId.fromRaw(s.digest()); + + assertEquals(m1, s1); + assertEquals(exp, s1); + } + + @Test + public void test2() throws NoSuchAlgorithmException { + ObjectId exp = ObjectId + .fromString("84983e441c3bd26ebaae4aa1f95129e5e54670f1"); + + MessageDigest m = MessageDigest.getInstance("SHA-1"); + m.update(TEST2.getBytes(StandardCharsets.UTF_8)); + ObjectId m1 = ObjectId.fromRaw(m.digest()); + + SHA1 s = SHA1.newInstance(); + s.update(TEST2.getBytes(StandardCharsets.UTF_8)); + ObjectId s1 = ObjectId.fromRaw(s.digest()); + + assertEquals(m1, s1); + assertEquals(exp, s1); + } +} diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 8b7862820..33c331b4a 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -138,6 +138,7 @@ Export-Package: org.eclipse.jgit.annotations;version="4.7.0", org.eclipse.jgit.storage.file, org.ietf.jgss", org.eclipse.jgit.util.io;version="4.7.0", + org.eclipse.jgit.util.sha1;version="4.7.0", org.eclipse.jgit.util.time;version="4.7.0" Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java index 1551ad615..d3ac62650 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java @@ -90,6 +90,7 @@ import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.TemporaryBuffer; import org.eclipse.jgit.util.io.CountingOutputStream; +import org.eclipse.jgit.util.sha1.SHA1; /** Inserts objects into the DFS. */ public class DfsInserter extends ObjectInserter { @@ -168,7 +169,7 @@ public ObjectId insert(int type, long len, InputStream in) } long offset = beginObject(type, len); - MessageDigest md = digest(); + SHA1 md = SHA1.newInstance(); md.update(Constants.encodedTypeString(type)); md.update((byte) ' '); md.update(Constants.encodeASCII(len)); @@ -183,7 +184,7 @@ public ObjectId insert(int type, long len, InputStream in) len -= n; } packOut.compress.finish(); - return endObject(ObjectId.fromRaw(md.digest()), offset); + return endObject(md.toObjectId(), offset); } private byte[] insertBuffer(long len) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java index a510431f3..c37c4daa4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java @@ -49,12 +49,11 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.channels.Channels; -import java.security.DigestOutputStream; -import java.security.MessageDigest; import java.text.MessageFormat; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; @@ -69,6 +68,7 @@ import org.eclipse.jgit.transport.PackParser; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.sha1.SHA1; /** Creates loose objects in a {@link ObjectDirectory}. */ class ObjectDirectoryInserter extends ObjectInserter { @@ -140,9 +140,9 @@ ObjectId insert(int type, long len, InputStream is, boolean createDuplicate) return insert(type, buf, 0, actLen, createDuplicate); } else { - MessageDigest md = digest(); + SHA1 md = SHA1.newInstance(); File tmp = toTemp(md, type, len, is); - ObjectId id = ObjectId.fromRaw(md.digest()); + ObjectId id = md.toObjectId(); return insertOneObject(tmp, id, createDuplicate); } } @@ -193,7 +193,7 @@ public void close() { } @SuppressWarnings("resource" /* java 7 */) - private File toTemp(final MessageDigest md, final int type, long len, + private File toTemp(final SHA1 md, final int type, long len, final InputStream is) throws IOException, FileNotFoundException, Error { boolean delete = true; @@ -205,7 +205,7 @@ private File toTemp(final MessageDigest md, final int type, long len, if (config.getFSyncObjectFiles()) out = Channels.newOutputStream(fOut.getChannel()); DeflaterOutputStream cOut = compress(out); - DigestOutputStream dOut = new DigestOutputStream(cOut, md); + SHA1OutputStream dOut = new SHA1OutputStream(cOut, md); writeHeader(dOut, type, len); final byte[] buf = buffer(); @@ -285,4 +285,25 @@ private static EOFException shortInput(long missing) { return new EOFException(MessageFormat.format( JGitText.get().inputDidntMatchLength, Long.valueOf(missing))); } + + private static class SHA1OutputStream extends FilterOutputStream { + private final SHA1 md; + + SHA1OutputStream(OutputStream out, SHA1 md) { + super(out); + this.md = md; + } + + @Override + public void write(int b) throws IOException { + md.update((byte) b); + out.write(b); + } + + @Override + public void write(byte[] in, int p, int n) throws IOException { + md.update(in, p, n); + out.write(in, p, n); + } + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java index 1a49ae9d7..4b14d121e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java @@ -208,6 +208,24 @@ public void fromRaw(final int[] ints, final int p) { w5 = ints[p + 4]; } + /** + * Convert an ObjectId from binary representation expressed in integers. + * + * @param a + * @param b + * @param c + * @param d + * @param e + * @since 4.7 + */ + public void set(int a, int b, int c, int d, int e) { + w1 = a; + w2 = b; + w3 = c; + w4 = d; + w5 = e; + } + /** * Convert an ObjectId from hex characters (US-ASCII). * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java index 2a2d67d25..991f03f82 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java @@ -248,8 +248,17 @@ private static final ObjectId fromHexString(final byte[] bs, int p) { } } - ObjectId(final int new_1, final int new_2, final int new_3, - final int new_4, final int new_5) { + /** + * Construct an ObjectId from 160 bits provided in 5 words. + * + * @param new_1 + * @param new_2 + * @param new_3 + * @param new_4 + * @param new_5 + * @since 4.7 + */ + public ObjectId(int new_1, int new_2, int new_3, int new_4, int new_5) { w1 = new_1; w2 = new_2; w3 = new_3; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java index 9cd2d1d29..b39603ada 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java @@ -50,10 +50,10 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; -import java.security.MessageDigest; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.transport.PackParser; +import org.eclipse.jgit.util.sha1.SHA1; /** * Inserts objects into an existing {@code ObjectDatabase}. @@ -177,15 +177,11 @@ public void close() { } } - /** Digest to compute the name of an object. */ - private final MessageDigest digest; - /** Temporary working buffer for streaming data through. */ private byte[] tempBuffer; /** Create a new inserter for a database. */ protected ObjectInserter() { - digest = Constants.newMessageDigest(); } /** @@ -220,9 +216,8 @@ protected byte[] buffer() { } /** @return digest to help compute an ObjectId */ - protected MessageDigest digest() { - digest.reset(); - return digest; + protected SHA1 digest() { + return SHA1.newInstance(); } /** @@ -252,13 +247,13 @@ public ObjectId idFor(int type, byte[] data) { * @return the name of the object. */ public ObjectId idFor(int type, byte[] data, int off, int len) { - MessageDigest md = digest(); + SHA1 md = SHA1.newInstance(); md.update(Constants.encodedTypeString(type)); md.update((byte) ' '); md.update(Constants.encodeASCII(len)); md.update((byte) 0); md.update(data, off, len); - return ObjectId.fromRaw(md.digest()); + return md.toObjectId(); } /** @@ -277,7 +272,7 @@ public ObjectId idFor(int type, byte[] data, int off, int len) { */ public ObjectId idFor(int objectType, long length, InputStream in) throws IOException { - MessageDigest md = digest(); + SHA1 md = SHA1.newInstance(); md.update(Constants.encodedTypeString(objectType)); md.update((byte) ' '); md.update(Constants.encodeASCII(length)); @@ -290,7 +285,7 @@ public ObjectId idFor(int objectType, long length, InputStream in) md.update(buf, 0, n); length -= n; } - return ObjectId.fromRaw(md.digest()); + return md.toObjectId(); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java index 93e7952bd..7d48f2a25 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java @@ -83,6 +83,7 @@ import org.eclipse.jgit.util.BlockList; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.sha1.SHA1; /** * Parses a pack stream and imports it for an {@link ObjectInserter}. @@ -116,8 +117,6 @@ public static enum Source { private byte[] hdrBuf; - private final MessageDigest objectDigest; - private final MutableObjectId tempObjectId; private InputStream in; @@ -206,7 +205,6 @@ protected PackParser(final ObjectDatabase odb, final InputStream src) { buf = new byte[BUFFER_SIZE]; tempBuffer = new byte[BUFFER_SIZE]; hdrBuf = new byte[64]; - objectDigest = Constants.newMessageDigest(); tempObjectId = new MutableObjectId(); packDigest = Constants.newMessageDigest(); checkObjectCollisions = true; @@ -667,12 +665,13 @@ private void resolveDeltas(DeltaVisit visit, final int type, JGitText.get().corruptionDetectedReReadingAt, Long.valueOf(visit.delta.position))); + SHA1 objectDigest = SHA1.newInstance(); objectDigest.update(Constants.encodedTypeString(type)); objectDigest.update((byte) ' '); objectDigest.update(Constants.encodeASCII(visit.data.length)); objectDigest.update((byte) 0); objectDigest.update(visit.data); - tempObjectId.fromRaw(objectDigest.digest(), 0); + objectDigest.digest(tempObjectId); verifySafeObject(tempObjectId, type, visit.data); @@ -1024,6 +1023,7 @@ private void indexOneObject() throws IOException { private void whole(final long pos, final int type, final long sz) throws IOException { + SHA1 objectDigest = SHA1.newInstance(); objectDigest.update(Constants.encodedTypeString(type)); objectDigest.update((byte) ' '); objectDigest.update(Constants.encodeASCII(sz)); @@ -1043,7 +1043,7 @@ private void whole(final long pos, final int type, final long sz) cnt += r; } inf.close(); - tempObjectId.fromRaw(objectDigest.digest(), 0); + objectDigest.digest(tempObjectId); checkContentLater = isCheckObjectCollisions() && readCurs.has(tempObjectId); data = null; @@ -1051,7 +1051,7 @@ private void whole(final long pos, final int type, final long sz) } else { data = inflateAndReturn(Source.INPUT, sz); objectDigest.update(data); - tempObjectId.fromRaw(objectDigest.digest(), 0); + objectDigest.digest(tempObjectId); verifySafeObject(tempObjectId, type, data); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 0d3c78888..b1b146c85 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -56,7 +56,6 @@ import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetEncoder; -import java.security.MessageDigest; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; @@ -99,6 +98,7 @@ import org.eclipse.jgit.util.TemporaryBuffer.LocalFile; import org.eclipse.jgit.util.io.AutoLFInputStream; import org.eclipse.jgit.util.io.EolStreamTypeUtil; +import org.eclipse.jgit.util.sha1.SHA1; /** * Walks a working directory tree as part of a {@link TreeWalk}. @@ -364,7 +364,7 @@ private byte[] idBufferBlob(final Entry e) { if (is == null) return zeroid; try { - state.initializeDigestAndReadBuffer(); + state.initializeReadBuffer(); final long len = e.getLength(); InputStream filteredIs = possiblyFilteredInputStream(e, is, len, @@ -1099,10 +1099,9 @@ private static long computeLength(InputStream in) throws IOException { } private byte[] computeHash(InputStream in, long length) throws IOException { - final MessageDigest contentDigest = state.contentDigest; + SHA1 contentDigest = SHA1.newInstance(); final byte[] contentReadBuffer = state.contentReadBuffer; - contentDigest.reset(); contentDigest.update(hblob); contentDigest.update((byte) ' '); @@ -1330,9 +1329,6 @@ private static final class IteratorState { /** File name character encoder. */ final CharsetEncoder nameEncoder; - /** Digest computer for {@link #contentId} computations. */ - MessageDigest contentDigest; - /** Buffer used to perform {@link #contentId} computations. */ byte[] contentReadBuffer; @@ -1347,9 +1343,8 @@ private static final class IteratorState { this.nameEncoder = Constants.CHARSET.newEncoder(); } - void initializeDigestAndReadBuffer() { - if (contentDigest == null) { - contentDigest = Constants.newMessageDigest(); + void initializeReadBuffer() { + if (contentReadBuffer == null) { contentReadBuffer = new byte[BUFFER_SIZE]; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java new file mode 100644 index 000000000..0a1dc1046 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2017, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.util.sha1; + +import java.util.Arrays; + +import org.eclipse.jgit.lib.MutableObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.NB; + +/** + * Pure Java implementation of SHA-1 from FIPS 180-1 / RFC 3174. + * + *

+ * See RFC 3174. + * + * @since 4.7 + */ +public class SHA1 { + /** @return a new context to compute a SHA-1 hash of data. */ + public static SHA1 newInstance() { + return new SHA1(); + } + + // Magic initialization constants defined by FIPS180. + private int h0 = 0x67452301; + private int h1 = 0xEFCDAB89; + private int h2 = 0x98BADCFE; + private int h3 = 0x10325476; + private int h4 = 0xC3D2E1F0; + private final int[] w = new int[80]; + + /** Buffer to accumulate partial blocks to 64 byte alignment. */ + private final byte[] buffer = new byte[64]; + + /** Total number of bytes in the message. */ + private long length; + + private SHA1() { + } + + /** + * Update the digest computation by adding a byte. + * + * @param b + */ + public void update(byte b) { + int bufferLen = (int) (length & 63); + length++; + buffer[bufferLen] = b; + if (bufferLen == 63) { + compress(buffer, 0); + } + } + + /** + * Update the digest computation by adding bytes to the message. + * + * @param in + * input array of bytes. + */ + public void update(byte[] in) { + update(in, 0, in.length); + } + + /** + * Update the digest computation by adding bytes to the message. + * + * @param in + * input array of bytes. + * @param p + * offset to start at from {@code in}. + * @param len + * number of bytes to hash. + */ + public void update(byte[] in, int p, int len) { + // SHA-1 compress can only process whole 64 byte blocks. + // Hold partial updates in buffer, whose length is the low bits. + int bufferLen = (int) (length & 63); + length += len; + + if (bufferLen > 0) { + int n = Math.min(64 - bufferLen, len); + System.arraycopy(in, p, buffer, bufferLen, n); + p += n; + len -= n; + if (bufferLen + n < 64) { + return; + } + compress(buffer, 0); + } + while (len >= 64) { + compress(in, p); + p += 64; + len -= 64; + } + if (len > 0) { + System.arraycopy(in, p, buffer, 0, len); + } + } + + private void compress(byte[] block, int p) { + // Method 1 from RFC 3174 section 6.1. + // Method 2 (circular queue of 16 words) is slower. + int a = h0, b = h1, c = h2, d = h3, e = h4; + + // Round 1: 0 <= t <= 15 comes from the input block. + for (int t = 0; t < 16; t++) { + int temp = NB.decodeInt32(block, p + (t << 2)); + w[t] = temp; + temp += ((a << 5) | (a >>> 27)) // S^5(A) + + (((c ^ d) & b) ^ d) // f: 0 <= t <= 19 + + e + 0x5A827999; + e = d; + d = c; + c = (b << 30) | (b >>> 2); // S^30(B) + b = a; + a = temp; + } + + // RFC 3174 6.1.b, extend state vector to 80 words. + for (int t = 16; t < 80; t++) { + int x = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]; + w[t] = (x << 1) | (x >>> 31); // S^1(...) + } + + // Round 1: tail + for (int t = 16; t < 20; t++) { + int temp = ((a << 5) | (a >>> 27)) // S^5(A) + + (((c ^ d) & b) ^ d) // f: 0 <= t <= 19 + + e + w[t] + 0x5A827999; + e = d; + d = c; + c = (b << 30) | (b >>> 2); // S^30(B) + b = a; + a = temp; + } + + // Round 2 + for (int t = 20; t < 40; t++) { + int temp = ((a << 5) | (a >>> 27)) // S^5(A) + + (b ^ c ^ d) // f: 20 <= t <= 39 + + e + w[t] + 0x6ED9EBA1; + e = d; + d = c; + c = (b << 30) | (b >>> 2); // S^30(B) + b = a; + a = temp; + } + + // Round 3 + for (int t = 40; t < 60; t++) { + int temp = ((a << 5) | (a >>> 27)) // S^5(A) + + ((b & c) | (d & (b | c))) // f: 40 <= t <= 59 + + e + w[t] + 0x8F1BBCDC; + e = d; + d = c; + c = (b << 30) | (b >>> 2); // S^30(B) + b = a; + a = temp; + } + + // Round 4 + for (int t = 60; t < 80; t++) { + int temp = ((a << 5) | (a >>> 27)) // S^5(A) + + (b ^ c ^ d) // f: 60 <= t <= 79 + + e + w[t] + 0xCA62C1D6; + e = d; + d = c; + c = (b << 30) | (b >>> 2); // S^30(B) + b = a; + a = temp; + } + + h0 += a; + h1 += b; + h2 += c; + h3 += d; + h4 += e; + } + + private void finish() { + int bufferLen = (int) (length & 63); + if (bufferLen > 55) { + // Last block is too small; pad, compress, pad another block. + buffer[bufferLen++] = (byte) 0x80; + Arrays.fill(buffer, bufferLen, 64, (byte) 0); + compress(buffer, 0); + Arrays.fill(buffer, 0, 56, (byte) 0); + } else { + // Last block can hold padding and length. + buffer[bufferLen++] = (byte) 0x80; + Arrays.fill(buffer, bufferLen, 56, (byte) 0); + } + + // SHA-1 appends the length of the message in bits after the + // padding block (above). Here length is in bytes. Multiply by + // 8 by shifting by 3 as part of storing the 64 bit byte length + // into the two words expected in the trailer. + NB.encodeInt32(buffer, 56, (int) (length >>> (32 - 3))); + NB.encodeInt32(buffer, 60, (int) (length << 3)); + compress(buffer, 0); + } + + /** + * Finish the digest and return the resulting hash. + *

+ * Once {@code digest()} is called, this instance should be discarded. + * + * @return the bytes for the resulting hash. + */ + public byte[] digest() { + finish(); + + byte[] b = new byte[20]; + NB.encodeInt32(b, 0, h0); + NB.encodeInt32(b, 4, h1); + NB.encodeInt32(b, 8, h2); + NB.encodeInt32(b, 12, h3); + NB.encodeInt32(b, 16, h4); + return b; + } + + /** + * Finish the digest and return the resulting hash. + *

+ * Once {@code digest()} is called, this instance should be discarded. + * + * @return the ObjectId for the resulting hash. + */ + public ObjectId toObjectId() { + finish(); + return new ObjectId(h0, h1, h2, h3, h4); + } + + /** + * Finish the digest and return the resulting hash. + *

+ * Once {@code digest()} is called, this instance should be discarded. + * + * @param id + * destination to copy the digest to. + */ + public void digest(MutableObjectId id) { + finish(); + id.set(h0, h1, h2, h3, h4); + } +}