/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.gallery3d.common; import java.io.IOException; import java.io.InputStream; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; /** * MD5-based digest Wrapper. */ public class Fingerprint { // Instance of the MessageDigest using our specified digest algorithm. private static final MessageDigest DIGESTER; /** * Name of the digest algorithm we use in {@link java.security.MessageDigest} */ private static final String DIGEST_MD5 = "md5"; // Version 1 streamId prefix. // Hard coded stream id length limit is 40-chars. Don't ask! private static final String STREAM_ID_CS_PREFIX = "cs_01_"; // 16 bytes for 128-bit fingerprint private static final int FINGERPRINT_BYTE_LENGTH; // length of prefix + 32 hex chars for 128-bit fingerprint private static final int STREAM_ID_CS_01_LENGTH; static { try { DIGESTER = MessageDigest.getInstance(DIGEST_MD5); FINGERPRINT_BYTE_LENGTH = DIGESTER.getDigestLength(); STREAM_ID_CS_01_LENGTH = STREAM_ID_CS_PREFIX.length() + (FINGERPRINT_BYTE_LENGTH * 2); } catch (NoSuchAlgorithmException e) { // can't continue, but really shouldn't happen throw new IllegalStateException(e); } } // md5 digest bytes. private final byte[] mMd5Digest; /** * Creates a new Fingerprint. */ public Fingerprint(byte[] bytes) { if ((bytes == null) || (bytes.length != FINGERPRINT_BYTE_LENGTH)) { throw new IllegalArgumentException(); } mMd5Digest = bytes; } /** * Creates a Fingerprint based on the contents of a file. * * Note that this will close() stream after calculating the digest. * @param byteCount length of original data will be stored at byteCount[0] as a side product * of the fingerprint calculation */ public static Fingerprint fromInputStream(InputStream stream, long[] byteCount) throws IOException { DigestInputStream in = null; long count = 0; try { in = new DigestInputStream(stream, DIGESTER); byte[] bytes = new byte[8192]; while (true) { // scan through file to compute a fingerprint. int n = in.read(bytes); if (n < 0) break; count += n; } } finally { if (in != null) in.close(); } if ((byteCount != null) && (byteCount.length > 0)) byteCount[0] = count; return new Fingerprint(in.getMessageDigest().digest()); } /** * Decodes a string stream id to a 128-bit fingerprint. */ public static Fingerprint fromStreamId(String streamId) { if ((streamId == null) || !streamId.startsWith(STREAM_ID_CS_PREFIX) || (streamId.length() != STREAM_ID_CS_01_LENGTH)) { throw new IllegalArgumentException("bad streamId: " + streamId); } // decode the hex bytes of the fingerprint portion byte[] bytes = new byte[FINGERPRINT_BYTE_LENGTH]; int byteIdx = 0; for (int idx = STREAM_ID_CS_PREFIX.length(); idx < STREAM_ID_CS_01_LENGTH; idx += 2) { int value = (toDigit(streamId, idx) << 4) | toDigit(streamId, idx + 1); bytes[byteIdx++] = (byte) (value & 0xff); } return new Fingerprint(bytes); } /** * Scans a list of strings for a valid streamId. * * @param streamIdList list of stream id's to be scanned * @return valid fingerprint or null if it can't be found */ public static Fingerprint extractFingerprint(List streamIdList) { for (String streamId : streamIdList) { if (streamId.startsWith(STREAM_ID_CS_PREFIX)) { return fromStreamId(streamId); } } return null; } /** * Encodes a 128-bit fingerprint as a string stream id. * * Stream id string is limited to 40 characters, which could be digits, lower case ASCII and * underscores. */ public String toStreamId() { StringBuilder streamId = new StringBuilder(STREAM_ID_CS_PREFIX); appendHexFingerprint(streamId, mMd5Digest); return streamId.toString(); } public byte[] getBytes() { return mMd5Digest; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Fingerprint)) return false; Fingerprint other = (Fingerprint) obj; return Arrays.equals(mMd5Digest, other.mMd5Digest); } public boolean equals(byte[] md5Digest) { return Arrays.equals(mMd5Digest, md5Digest); } @Override public int hashCode() { return Arrays.hashCode(mMd5Digest); } // Utility methods. private static int toDigit(String streamId, int index) { int digit = Character.digit(streamId.charAt(index), 16); if (digit < 0) { throw new IllegalArgumentException("illegal hex digit in " + streamId); } return digit; } private static void appendHexFingerprint(StringBuilder sb, byte[] bytes) { for (int idx = 0; idx < FINGERPRINT_BYTE_LENGTH; idx++) { int value = bytes[idx]; sb.append(Integer.toHexString((value >> 4) & 0x0f)); sb.append(Integer.toHexString(value& 0x0f)); } } }