Merge "Payload check in propertyHalService" into rvc-dev
diff --git a/service/src/com/android/car/hal/PropertyHalService.java b/service/src/com/android/car/hal/PropertyHalService.java
index 192b7ec..15038cd 100644
--- a/service/src/com/android/car/hal/PropertyHalService.java
+++ b/service/src/com/android/car/hal/PropertyHalService.java
@@ -32,6 +32,7 @@
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
+import android.os.Build;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -374,6 +375,12 @@
                     Log.e(TAG, "Property is not supported: 0x" + toHexString(v.prop));
                     continue;
                 }
+                // Check payload if it is a userdebug build.
+                if (Build.IS_DEBUGGABLE && !mPropIds.checkPayload(v)) {
+                    Log.e(TAG, "Drop event for property: " + v + " because it is failed "
+                            + "in payload checking.");
+                    continue;
+                }
                 int mgrPropId = halToManagerPropId(v.prop);
                 CarPropertyValue<?> propVal;
                 if (isMixedTypeProperty(v.prop)) {
diff --git a/service/src/com/android/car/hal/PropertyHalServiceIds.java b/service/src/com/android/car/hal/PropertyHalServiceIds.java
index b9f3134..3772843 100644
--- a/service/src/com/android/car/hal/PropertyHalServiceIds.java
+++ b/service/src/com/android/car/hal/PropertyHalServiceIds.java
@@ -21,15 +21,34 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.car.Car;
+import android.car.VehicleHvacFanDirection;
 import android.car.hardware.property.VehicleVendorPermission;
+import android.hardware.automotive.vehicle.V2_0.EvConnectorType;
+import android.hardware.automotive.vehicle.V2_0.FuelType;
+import android.hardware.automotive.vehicle.V2_0.PortLocationType;
+import android.hardware.automotive.vehicle.V2_0.VehicleAreaSeat;
+import android.hardware.automotive.vehicle.V2_0.VehicleGear;
+import android.hardware.automotive.vehicle.V2_0.VehicleIgnitionState;
+import android.hardware.automotive.vehicle.V2_0.VehicleLightState;
+import android.hardware.automotive.vehicle.V2_0.VehicleLightSwitch;
+import android.hardware.automotive.vehicle.V2_0.VehicleOilLevel;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
+import android.hardware.automotive.vehicle.V2_0.VehicleSeatOccupancyState;
+import android.hardware.automotive.vehicle.V2_0.VehicleTurnSignal;
+import android.hardware.automotive.vehicle.V2_0.VehicleUnit;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
 
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Helper class to define which property IDs are used by PropertyHalService.  This class binds the
@@ -45,7 +64,37 @@
      */
     private final SparseArray<Pair<String, String>> mProps;
     private final HashSet<Integer> mPropForUnits;
+    // Key: propId, Value: possible value for the property
+    private final HashMap<Integer, Set<Integer>> mPropToValidValue;
+    private final HashMap<Integer, Integer> mPropToValidBitFlag;
     private static final String TAG = "PropertyHalServiceIds";
+    // Enums are used as return value in Vehicle HAL.
+    private static final Set<Integer> FUEL_TYPE =
+            new HashSet<>(getIntegersFromDataEnums(FuelType.class));
+    private static final Set<Integer> EV_CONNECTOR_TYPE =
+            new HashSet<>(getIntegersFromDataEnums(EvConnectorType.class));
+    private static final Set<Integer> PORT_LOCATION =
+            new HashSet<>(getIntegersFromDataEnums(PortLocationType.class));
+    private static final Set<Integer> VEHICLE_SEAT =
+            new HashSet<>(getIntegersFromDataEnums(VehicleAreaSeat.class));
+    private static final Set<Integer> OIL_LEVEL =
+            new HashSet<>(getIntegersFromDataEnums(VehicleOilLevel.class));
+    private static final Set<Integer> VEHICLE_GEAR =
+            new HashSet<>(getIntegersFromDataEnums(VehicleGear.class));
+    private static final Set<Integer> TURN_SIGNAL =
+            new HashSet<>(getIntegersFromDataEnums(VehicleTurnSignal.class));
+    private static final Set<Integer> IGNITION_STATE =
+            new HashSet<>(getIntegersFromDataEnums(VehicleIgnitionState.class));
+    private static final Set<Integer> VEHICLE_UNITS =
+            new HashSet<>(getIntegersFromDataEnums(VehicleUnit.class));
+    private static final Set<Integer> SEAT_OCCUPANCY_STATE =
+            new HashSet<>(getIntegersFromDataEnums(VehicleSeatOccupancyState.class));
+    private static final Set<Integer> VEHICLE_LIGHT_STATE =
+            new HashSet<>(getIntegersFromDataEnums(VehicleLightState.class));
+    private static final Set<Integer> VEHICLE_LIGHT_SWITCH =
+            new HashSet<>(getIntegersFromDataEnums(VehicleLightSwitch.class));
+    private static final int HVAC_FAN_DIRECTION_COMBINATIONS =
+            generateAllCombination(VehicleHvacFanDirection.class);
 
     // default vendor permission
     private static final int PERMISSION_CAR_VENDOR_DEFAULT = 0x00000000;
@@ -103,6 +152,8 @@
     public PropertyHalServiceIds() {
         mProps = new SparseArray<>();
         mPropForUnits = new HashSet<>();
+        mPropToValidValue = new HashMap<>();
+        mPropToValidBitFlag = new HashMap<>();
         // Add propertyId and read/write permissions
         // Cabin Properties
         mProps.put(VehicleProperty.DOOR_POS, new Pair<>(
@@ -485,6 +536,42 @@
         mProps.put(VehicleProperty.SUPPORT_CUSTOMIZE_VENDOR_PERMISSION, new Pair<>(
                 Car.PERMISSION_READ_CAR_VENDOR_PERMISSION_INFO,
                 null));
+
+        // mPropToValidValue should contain all properties which has @data_enum in types.hal
+        mPropToValidValue.put(VehicleProperty.INFO_FUEL_TYPE, FUEL_TYPE);
+        mPropToValidValue.put(VehicleProperty.INFO_EV_CONNECTOR_TYPE, EV_CONNECTOR_TYPE);
+        mPropToValidValue.put(VehicleProperty.INFO_FUEL_DOOR_LOCATION, PORT_LOCATION);
+        mPropToValidValue.put(VehicleProperty.INFO_DRIVER_SEAT, VEHICLE_SEAT);
+        mPropToValidValue.put(VehicleProperty.INFO_MULTI_EV_PORT_LOCATIONS, PORT_LOCATION);
+        mPropToValidValue.put(VehicleProperty.ENGINE_OIL_LEVEL, OIL_LEVEL);
+        mPropToValidValue.put(VehicleProperty.GEAR_SELECTION, VEHICLE_GEAR);
+        mPropToValidValue.put(VehicleProperty.CURRENT_GEAR, VEHICLE_GEAR);
+        mPropToValidValue.put(VehicleProperty.TURN_SIGNAL_STATE, TURN_SIGNAL);
+        mPropToValidValue.put(VehicleProperty.IGNITION_STATE, IGNITION_STATE);
+        mPropToValidValue.put(VehicleProperty.HVAC_TEMPERATURE_DISPLAY_UNITS, VEHICLE_UNITS);
+        mPropToValidValue.put(VehicleProperty.DISTANCE_DISPLAY_UNITS, VEHICLE_UNITS);
+        mPropToValidValue.put(VehicleProperty.FUEL_VOLUME_DISPLAY_UNITS, VEHICLE_UNITS);
+        mPropToValidValue.put(VehicleProperty.TIRE_PRESSURE_DISPLAY_UNITS, VEHICLE_UNITS);
+        mPropToValidValue.put(VehicleProperty.EV_BATTERY_DISPLAY_UNITS, VEHICLE_UNITS);
+        mPropToValidValue.put(VehicleProperty.SEAT_OCCUPANCY, SEAT_OCCUPANCY_STATE);
+        mPropToValidValue.put(VehicleProperty.HIGH_BEAM_LIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.HEADLIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.FOG_LIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.HAZARD_LIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.CABIN_LIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.READING_LIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.HEADLIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+        mPropToValidValue.put(VehicleProperty.HIGH_BEAM_LIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+        mPropToValidValue.put(VehicleProperty.FOG_LIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+        mPropToValidValue.put(VehicleProperty.HAZARD_LIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+        mPropToValidValue.put(VehicleProperty.CABIN_LIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+        mPropToValidValue.put(VehicleProperty.READING_LIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+
+        // mPropToValidBitFlag contains all properties which return values are combinations of bits
+        mPropToValidBitFlag.put(VehicleProperty.HVAC_FAN_DIRECTION_AVAILABLE,
+                HVAC_FAN_DIRECTION_COMBINATIONS);
+        mPropToValidBitFlag.put(VehicleProperty.HVAC_FAN_DIRECTION,
+                HVAC_FAN_DIRECTION_COMBINATIONS);
     }
 
     /**
@@ -666,4 +753,108 @@
         }
     }
 
+    /**
+     * Checks property value's format for all properties. Checks property value range if property
+     * has @data_enum flag in types.hal.
+     * @return true if property value's payload is valid.
+     */
+    public boolean checkPayload(VehiclePropValue propValue) {
+        // Mixed property uses config array to indicate the data format. Checked it when convert it
+        // to CarPropertyValue.
+        if ((propValue.prop & VehiclePropertyType.MASK) == VehiclePropertyType.MIXED) {
+            return true;
+        }
+        if (!checkFormatForAllProperties(propValue)) {
+            Log.e(TAG, "Property value" + propValue + "has an invalid data format");
+            return false;
+        }
+        if (mPropToValidValue.containsKey(propValue.prop)) {
+            return checkDataEnum(propValue);
+        }
+        if (mPropToValidBitFlag.containsKey(propValue.prop)) {
+            return checkValidBitFlag(propValue);
+        }
+        return true;
+    }
+
+    private boolean checkValidBitFlag(VehiclePropValue propValue) {
+        int flagCombination = mPropToValidBitFlag.get(propValue.prop);
+        for (int value : propValue.value.int32Values) {
+            if ((value & flagCombination) != value) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean checkFormatForAllProperties(VehiclePropValue propValue) {
+        int propId = propValue.prop;
+        VehiclePropValue.RawValue rawValue = propValue.value;
+        //Records sum size of int32values, floatValue, int64Values, bytes, String
+        int sizeOfAllValue = rawValue.int32Values.size() + rawValue.floatValues.size()
+                + rawValue.int64Values.size() + rawValue.bytes.size()
+                + rawValue.stringValue.length();
+        if (sizeOfAllValue == 0) {
+            Log.e(TAG, "Property value is empty: " + propValue);
+            return false;
+        }
+        switch (propId & VehiclePropertyType.MASK) {
+            case VehiclePropertyType.BOOLEAN:
+            case VehiclePropertyType.INT32:
+                return sizeOfAllValue == 1 && rawValue.int32Values.size() == 1;
+            case VehiclePropertyType.FLOAT:
+                return sizeOfAllValue == 1 && rawValue.floatValues.size() == 1;
+            case VehiclePropertyType.INT64:
+                return sizeOfAllValue == 1 && rawValue.int64Values.size() == 1;
+            case VehiclePropertyType.FLOAT_VEC:
+                return sizeOfAllValue == rawValue.floatValues.size();
+            case VehiclePropertyType.INT64_VEC:
+                return sizeOfAllValue == rawValue.int64Values.size();
+            case VehiclePropertyType.INT32_VEC:
+                return sizeOfAllValue == rawValue.int32Values.size();
+            case VehiclePropertyType.BYTES:
+                return sizeOfAllValue == rawValue.bytes.size();
+            case VehiclePropertyType.STRING:
+                return sizeOfAllValue == rawValue.stringValue.length();
+            default:
+                throw new IllegalArgumentException("Unexpected property type for propId: "
+                        + Integer.toHexString(propId));
+        }
+    }
+    private boolean checkDataEnum(VehiclePropValue propValue) {
+        int propId = propValue.prop;
+        VehiclePropValue.RawValue rawValue = propValue.value;
+        Set<Integer> validValue = mPropToValidValue.get(propId);
+        for (int value : rawValue.int32Values) {
+            if (!validValue.contains(value)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static List<Integer> getIntegersFromDataEnums(Class clazz) {
+        Field[] fields = clazz.getDeclaredFields();
+        List<Integer> integerList = new ArrayList<>(5);
+        for (Field f : fields) {
+            if (f.getType() == int.class) {
+                try {
+                    integerList.add(f.getInt(clazz));
+                } catch (Exception e) {
+                    Log.w(TAG, "Failed to get value");
+                }
+            }
+        }
+        return integerList;
+    }
+
+    // Generate all combinations at once
+    private static int generateAllCombination(Class clazz) {
+        List<Integer> allBits = getIntegersFromDataEnums(clazz);
+        int combination = allBits.get(0);
+        for (int i = 1; i < allBits.size(); i++) {
+            combination |= allBits.get(i);
+        }
+        return combination;
+    }
 }
diff --git a/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java b/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
index 520e910..890981a 100644
--- a/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
+++ b/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
@@ -29,6 +29,7 @@
 import android.car.drivingstate.CarUxRestrictionsManager;
 import android.hardware.automotive.vehicle.V2_0.VehicleGear;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.os.Build;
 import android.os.SystemClock;
 import android.util.Log;
 
@@ -211,9 +212,14 @@
                         .setTimestamp(SystemClock.elapsedRealtimeNanos())
                         .build());
         drivingEvent = listener.waitForDrivingStateChange();
-        assertNotNull(drivingEvent);
-        assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_IDLING);
-
+        if (Build.IS_DEBUGGABLE) {
+            // In userdebug build, payloadChecker in HAL drops the invalid event.
+            assertNull(drivingEvent);
+        } else {
+            assertNotNull(drivingEvent);
+            assertThat(drivingEvent.eventValue).isEqualTo(
+                    CarDrivingStateEvent.DRIVING_STATE_IDLING);
+        }
         // Now, send in an invalid speed value as well, now the driving state will be unknown and
         // the UX restrictions will change to fully restricted.
         listener.reset();
@@ -224,13 +230,19 @@
                         .setTimestamp(SystemClock.elapsedRealtimeNanos())
                         .build());
         drivingEvent = listener.waitForDrivingStateChange();
-        assertNotNull(drivingEvent);
-        assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
-        restrictions = listener.waitForUxRestrictionsChange();
-        assertNotNull(restrictions);
-        assertTrue(restrictions.isRequiresDistractionOptimization());
-        assertThat(restrictions.getActiveRestrictions())
-                .isEqualTo(CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
+        if (Build.IS_DEBUGGABLE) {
+            // In userdebug build, payloadChecker in HAL drops the invalid event.
+            assertNull(drivingEvent);
+        } else {
+            assertNotNull(drivingEvent);
+            assertThat(drivingEvent.eventValue).isEqualTo(
+                    CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
+            restrictions = listener.waitForUxRestrictionsChange();
+            assertNotNull(restrictions);
+            assertTrue(restrictions.isRequiresDistractionOptimization());
+            assertThat(restrictions.getActiveRestrictions())
+                    .isEqualTo(CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
+        }
         mCarDrivingStateManager.unregisterListener();
         mCarUxRManager.unregisterListener();
     }
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java b/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java
index 59498cb..e128b19 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java
@@ -17,12 +17,22 @@
 package com.android.car.hal;
 
 import android.car.Car;
+import android.car.VehicleHvacFanDirection;
 import android.car.VehiclePropertyIds;
+import android.hardware.automotive.vehicle.V2_0.VehicleGear;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VehicleUnit;
 import android.hardware.automotive.vehicle.V2_0.VehicleVendorPermission;
+import android.os.SystemClock;
 import android.util.Log;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+
+import com.google.common.truth.Truth;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -53,6 +63,32 @@
             VehiclePropertyIds.HVAC_FAN_SPEED, VehiclePropertyIds.DOOR_LOCK};
     private static final List<Integer> CONFIG_ARRAY = new ArrayList<>();
     private static final List<Integer> CONFIG_ARRAY_INVALID = new ArrayList<>();
+    //payload test
+    private static final VehiclePropValue GEAR_WITH_VALID_VALUE =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
+            .addIntValue(VehicleGear.GEAR_DRIVE)
+            .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
+    private static final VehiclePropValue GEAR_WITH_EXTRA_VALUE =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
+                    .addIntValue(VehicleGear.GEAR_DRIVE)
+                    .addIntValue(VehicleGear.GEAR_1)
+                    .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
+    private static final VehiclePropValue GEAR_WITH_INVALID_VALUE =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
+                    .addIntValue(VehicleUnit.KILOPASCAL)
+                    .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
+    private static final VehiclePropValue GEAR_WITH_INVALID_TYPE_VALUE =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
+                    .addFloatValue(1.0f)
+                    .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
+    private static final VehiclePropValue HVAC_FAN_DIRECTIONS_VALID =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.HVAC_FAN_DIRECTION)
+                    .addIntValue(VehicleHvacFanDirection.FACE | VehicleHvacFanDirection.FLOOR)
+                    .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
+    private static final VehiclePropValue HVAC_FAN_DIRECTIONS_INVALID =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.HVAC_FAN_DIRECTION)
+                    .addIntValue(VehicleHvacFanDirection.FACE | 0x100)
+                    .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
     @Before
     public void setUp() {
         mPropertyHalServiceIds = new PropertyHalServiceIds();
@@ -90,7 +126,6 @@
         Assert.assertEquals(Car.PERMISSION_CONTROL_CAR_CLIMATE,
                 mPropertyHalServiceIds.getWritePermission(VehiclePropertyIds.HVAC_FAN_SPEED));
     }
-
     /**
      * Test {@link PropertyHalServiceIds#customizeVendorPermission(List)}
      */
@@ -143,4 +178,19 @@
         }
     }
 
+    /**
+     * Test {@link PropertyHalServiceIds#checkPayload(VehiclePropValue)}
+     */
+    @Test
+    public void testPayload() {
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_VALID_VALUE)).isTrue();
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_EXTRA_VALUE)).isFalse();
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_INVALID_VALUE)).isFalse();
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_INVALID_TYPE_VALUE))
+                .isFalse();
+
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(HVAC_FAN_DIRECTIONS_VALID)).isTrue();
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(HVAC_FAN_DIRECTIONS_INVALID))
+                .isFalse();
+    }
 }