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
17package android.content.pm;
18
19import android.os.Parcel;
20import android.os.Parcelable;
21
22import java.io.UnsupportedEncodingException;
23import java.security.SecureRandom;
24import 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 */
35public 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