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 package com.android.bedstead.dpmwrapper;
17 
18 import static com.android.bedstead.dpmwrapper.Utils.EXTRA_ARG_PREFIX;
19 import static com.android.bedstead.dpmwrapper.Utils.VERBOSE;
20 
21 import android.annotation.Nullable;
22 import android.content.Intent;
23 import android.os.Bundle;
24 import android.os.CpuUsageInfo;
25 import android.os.Parcelable;
26 import android.util.ArraySet;
27 import android.util.Log;
28 
29 import java.io.Serializable;
30 import java.security.PrivateKey;
31 import java.security.cert.Certificate;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Set;
35 
36 final class DataFormatter {
37 
38     private static final String TAG = DataFormatter.class.getSimpleName();
39 
40     // NOTE: Bundle has a putObject() method that would make it much easier to marshal the args,
41     // but unfortunately there is no Intent.putObjectExtra() method (and intent.getBundle() returns
42     // a copy, so we need to explicitly marshal any supported type).
43     private static final String TYPE_BOOLEAN = "boolean";
44     private static final String TYPE_INT = "int";
45     private static final String TYPE_LONG = "long";
46     private static final String TYPE_BYTE_ARRAY = "byte_array";
47     private static final String TYPE_FLOAT_ARRAY = "float_array";
48     private static final String TYPE_STRING_OR_CHAR_SEQUENCE = "string";
49     private static final String TYPE_PARCELABLE = "parcelable";
50     private static final String TYPE_SERIALIZABLE = "serializable";
51     private static final String TYPE_ARRAY_LIST_STRING = "array_list_string";
52     private static final String TYPE_ARRAY_LIST_PARCELABLE = "array_list_parcelable";
53     // NOTE: the value of a TYPE_ARRAY_LIST_BYTE_ARRAY is its length - the individual elements
54     // are contained on separate extras, one per index, whose name is defined by
55     // getExtraNameForArrayListElement()
56     private static final String TYPE_ARRAY_LIST_BYTE_ARRAY = "array_list_byte_array";
57     private static final String TYPE_SET_STRING = "set_string";
58     // Must handle each array of parcelable subclass , as they need to be explicitly converted
59     private static final String TYPE_CPU_USAFE_INFO_ARRAY = "cpu_usage_info_array";
60     private static final String TYPE_CERTIFICATE = "certificate";
61     private static final String TYPE_PRIVATE_KEY = "private_key";
62 
63     // Used when a method is called passing a null argument - the proper method will have to be
64     // infered using findMethod()
65     private static final String TYPE_NULL = "null";
66 
addArg(Intent intent, Object[] args, int index)67     static void addArg(Intent intent, Object[] args, int index) {
68         Object value = args[index];
69         String extraTypeName = getArgExtraTypeName(index);
70         String extraValueName = getArgExtraValueName(index);
71         if (VERBOSE) {
72             Log.v(TAG, "addArg(" + index + "): typeName= " + extraTypeName
73                     + ", valueName= " + extraValueName);
74         }
75         if (value == null) {
76             logMarshalling("Adding Null", index, extraTypeName, TYPE_NULL, extraValueName, value);
77             intent.putExtra(extraTypeName, TYPE_NULL);
78             return;
79 
80         }
81         if ((value instanceof Boolean)) {
82             logMarshalling("Adding Boolean", index, extraTypeName, TYPE_BOOLEAN, extraValueName,
83                     value);
84             intent.putExtra(extraTypeName, TYPE_BOOLEAN);
85             intent.putExtra(extraValueName, ((Boolean) value).booleanValue());
86             return;
87         }
88         if ((value instanceof Integer)) {
89             logMarshalling("Adding Integer", index, extraTypeName, TYPE_INT, extraValueName, value);
90             intent.putExtra(extraTypeName, TYPE_INT);
91             intent.putExtra(extraValueName, ((Integer) value).intValue());
92             return;
93         }
94         if ((value instanceof Long)) {
95             logMarshalling("Adding Long", index, extraTypeName, TYPE_LONG, extraValueName, value);
96             intent.putExtra(extraTypeName, TYPE_LONG);
97             intent.putExtra(extraValueName, ((Long) value).longValue());
98             return;
99         }
100         if ((value instanceof byte[])) {
101             logMarshalling("Adding byte[]", index, extraTypeName, TYPE_BYTE_ARRAY, extraValueName,
102                     value);
103             intent.putExtra(extraTypeName, TYPE_BYTE_ARRAY);
104             intent.putExtra(extraValueName, (byte[]) value);
105             return;
106         }
107         if ((value instanceof float[])) {
108             logMarshalling("Adding float[]", index, extraTypeName, TYPE_FLOAT_ARRAY, extraValueName,
109                     value);
110             intent.putExtra(extraTypeName, TYPE_FLOAT_ARRAY);
111             intent.putExtra(extraValueName, (float[]) value);
112             return;
113         }
114         if ((value instanceof CpuUsageInfo[])) {
115             logMarshalling("Adding CpuUsageInfo[]", index, extraTypeName,
116                     TYPE_CPU_USAFE_INFO_ARRAY, extraValueName, value);
117             intent.putExtra(extraTypeName, TYPE_CPU_USAFE_INFO_ARRAY);
118             intent.putExtra(extraValueName, (CpuUsageInfo[]) value);
119             return;
120         }
121         if ((value instanceof CharSequence)) {
122             logMarshalling("Adding CharSequence", index, extraTypeName,
123                     TYPE_STRING_OR_CHAR_SEQUENCE, extraValueName, value);
124             intent.putExtra(extraTypeName, TYPE_STRING_OR_CHAR_SEQUENCE);
125             intent.putExtra(extraValueName, (CharSequence) value);
126             return;
127         }
128         if (value instanceof PrivateKey) {
129             if (!(value instanceof Serializable)) {
130                 throw new IllegalArgumentException("PrivateKey is not Serializable: "  + value);
131             }
132             logMarshalling("Adding PrivateKey", index, extraTypeName, TYPE_PRIVATE_KEY,
133                     extraValueName, value);
134             intent.putExtra(extraTypeName, TYPE_PRIVATE_KEY);
135             intent.putExtra(extraValueName, (Serializable) value);
136             return;
137         }
138         if ((value instanceof Parcelable)) {
139             logMarshalling("Adding Parcelable", index, extraTypeName, TYPE_PARCELABLE,
140                     extraValueName, value);
141             intent.putExtra(extraTypeName, TYPE_PARCELABLE);
142             intent.putExtra(extraValueName, (Parcelable) value);
143             return;
144         }
145 
146         if (value instanceof Certificate) {
147             if (!(value instanceof Serializable)) {
148                 throw new IllegalArgumentException("Certificate is not Serializable: "  + value);
149             }
150             logMarshalling("Adding Certificate", index, extraTypeName, TYPE_CERTIFICATE,
151                     extraValueName, value);
152             intent.putExtra(extraTypeName, TYPE_CERTIFICATE);
153             intent.putExtra(extraValueName, (Serializable) value);
154             return;
155         }
156 
157         if ((value instanceof List<?>)) {
158             List<?> list = (List<?>) value;
159 
160             String type = null;
161             if (list.isEmpty()) {
162                 Log.w(TAG, "Empty list at index " + index + "; assuming it's List<String>");
163                 type = TYPE_ARRAY_LIST_STRING;
164             } else {
165                 Object firstItem = list.get(0);
166                 if (firstItem instanceof String) {
167                     type = TYPE_ARRAY_LIST_STRING;
168                 } else if (firstItem instanceof Parcelable) {
169                     type = TYPE_ARRAY_LIST_PARCELABLE;
170                 } else if (firstItem instanceof byte[]) {
171                     type = TYPE_ARRAY_LIST_BYTE_ARRAY;
172                 } else {
173                     throw new IllegalArgumentException("Unsupported List type at index " + index
174                             + ": " + firstItem);
175                 }
176             }
177 
178             logMarshalling("Adding " + type, index, extraTypeName, type, extraValueName, value);
179             intent.putExtra(extraTypeName, type);
180             switch (type) {
181                 case TYPE_ARRAY_LIST_STRING:
182                     @SuppressWarnings("unchecked")
183                     ArrayList<String> arrayListString = (value instanceof ArrayList)
184                             ? (ArrayList<String>) list
185                             : new ArrayList<>((List<String>) list);
186                     intent.putStringArrayListExtra(extraValueName, arrayListString);
187                     break;
188                 case TYPE_ARRAY_LIST_PARCELABLE:
189                     @SuppressWarnings("unchecked")
190                     ArrayList<Parcelable> arrayListParcelable = (value instanceof ArrayList)
191                             ? (ArrayList<Parcelable>) list
192                             : new ArrayList<>((List<Parcelable>) list);
193                     intent.putParcelableArrayListExtra(extraValueName, arrayListParcelable);
194                     break;
195                 case TYPE_ARRAY_LIST_BYTE_ARRAY:
196                     @SuppressWarnings("unchecked")
197                     ArrayList<byte[]> arrayListByteArray = (value instanceof ArrayList)
198                             ? (ArrayList<byte[]>) list
199                             : new ArrayList<>((List<byte[]>) list);
200                     int listSize = arrayListByteArray.size();
201                     intent.putExtra(extraValueName, listSize);
202                     for (int i = 0; i < listSize; i++) {
203                         intent.putExtra(getExtraNameForArrayListElement(extraValueName, i),
204                                 arrayListByteArray.get(i));
205                     }
206                     break;
207                 default:
208                     // should never happen because type is checked above
209                     throw new AssertionError("invalid type conversion: " + type);
210             }
211             return;
212         }
213 
214         // TODO(b/176993670): ArraySet<> is encapsulate as ArrayList<>, so most of the code below
215         // could be reused (right now it was copy-and-paste from ArrayList<>, minus the Parcelable
216         // part.
217         if ((value instanceof Set<?>)) {
218             Set<?> set = (Set<?>) value;
219 
220             String type = null;
221             if (set.isEmpty()) {
222                 Log.w(TAG, "Empty set at index " + index + "; assuming it's Set<String>");
223                 type = TYPE_SET_STRING;
224             } else {
225                 Object firstItem = set.iterator().next();
226                 if (firstItem instanceof String) {
227                     type = TYPE_SET_STRING;
228                 } else {
229                     throw new IllegalArgumentException("Unsupported Set type at index "
230                             + index + ": " + firstItem);
231                 }
232             }
233 
234             logMarshalling("Adding " + type, index, extraTypeName, type, extraValueName, value);
235             intent.putExtra(extraTypeName, type);
236             switch (type) {
237                 case TYPE_SET_STRING:
238                     @SuppressWarnings("unchecked")
239                     Set<String> stringSet = (Set<String>) value;
240                     intent.putStringArrayListExtra(extraValueName, new ArrayList<>(stringSet));
241                     break;
242                 default:
243                     // should never happen because type is checked above
244                     throw new AssertionError("invalid type conversion: " + type);
245             }
246             return;
247         }
248 
249         if ((value instanceof Serializable)) {
250             logMarshalling("Adding Serializable", index, extraTypeName, TYPE_SERIALIZABLE,
251                     extraValueName, value);
252             intent.putExtra(extraTypeName, TYPE_SERIALIZABLE);
253             intent.putExtra(extraValueName, (Serializable) value);
254             return;
255         }
256 
257         throw new IllegalArgumentException("Unsupported value type at index " + index + ": "
258                 + (value == null ? "null" : value.getClass()));
259     }
260 
getExtraNameForArrayListElement(String baseExtraName, int index)261     private static String getExtraNameForArrayListElement(String baseExtraName, int index) {
262         return baseExtraName + "_" + index;
263     }
264 
getArg(Bundle extras, Object[] args, @Nullable Class<?>[] parameterTypes, int index)265     static void getArg(Bundle extras, Object[] args, @Nullable Class<?>[] parameterTypes,
266             int index) {
267         String extraTypeName = getArgExtraTypeName(index);
268         String extraValueName = getArgExtraValueName(index);
269         String type = extras.getString(extraTypeName);
270         if (VERBOSE) {
271             Log.v(TAG, "getArg(" + index + "): typeName= " + extraTypeName + ", type=" + type
272                     + ", valueName= " + extraValueName);
273         }
274         Object value = null;
275         switch (type) {
276             case TYPE_NULL:
277                 logMarshalling("Got null", index, extraTypeName, type, extraValueName, value);
278                 break;
279             case TYPE_SET_STRING:
280                 @SuppressWarnings("unchecked")
281                 ArrayList<String> list = (ArrayList<String>) extras.get(extraValueName);
282                 value = new ArraySet<String>(list);
283                 logMarshalling("Got ArraySet<String>", index, extraTypeName, type, extraValueName,
284                         value);
285                 break;
286             case TYPE_CPU_USAFE_INFO_ARRAY:
287                 Parcelable[] raw = (Parcelable[]) extras.get(extraValueName);
288                 CpuUsageInfo[] cast = new CpuUsageInfo[raw.length];
289                 for (int i = 0; i < raw.length; i++) {
290                     cast[i] = (CpuUsageInfo) raw[i];
291                 }
292                 value = cast;
293                 logMarshalling("Got CpuUsageInfo[]", index, extraTypeName, type, extraValueName,
294                         value);
295                 break;
296             case TYPE_ARRAY_LIST_BYTE_ARRAY:
297                 int size = extras.getInt(extraValueName);
298                 ArrayList<byte[]> array = new ArrayList<>(size);
299                 for (int i = 0; i < size; i++) {
300                     String extraName = getExtraNameForArrayListElement(extraValueName, i);
301                     array.add(extras.getByteArray(extraName));
302                 }
303                 value = array;
304                 break;
305             case TYPE_ARRAY_LIST_STRING:
306             case TYPE_ARRAY_LIST_PARCELABLE:
307             case TYPE_BYTE_ARRAY:
308             case TYPE_FLOAT_ARRAY:
309             case TYPE_BOOLEAN:
310             case TYPE_INT:
311             case TYPE_LONG:
312             case TYPE_STRING_OR_CHAR_SEQUENCE:
313             case TYPE_PARCELABLE:
314             case TYPE_SERIALIZABLE:
315             case TYPE_CERTIFICATE:
316             case TYPE_PRIVATE_KEY:
317                 value = extras.get(extraValueName);
318                 logMarshalling("Got generic", index, extraTypeName, type, extraValueName, value);
319                 break;
320             default:
321                 throw new IllegalArgumentException("Unsupported value type at index " + index + ": "
322                         + extraTypeName);
323         }
324         if (parameterTypes != null) {
325             Class<?> parameterType = null;
326             // Must convert special types (like primitive to Object, generic list to list, etc...),
327             // but not those that can be inferred from getClass() (like String or array)
328             switch (type) {
329                 case TYPE_NULL:
330                     break;
331                 case TYPE_BOOLEAN:
332                     parameterType = boolean.class;
333                     break;
334                 case TYPE_INT:
335                     parameterType = int.class;
336                     break;
337                 case TYPE_LONG:
338                     parameterType = long.class;
339                     break;
340                 case TYPE_STRING_OR_CHAR_SEQUENCE:
341                     // A String is a CharSequence, but most methods take String, so we're assuming
342                     // a string and handle the exceptional cases on findMethod()
343                     parameterType = String.class;
344                     break;
345                 case TYPE_ARRAY_LIST_STRING:
346                     parameterType = List.class;
347                     break;
348                 case TYPE_SET_STRING:
349                     parameterType = Set.class;
350                     break;
351                 case TYPE_PRIVATE_KEY:
352                     parameterType = PrivateKey.class;
353                     break;
354                 case TYPE_CERTIFICATE:
355                     parameterType = Certificate.class;
356                     break;
357                 default:
358                     parameterType = value.getClass();
359             }
360             parameterTypes[index] = parameterType;
361         }
362         args[index] = value;
363     }
364 
getArgExtraTypeName(int index)365     static String getArgExtraTypeName(int index) {
366         return EXTRA_ARG_PREFIX + index + "_type";
367     }
368 
getArgExtraValueName(int index)369     static String getArgExtraValueName(int index) {
370         return EXTRA_ARG_PREFIX + index + "_value";
371     }
372 
logMarshalling(String operation, int index, String typeName, String type, String valueName, Object value)373     private static void logMarshalling(String operation, int index, String typeName,
374             String type, String valueName, Object value) {
375         if (VERBOSE) {
376             Log.v(TAG, operation + " on " + index + ": typeName=" + typeName + ", type=" + type
377                     + ", valueName=" + valueName + ", value=" + value);
378         }
379     }
380 
DataFormatter()381     private DataFormatter() {
382         throw new UnsupportedOperationException("contains only static methods");
383     }
384 }
385