1 /*
2  * Copyright (C) 2023 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.car.hal.property;
18 
19 import static android.car.Car.PERMISSION_VENDOR_EXTENSION;
20 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.annotation.Nullable;
24 import android.car.VehiclePropertyIds;
25 import android.car.builtin.os.TraceHelper;
26 import android.car.builtin.util.Slogf;
27 import android.car.feature.FeatureFlags;
28 import android.car.feature.FeatureFlagsImpl;
29 import android.content.Context;
30 import android.hardware.automotive.vehicle.VehiclePropertyStatus;
31 import android.hardware.automotive.vehicle.VehiclePropertyType;
32 import android.os.Trace;
33 import android.util.ArraySet;
34 import android.util.JsonReader;
35 import android.util.SparseArray;
36 
37 import com.android.car.CarLog;
38 import com.android.car.hal.BidirectionalSparseIntArray;
39 import com.android.car.hal.HalPropValue;
40 import com.android.car.hal.property.PropertyPermissionInfo.AllOfPermissions;
41 import com.android.car.hal.property.PropertyPermissionInfo.AnyOfPermissions;
42 import com.android.car.hal.property.PropertyPermissionInfo.PermissionCondition;
43 import com.android.car.hal.property.PropertyPermissionInfo.PropertyPermissions;
44 import com.android.car.hal.property.PropertyPermissionInfo.PropertyPermissionsBuilder;
45 import com.android.car.hal.property.PropertyPermissionInfo.SinglePermission;
46 import com.android.car.internal.property.CarPropertyHelper;
47 import com.android.internal.annotations.GuardedBy;
48 import com.android.internal.annotations.VisibleForTesting;
49 
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.InputStreamReader;
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.List;
56 import java.util.Set;
57 
58 /**
59  * A singleton class to define which AIDL HAL property IDs are used by PropertyHalService.
60  * This class binds the read and write permissions to the property ID.
61  */
62 public class PropertyHalServiceConfigs {
63     private static final long TRACE_TAG = TraceHelper.TRACE_TAG_CAR_SERVICE;
64     private static final Object sLock = new Object();
65     @GuardedBy("sLock")
66     private static PropertyHalServiceConfigs sPropertyHalServiceConfigs;
67     private static final PermissionCondition SINGLE_PERMISSION_VENDOR_EXTENSION =
68             new SinglePermission(PERMISSION_VENDOR_EXTENSION);
69     /**
70      * Represents one VHAL property that is exposed through
71      * {@link android.car.hardware.property.CarPropertyManager}.
72      *
73      * Note that the property ID is defined in {@link android.car.VehiclePropertyIds} and it might
74      * be different than the property ID used by VHAL, defined in {@link VehicleProperty}.
75      * The latter is represented by {@code halPropId}.
76      *
77      * If the property is an {@code Integer} enum property, its supported enum values are listed
78      * in {@code dataEnums}. If the property is a flag-combination property, its valid bit flag is
79      * listed in {@code validBitFlag}.
80      */
81     @VisibleForTesting
CarSvcPropertyConfig( int propertyId, int halPropId, String propertyName, String description, PropertyPermissions permissions, @Nullable Set<Integer> dataEnums, @Nullable Integer validBitFlag)82     /* package */ record CarSvcPropertyConfig(
83             int propertyId, int halPropId, String propertyName, String description,
84             PropertyPermissions permissions, @Nullable Set<Integer> dataEnums,
85             @Nullable Integer validBitFlag) {
86         public CarSvcPropertyConfig {
87             requireNonNull(propertyName);
88             requireNonNull(description);
89             requireNonNull(permissions);
90         }
91     }
92 
93     private static final String CONFIG_RESOURCE_NAME = "CarSvcProps.json";
94     private static final String JSON_FIELD_NAME_PROPERTIES = "properties";
95 
96     private static final String VIC_FLAG_NAME = "FLAG_ANDROID_VIC_VEHICLE_PROPERTIES";
97     private static final String REMOVE_SYSTEM_API_TAGS_FLAG_NAME =
98             "FLAG_VEHICLE_PROPERTY_REMOVE_SYSTEM_API_TAGS";
99     private static final String FLAG_25Q2_3P_PERMISSIONS =
100             "FLAG_VEHICLE_PROPERTY_25Q2_3P_PERMISSIONS";
101     private static final String B_FLAG_NAME = "FLAG_ANDROID_B_VEHICLE_PROPERTIES";
102 
103     private final FeatureFlags mFeatureFlags;
104 
105     private static final String TAG = CarLog.tagFor(PropertyHalServiceConfigs.class);
106 
107     private final SparseArray<Set<Integer>> mHalPropIdToEnumSet = new SparseArray<>();
108     private final SparseArray<CarSvcPropertyConfig> mHalPropIdToCarSvcConfig;
109     private final BidirectionalSparseIntArray mMgrPropIdToHalPropId;
110 
111     private final Object mLock = new Object();
112     @GuardedBy("mLock")
113     private final SparseArray<PropertyPermissions> mVendorHalPropIdToPermissions =
114             new SparseArray<>();
115 
116     /**
117      * Should only be used in unit tests. Use {@link getInsance} instead.
118      */
119     @VisibleForTesting
PropertyHalServiceConfigs(@ullable FeatureFlags featureFlags)120     public PropertyHalServiceConfigs(@Nullable FeatureFlags featureFlags) {
121         Trace.traceBegin(TRACE_TAG, "initialize PropertyHalServiceConfigs");
122         if (featureFlags == null) {
123             mFeatureFlags = new FeatureFlagsImpl();
124         } else {
125             mFeatureFlags = featureFlags;
126         }
127         try (InputStream defaultConfigInputStream = this.getClass().getClassLoader()
128                     .getResourceAsStream(CONFIG_RESOURCE_NAME)) {
129             mHalPropIdToCarSvcConfig = parseJsonConfig(defaultConfigInputStream,
130                     "defaultResource");
131         } catch (IOException e) {
132             String errorMsg = "failed to open/close resource input stream for: "
133                     + CONFIG_RESOURCE_NAME;
134             Slogf.e(TAG, errorMsg, e);
135             throw new IllegalStateException(errorMsg, e);
136         }
137         List<Integer> halPropIdMgrIds = new ArrayList<>();
138         for (int i = 0; i < mHalPropIdToCarSvcConfig.size(); i++) {
139             CarSvcPropertyConfig config = mHalPropIdToCarSvcConfig.valueAt(i);
140             if (config.halPropId() != config.propertyId()) {
141                 halPropIdMgrIds.add(config.propertyId());
142                 halPropIdMgrIds.add(config.halPropId());
143             }
144         }
145         int[] halPropIdMgrIdArray = new int[halPropIdMgrIds.size()];
146         for (int i = 0; i < halPropIdMgrIds.size(); i++) {
147             halPropIdMgrIdArray[i] = halPropIdMgrIds.get(i);
148         }
149         mMgrPropIdToHalPropId = BidirectionalSparseIntArray.create(halPropIdMgrIdArray);
150         Trace.traceEnd(TRACE_TAG);
151     }
152 
153     /**
154      * Gets the hard-coded HAL property ID to enum value set. For unit test only.
155      *
156      * TODO(b/293354967): Remove this once we migrate to runtime config.
157      */
158     @VisibleForTesting
getHalPropIdToEnumSet()159     /* package */ SparseArray<Set<Integer>> getHalPropIdToEnumSet() {
160         return mHalPropIdToEnumSet;
161     }
162 
163     /**
164      * Gets a list of all system VHAL property IDs. For unit test only.
165      */
166     @VisibleForTesting
getAllSystemHalPropIds()167     /* package */ List<Integer> getAllSystemHalPropIds() {
168         List<Integer> halPropIds = new ArrayList<>();
169         for (int i = 0; i < mHalPropIdToCarSvcConfig.size(); i++) {
170             halPropIds.add(mHalPropIdToCarSvcConfig.keyAt(i));
171         }
172         return halPropIds;
173     }
174 
175     /**
176      * Gets the singleton instance for {@link PropertyHalServiceConfigs}.
177      */
getInstance()178     public static PropertyHalServiceConfigs getInstance() {
179         synchronized (sLock) {
180             if (sPropertyHalServiceConfigs == null) {
181                 sPropertyHalServiceConfigs = new PropertyHalServiceConfigs(
182                         /* featureFlags= */ null);
183             }
184             return sPropertyHalServiceConfigs;
185         }
186     }
187 
188     /**
189      * Create a new instance for {@link PropertyHalServiceConfigs}.
190      */
191     @VisibleForTesting
newConfigs()192     public static PropertyHalServiceConfigs newConfigs() {
193         return new PropertyHalServiceConfigs(/* featureFlags= */ null);
194     }
195 
196     /**
197      * Returns all possible supported enum values for the {@code halPropId}. If property does not
198      * support an enum, then it returns {@code null}.
199      */
200     @Nullable
getAllPossibleSupportedEnumValues(int halPropId)201     public Set<Integer> getAllPossibleSupportedEnumValues(int halPropId) {
202         if (!mHalPropIdToCarSvcConfig.contains(halPropId)) {
203             return null;
204         }
205         Set<Integer> dataEnums = mHalPropIdToCarSvcConfig.get(halPropId).dataEnums;
206         return dataEnums != null ? Collections.unmodifiableSet(dataEnums) : null;
207     }
208 
209     /**
210      * Checks property value's format for all properties. Checks property value range if property
211      * has @data_enum flag in types.hal.
212      * @return true if property value's payload is valid.
213      */
checkPayload(HalPropValue propValue)214     public boolean checkPayload(HalPropValue propValue) {
215         int propId = propValue.getPropId();
216         // Mixed property uses config array to indicate the data format. Checked it when convert it
217         // to CarPropertyValue.
218         if ((propId & VehiclePropertyType.MASK) == VehiclePropertyType.MIXED) {
219             return true;
220         }
221         if (propValue.getStatus() != VehiclePropertyStatus.AVAILABLE) {
222             return true;
223         }
224         if (!checkFormatForAllProperties(propValue)) {
225             Slogf.e(TAG, "Property value: " + propValue + " has an invalid data format");
226             return false;
227         }
228         CarSvcPropertyConfig carSvcPropertyConfig = mHalPropIdToCarSvcConfig.get(propId);
229         if (carSvcPropertyConfig == null) {
230             // This is not a system property.
231             return true;
232         }
233         if (carSvcPropertyConfig.dataEnums() != null) {
234             return checkDataEnum(propValue, carSvcPropertyConfig.dataEnums());
235         }
236         if (carSvcPropertyConfig.validBitFlag() != null) {
237             return checkValidBitFlag(propValue, carSvcPropertyConfig.validBitFlag());
238         }
239         return true;
240     }
241 
242     /**
243      * Gets readPermission using a HAL-level propertyId.
244      *
245      * @param halPropId HAL-level propertyId
246      * @return PermissionCondition object that represents halPropId's readPermission
247      */
248     @Nullable
getReadPermission(int halPropId)249     public PermissionCondition getReadPermission(int halPropId) {
250         if (CarPropertyHelper.isVendorOrBackportedProperty(halPropId)) {
251             return getVendorReadPermission(halPropId);
252         }
253         CarSvcPropertyConfig carSvcPropertyConfig = mHalPropIdToCarSvcConfig.get(
254                 halPropId);
255         if (carSvcPropertyConfig == null) {
256             Slogf.w(TAG, "halPropId: "  + halPropIdToName(halPropId) + " is not supported,"
257                     + " no read permission");
258             return null;
259         }
260         return carSvcPropertyConfig.permissions().readPermission();
261     }
262 
263     @Nullable
getVendorReadPermission(int halPropId)264     private PermissionCondition getVendorReadPermission(int halPropId) {
265         String halPropIdName = halPropIdToName(halPropId);
266         synchronized (mLock) {
267             PropertyPermissions propertyPermissions = mVendorHalPropIdToPermissions.get(halPropId);
268             if (propertyPermissions == null) {
269                 Slogf.v(TAG, "no custom vendor read permission for: " + halPropIdName
270                         + ", default to PERMISSION_VENDOR_EXTENSION");
271                 return SINGLE_PERMISSION_VENDOR_EXTENSION;
272             }
273             PermissionCondition readPermission = propertyPermissions.readPermission();
274             if (readPermission == null) {
275                 Slogf.v(TAG, "vendor propId is not available for reading: " + halPropIdName);
276             }
277             return readPermission;
278         }
279     }
280 
281     /**
282      * Gets writePermission using a HAL-level propertyId.
283      *
284      * @param halPropId HAL-level propertyId
285      * @return PermissionCondition object that represents halPropId's writePermission
286      */
287     @Nullable
getWritePermission(int halPropId)288     public PermissionCondition getWritePermission(int halPropId) {
289         if (CarPropertyHelper.isVendorOrBackportedProperty(halPropId)) {
290             return getVendorWritePermission(halPropId);
291         }
292         CarSvcPropertyConfig carSvcPropertyConfig = mHalPropIdToCarSvcConfig.get(
293                 halPropId);
294         if (carSvcPropertyConfig == null) {
295             Slogf.w(TAG, "halPropId: "  + halPropIdToName(halPropId) + " is not supported,"
296                     + " no write permission");
297             return null;
298         }
299         return carSvcPropertyConfig.permissions().writePermission();
300     }
301 
302     @Nullable
getVendorWritePermission(int halPropId)303     private PermissionCondition getVendorWritePermission(int halPropId) {
304         String halPropIdName = halPropIdToName(halPropId);
305         synchronized (mLock) {
306             PropertyPermissions propertyPermissions = mVendorHalPropIdToPermissions.get(halPropId);
307             if (propertyPermissions == null) {
308                 Slogf.v(TAG, "no custom vendor write permission for: " + halPropIdName
309                         + ", default to PERMISSION_VENDOR_EXTENSION");
310                 return SINGLE_PERMISSION_VENDOR_EXTENSION;
311             }
312             PermissionCondition writePermission = propertyPermissions.writePermission();
313             if (writePermission == null) {
314                 Slogf.v(TAG, "vendor propId is not available for writing: " + halPropIdName);
315             }
316             return writePermission;
317         }
318     }
319 
320     /**
321      * Checks if readPermission is granted for a HAL-level propertyId in a given context.
322      *
323      * @param halPropId HAL-level propertyId
324      * @param context Context to check
325      * @return readPermission is granted or not.
326      */
isReadable(Context context, int halPropId)327     public boolean isReadable(Context context, int halPropId) {
328         PermissionCondition readPermission = getReadPermission(halPropId);
329         if (readPermission == null) {
330             Slogf.v(TAG, "propId is not readable: " + halPropIdToName(halPropId));
331             return false;
332         }
333         return readPermission.isMet(context);
334     }
335 
336     /**
337      * Checks if writePermission is granted for a HAL-level propertyId in a given context.
338      *
339      * @param halPropId HAL-level propertyId
340      * @param context Context to check
341      * @return writePermission is granted or not.
342      */
isWritable(Context context, int halPropId)343     public boolean isWritable(Context context, int halPropId) {
344         PermissionCondition writePermission = getWritePermission(halPropId);
345         if (writePermission == null) {
346             Slogf.v(TAG, "propId is not writable: " + halPropIdToName(halPropId));
347             return false;
348         }
349         return writePermission.isMet(context);
350     }
351 
352     /**
353      * Checks if property ID is in the list of known IDs that PropertyHalService is interested it.
354      */
isSupportedProperty(int propId)355     public boolean isSupportedProperty(int propId) {
356         return mHalPropIdToCarSvcConfig.get(propId) != null
357                 || CarPropertyHelper.isVendorOrBackportedProperty(propId);
358     }
359 
360     /**
361      * Overrides the permission map for vendor properties
362      *
363      * @param configArray the configArray for
364      * {@link VehicleProperty#SUPPORT_CUSTOMIZE_VENDOR_PERMISSION}
365      */
customizeVendorPermission(int[] configArray)366     public void customizeVendorPermission(int[] configArray) {
367         if (configArray == null || configArray.length % 3 != 0) {
368             throw new IllegalArgumentException(
369                     "ConfigArray for SUPPORT_CUSTOMIZE_VENDOR_PERMISSION is wrong");
370         }
371         int index = 0;
372         while (index < configArray.length) {
373             int propId = configArray[index++];
374             if (!CarPropertyHelper.isVendorOrBackportedProperty(propId)) {
375                 throw new IllegalArgumentException("Property Id: " + propId
376                         + " is not in vendor range");
377             }
378             int readPermission = configArray[index++];
379             int writePermission = configArray[index++];
380             String readPermissionStr = PropertyPermissionInfo.toPermissionString(
381                     readPermission, propId);
382             String writePermissionStr = PropertyPermissionInfo.toPermissionString(
383                     writePermission, propId);
384             synchronized (mLock) {
385                 if (mVendorHalPropIdToPermissions.get(propId) != null) {
386                     Slogf.e(TAG, "propId is a vendor property that already exists in "
387                             + "mVendorHalPropIdToPermissions and thus cannot have its "
388                             + "permissions overwritten: " + halPropIdToName(propId));
389                     continue;
390                 }
391 
392                 PropertyPermissionsBuilder propertyPermissionBuilder =
393                         new PropertyPermissionsBuilder();
394                 if (readPermissionStr != null) {
395                     propertyPermissionBuilder.setReadPermission(
396                             new SinglePermission(readPermissionStr));
397                 }
398                 if (writePermissionStr != null) {
399                     propertyPermissionBuilder.setWritePermission(
400                             new SinglePermission(writePermissionStr));
401                 }
402 
403                 mVendorHalPropIdToPermissions.put(propId, propertyPermissionBuilder.build());
404             }
405         }
406     }
407 
408     /**
409      * Converts manager property ID to Vehicle HAL property ID.
410      */
managerToHalPropId(int mgrPropId)411     public int managerToHalPropId(int mgrPropId) {
412         return mMgrPropIdToHalPropId.getValue(mgrPropId, mgrPropId);
413     }
414 
415     /**
416      * Converts Vehicle HAL property ID to manager property ID.
417      */
halToManagerPropId(int halPropId)418     public int halToManagerPropId(int halPropId) {
419         return mMgrPropIdToHalPropId.getKey(halPropId, halPropId);
420     }
421 
422     /**
423      * Print out the name for a VHAL property Id.
424      *
425      * For debugging only.
426      */
halPropIdToName(int halPropId)427     public String halPropIdToName(int halPropId) {
428         return VehiclePropertyIds.toString(halToManagerPropId(halPropId));
429     }
430 
checkValidBitFlag(HalPropValue propValue, int flagCombination)431     private static boolean checkValidBitFlag(HalPropValue propValue, int flagCombination) {
432         for (int i = 0; i < propValue.getInt32ValuesSize(); i++) {
433             int value = propValue.getInt32Value(i);
434             if ((value & flagCombination) != value) {
435                 return false;
436             }
437         }
438         return true;
439     }
440 
441     /**
442      * Parses a car service JSON config file. Only exposed for testing.
443      */
444     @VisibleForTesting
parseJsonConfig(InputStream configFile, String path)445     /* package */ SparseArray<CarSvcPropertyConfig> parseJsonConfig(InputStream configFile,
446             String path) {
447         try {
448             SparseArray<CarSvcPropertyConfig> configs = new SparseArray<>();
449             try (var reader = new JsonReader(new InputStreamReader(configFile, "UTF-8"))) {
450                 reader.setLenient(true);
451                 parseObjectEntry(reader, () -> {
452                     if (!reader.nextName().equals(JSON_FIELD_NAME_PROPERTIES)) {
453                         reader.skipValue();
454                         return;
455                     }
456                     parseObjectEntry(reader, () -> {
457                         String propertyName = reader.nextName();
458                         CarSvcPropertyConfig config;
459                         try {
460                             config = readPropertyObject(propertyName, reader);
461                         } catch (IllegalArgumentException e) {
462                             throw new IllegalArgumentException("Invalid json config for property: "
463                                      + propertyName + ", error: " + e);
464                         }
465                         if (config == null) {
466                             return;
467                         }
468                         configs.put(config.halPropId(), config);
469                     });
470                 });
471             }
472             return configs;
473         } catch (IllegalStateException | IOException e) {
474             throw new IllegalArgumentException("Config file: " + path
475                     + " does not contain a valid JSON object.", e);
476         }
477     }
478 
readPropertyObject( String propertyName, JsonReader reader)479     private @Nullable CarSvcPropertyConfig readPropertyObject(
480             String propertyName, JsonReader reader) throws IOException {
481         String featureFlag = null;
482         boolean deprecated = false;
483         int propertyId = 0;
484         int vhalPropertyId = 0;
485         String description = null;
486         ArraySet<Integer> dataEnums = new ArraySet<Integer>();
487         List<Integer> dataFlags = new ArrayList<>();
488         PermissionCondition readPermission = null;
489         PermissionCondition writePermission = null;
490         // Starts parsing each field.
491         reader.beginObject();
492         while (reader.hasNext()) {
493             String name = reader.nextName();
494             switch (name) {
495                 case "featureFlag":
496                     featureFlag = reader.nextString();
497                     break;
498                 case "deprecated":
499                     deprecated = reader.nextBoolean();
500                     break;
501                 case "propertyId":
502                     propertyId = reader.nextInt();
503                     break;
504                 case "vhalPropertyId":
505                     vhalPropertyId = reader.nextInt();
506                     break;
507                 case "description":
508                     description = reader.nextString();
509                     break;
510                 case "dataEnums":
511                     reader.beginArray();
512                     while (reader.hasNext()) {
513                         dataEnums.add(reader.nextInt());
514                     }
515                     reader.endArray();
516                     break;
517                 case "dataFlags":
518                     reader.beginArray();
519                     while (reader.hasNext()) {
520                         dataFlags.add(reader.nextInt());
521                     }
522                     reader.endArray();
523                     break;
524                 case "readPermission":
525                     try {
526                         readPermission = parsePermissionCondition(reader);
527                     } catch (IllegalArgumentException e) {
528                         throw new IllegalArgumentException(
529                                 "Failed to parse read permissions for property: " + propertyName
530                                 + ", error: " + e);
531                     }
532                     break;
533                 case "writePermission":
534                     try {
535                         writePermission = parsePermissionCondition(reader);
536                     } catch (IllegalArgumentException e) {
537                         throw new IllegalArgumentException(
538                                 "Failed to parse write permissions for property: " + propertyName
539                                 + ", error: " + e);
540                     }
541                     break;
542                 default:
543                     reader.skipValue();
544             }
545         }
546         reader.endObject();
547 
548         // Finished parsing each field, now check whether the required fields are present and
549         // assign them to config.
550         if (deprecated) {
551             return null;
552         }
553         if (featureFlag != null) {
554             switch (featureFlag) {
555                 case VIC_FLAG_NAME:
556                     // do nothing since Android V release is already cut
557                     break;
558                 case REMOVE_SYSTEM_API_TAGS_FLAG_NAME:
559                     // do nothing as no behavior change
560                     break;
561                 case FLAG_25Q2_3P_PERMISSIONS:
562                     // do nothing as no behavior change
563                     break;
564                 case B_FLAG_NAME:
565                     if (!mFeatureFlags.androidBVehicleProperties()) {
566                         Slogf.w(TAG, "The required feature flag for property: %s is not enabled, "
567                                 + "so its config is ignored", propertyName);
568                         return null;
569                     }
570                     break;
571                 default:
572                     throw new IllegalArgumentException("Unknown feature flag: "
573                             + featureFlag + " for property: " + propertyName);
574             }
575         }
576         if (description == null) {
577             throw new IllegalArgumentException("Missing required description field for property: "
578                     + propertyName);
579         }
580         if (propertyId == 0) {
581             throw new IllegalArgumentException("Missing required propertyId field for property: "
582                     + propertyName);
583         }
584         int halPropId;
585         if (vhalPropertyId != 0) {
586             halPropId = vhalPropertyId;
587         } else {
588             halPropId = propertyId;
589         }
590         if (dataEnums.isEmpty()) {
591             dataEnums = null;
592         }
593         Integer validBitFlag = null;
594         if (!dataFlags.isEmpty()) {
595             validBitFlag = generateAllCombination(dataFlags);
596         }
597         if (readPermission == null && writePermission == null) {
598             throw new IllegalArgumentException(
599                     "No read or write permission specified for: " + propertyName);
600         }
601         var builder = new PropertyPermissionsBuilder();
602         if (readPermission != null) {
603             builder.setReadPermission(readPermission);
604         }
605         if (writePermission != null) {
606             builder.setWritePermission(writePermission);
607         }
608         PropertyPermissions permissions = builder.build();
609         return new CarSvcPropertyConfig(propertyId, halPropId, propertyName, description,
610                 permissions, dataEnums, validBitFlag);
611     }
612 
613     private interface RunanbleWithException {
run()614         void run() throws IOException;
615     }
616 
parseObjectEntry(JsonReader reader, RunanbleWithException forEachEntry)617     private static void parseObjectEntry(JsonReader reader, RunanbleWithException forEachEntry)
618             throws IOException {
619         reader.beginObject();
620         while (reader.hasNext()) {
621             forEachEntry.run();
622         }
623         reader.endObject();
624     }
625 
parsePermissionCondition(JsonReader reader)626     private static PermissionCondition parsePermissionCondition(JsonReader reader)
627             throws IOException {
628         // we only have one type, use a list to be effective-final.
629         List<String> types = new ArrayList<>();
630         List<PermissionCondition> permissions = new ArrayList<>();
631         parseObjectEntry(reader, () -> {
632             String name = reader.nextName();
633             switch (name) {
634                 case "type":
635                     types.add(reader.nextString());
636                     break;
637                 case "value":
638                     try {
639                         permissions.add(new SinglePermission(reader.nextString()));
640                     } catch (IllegalStateException e) {
641                         // The value field is not a string, then it must be an array.
642                         reader.beginArray();
643                         while (reader.hasNext()) {
644                             permissions.add(parsePermissionCondition(reader));
645                         }
646                         reader.endArray();
647                     }
648                     break;
649                 default:
650                     reader.skipValue();
651             }
652         });
653         if (types.size() == 0) {
654             throw new IllegalArgumentException("Missing type field for permission");
655         }
656         String type = types.get(0);
657         if (permissions.size() < 1) {
658             throw new IllegalArgumentException("Missing valid value field for permission");
659         }
660         if (type.equals("single")) {
661             return permissions.get(0);
662         }
663         if (type.equals("anyOf")) {
664             return new AnyOfPermissions(permissions.toArray(new PermissionCondition[0]));
665         }
666         if (type.equals("allOf")) {
667             return new AllOfPermissions(permissions.toArray(new PermissionCondition[0]));
668         }
669         throw new IllegalArgumentException("Invalid permission type: " + type);
670     }
671 
checkFormatForAllProperties(HalPropValue propValue)672     private static boolean checkFormatForAllProperties(HalPropValue propValue) {
673         int propId = propValue.getPropId();
674         int vehiclePropertyType = propId & VehiclePropertyType.MASK;
675 
676         // Records sum size of int32values, floatValue, int64Values, bytes, String
677         int sizeOfAllValue = propValue.getInt32ValuesSize() + propValue.getFloatValuesSize()
678                 + propValue.getInt64ValuesSize() + propValue.getByteValuesSize()
679                 + propValue.getStringValue().length();
680         if (sizeOfAllValue == 0
681                 && vehiclePropertyType != VehiclePropertyType.FLOAT_VEC
682                 && vehiclePropertyType != VehiclePropertyType.INT64_VEC
683                 && vehiclePropertyType != VehiclePropertyType.INT32_VEC) {
684             Slogf.e(TAG, "Property value is empty: " + propValue);
685             return false;
686         }
687 
688         switch (vehiclePropertyType) {
689             case VehiclePropertyType.BOOLEAN:
690             case VehiclePropertyType.INT32:
691                 return sizeOfAllValue == 1 && propValue.getInt32ValuesSize() == 1;
692             case VehiclePropertyType.FLOAT:
693                 return sizeOfAllValue == 1 && propValue.getFloatValuesSize() == 1;
694             case VehiclePropertyType.INT64:
695                 return sizeOfAllValue == 1 && propValue.getInt64ValuesSize() == 1;
696             case VehiclePropertyType.FLOAT_VEC:
697                 return sizeOfAllValue == propValue.getFloatValuesSize();
698             case VehiclePropertyType.INT64_VEC:
699                 return sizeOfAllValue == propValue.getInt64ValuesSize();
700             case VehiclePropertyType.INT32_VEC:
701                 return sizeOfAllValue == propValue.getInt32ValuesSize();
702             case VehiclePropertyType.BYTES:
703                 return sizeOfAllValue == propValue.getByteValuesSize();
704             case VehiclePropertyType.STRING:
705                 return sizeOfAllValue == propValue.getStringValue().length();
706             default:
707                 throw new IllegalArgumentException("Unexpected property type for propId: "
708                         + Integer.toHexString(propId));
709         }
710     }
711 
checkDataEnum(HalPropValue propValue, Set<Integer> enums)712     private static boolean checkDataEnum(HalPropValue propValue, Set<Integer> enums) {
713         for (int i = 0; i < propValue.getInt32ValuesSize(); i++) {
714             if (!enums.contains(propValue.getInt32Value(i))) {
715                 return false;
716             }
717         }
718         return true;
719     }
720 
generateAllCombination(List<Integer> bitFlags)721     private static int generateAllCombination(List<Integer> bitFlags) {
722         int combination = bitFlags.get(0);
723         for (int i = 1; i < bitFlags.size(); i++) {
724             combination |= bitFlags.get(i);
725         }
726         return combination;
727     }
728 }
729