1 /*
2  * Copyright (C) 2021 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.server.pm;
18 
19 import android.annotation.NonNull;
20 import android.text.TextUtils;
21 
22 import com.android.internal.util.HexDump;
23 
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.List;
27 
28 class PerPackageReadTimeouts {
tryParseLong(String str, long defaultValue)29     static long tryParseLong(String str, long defaultValue) {
30         try {
31             return Long.parseLong(str);
32         } catch (NumberFormatException nfe) {
33             return defaultValue;
34         }
35     }
36 
tryParseSha256(String str)37     static byte[] tryParseSha256(String str) {
38         if (TextUtils.isEmpty(str)) {
39             return null;
40         }
41         try {
42             return HexDump.hexStringToByteArray(str);
43         } catch (RuntimeException e) {
44             return null;
45         }
46     }
47 
48     static class Timeouts {
49         public final long minTimeUs;
50         public final long minPendingTimeUs;
51         public final long maxPendingTimeUs;
52 
53         // 3600000000us == 1hr
54         public static final Timeouts DEFAULT = new Timeouts(3600000000L, 3600000000L, 3600000000L);
55 
Timeouts(long minTimeUs, long minPendingTimeUs, long maxPendingTimeUs)56         private Timeouts(long minTimeUs, long minPendingTimeUs, long maxPendingTimeUs) {
57             this.minTimeUs = minTimeUs;
58             this.minPendingTimeUs = minPendingTimeUs;
59             this.maxPendingTimeUs = maxPendingTimeUs;
60         }
61 
parse(String timeouts)62         static Timeouts parse(String timeouts) {
63             String[] splits = timeouts.split(":", 3);
64             if (splits.length != 3) {
65                 return DEFAULT;
66             }
67             final long minTimeUs = tryParseLong(splits[0], DEFAULT.minTimeUs);
68             final long minPendingTimeUs = tryParseLong(splits[1], DEFAULT.minPendingTimeUs);
69             final long maxPendingTimeUs = tryParseLong(splits[2], DEFAULT.maxPendingTimeUs);
70             if (0 <= minTimeUs && minTimeUs <= minPendingTimeUs
71                     && minPendingTimeUs <= maxPendingTimeUs) {
72                 // validity check
73                 return new Timeouts(minTimeUs, minPendingTimeUs, maxPendingTimeUs);
74             }
75             return DEFAULT;
76         }
77     }
78 
79     static class VersionCodes {
80         public final long minVersionCode;
81         public final long maxVersionCode;
82 
83         public static final VersionCodes ALL_VERSION_CODES = new VersionCodes(Long.MIN_VALUE,
84                 Long.MAX_VALUE);
85 
VersionCodes(long minVersionCode, long maxVersionCode)86         private VersionCodes(long minVersionCode, long maxVersionCode) {
87             this.minVersionCode = minVersionCode;
88             this.maxVersionCode = maxVersionCode;
89         }
90 
parse(String codes)91         static VersionCodes parse(String codes) {
92             if (TextUtils.isEmpty(codes)) {
93                 return ALL_VERSION_CODES;
94             }
95             String[] splits = codes.split("-", 2);
96             switch (splits.length) {
97                 case 1: {
98                     // single version code
99                     try {
100                         final long versionCode = Long.parseLong(splits[0]);
101                         return new VersionCodes(versionCode, versionCode);
102                     } catch (NumberFormatException nfe) {
103                         return ALL_VERSION_CODES;
104                     }
105                 }
106                 case 2: {
107                     final long minVersionCode = tryParseLong(splits[0],
108                             ALL_VERSION_CODES.minVersionCode);
109                     final long maxVersionCode = tryParseLong(splits[1],
110                             ALL_VERSION_CODES.maxVersionCode);
111                     if (minVersionCode <= maxVersionCode) {
112                         return new VersionCodes(minVersionCode, maxVersionCode);
113                     }
114                     break;
115                 }
116             }
117             return ALL_VERSION_CODES;
118         }
119     }
120 
121     public final String packageName;
122     public final byte[] sha256certificate;
123     public final VersionCodes versionCodes;
124     public final Timeouts timeouts;
125 
PerPackageReadTimeouts(String packageName, byte[] sha256certificate, VersionCodes versionCodes, Timeouts timeouts)126     private PerPackageReadTimeouts(String packageName, byte[] sha256certificate,
127             VersionCodes versionCodes, Timeouts timeouts) {
128         this.packageName = packageName;
129         this.sha256certificate = sha256certificate;
130         this.versionCodes = versionCodes;
131         this.timeouts = timeouts;
132     }
133 
134     @SuppressWarnings("fallthrough")
parse(String timeoutsStr, VersionCodes defaultVersionCodes, Timeouts defaultTimeouts)135     static PerPackageReadTimeouts parse(String timeoutsStr, VersionCodes defaultVersionCodes,
136             Timeouts defaultTimeouts) {
137         String packageName = null;
138         byte[] sha256certificate = null;
139         VersionCodes versionCodes = defaultVersionCodes;
140         Timeouts timeouts = defaultTimeouts;
141 
142         final String[] splits = timeoutsStr.split(":", 4);
143         switch (splits.length) {
144             case 4:
145                 timeouts = Timeouts.parse(splits[3]);
146                 // fall through
147             case 3:
148                 versionCodes = VersionCodes.parse(splits[2]);
149                 // fall through
150             case 2:
151                 sha256certificate = tryParseSha256(splits[1]);
152                 // fall through
153             case 1:
154                 packageName = splits[0];
155                 break;
156             default:
157                 return null;
158         }
159         if (TextUtils.isEmpty(packageName)) {
160             return null;
161         }
162 
163         return new PerPackageReadTimeouts(packageName, sha256certificate, versionCodes,
164                 timeouts);
165     }
166 
parseDigestersList(String defaultTimeoutsStr, String knownDigestersList)167     static @NonNull List<PerPackageReadTimeouts> parseDigestersList(String defaultTimeoutsStr,
168             String knownDigestersList) {
169         if (TextUtils.isEmpty(knownDigestersList)) {
170             return Collections.emptyList();
171         }
172 
173         final VersionCodes defaultVersionCodes = VersionCodes.ALL_VERSION_CODES;
174         final Timeouts defaultTimeouts = Timeouts.parse(defaultTimeoutsStr);
175 
176         String[] packages = knownDigestersList.split(",");
177         List<PerPackageReadTimeouts> result = new ArrayList<>(packages.length);
178         for (int i = 0, size = packages.length; i < size; ++i) {
179             PerPackageReadTimeouts timeouts = PerPackageReadTimeouts.parse(packages[i],
180                     defaultVersionCodes, defaultTimeouts);
181             if (timeouts != null) {
182                 result.add(timeouts);
183             }
184         }
185         return result;
186     }
187 }
188