1cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root/*
2cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root * Copyright (C) 2011 The Android Open Source Project
3cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root *
4cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root * Licensed under the Apache License, Version 2.0 (the "License");
5cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root * you may not use this file except in compliance with the License.
6cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root * You may obtain a copy of the License at
7cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root *
8cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root *      http://www.apache.org/licenses/LICENSE-2.0
9cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root *
10cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root * Unless required by applicable law or agreed to in writing, software
11cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root * distributed under the License is distributed on an "AS IS" BASIS,
12cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root * See the License for the specific language governing permissions and
14cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root * limitations under the License.
15cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root */
16cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
17cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Rootpackage com.android.server.pm;
18cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
19cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Rootimport com.android.internal.util.XmlUtils;
20cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
21cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Rootimport org.xmlpull.v1.XmlPullParser;
22cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Rootimport org.xmlpull.v1.XmlPullParserException;
23cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Rootimport org.xmlpull.v1.XmlSerializer;
24cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
25cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Rootimport android.content.pm.Signature;
26cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Rootimport android.util.Log;
27cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
28cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Rootimport java.io.IOException;
29cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Rootimport java.util.ArrayList;
30cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
31cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Rootclass PackageSignatures {
32cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    Signature[] mSignatures;
33cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
34cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    PackageSignatures(PackageSignatures orig) {
35cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        if (orig != null && orig.mSignatures != null) {
36cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            mSignatures = orig.mSignatures.clone();
37cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        }
38cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    }
39cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
40cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    PackageSignatures(Signature[] sigs) {
41cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        assignSignatures(sigs);
42cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    }
43cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
44cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    PackageSignatures() {
45cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    }
46cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
47cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    void writeXml(XmlSerializer serializer, String tagName,
48cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            ArrayList<Signature> pastSignatures) throws IOException {
49cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        if (mSignatures == null) {
50cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            return;
51cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        }
52cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        serializer.startTag(null, tagName);
53cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        serializer.attribute(null, "count",
54cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                Integer.toString(mSignatures.length));
55cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        for (int i=0; i<mSignatures.length; i++) {
56cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            serializer.startTag(null, "cert");
57cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            final Signature sig = mSignatures[i];
58cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            final int sigHash = sig.hashCode();
59cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            final int numPast = pastSignatures.size();
60cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            int j;
61cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            for (j=0; j<numPast; j++) {
62cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                Signature pastSig = pastSignatures.get(j);
63cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                if (pastSig.hashCode() == sigHash && pastSig.equals(sig)) {
64cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                    serializer.attribute(null, "index", Integer.toString(j));
65cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                    break;
66cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                }
67cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            }
68cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            if (j >= numPast) {
69cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                pastSignatures.add(sig);
70cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                serializer.attribute(null, "index", Integer.toString(numPast));
71cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                serializer.attribute(null, "key", sig.toCharsString());
72cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            }
73cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            serializer.endTag(null, "cert");
74cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        }
75cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        serializer.endTag(null, tagName);
76cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    }
77cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
78cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    void readXml(XmlPullParser parser, ArrayList<Signature> pastSignatures)
79cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            throws IOException, XmlPullParserException {
80cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        String countStr = parser.getAttributeValue(null, "count");
81cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        if (countStr == null) {
82cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            PackageManagerService.reportSettingsProblem(Log.WARN,
83cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                    "Error in package manager settings: <signatures> has"
84cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                       + " no count at " + parser.getPositionDescription());
85cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            XmlUtils.skipCurrentTag(parser);
86cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        }
87cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        final int count = Integer.parseInt(countStr);
88cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        mSignatures = new Signature[count];
89cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        int pos = 0;
90cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
91cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        int outerDepth = parser.getDepth();
92cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        int type;
93cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
94cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root               && (type != XmlPullParser.END_TAG
95cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                       || parser.getDepth() > outerDepth)) {
96cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            if (type == XmlPullParser.END_TAG
97cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                    || type == XmlPullParser.TEXT) {
98cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                continue;
99cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            }
100cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
101cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            String tagName = parser.getName();
102cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            if (tagName.equals("cert")) {
103cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                if (pos < count) {
104cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                    String index = parser.getAttributeValue(null, "index");
105cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                    if (index != null) {
106cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                        try {
107cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                            int idx = Integer.parseInt(index);
108cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                            String key = parser.getAttributeValue(null, "key");
109cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                            if (key == null) {
110cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                if (idx >= 0 && idx < pastSignatures.size()) {
111cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                    Signature sig = pastSignatures.get(idx);
112cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                    if (sig != null) {
113cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                        mSignatures[pos] = pastSignatures.get(idx);
114cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                        pos++;
115cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                    } else {
116cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                        PackageManagerService.reportSettingsProblem(Log.WARN,
117cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                                "Error in package manager settings: <cert> "
118cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                                   + "index " + index + " is not defined at "
119cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                                   + parser.getPositionDescription());
120cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                    }
121cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                } else {
122cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                    PackageManagerService.reportSettingsProblem(Log.WARN,
123cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                            "Error in package manager settings: <cert> "
124cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                               + "index " + index + " is out of bounds at "
125cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                               + parser.getPositionDescription());
126cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                }
127cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                            } else {
128cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                while (pastSignatures.size() <= idx) {
129cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                    pastSignatures.add(null);
130cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                }
131cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                Signature sig = new Signature(key);
132cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                pastSignatures.set(idx, sig);
133cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                mSignatures[pos] = sig;
134cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                pos++;
135cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                            }
136cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                        } catch (NumberFormatException e) {
137cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                            PackageManagerService.reportSettingsProblem(Log.WARN,
138cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                    "Error in package manager settings: <cert> "
139cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                       + "index " + index + " is not a number at "
140cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                       + parser.getPositionDescription());
1411137341885d8dc451dddc2e01319fb0fab00bbc3Kenny Root                        } catch (IllegalArgumentException e) {
1421137341885d8dc451dddc2e01319fb0fab00bbc3Kenny Root                            PackageManagerService.reportSettingsProblem(Log.WARN,
1431137341885d8dc451dddc2e01319fb0fab00bbc3Kenny Root                                    "Error in package manager settings: <cert> "
1441137341885d8dc451dddc2e01319fb0fab00bbc3Kenny Root                                       + "index " + index + " has an invalid signature at "
1451137341885d8dc451dddc2e01319fb0fab00bbc3Kenny Root                                       + parser.getPositionDescription() + ": "
1461137341885d8dc451dddc2e01319fb0fab00bbc3Kenny Root                                       + e.getMessage());
147cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                        }
148cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                    } else {
149cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                        PackageManagerService.reportSettingsProblem(Log.WARN,
150cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                "Error in package manager settings: <cert> has"
151cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                                   + " no index at " + parser.getPositionDescription());
152cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                    }
153cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                } else {
154cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                    PackageManagerService.reportSettingsProblem(Log.WARN,
155cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                            "Error in package manager settings: too "
156cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                               + "many <cert> tags, expected " + count
157cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                               + " at " + parser.getPositionDescription());
158cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                }
159cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            } else {
160cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                PackageManagerService.reportSettingsProblem(Log.WARN,
161cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                        "Unknown element under <cert>: "
162cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                        + parser.getName());
163cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            }
164cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            XmlUtils.skipCurrentTag(parser);
165cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        }
166cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
167cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        if (pos < count) {
168cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            // Should never happen -- there is an error in the written
169cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            // settings -- but if it does we don't want to generate
170cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            // a bad array.
171cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            Signature[] newSigs = new Signature[pos];
172cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            System.arraycopy(mSignatures, 0, newSigs, 0, pos);
173cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            mSignatures = newSigs;
174cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        }
175cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    }
176cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
177cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    void assignSignatures(Signature[] sigs) {
178cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        if (sigs == null) {
179cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            mSignatures = null;
180cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            return;
181cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        }
182cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        mSignatures = new Signature[sigs.length];
183cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        for (int i=0; i<sigs.length; i++) {
184cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            mSignatures[i] = sigs[i];
185cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        }
186cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    }
187cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root
188cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    @Override
189cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    public String toString() {
190cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        StringBuffer buf = new StringBuffer(128);
191cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        buf.append("PackageSignatures{");
192cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        buf.append(Integer.toHexString(System.identityHashCode(this)));
193cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        buf.append(" [");
194cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        if (mSignatures != null) {
195cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            for (int i=0; i<mSignatures.length; i++) {
196cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                if (i > 0) buf.append(", ");
197cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                buf.append(Integer.toHexString(
198cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root                        System.identityHashCode(mSignatures[i])));
199cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root            }
200cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        }
201cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        buf.append("]}");
202cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root        return buf.toString();
203cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root    }
204cf0b38ca6e5aa5efded7dbdbb623f6cd2746c96aKenny Root}