10aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root/* 20aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * Copyright (C) 2011 The Android Open Source Project 30aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * 40aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * Licensed under the Apache License, Version 2.0 (the "License"); 50aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * you may not use this file except in compliance with the License. 60aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * You may obtain a copy of the License at 70aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * 80aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * http://www.apache.org/licenses/LICENSE-2.0 90aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * 100aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * Unless required by applicable law or agreed to in writing, software 110aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * distributed under the License is distributed on an "AS IS" BASIS, 120aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * See the License for the specific language governing permissions and 140aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * limitations under the License. 150aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root */ 160aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 170aaa0d931716e9f57a1d84d795fab2df75092756Kenny Rootpackage android.content.pm; 180aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 190aaa0d931716e9f57a1d84d795fab2df75092756Kenny Rootimport android.os.Parcel; 200aaa0d931716e9f57a1d84d795fab2df75092756Kenny Rootimport android.os.Parcelable; 210aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 220aaa0d931716e9f57a1d84d795fab2df75092756Kenny Rootimport java.io.UnsupportedEncodingException; 230aaa0d931716e9f57a1d84d795fab2df75092756Kenny Rootimport java.security.SecureRandom; 240aaa0d931716e9f57a1d84d795fab2df75092756Kenny Rootimport java.util.Random; 250aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 260aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root/** 270aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * An identity that uniquely identifies a particular device. In this 280aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * implementation, the identity is represented as a 64-bit integer encoded to a 290aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * 13-character string using RFC 4648's Base32 encoding without the trailing 300aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * padding. This makes it easy for users to read and write the code without 310aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * confusing 'I' (letter) with '1' (one) or 'O' (letter) with '0' (zero). 320aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * 330aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * @hide 340aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root */ 350aaa0d931716e9f57a1d84d795fab2df75092756Kenny Rootpublic class VerifierDeviceIdentity implements Parcelable { 360aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root /** 370aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * Encoded size of a long (64-bit) into Base32. This format will end up 380aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * looking like XXXX-XXXX-XXXX-X (length ignores hyphens) when applied with 390aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * the GROUP_SIZE below. 400aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root */ 410aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root private static final int LONG_SIZE = 13; 420aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 430aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root /** 440aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * Size of groupings when outputting as strings. This helps people read it 450aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * out and keep track of where they are. 460aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root */ 470aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root private static final int GROUP_SIZE = 4; 480aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 490aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root private final long mIdentity; 500aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 510aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root private final String mIdentityString; 520aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 530aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root /** 540aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * Create a verifier device identity from a long. 550aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * 560aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * @param identity device identity in a 64-bit integer. 570aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * @throws 580aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root */ 590aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root public VerifierDeviceIdentity(long identity) { 600aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root mIdentity = identity; 610aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root mIdentityString = encodeBase32(identity); 620aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 630aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 640aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root private VerifierDeviceIdentity(Parcel source) { 650aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root final long identity = source.readLong(); 660aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 670aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root mIdentity = identity; 680aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root mIdentityString = encodeBase32(identity); 690aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 700aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 710aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root /** 720aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * Generate a new device identity. 730aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * 740aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * @return random uniformly-distributed device identity 750aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root */ 760aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root public static VerifierDeviceIdentity generate() { 770aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root final SecureRandom sr = new SecureRandom(); 780aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return generate(sr); 790aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 800aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 810aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root /** 820aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * Generate a new device identity using a provided random number generator 830aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * class. This is used for testing. 840aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * 850aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * @param rng random number generator to retrieve the next long from 860aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * @return verifier device identity based on the input from the provided 870aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * random number generator 880aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root */ 890aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root static VerifierDeviceIdentity generate(Random rng) { 900aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root long identity = rng.nextLong(); 910aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return new VerifierDeviceIdentity(identity); 920aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 930aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 940aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root private static final char ENCODE[] = { 950aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 960aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 970aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 980aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 'Y', 'Z', '2', '3', '4', '5', '6', '7', 990aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root }; 1000aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1010aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root private static final char SEPARATOR = '-'; 1020aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1030aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root private static final String encodeBase32(long input) { 1040aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root final char[] alphabet = ENCODE; 1050aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1060aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root /* 1070aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * Make a character array with room for the separators between each 1080aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * group. 1090aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root */ 1100aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root final char encoded[] = new char[LONG_SIZE + (LONG_SIZE / GROUP_SIZE)]; 1110aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1120aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root int index = encoded.length; 1130aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root for (int i = 0; i < LONG_SIZE; i++) { 1140aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root /* 1150aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * Make sure we don't put a separator at the beginning. Since we're 1160aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * building from the rear of the array, we use (LONG_SIZE % 1170aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * GROUP_SIZE) to make the odd-size group appear at the end instead 1180aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * of the beginning. 1190aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root */ 1200aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root if (i > 0 && (i % GROUP_SIZE) == (LONG_SIZE % GROUP_SIZE)) { 1210aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root encoded[--index] = SEPARATOR; 1220aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 1230aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1240aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root /* 1250aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * Extract 5 bits of data, then shift it out. 1260aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root */ 1270aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root final int group = (int) (input & 0x1F); 1280aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root input >>>= 5; 1290aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1300aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root encoded[--index] = alphabet[group]; 1310aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 1320aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1330aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return String.valueOf(encoded); 1340aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 1350aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1360aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root // TODO move this out to its own class (android.util.Base32) 1370aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root private static final long decodeBase32(byte[] input) throws IllegalArgumentException { 1380aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root long output = 0L; 1390aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root int numParsed = 0; 1400aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1410aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root final int N = input.length; 1420aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root for (int i = 0; i < N; i++) { 1430aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root final int group = input[i]; 1440aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1450aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root /* 1460aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * This essentially does the reverse of the ENCODED alphabet above 1470aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root * without a table. A..Z are 0..25 and 2..7 are 26..31. 1480aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root */ 1490aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root final int value; 1500aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root if ('A' <= group && group <= 'Z') { 1510aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root value = group - 'A'; 1520aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } else if ('2' <= group && group <= '7') { 1530aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root value = group - ('2' - 26); 1540aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } else if (group == SEPARATOR) { 1550aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root continue; 156a0f264e1afa3c0a00a5af0db362f884b122d978dKenny Root } else if ('a' <= group && group <= 'z') { 157a0f264e1afa3c0a00a5af0db362f884b122d978dKenny Root /* Lowercase letters should be the same as uppercase for Base32 */ 158a0f264e1afa3c0a00a5af0db362f884b122d978dKenny Root value = group - 'a'; 159a0f264e1afa3c0a00a5af0db362f884b122d978dKenny Root } else if (group == '0') { 160a0f264e1afa3c0a00a5af0db362f884b122d978dKenny Root /* Be nice to users that mistake O (letter) for 0 (zero) */ 161a0f264e1afa3c0a00a5af0db362f884b122d978dKenny Root value = 'O' - 'A'; 162a0f264e1afa3c0a00a5af0db362f884b122d978dKenny Root } else if (group == '1') { 163a0f264e1afa3c0a00a5af0db362f884b122d978dKenny Root /* Be nice to users that mistake I (letter) for 1 (one) */ 164a0f264e1afa3c0a00a5af0db362f884b122d978dKenny Root value = 'I' - 'A'; 1650aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } else { 1660aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root throw new IllegalArgumentException("base base-32 character: " + group); 1670aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 1680aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1690aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root output = (output << 5) | value; 1700aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root numParsed++; 1710aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1720aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root if (numParsed == 1) { 1730aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root if ((value & 0xF) != value) { 1740aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root throw new IllegalArgumentException("illegal start character; will overflow"); 1750aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 1760aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } else if (numParsed > 13) { 1770aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root throw new IllegalArgumentException("too long; should have 13 characters"); 1780aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 1790aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 1800aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1810aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root if (numParsed != 13) { 1820aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root throw new IllegalArgumentException("too short; should have 13 characters"); 1830aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 1840aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1850aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return output; 1860aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 1870aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1880aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root @Override 1890aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root public int hashCode() { 1900aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return (int) mIdentity; 1910aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 1920aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1930aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root @Override 1940aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root public boolean equals(Object other) { 1950aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root if (!(other instanceof VerifierDeviceIdentity)) { 1960aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return false; 1970aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 1980aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 1990aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root final VerifierDeviceIdentity o = (VerifierDeviceIdentity) other; 2000aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return mIdentity == o.mIdentity; 2010aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 2020aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 2030aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root @Override 2040aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root public String toString() { 2050aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return mIdentityString; 2060aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 2070aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 2080aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root public static VerifierDeviceIdentity parse(String deviceIdentity) 2090aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root throws IllegalArgumentException { 2100aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root final byte[] input; 2110aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root try { 2120aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root input = deviceIdentity.getBytes("US-ASCII"); 2130aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } catch (UnsupportedEncodingException e) { 2140aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root throw new IllegalArgumentException("bad base-32 characters in input"); 2150aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 2160aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 2170aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return new VerifierDeviceIdentity(decodeBase32(input)); 2180aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 2190aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 2200aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root @Override 2210aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root public int describeContents() { 2220aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return 0; 2230aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 2240aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 2250aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root @Override 2260aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root public void writeToParcel(Parcel dest, int flags) { 2270aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root dest.writeLong(mIdentity); 2280aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 2290aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 2300aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root public static final Parcelable.Creator<VerifierDeviceIdentity> CREATOR 2310aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root = new Parcelable.Creator<VerifierDeviceIdentity>() { 2320aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root public VerifierDeviceIdentity createFromParcel(Parcel source) { 2330aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return new VerifierDeviceIdentity(source); 2340aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 2350aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root 2360aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root public VerifierDeviceIdentity[] newArray(int size) { 2370aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root return new VerifierDeviceIdentity[size]; 2380aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root } 2390aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root }; 2400aaa0d931716e9f57a1d84d795fab2df75092756Kenny Root} 241