Home | History | Annotate | Download | only in pm
      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 android.content.pm;
     18 
     19 import android.os.Parcel;
     20 import android.os.Parcelable;
     21 
     22 import java.io.UnsupportedEncodingException;
     23 import java.security.SecureRandom;
     24 import java.util.Random;
     25 
     26 /**
     27  * An identity that uniquely identifies a particular device. In this
     28  * implementation, the identity is represented as a 64-bit integer encoded to a
     29  * 13-character string using RFC 4648's Base32 encoding without the trailing
     30  * padding. This makes it easy for users to read and write the code without
     31  * confusing 'I' (letter) with '1' (one) or 'O' (letter) with '0' (zero).
     32  *
     33  * @hide
     34  */
     35 public class VerifierDeviceIdentity implements Parcelable {
     36     /**
     37      * Encoded size of a long (64-bit) into Base32. This format will end up
     38      * looking like XXXX-XXXX-XXXX-X (length ignores hyphens) when applied with
     39      * the GROUP_SIZE below.
     40      */
     41     private static final int LONG_SIZE = 13;
     42 
     43     /**
     44      * Size of groupings when outputting as strings. This helps people read it
     45      * out and keep track of where they are.
     46      */
     47     private static final int GROUP_SIZE = 4;
     48 
     49     private final long mIdentity;
     50 
     51     private final String mIdentityString;
     52 
     53     /**
     54      * Create a verifier device identity from a long.
     55      *
     56      * @param identity device identity in a 64-bit integer.
     57      * @throws
     58      */
     59     public VerifierDeviceIdentity(long identity) {
     60         mIdentity = identity;
     61         mIdentityString = encodeBase32(identity);
     62     }
     63 
     64     private VerifierDeviceIdentity(Parcel source) {
     65         final long identity = source.readLong();
     66 
     67         mIdentity = identity;
     68         mIdentityString = encodeBase32(identity);
     69     }
     70 
     71     /**
     72      * Generate a new device identity.
     73      *
     74      * @return random uniformly-distributed device identity
     75      */
     76     public static VerifierDeviceIdentity generate() {
     77         final SecureRandom sr = new SecureRandom();
     78         return generate(sr);
     79     }
     80 
     81     /**
     82      * Generate a new device identity using a provided random number generator
     83      * class. This is used for testing.
     84      *
     85      * @param rng random number generator to retrieve the next long from
     86      * @return verifier device identity based on the input from the provided
     87      *         random number generator
     88      */
     89     static VerifierDeviceIdentity generate(Random rng) {
     90         long identity = rng.nextLong();
     91         return new VerifierDeviceIdentity(identity);
     92     }
     93 
     94     private static final char ENCODE[] = {
     95         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
     96         'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
     97         'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
     98         'Y', 'Z', '2', '3', '4', '5', '6', '7',
     99     };
    100 
    101     private static final char SEPARATOR = '-';
    102 
    103     private static final String encodeBase32(long input) {
    104         final char[] alphabet = ENCODE;
    105 
    106         /*
    107          * Make a character array with room for the separators between each
    108          * group.
    109          */
    110         final char encoded[] = new char[LONG_SIZE + (LONG_SIZE / GROUP_SIZE)];
    111 
    112         int index = encoded.length;
    113         for (int i = 0; i < LONG_SIZE; i++) {
    114             /*
    115              * Make sure we don't put a separator at the beginning. Since we're
    116              * building from the rear of the array, we use (LONG_SIZE %
    117              * GROUP_SIZE) to make the odd-size group appear at the end instead
    118              * of the beginning.
    119              */
    120             if (i > 0 && (i % GROUP_SIZE) == (LONG_SIZE % GROUP_SIZE)) {
    121                 encoded[--index] = SEPARATOR;
    122             }
    123 
    124             /*
    125              * Extract 5 bits of data, then shift it out.
    126              */
    127             final int group = (int) (input & 0x1F);
    128             input >>>= 5;
    129 
    130             encoded[--index] = alphabet[group];
    131         }
    132 
    133         return String.valueOf(encoded);
    134     }
    135 
    136     // TODO move this out to its own class (android.util.Base32)
    137     private static final long decodeBase32(byte[] input) throws IllegalArgumentException {
    138         long output = 0L;
    139         int numParsed = 0;
    140 
    141         final int N = input.length;
    142         for (int i = 0; i < N; i++) {
    143             final int group = input[i];
    144 
    145             /*
    146              * This essentially does the reverse of the ENCODED alphabet above
    147              * without a table. A..Z are 0..25 and 2..7 are 26..31.
    148              */
    149             final int value;
    150             if ('A' <= group && group <= 'Z') {
    151                 value = group - 'A';
    152             } else if ('2' <= group && group <= '7') {
    153                 value = group - ('2' - 26);
    154             } else if (group == SEPARATOR) {
    155                 continue;
    156             } else if ('a' <= group && group <= 'z') {
    157                 /* Lowercase letters should be the same as uppercase for Base32 */
    158                 value = group - 'a';
    159             } else if (group == '0') {
    160                 /* Be nice to users that mistake O (letter) for 0 (zero) */
    161                 value = 'O' - 'A';
    162             } else if (group == '1') {
    163                 /* Be nice to users that mistake I (letter) for 1 (one) */
    164                 value = 'I' - 'A';
    165             } else {
    166                 throw new IllegalArgumentException("base base-32 character: " + group);
    167             }
    168 
    169             output = (output << 5) | value;
    170             numParsed++;
    171 
    172             if (numParsed == 1) {
    173                 if ((value & 0xF) != value) {
    174                     throw new IllegalArgumentException("illegal start character; will overflow");
    175                 }
    176             } else if (numParsed > 13) {
    177                 throw new IllegalArgumentException("too long; should have 13 characters");
    178             }
    179         }
    180 
    181         if (numParsed != 13) {
    182             throw new IllegalArgumentException("too short; should have 13 characters");
    183         }
    184 
    185         return output;
    186     }
    187 
    188     @Override
    189     public int hashCode() {
    190         return (int) mIdentity;
    191     }
    192 
    193     @Override
    194     public boolean equals(Object other) {
    195         if (!(other instanceof VerifierDeviceIdentity)) {
    196             return false;
    197         }
    198 
    199         final VerifierDeviceIdentity o = (VerifierDeviceIdentity) other;
    200         return mIdentity == o.mIdentity;
    201     }
    202 
    203     @Override
    204     public String toString() {
    205         return mIdentityString;
    206     }
    207 
    208     public static VerifierDeviceIdentity parse(String deviceIdentity)
    209             throws IllegalArgumentException {
    210         final byte[] input;
    211         try {
    212             input = deviceIdentity.getBytes("US-ASCII");
    213         } catch (UnsupportedEncodingException e) {
    214             throw new IllegalArgumentException("bad base-32 characters in input");
    215         }
    216 
    217         return new VerifierDeviceIdentity(decodeBase32(input));
    218     }
    219 
    220     @Override
    221     public int describeContents() {
    222         return 0;
    223     }
    224 
    225     @Override
    226     public void writeToParcel(Parcel dest, int flags) {
    227         dest.writeLong(mIdentity);
    228     }
    229 
    230     public static final Parcelable.Creator<VerifierDeviceIdentity> CREATOR
    231             = new Parcelable.Creator<VerifierDeviceIdentity>() {
    232         public VerifierDeviceIdentity createFromParcel(Parcel source) {
    233             return new VerifierDeviceIdentity(source);
    234         }
    235 
    236         public VerifierDeviceIdentity[] newArray(int size) {
    237             return new VerifierDeviceIdentity[size];
    238         }
    239     };
    240 }
    241