Home | History | Annotate | Download | only in common
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.gallery3d.common;
     18 
     19 import android.content.ContentResolver;
     20 import android.net.Uri;
     21 
     22 import java.io.FileInputStream;
     23 import java.io.IOException;
     24 import java.io.InputStream;
     25 import java.security.DigestInputStream;
     26 import java.security.MessageDigest;
     27 import java.security.NoSuchAlgorithmException;
     28 import java.util.Arrays;
     29 import java.util.List;
     30 
     31 /**
     32  * MD5-based digest Wrapper.
     33  */
     34 public class Fingerprint {
     35     // Instance of the MessageDigest using our specified digest algorithm.
     36     private static final MessageDigest DIGESTER;
     37 
     38     /**
     39      * Name of the digest algorithm we use in {@link java.security.MessageDigest}
     40      */
     41     private static final String DIGEST_MD5 = "md5";
     42 
     43     // Version 1 streamId prefix.
     44     // Hard coded stream id length limit is 40-chars. Don't ask!
     45     private static final String STREAM_ID_CS_PREFIX = "cs_01_";
     46 
     47     // 16 bytes for 128-bit fingerprint
     48     private static final int FINGERPRINT_BYTE_LENGTH;
     49 
     50     // length of prefix + 32 hex chars for 128-bit fingerprint
     51     private static final int STREAM_ID_CS_01_LENGTH;
     52 
     53     static {
     54         try {
     55             DIGESTER = MessageDigest.getInstance(DIGEST_MD5);
     56             FINGERPRINT_BYTE_LENGTH = DIGESTER.getDigestLength();
     57             STREAM_ID_CS_01_LENGTH = STREAM_ID_CS_PREFIX.length()
     58                     + (FINGERPRINT_BYTE_LENGTH * 2);
     59         } catch (NoSuchAlgorithmException e) {
     60             // can't continue, but really shouldn't happen
     61             throw new IllegalStateException(e);
     62         }
     63     }
     64 
     65     // md5 digest bytes.
     66     private final byte[] mMd5Digest;
     67 
     68     /**
     69      * Creates a new Fingerprint.
     70      */
     71     public Fingerprint(byte[] bytes) {
     72         if ((bytes == null) || (bytes.length != FINGERPRINT_BYTE_LENGTH)) {
     73             throw new IllegalArgumentException();
     74         }
     75         mMd5Digest = bytes;
     76     }
     77 
     78     /**
     79      * Creates a Fingerprint based on the contents of a file.
     80      *
     81      * Note that this will close() stream after calculating the digest.
     82      * @param byteCount length of original data will be stored at byteCount[0] as a side product
     83      *        of the fingerprint calculation
     84      */
     85     public static Fingerprint fromInputStream(InputStream stream, long[] byteCount)
     86             throws IOException {
     87         DigestInputStream in = null;
     88         long count = 0;
     89         try {
     90             in = new DigestInputStream(stream, DIGESTER);
     91             byte[] bytes = new byte[8192];
     92             while (true) {
     93                 // scan through file to compute a fingerprint.
     94                 int n = in.read(bytes);
     95                 if (n < 0) break;
     96                 count += n;
     97             }
     98         } finally {
     99             if (in != null) in.close();
    100         }
    101         if ((byteCount != null) && (byteCount.length > 0)) byteCount[0] = count;
    102         return new Fingerprint(in.getMessageDigest().digest());
    103     }
    104 
    105     /**
    106      * Decodes a string stream id to a 128-bit fingerprint.
    107      */
    108     public static Fingerprint fromStreamId(String streamId) {
    109         if ((streamId == null)
    110                 || !streamId.startsWith(STREAM_ID_CS_PREFIX)
    111                 || (streamId.length() != STREAM_ID_CS_01_LENGTH)) {
    112             throw new IllegalArgumentException("bad streamId: " + streamId);
    113         }
    114 
    115         // decode the hex bytes of the fingerprint portion
    116         byte[] bytes = new byte[FINGERPRINT_BYTE_LENGTH];
    117         int byteIdx = 0;
    118         for (int idx = STREAM_ID_CS_PREFIX.length(); idx < STREAM_ID_CS_01_LENGTH;
    119                 idx += 2) {
    120             int value = (toDigit(streamId, idx) << 4) | toDigit(streamId, idx + 1);
    121             bytes[byteIdx++] = (byte) (value & 0xff);
    122         }
    123         return new Fingerprint(bytes);
    124     }
    125 
    126     /**
    127      * Scans a list of strings for a valid streamId.
    128      *
    129      * @param streamIdList list of stream id's to be scanned
    130      * @return valid fingerprint or null if it can't be found
    131      */
    132     public static Fingerprint extractFingerprint(List<String> streamIdList) {
    133         for (String streamId : streamIdList) {
    134             if (streamId.startsWith(STREAM_ID_CS_PREFIX)) {
    135                 return fromStreamId(streamId);
    136             }
    137         }
    138         return null;
    139     }
    140 
    141     /**
    142      * Encodes a 128-bit fingerprint as a string stream id.
    143      *
    144      * Stream id string is limited to 40 characters, which could be digits, lower case ASCII and
    145      * underscores.
    146      */
    147     public String toStreamId() {
    148         StringBuilder streamId = new StringBuilder(STREAM_ID_CS_PREFIX);
    149         appendHexFingerprint(streamId, mMd5Digest);
    150         return streamId.toString();
    151     }
    152 
    153     public byte[] getBytes() {
    154         return mMd5Digest;
    155     }
    156 
    157     @Override
    158     public boolean equals(Object obj) {
    159         if (this == obj) return true;
    160         if (!(obj instanceof Fingerprint)) return false;
    161         Fingerprint other = (Fingerprint) obj;
    162         return Arrays.equals(mMd5Digest, other.mMd5Digest);
    163     }
    164 
    165     public boolean equals(byte[] md5Digest) {
    166         return Arrays.equals(mMd5Digest, md5Digest);
    167     }
    168 
    169     @Override
    170     public int hashCode() {
    171         return Arrays.hashCode(mMd5Digest);
    172     }
    173 
    174     // Utility methods.
    175 
    176     private static int toDigit(String streamId, int index) {
    177         int digit = Character.digit(streamId.charAt(index), 16);
    178         if (digit < 0) {
    179             throw new IllegalArgumentException("illegal hex digit in " + streamId);
    180         }
    181         return digit;
    182     }
    183 
    184     private static void appendHexFingerprint(StringBuilder sb, byte[] bytes) {
    185         for (int idx = 0; idx < FINGERPRINT_BYTE_LENGTH; idx++) {
    186             int value = bytes[idx];
    187             sb.append(Integer.toHexString((value >> 4) & 0x0f));
    188             sb.append(Integer.toHexString(value& 0x0f));
    189         }
    190     }
    191 }
    192