/*
* 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<String> 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));
}
}
}