Merge changes Ic0d7b4d2,I3441e630,Ie504e687 into rvc-dev
* changes:
gralloc4-vts: Test metadata is consistent in cloned buffers
gralloc4-vts: Test layout and component info of RAW12
gralloc4-vts: Test layout and component info of RAW10
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp
index f712ea2..2c2f23c 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp
@@ -158,6 +158,20 @@
return sendUserHalResponse(std::move(mSwitchUserResponseFromCmd), requestId);
}
+ if (value.value.int32Values.size() > 1) {
+ auto messageType = static_cast<SwitchUserMessageType>(value.value.int32Values[1]);
+ switch (messageType) {
+ case SwitchUserMessageType::LEGACY_ANDROID_SWITCH:
+ ALOGI("request is LEGACY_ANDROID_SWITCH; ignoring it");
+ return {};
+ case SwitchUserMessageType::ANDROID_POST_SWITCH:
+ ALOGI("request is ANDROID_POST_SWITCH; ignoring it");
+ return {};
+ default:
+ break;
+ }
+ }
+
// Returns default response
auto updatedValue = std::unique_ptr<VehiclePropValue>(new VehiclePropValue);
updatedValue->prop = SWITCH_USER;
diff --git a/automotive/vehicle/2.0/types.hal b/automotive/vehicle/2.0/types.hal
index 341aae7..acdea8a 100644
--- a/automotive/vehicle/2.0/types.hal
+++ b/automotive/vehicle/2.0/types.hal
@@ -2546,9 +2546,8 @@
* NOTE: if the HAL doesn't support user management, then it should not define this property,
* which in turn would disable the other user-related properties (for example, the Android
* system would never issue them and user-related requests from the HAL layer would be ignored
- * by the Android System). But if it supports user management, then it must support all
- * user-related properties (INITIAL_USER_INFO, SWITCH_USER, CREATE_USER, REMOVE_USER,
- * and USER_IDENTIFICATION_ASSOCIATION).
+ * by the Android System). But if it supports user management, then it must support all core
+ * user-related properties (INITIAL_USER_INFO, SWITCH_USER, CREATE_USER, and REMOVE_USER).
*
* @change_mode VehiclePropertyChangeMode:ON_CHANGE
* @access VehiclePropertyAccess:READ_WRITE
@@ -2818,6 +2817,10 @@
* Property used to associate (or query the association) the current user with vehicle-specific
* identification mechanisms (such as key FOB).
*
+ * This is an optional user management property - the OEM could still support user management
+ * without defining it. In fact, this property could be used without supporting the core
+ * user-related functions described on INITIAL_USER_INFO.
+ *
* To query the association, the Android system gets the property, passing a VehiclePropValue
* containing the types of associations are being queried, as defined by
* UserIdentificationGetRequest. The HAL must return right away, returning a VehiclePropValue
diff --git a/camera/device/3.2/ICameraDeviceCallback.hal b/camera/device/3.2/ICameraDeviceCallback.hal
index dec3bd8..607502e 100644
--- a/camera/device/3.2/ICameraDeviceCallback.hal
+++ b/camera/device/3.2/ICameraDeviceCallback.hal
@@ -95,7 +95,8 @@
* statuses must be STATUS_ERROR, and the result metadata must be an
* empty buffer. In addition, notify() must be called with a ERROR_REQUEST
* message. In this case, individual ERROR_RESULT/ERROR_BUFFER messages
- * must not be sent.
+ * must not be sent. Note that valid partial results are still allowed
+ * as long as the final result metadata fails to be generated.
*
* Performance requirements:
*
diff --git a/camera/provider/2.4/vts/functional/AndroidTest.xml b/camera/provider/2.4/vts/functional/AndroidTest.xml
new file mode 100644
index 0000000..05e1639
--- /dev/null
+++ b/camera/provider/2.4/vts/functional/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs VtsHalCameraProviderV2_4TargetTest.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="VtsHalCameraProviderV2_4TargetTest->/data/local/tmp/VtsHalCameraProviderV2_4TargetTest" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsHalCameraProviderV2_4TargetTest" />
+ <option name="native-test-timeout" value="180000"/>
+ </test>
+</configuration>
diff --git a/camera/provider/2.6/ICameraProvider.hal b/camera/provider/2.6/ICameraProvider.hal
index ed1d31d..b8873a6 100644
--- a/camera/provider/2.6/ICameraProvider.hal
+++ b/camera/provider/2.6/ICameraProvider.hal
@@ -76,12 +76,16 @@
* configuration settings exposed through camera metadata), should the sum
* of resource costs for the combination be <= 100.
*
- * The lists of camera id combinations returned by this method may contain
- * hidden physical camera ids. If a combination does contain hidden physical
- * camera ids, the camera framework must be able to open any logical cameras
- * that contain these hidden physical camera ids in their
- * ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS list, in addition to the other
- * camera ids advertised in the combination, for concurrent operation.
+ * For guaranteed concurrent camera operation, the camera framework must call
+ * ICameraDevice.open() on all devices (intended for concurrent operation), before configuring
+ * any streams on them. This gives the camera HAL process an opportunity to potentially
+ * distribute hardware resources better before stream configuration.
+ *
+ * Due to potential hardware constraints around internal switching of physical camera devices,
+ * a device's complete ZOOM_RATIO_RANGE(if supported), may not apply during concurrent
+ * operation. If ZOOM_RATIO is supported, camera HALs must ensure ZOOM_RATIO_RANGE of
+ * [1.0, ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM] is supported by that device, during
+ * concurrent operation.
*
* @return status Status code for the operation
* @return cameraIds a list of camera id combinations that support
diff --git a/current.txt b/current.txt
index e2d1408..2608b54 100644
--- a/current.txt
+++ b/current.txt
@@ -588,6 +588,7 @@
578f640c653726d58f99c84a7e1bb63862e21ef7cbb4f7d95c3cc62de00dca35 android.hardware.automotive.evs@1.0::IEvsDisplay
f5bc6aa840db933cb9fd36668b06d3e2021cf5384bb70e459f22e2f2f921fba5 android.hardware.automotive.evs@1.0::IEvsEnumerator
d3a344b7bd4c0d2658ae7209f55a979b8f53f361fd00f4fca29d5baa56d11fd2 android.hardware.automotive.evs@1.0::types
+d123013165a19b6353cdc46a57b2ff4a17179619d36dbd595dfcf15dcd099af6 android.hardware.camera.device@3.2::ICameraDeviceCallback # b/155353799
2410dd02d67786a732d36e80b0f8ccf55086604ef37f9838e2013ff2c571e404 android.hardware.camera.device@3.5::types
cd06a7911b9acd4a653bbf7133888878fbcb3f84be177c7a3f1becaae3d8618f android.hardware.camera.metadata@3.2::types
5cf81b1001296fbb3c5b3d275a859244f61cec5fa858d7be9cca46c5b7dfa733 android.hardware.camera.metadata@3.2::types # b/150331548
@@ -676,7 +677,7 @@
a718c8a3acaa938de5a57923e8c4625ed7ca051e05a1d930ba6998557d7b57c8 android.hardware.camera.device@3.6::ICameraOfflineSession
a35d5151b48505f06a775b38c0e2e265f80a845d92802324c643565807f81c53 android.hardware.camera.device@3.6::types
02bdf82dba7dce273a554b4474468a8fb1fb4f61ab65da95eb16e080df63fff6 android.hardware.camera.metadata@3.5::types
-21086e1c7a2acc0ebe0ff8561b11f3c2009be687a92d79b608a5f00b16c5f598 android.hardware.camera.provider@2.6::ICameraProvider
+7d6b362681f4a4fd0be95535d8913d8de9a26f0765c1bdda4bd837dea8c25db6 android.hardware.camera.provider@2.6::ICameraProvider
8f8d9463508ff9cae88eb35c429fd0e2dbca0ca8f5de7fdf836cc0c4370becb6 android.hardware.camera.provider@2.6::ICameraProviderCallback
1edf7aef68ef3bd577a1175b1462fb82e3e39f01c6915dda61fba121028df283 android.hardware.camera.provider@2.6::types
c1aa508d00b66ed5feefea398fd5edf28fa651ac89773adad7dfda4e0a73a952 android.hardware.cas@1.2::ICas
diff --git a/gnss/1.1/vts/functional/Android.bp b/gnss/1.1/vts/functional/Android.bp
index 0d540b7..94bfb89 100644
--- a/gnss/1.1/vts/functional/Android.bp
+++ b/gnss/1.1/vts/functional/Android.bp
@@ -25,6 +25,7 @@
static_libs: [
"android.hardware.gnss@1.0",
"android.hardware.gnss@1.1",
+ "android.hardware.gnss@2.0",
"android.hardware.gnss@common-vts-lib",
],
shared_libs: [
diff --git a/gnss/2.0/vts/functional/gnss_hal_test.cpp b/gnss/2.0/vts/functional/gnss_hal_test.cpp
index 59e18f3..1cb44c5 100644
--- a/gnss/2.0/vts/functional/gnss_hal_test.cpp
+++ b/gnss/2.0/vts/functional/gnss_hal_test.cpp
@@ -247,3 +247,46 @@
capabilities_cbq_.store(capabilities);
return Void();
}
+
+GnssConstellationType_1_0 GnssHalTest::startLocationAndGetNonGpsConstellation() {
+ const int kLocationsToAwait = 3;
+
+ gnss_cb_->location_cbq_.reset();
+ StartAndCheckLocations(kLocationsToAwait);
+ const int location_called_count = gnss_cb_->location_cbq_.calledCount();
+
+ // Tolerate 1 less sv status to handle edge cases in reporting.
+ int sv_info_list_cbq_size = gnss_cb_->sv_info_list_cbq_.size();
+ EXPECT_GE(sv_info_list_cbq_size + 1, kLocationsToAwait);
+ ALOGD("Observed %d GnssSvStatus, while awaiting %d Locations (%d received)",
+ sv_info_list_cbq_size, kLocationsToAwait, location_called_count);
+
+ // Find first non-GPS constellation to blacklist. Exclude IRNSS in GnssConstellationType_2_0
+ // as blacklisting of this constellation is not supported in gnss@2.0.
+ const int kGnssSvStatusTimeout = 2;
+ GnssConstellationType_1_0 constellation_to_blacklist = GnssConstellationType_1_0::UNKNOWN;
+ for (int i = 0; i < sv_info_list_cbq_size; ++i) {
+ hidl_vec<IGnssCallback_2_0::GnssSvInfo> sv_info_list;
+ gnss_cb_->sv_info_list_cbq_.retrieve(sv_info_list, kGnssSvStatusTimeout);
+ for (IGnssCallback_2_0::GnssSvInfo sv_info : sv_info_list) {
+ if ((sv_info.v1_0.svFlag & IGnssCallback_2_0::GnssSvFlags::USED_IN_FIX) &&
+ (sv_info.constellation != GnssConstellationType_2_0::UNKNOWN) &&
+ (sv_info.constellation != GnssConstellationType_2_0::IRNSS) &&
+ (sv_info.constellation != GnssConstellationType_2_0::GPS)) {
+ // found a non-GPS V1_0 constellation
+ constellation_to_blacklist = Utils::mapConstellationType(sv_info.constellation);
+ break;
+ }
+ }
+ if (constellation_to_blacklist != GnssConstellationType_1_0::UNKNOWN) {
+ break;
+ }
+ }
+
+ if (constellation_to_blacklist == GnssConstellationType_1_0::UNKNOWN) {
+ ALOGI("No non-GPS constellations found, constellation blacklist test less effective.");
+ // Proceed functionally to blacklist something.
+ constellation_to_blacklist = GnssConstellationType_1_0::GLONASS;
+ }
+ return constellation_to_blacklist;
+}
diff --git a/gnss/2.0/vts/functional/gnss_hal_test.h b/gnss/2.0/vts/functional/gnss_hal_test.h
index a02a9ff..7fbd735 100644
--- a/gnss/2.0/vts/functional/gnss_hal_test.h
+++ b/gnss/2.0/vts/functional/gnss_hal_test.h
@@ -31,6 +31,9 @@
using android::hardware::gnss::V1_0::GnssLocationFlags;
using android::hardware::gnss::V2_0::IGnss;
+using GnssConstellationType_1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
+using GnssConstellationType_2_0 = android::hardware::gnss::V2_0::GnssConstellationType;
+
using GnssLocation_1_0 = android::hardware::gnss::V1_0::GnssLocation;
using GnssLocation_2_0 = android::hardware::gnss::V2_0::GnssLocation;
@@ -194,6 +197,16 @@
*/
void SetPositionMode(const int min_interval_msec, const bool low_power_mode);
+ /*
+ * startLocationAndGetNonGpsConstellation:
+ * 1. Start location
+ * 2. Find and return first non-GPS constellation
+ *
+ * Note that location is not stopped in this method. The client should call
+ * StopAndClearLocations() after the call.
+ */
+ GnssConstellationType_1_0 startLocationAndGetNonGpsConstellation();
+
sp<IGnss> gnss_hal_; // GNSS HAL to call into
sp<GnssCallback> gnss_cb_; // Primary callback interface
};
diff --git a/gnss/2.0/vts/functional/gnss_hal_test_cases.cpp b/gnss/2.0/vts/functional/gnss_hal_test_cases.cpp
index 094c7c1..51dcf0d 100644
--- a/gnss/2.0/vts/functional/gnss_hal_test_cases.cpp
+++ b/gnss/2.0/vts/functional/gnss_hal_test_cases.cpp
@@ -24,8 +24,6 @@
using android::hardware::hidl_string;
using android::hardware::hidl_vec;
-using GnssConstellationType_2_0 = android::hardware::gnss::V2_0::GnssConstellationType;
-using GnssConstellationType_1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
using IGnssConfiguration_2_0 = android::hardware::gnss::V2_0::IGnssConfiguration;
using IGnssConfiguration_1_1 = android::hardware::gnss::V1_1::IGnssConfiguration;
using IAGnssRil_2_0 = android::hardware::gnss::V2_0::IAGnssRil;
@@ -492,31 +490,6 @@
}
/*
- * MapConstellationType:
- * Given a GnssConstellationType_2_0 type constellation, maps to its equivalent
- * GnssConstellationType_1_0 type constellation. For constellations that do not have
- * an equivalent value, maps to GnssConstellationType_1_0::UNKNOWN
- */
-GnssConstellationType_1_0 MapConstellationType(GnssConstellationType_2_0 constellation) {
- switch (constellation) {
- case GnssConstellationType_2_0::GPS:
- return GnssConstellationType_1_0::GPS;
- case GnssConstellationType_2_0::SBAS:
- return GnssConstellationType_1_0::SBAS;
- case GnssConstellationType_2_0::GLONASS:
- return GnssConstellationType_1_0::GLONASS;
- case GnssConstellationType_2_0::QZSS:
- return GnssConstellationType_1_0::QZSS;
- case GnssConstellationType_2_0::BEIDOU:
- return GnssConstellationType_1_0::BEIDOU;
- case GnssConstellationType_2_0::GALILEO:
- return GnssConstellationType_1_0::GALILEO;
- default:
- return GnssConstellationType_1_0::UNKNOWN;
- }
-}
-
-/*
* FindStrongFrequentNonGpsSource:
*
* Search through a GnssSvStatus list for the strongest non-GPS satellite observed enough times
@@ -555,7 +528,7 @@
(sv_info.constellation != GnssConstellationType_2_0::GPS)) {
ComparableBlacklistedSource source;
source.id.svid = sv_info.v1_0.svid;
- source.id.constellation = MapConstellationType(sv_info.constellation);
+ source.id.constellation = Utils::mapConstellationType(sv_info.constellation);
const auto& itSignal = mapSignals.find(source);
if (itSignal == mapSignals.end()) {
@@ -694,7 +667,7 @@
hidl_vec<IGnssCallback_2_0::GnssSvInfo> sv_info_list;
gnss_cb_->sv_info_list_cbq_.retrieve(sv_info_list, kGnssSvStatusTimeout);
for (IGnssCallback_2_0::GnssSvInfo sv_info : sv_info_list) {
- auto constellation = MapConstellationType(sv_info.constellation);
+ auto constellation = Utils::mapConstellationType(sv_info.constellation);
EXPECT_FALSE((sv_info.v1_0.svid == source_to_blacklist.svid) &&
(constellation == source_to_blacklist.constellation) &&
(sv_info.v1_0.svFlag & IGnssCallback::GnssSvFlags::USED_IN_FIX));
@@ -736,7 +709,7 @@
hidl_vec<IGnssCallback_2_0::GnssSvInfo> sv_info_list;
gnss_cb_->sv_info_list_cbq_.retrieve(sv_info_list, kGnssSvStatusTimeout);
for (IGnssCallback_2_0::GnssSvInfo sv_info : sv_info_list) {
- auto constellation = MapConstellationType(sv_info.constellation);
+ auto constellation = Utils::mapConstellationType(sv_info.constellation);
if ((sv_info.v1_0.svid == source_to_blacklist.svid) &&
(constellation == source_to_blacklist.constellation) &&
(sv_info.v1_0.svFlag & IGnssCallback::GnssSvFlags::USED_IN_FIX)) {
@@ -752,7 +725,7 @@
}
/*
- * BlacklistConstellation:
+ * BlacklistConstellationWithLocationOff:
*
* 1) Turns on location, waits for 3 locations, ensuring they are valid, and checks corresponding
* GnssStatus for any non-GPS constellations.
@@ -761,12 +734,11 @@
* GnssStatus does not use any constellation but GPS.
* 4a & b) Clean up by turning off location, and send in empty blacklist.
*/
-TEST_P(GnssHalTest, BlacklistConstellation) {
+TEST_P(GnssHalTest, BlacklistConstellationWithLocationOff) {
if (!IsGnssHalVersion_2_0()) {
ALOGI("Test BlacklistConstellation skipped. GNSS HAL version is greater than 2.0.");
return;
}
-
if (!(gnss_cb_->last_capabilities_ & IGnssCallback::Capabilities::SATELLITE_BLACKLIST)) {
ALOGI("Test BlacklistConstellation skipped. SATELLITE_BLACKLIST capability not supported.");
return;
@@ -774,43 +746,12 @@
const int kLocationsToAwait = 3;
- gnss_cb_->location_cbq_.reset();
- StartAndCheckLocations(kLocationsToAwait);
- const int location_called_count = gnss_cb_->location_cbq_.calledCount();
+ // Find first non-GPS constellation to blacklist
+ GnssConstellationType_1_0 constellation_to_blacklist = startLocationAndGetNonGpsConstellation();
- // Tolerate 1 less sv status to handle edge cases in reporting.
- int sv_info_list_cbq_size = gnss_cb_->sv_info_list_cbq_.size();
- EXPECT_GE(sv_info_list_cbq_size + 1, kLocationsToAwait);
- ALOGD("Observed %d GnssSvStatus, while awaiting %d Locations (%d received)",
- sv_info_list_cbq_size, kLocationsToAwait, location_called_count);
+ // Turns off location
+ StopAndClearLocations();
- // Find first non-GPS constellation to blacklist. Exclude IRNSS in GnssConstellationType_2_0
- // as blacklisting of this constellation is not supported in gnss@2.0.
- const int kGnssSvStatusTimeout = 2;
- GnssConstellationType_1_0 constellation_to_blacklist = GnssConstellationType_1_0::UNKNOWN;
- for (int i = 0; i < sv_info_list_cbq_size; ++i) {
- hidl_vec<IGnssCallback_2_0::GnssSvInfo> sv_info_list;
- gnss_cb_->sv_info_list_cbq_.retrieve(sv_info_list, kGnssSvStatusTimeout);
- for (IGnssCallback_2_0::GnssSvInfo sv_info : sv_info_list) {
- if ((sv_info.v1_0.svFlag & IGnssCallback::GnssSvFlags::USED_IN_FIX) &&
- (sv_info.constellation != GnssConstellationType_2_0::UNKNOWN) &&
- (sv_info.constellation != GnssConstellationType_2_0::IRNSS) &&
- (sv_info.constellation != GnssConstellationType_2_0::GPS)) {
- // found a non-GPS V1_0 constellation
- constellation_to_blacklist = MapConstellationType(sv_info.constellation);
- break;
- }
- }
- if (constellation_to_blacklist != GnssConstellationType_1_0::UNKNOWN) {
- break;
- }
- }
-
- if (constellation_to_blacklist == GnssConstellationType_1_0::UNKNOWN) {
- ALOGI("No non-GPS constellations found, constellation blacklist test less effective.");
- // Proceed functionally to blacklist something.
- constellation_to_blacklist = GnssConstellationType_1_0::GLONASS;
- }
IGnssConfiguration_1_1::BlacklistedSource source_to_blacklist;
source_to_blacklist.constellation = constellation_to_blacklist;
source_to_blacklist.svid = 0; // documented wildcard for all satellites in this constellation
@@ -824,6 +765,7 @@
sources.resize(1);
sources[0] = source_to_blacklist;
+ // setBlacklist when location is off.
auto result = gnss_configuration_hal->setBlacklist(sources);
ASSERT_TRUE(result.isOk());
EXPECT_TRUE(result);
@@ -835,15 +777,93 @@
StartAndCheckLocations(kLocationsToAwait);
// Tolerate 1 less sv status to handle edge cases in reporting.
- sv_info_list_cbq_size = gnss_cb_->sv_info_list_cbq_.size();
+ int sv_info_list_cbq_size = gnss_cb_->sv_info_list_cbq_.size();
EXPECT_GE(sv_info_list_cbq_size + 1, kLocationsToAwait);
ALOGD("Observed %d GnssSvStatus, while awaiting %d Locations", sv_info_list_cbq_size,
kLocationsToAwait);
+ const int kGnssSvStatusTimeout = 2;
for (int i = 0; i < sv_info_list_cbq_size; ++i) {
hidl_vec<IGnssCallback_2_0::GnssSvInfo> sv_info_list;
gnss_cb_->sv_info_list_cbq_.retrieve(sv_info_list, kGnssSvStatusTimeout);
for (IGnssCallback_2_0::GnssSvInfo sv_info : sv_info_list) {
- auto constellation = MapConstellationType(sv_info.constellation);
+ auto constellation = Utils::mapConstellationType(sv_info.constellation);
+ EXPECT_FALSE((constellation == source_to_blacklist.constellation) &&
+ (sv_info.v1_0.svFlag & IGnssCallback::GnssSvFlags::USED_IN_FIX));
+ }
+ }
+
+ // clean up
+ StopAndClearLocations();
+ sources.resize(0);
+ result = gnss_configuration_hal->setBlacklist(sources);
+ ASSERT_TRUE(result.isOk());
+ EXPECT_TRUE(result);
+}
+
+/*
+ * BlacklistConstellationWithLocationOn:
+ *
+ * 1) Turns on location, waits for 3 locations, ensuring they are valid, and checks corresponding
+ * GnssStatus for any non-GPS constellations.
+ * 2a & b) Blacklist first non-GPS constellations, and turns off location.
+ * 3) Restart location, wait for 3 locations, ensuring they are valid, and checks corresponding
+ * GnssStatus does not use any constellation but GPS.
+ * 4a & b) Clean up by turning off location, and send in empty blacklist.
+ */
+TEST_P(GnssHalTest, BlacklistConstellationWithLocationOn) {
+ if (!IsGnssHalVersion_2_0()) {
+ ALOGI("Test BlacklistConstellation skipped. GNSS HAL version is greater than 2.0.");
+ return;
+ }
+
+ if (!(gnss_cb_->last_capabilities_ & IGnssCallback::Capabilities::SATELLITE_BLACKLIST)) {
+ ALOGI("Test BlacklistConstellation skipped. SATELLITE_BLACKLIST capability not supported.");
+ return;
+ }
+
+ const int kLocationsToAwait = 3;
+
+ // Find first non-GPS constellation to blacklist
+ GnssConstellationType_1_0 constellation_to_blacklist = startLocationAndGetNonGpsConstellation();
+
+ IGnssConfiguration_1_1::BlacklistedSource source_to_blacklist;
+ source_to_blacklist.constellation = constellation_to_blacklist;
+ source_to_blacklist.svid = 0; // documented wildcard for all satellites in this constellation
+
+ auto gnss_configuration_hal_return = gnss_hal_->getExtensionGnssConfiguration_1_1();
+ ASSERT_TRUE(gnss_configuration_hal_return.isOk());
+ sp<IGnssConfiguration_1_1> gnss_configuration_hal = gnss_configuration_hal_return;
+ ASSERT_NE(gnss_configuration_hal, nullptr);
+
+ hidl_vec<IGnssConfiguration_1_1::BlacklistedSource> sources;
+ sources.resize(1);
+ sources[0] = source_to_blacklist;
+
+ // setBlacklist when location is on.
+ auto result = gnss_configuration_hal->setBlacklist(sources);
+ ASSERT_TRUE(result.isOk());
+ EXPECT_TRUE(result);
+
+ // Turns off location
+ StopAndClearLocations();
+
+ // retry and ensure constellation not used
+ gnss_cb_->sv_info_list_cbq_.reset();
+
+ gnss_cb_->location_cbq_.reset();
+ StartAndCheckLocations(kLocationsToAwait);
+
+ // Tolerate 1 less sv status to handle edge cases in reporting.
+ int sv_info_list_cbq_size = gnss_cb_->sv_info_list_cbq_.size();
+ EXPECT_GE(sv_info_list_cbq_size + 1, kLocationsToAwait);
+ ALOGD("Observed %d GnssSvStatus, while awaiting %d Locations", sv_info_list_cbq_size,
+ kLocationsToAwait);
+ const int kGnssSvStatusTimeout = 2;
+ for (int i = 0; i < sv_info_list_cbq_size; ++i) {
+ hidl_vec<IGnssCallback_2_0::GnssSvInfo> sv_info_list;
+ gnss_cb_->sv_info_list_cbq_.retrieve(sv_info_list, kGnssSvStatusTimeout);
+ for (IGnssCallback_2_0::GnssSvInfo sv_info : sv_info_list) {
+ auto constellation = Utils::mapConstellationType(sv_info.constellation);
EXPECT_FALSE((constellation == source_to_blacklist.constellation) &&
(sv_info.v1_0.svFlag & IGnssCallback::GnssSvFlags::USED_IN_FIX));
}
diff --git a/gnss/common/utils/vts/Android.bp b/gnss/common/utils/vts/Android.bp
index fd9613b..4c6d443 100644
--- a/gnss/common/utils/vts/Android.bp
+++ b/gnss/common/utils/vts/Android.bp
@@ -29,6 +29,7 @@
export_include_dirs: ["include"],
shared_libs: [
"android.hardware.gnss@1.0",
+ "android.hardware.gnss@2.0",
"android.hardware.gnss.measurement_corrections@1.0",
"android.hardware.gnss.measurement_corrections@1.1",
],
diff --git a/gnss/common/utils/vts/Utils.cpp b/gnss/common/utils/vts/Utils.cpp
index 4b5a50f..9bf68e6 100644
--- a/gnss/common/utils/vts/Utils.cpp
+++ b/gnss/common/utils/vts/Utils.cpp
@@ -169,6 +169,31 @@
return mockCorrections_1_1;
}
+/*
+ * MapConstellationType:
+ * Given a GnssConstellationType_2_0 type constellation, maps to its equivalent
+ * GnssConstellationType_1_0 type constellation. For constellations that do not have
+ * an equivalent value, maps to GnssConstellationType_1_0::UNKNOWN
+ */
+GnssConstellationType_1_0 Utils::mapConstellationType(GnssConstellationType_2_0 constellation) {
+ switch (constellation) {
+ case GnssConstellationType_2_0::GPS:
+ return GnssConstellationType_1_0::GPS;
+ case GnssConstellationType_2_0::SBAS:
+ return GnssConstellationType_1_0::SBAS;
+ case GnssConstellationType_2_0::GLONASS:
+ return GnssConstellationType_1_0::GLONASS;
+ case GnssConstellationType_2_0::QZSS:
+ return GnssConstellationType_1_0::QZSS;
+ case GnssConstellationType_2_0::BEIDOU:
+ return GnssConstellationType_1_0::BEIDOU;
+ case GnssConstellationType_2_0::GALILEO:
+ return GnssConstellationType_1_0::GALILEO;
+ default:
+ return GnssConstellationType_1_0::UNKNOWN;
+ }
+}
+
} // namespace common
} // namespace gnss
} // namespace hardware
diff --git a/gnss/common/utils/vts/include/Utils.h b/gnss/common/utils/vts/include/Utils.h
index c3cdd18..9c838b2 100644
--- a/gnss/common/utils/vts/include/Utils.h
+++ b/gnss/common/utils/vts/include/Utils.h
@@ -18,9 +18,12 @@
#define android_hardware_gnss_common_vts_Utils_H_
#include <android/hardware/gnss/1.0/IGnss.h>
+#include <android/hardware/gnss/2.0/IGnss.h>
#include <android/hardware/gnss/measurement_corrections/1.0/IMeasurementCorrections.h>
#include <android/hardware/gnss/measurement_corrections/1.1/IMeasurementCorrections.h>
+using GnssConstellationType_1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
+using GnssConstellationType_2_0 = android::hardware::gnss::V2_0::GnssConstellationType;
using GnssLocation = ::android::hardware::gnss::V1_0::GnssLocation;
using namespace android::hardware::gnss::measurement_corrections::V1_0;
@@ -44,6 +47,8 @@
bool check_more_accuracies);
static const MeasurementCorrections_1_0 getMockMeasurementCorrections();
static const MeasurementCorrections_1_1 getMockMeasurementCorrections_1_1();
+
+ static GnssConstellationType_1_0 mapConstellationType(GnssConstellationType_2_0 constellation);
};
} // namespace common
diff --git a/graphics/common/aidl/android/hardware/graphics/common/PlaneLayout.aidl b/graphics/common/aidl/android/hardware/graphics/common/PlaneLayout.aidl
index ccb0690..b329cb2 100644
--- a/graphics/common/aidl/android/hardware/graphics/common/PlaneLayout.aidl
+++ b/graphics/common/aidl/android/hardware/graphics/common/PlaneLayout.aidl
@@ -100,10 +100,11 @@
long totalSizeInBytes;
/**
- * Horizontal and vertical subsampling. Must be a positive power of 2.
+ * Horizontal and vertical subsampling. Must be a positive power of 2. A value of 1
+ * indicates no subsampling.
*
* These fields indicate the number of horizontally or vertically adjacent pixels that use
- * the same pixel data. A value of 1 indicates no subsampling.
+ * the same pixel data.
*/
long horizontalSubsampling;
long verticalSubsampling;
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
index d7f47e8..3b8fbd9 100644
--- a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
@@ -160,17 +160,10 @@
* ItemsRequestBytes
* ]
*
- * SessionTranscript = [
- * DeviceEngagementBytes,
- * EReaderKeyBytes
- * ]
+ * SessionTranscript = any
*
- * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
- * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
* ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
*
- * EReaderKey.Pub = COSE_Key ; Ephemeral public key provided by reader
- *
* The public key corresponding to the key used to made signature, can be found in the
* 'x5chain' unprotected header element of the COSE_Sign1 structure (as as described
* in 'draft-ietf-cose-x509-04'). There will be at least one certificate in said element
@@ -184,8 +177,12 @@
*
* If the SessionTranscript CBOR is not empty, the X and Y coordinates of the public
* part of the key-pair previously generated by createEphemeralKeyPair() must appear
- * somewhere in the bytes of DeviceEngagement structure. Both X and Y should be in
- * uncompressed form. If this is not satisfied, the call fails with
+ * somewhere in the bytes of the CBOR. Each of these coordinates must appear encoded
+ * with the most significant bits first and use the exact amount of bits indicated by
+ * the key size of the ephemeral keys. For example, if the ephemeral key is using the
+ * P-256 curve then the 32 bytes for the X coordinate encoded with the most significant
+ * bits first must appear somewhere in the CBOR and ditto for the 32 bytes for the Y
+ * coordinate. If this is not satisfied, the call fails with
* STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND.
*
* @param accessControlProfiles
@@ -298,13 +295,8 @@
*
* DocType = tstr
*
- * SessionTranscript = [
- * DeviceEngagementBytes,
- * EReaderKeyBytes
- * ]
+ * SessionTranscript = any
*
- * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
- * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
* DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
*
* where
@@ -356,8 +348,9 @@
*
* - subjectPublicKeyInfo: must contain attested public key.
*
- * @param out signingKeyBlob contains an encrypted copy of the newly-generated private
- * signing key.
+ * @param out signingKeyBlob contains an AES-GCM-ENC(storageKey, R, signingKey, docType)
+ * where signingKey is an EC private key in uncompressed form. That is, the returned
+ * blob is an encrypted copy of the newly-generated private signing key.
*
* @return an X.509 certificate for the new signing key, signed by the credential key.
*/
diff --git a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
index b7ad283..297fd1d 100644
--- a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -29,9 +29,27 @@
* Gets the certificate chain for credentialKey which can be used to prove the hardware
* characteristics to an issuing authority. Must not be called more than once.
*
+ * The following non-optional fields for the X.509 certificate shall be set as follows:
+ *
+ * - version: INTEGER 2 (means v3 certificate).
+ *
+ * - serialNumber: INTEGER 1 (fixed value: same on all certs).
+ *
+ * - signature: must be set to ECDSA.
+ *
+ * - subject: CN shall be set to "Android Identity Credential Key".
+ *
+ * - issuer: shall be set to "credentialStoreName (credentialStoreAuthorName)" using the
+ * values returned in HardwareInformation.
+ *
+ * - validity: should be from current time and expire at the same time as the
+ * attestation batch certificate used.
+ *
+ * - subjectPublicKeyInfo: must contain attested public key.
+ *
* The certificate chain must be generated using Keymaster Attestation
* (see https://source.android.com/security/keystore/attestation) with the
- * following additional requirements:
+ * following additional requirements on the data in the attestation extension:
*
* - The attestationVersion field in the attestation extension must be at least 3.
*
@@ -109,7 +127,8 @@
* in Tag::ATTESTATION_APPLICATION_ID. This schema is described in
* https://developer.android.com/training/articles/security-key-attestation#certificate_schema_attestationid
*
- * @param attestationChallenge a challenge set by the issuer to ensure freshness.
+ * @param attestationChallenge a challenge set by the issuer to ensure freshness. If
+ * this is empty, the call fails with STATUS_INVALID_DATA.
*
* @return the X.509 certificate chain for the credentialKey
*/
@@ -250,6 +269,7 @@
* CredentialKeys = [
* bstr, ; storageKey, a 128-bit AES key
* bstr ; credentialPrivKey, the private key for credentialKey
+ * ; in uncompressed form
* ]
*
* @param out proofOfProvisioningSignature proves to the IA that the credential was imported
diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/IdentityCredential.cpp
index 8a00d22..f3c4bbf 100644
--- a/identity/aidl/default/IdentityCredential.cpp
+++ b/identity/aidl/default/IdentityCredential.cpp
@@ -164,6 +164,7 @@
}
*outChallenge = challenge;
+ authChallenge_ = challenge;
return ndk::ScopedAStatus::ok();
}
@@ -223,7 +224,8 @@
}
if (authToken.challenge != int64_t(authChallenge)) {
- LOG(ERROR) << "Challenge in authToken doesn't match the challenge we created";
+ LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") "
+ << "doesn't match the challenge we created (" << authChallenge << ")";
return false;
}
return true;
@@ -341,28 +343,6 @@
//
// We do this by just searching for the X and Y coordinates.
if (sessionTranscript.size() > 0) {
- const cppbor::Array* array = sessionTranscriptItem_->asArray();
- if (array == nullptr || array->size() != 2) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
- "SessionTranscript is not an array with two items"));
- }
- const cppbor::Semantic* taggedEncodedDE = (*array)[0]->asSemantic();
- if (taggedEncodedDE == nullptr || taggedEncodedDE->value() != 24) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
- "First item in SessionTranscript array is not a "
- "semantic with value 24"));
- }
- const cppbor::Bstr* encodedDE = (taggedEncodedDE->child())->asBstr();
- if (encodedDE == nullptr) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
- "Child of semantic in first item in SessionTranscript "
- "array is not a bstr"));
- }
- const vector<uint8_t>& bytesDE = encodedDE->value();
-
auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
if (!getXYSuccess) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
@@ -370,8 +350,10 @@
"Error extracting X and Y from ePub"));
}
if (sessionTranscript.size() > 0 &&
- !(memmem(bytesDE.data(), bytesDE.size(), ePubX.data(), ePubX.size()) != nullptr &&
- memmem(bytesDE.data(), bytesDE.size(), ePubY.data(), ePubY.size()) != nullptr)) {
+ !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(),
+ ePubX.size()) != nullptr &&
+ memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(),
+ ePubY.size()) != nullptr)) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
"Did not find ephemeral public key's X and Y coordinates in "
@@ -478,9 +460,10 @@
}
// Validate all the access control profiles in the requestData.
- bool haveAuthToken = (authToken.mac.size() > 0);
+ bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0));
for (const auto& profile : accessControlProfiles) {
if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
+ LOG(ERROR) << "Error checking MAC for profile";
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA,
"Error checking MAC for profile"));
diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/WritableIdentityCredential.cpp
index 7732c33..c218866 100644
--- a/identity/aidl/default/WritableIdentityCredential.cpp
+++ b/identity/aidl/default/WritableIdentityCredential.cpp
@@ -65,6 +65,10 @@
IIdentityCredentialStore::STATUS_FAILED,
"Error attestation certificate previously generated"));
}
+ if (attestationChallenge.empty()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge can not be empty"));
+ }
vector<uint8_t> challenge(attestationChallenge.begin(), attestationChallenge.end());
vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end());
@@ -165,6 +169,13 @@
"userAuthenticationRequired is false but timeout is non-zero"));
}
+ // If |userAuthenticationRequired| is true, then |secureUserId| must be non-zero.
+ if (userAuthenticationRequired && secureUserId == 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "userAuthenticationRequired is true but secureUserId is zero"));
+ }
+
profile.id = id;
profile.readerCertificate = readerCertificate;
profile.userAuthenticationRequired = userAuthenticationRequired;
diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp
index 5b075c6..cd6f9b0 100644
--- a/identity/aidl/vts/Android.bp
+++ b/identity/aidl/vts/Android.bp
@@ -10,17 +10,20 @@
"VtsIdentityTestUtils.cpp",
"VtsAttestationTests.cpp",
"VtsAttestationParserSupport.cpp",
+ "UserAuthTests.cpp",
+ "ReaderAuthTests.cpp",
],
shared_libs: [
"android.hardware.keymaster@4.0",
"libbinder",
"libcrypto",
- "libkeymaster_portable",
- "libsoft_attestation_cert",
- "libpuresoftkeymasterdevice",
+ "android.hardware.keymaster-ndk_platform",
],
static_libs: [
"libcppbor",
+ "libkeymaster_portable",
+ "libsoft_attestation_cert",
+ "libpuresoftkeymasterdevice",
"android.hardware.identity-support-lib",
"android.hardware.identity-cpp",
"android.hardware.keymaster-cpp",
diff --git a/identity/aidl/vts/ReaderAuthTests.cpp b/identity/aidl/vts/ReaderAuthTests.cpp
new file mode 100644
index 0000000..680ba5b
--- /dev/null
+++ b/identity/aidl/vts/ReaderAuthTests.cpp
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ReaderAuthTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "VtsIdentityTestUtils.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class ReaderAuthTests : public testing::TestWithParam<string> {
+ public:
+ virtual void SetUp() override {
+ credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+ String16(GetParam().c_str()));
+ ASSERT_NE(credentialStore_, nullptr);
+ }
+
+ void provisionData();
+ void retrieveData(const vector<uint8_t>& readerPrivateKey,
+ const vector<vector<uint8_t>>& readerCertChain, bool expectSuccess,
+ bool leaveOutAccessibleToAllFromRequestMessage);
+
+ // Set by provisionData
+ vector<uint8_t> readerPublicKey_;
+ vector<uint8_t> readerPrivateKey_;
+ vector<uint8_t> intermediateAPublicKey_;
+ vector<uint8_t> intermediateAPrivateKey_;
+ vector<uint8_t> intermediateBPublicKey_;
+ vector<uint8_t> intermediateBPrivateKey_;
+ vector<uint8_t> intermediateCPublicKey_;
+ vector<uint8_t> intermediateCPrivateKey_;
+
+ vector<uint8_t> cert_A_SelfSigned_;
+
+ vector<uint8_t> cert_B_SelfSigned_;
+
+ vector<uint8_t> cert_B_SignedBy_C_;
+
+ vector<uint8_t> cert_C_SelfSigned_;
+
+ vector<uint8_t> cert_reader_SelfSigned_;
+ vector<uint8_t> cert_reader_SignedBy_A_;
+ vector<uint8_t> cert_reader_SignedBy_B_;
+
+ SecureAccessControlProfile sacp0_;
+ SecureAccessControlProfile sacp1_;
+ SecureAccessControlProfile sacp2_;
+ SecureAccessControlProfile sacp3_;
+
+ vector<uint8_t> encContentAccessibleByA_;
+ vector<uint8_t> encContentAccessibleByAorB_;
+ vector<uint8_t> encContentAccessibleByB_;
+ vector<uint8_t> encContentAccessibleByC_;
+ vector<uint8_t> encContentAccessibleByAll_;
+ vector<uint8_t> encContentAccessibleByNone_;
+
+ vector<uint8_t> credentialData_;
+
+ // Set by retrieveData()
+ bool canGetAccessibleByA_;
+ bool canGetAccessibleByAorB_;
+ bool canGetAccessibleByB_;
+ bool canGetAccessibleByC_;
+ bool canGetAccessibleByAll_;
+ bool canGetAccessibleByNone_;
+
+ sp<IIdentityCredentialStore> credentialStore_;
+};
+
+pair<vector<uint8_t>, vector<uint8_t>> generateReaderKey() {
+ optional<vector<uint8_t>> keyPKCS8 = support::createEcKeyPair();
+ optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(keyPKCS8.value());
+ optional<vector<uint8_t>> privateKey = support::ecKeyPairGetPrivateKey(keyPKCS8.value());
+ return make_pair(publicKey.value(), privateKey.value());
+}
+
+vector<uint8_t> generateReaderCert(const vector<uint8_t>& publicKey,
+ const vector<uint8_t>& signingKey) {
+ time_t validityNotBefore = 0;
+ time_t validityNotAfter = 0xffffffff;
+ optional<vector<uint8_t>> cert =
+ support::ecPublicKeyGenerateCertificate(publicKey, signingKey, "24601", "Issuer",
+ "Subject", validityNotBefore, validityNotAfter);
+ return cert.value();
+}
+
+void ReaderAuthTests::provisionData() {
+ // Keys and certificates for intermediates.
+ tie(intermediateAPublicKey_, intermediateAPrivateKey_) = generateReaderKey();
+ tie(intermediateBPublicKey_, intermediateBPrivateKey_) = generateReaderKey();
+ tie(intermediateCPublicKey_, intermediateCPrivateKey_) = generateReaderKey();
+
+ cert_A_SelfSigned_ = generateReaderCert(intermediateAPublicKey_, intermediateAPrivateKey_);
+
+ cert_B_SelfSigned_ = generateReaderCert(intermediateBPublicKey_, intermediateBPrivateKey_);
+
+ cert_B_SignedBy_C_ = generateReaderCert(intermediateBPublicKey_, intermediateCPrivateKey_);
+
+ cert_C_SelfSigned_ = generateReaderCert(intermediateCPublicKey_, intermediateCPrivateKey_);
+
+ // Key and self-signed certificate reader
+ tie(readerPublicKey_, readerPrivateKey_) = generateReaderKey();
+ cert_reader_SelfSigned_ = generateReaderCert(readerPublicKey_, readerPrivateKey_);
+
+ // Certificate for reader signed by intermediates
+ cert_reader_SignedBy_A_ = generateReaderCert(readerPublicKey_, intermediateAPrivateKey_);
+ cert_reader_SignedBy_B_ = generateReaderCert(readerPublicKey_, intermediateBPrivateKey_);
+
+ string docType = "org.iso.18013-5.2019.mdl";
+ bool testCredential = true;
+ sp<IWritableIdentityCredential> wc;
+ ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
+
+ vector<uint8_t> attestationApplicationId = {};
+ vector<uint8_t> attestationChallenge = {1};
+ vector<Certificate> certChain;
+ ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+ &certChain)
+ .isOk());
+
+ size_t proofOfProvisioningSize =
+ 465 + cert_A_SelfSigned_.size() + cert_B_SelfSigned_.size() + cert_C_SelfSigned_.size();
+ ASSERT_TRUE(wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize).isOk());
+
+ // Not in v1 HAL, may fail
+ wc->startPersonalization(4 /* numAccessControlProfiles */,
+ {6} /* numDataElementsPerNamespace */);
+
+ // AIDL expects certificates wrapped in the Certificate type...
+ Certificate cert_A;
+ Certificate cert_B;
+ Certificate cert_C;
+ cert_A.encodedCertificate = cert_A_SelfSigned_;
+ cert_B.encodedCertificate = cert_B_SelfSigned_;
+ cert_C.encodedCertificate = cert_C_SelfSigned_;
+
+ // Access control profile 0: accessible by A
+ ASSERT_TRUE(wc->addAccessControlProfile(0, cert_A, false, 0, 0, &sacp0_).isOk());
+
+ // Access control profile 1: accessible by B
+ ASSERT_TRUE(wc->addAccessControlProfile(1, cert_B, false, 0, 0, &sacp1_).isOk());
+
+ // Access control profile 2: accessible by C
+ ASSERT_TRUE(wc->addAccessControlProfile(2, cert_C, false, 0, 0, &sacp2_).isOk());
+
+ // Access control profile 3: open access
+ ASSERT_TRUE(wc->addAccessControlProfile(3, {}, false, 0, 0, &sacp3_).isOk());
+
+ // Data Element: "Accessible by A"
+ ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "Accessible by A", 1).isOk());
+ ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByA_).isOk());
+
+ // Data Element: "Accessible by A or B"
+ ASSERT_TRUE(wc->beginAddEntry({0, 1}, "ns", "Accessible by A or B", 1).isOk());
+ ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByAorB_).isOk());
+
+ // Data Element: "Accessible by B"
+ ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Accessible by B", 1).isOk());
+ ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByB_).isOk());
+
+ // Data Element: "Accessible by C"
+ ASSERT_TRUE(wc->beginAddEntry({2}, "ns", "Accessible by C", 1).isOk());
+ ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByC_).isOk());
+
+ // Data Element: "Accessible by All"
+ ASSERT_TRUE(wc->beginAddEntry({3}, "ns", "Accessible by All", 1).isOk());
+ ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByAll_).isOk());
+
+ // Data Element: "Accessible by None"
+ ASSERT_TRUE(wc->beginAddEntry({}, "ns", "Accessible by None", 1).isOk());
+ ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByNone_).isOk());
+
+ vector<uint8_t> proofOfProvisioningSignature;
+ ASSERT_TRUE(wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature).isOk());
+}
+
+RequestDataItem buildRequestDataItem(const string& name, size_t size,
+ vector<int32_t> accessControlProfileIds) {
+ RequestDataItem item;
+ item.name = name;
+ item.size = size;
+ item.accessControlProfileIds = accessControlProfileIds;
+ return item;
+}
+
+void ReaderAuthTests::retrieveData(const vector<uint8_t>& readerPrivateKey,
+ const vector<vector<uint8_t>>& readerCertChain,
+ bool expectSuccess,
+ bool leaveOutAccessibleToAllFromRequestMessage) {
+ canGetAccessibleByA_ = false;
+ canGetAccessibleByAorB_ = false;
+ canGetAccessibleByB_ = false;
+ canGetAccessibleByC_ = false;
+ canGetAccessibleByAll_ = false;
+ canGetAccessibleByNone_ = false;
+
+ sp<IIdentityCredential> c;
+ ASSERT_TRUE(credentialStore_
+ ->getCredential(
+ CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+ credentialData_, &c)
+ .isOk());
+
+ optional<vector<uint8_t>> readerEKeyPair = support::createEcKeyPair();
+ optional<vector<uint8_t>> readerEPublicKey =
+ support::ecKeyPairGetPublicKey(readerEKeyPair.value());
+ ASSERT_TRUE(c->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk());
+
+ vector<uint8_t> eKeyPair;
+ ASSERT_TRUE(c->createEphemeralKeyPair(&eKeyPair).isOk());
+ optional<vector<uint8_t>> ePublicKey = support::ecKeyPairGetPublicKey(eKeyPair);
+
+ // Calculate requestData field and sign it with the reader key.
+ auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey.value());
+ ASSERT_TRUE(getXYSuccess);
+ cppbor::Map deviceEngagement = cppbor::Map().add("ephX", ephX).add("ephY", ephY);
+ vector<uint8_t> deviceEngagementBytes = deviceEngagement.encode();
+ vector<uint8_t> eReaderPubBytes = cppbor::Tstr("ignored").encode();
+ cppbor::Array sessionTranscript = cppbor::Array()
+ .add(cppbor::Semantic(24, deviceEngagementBytes))
+ .add(cppbor::Semantic(24, eReaderPubBytes));
+ vector<uint8_t> sessionTranscriptBytes = sessionTranscript.encode();
+
+ vector<uint8_t> itemsRequestBytes;
+ if (leaveOutAccessibleToAllFromRequestMessage) {
+ itemsRequestBytes =
+ cppbor::Map("nameSpaces",
+ cppbor::Map().add("ns", cppbor::Map()
+ .add("Accessible by A", false)
+ .add("Accessible by A or B", false)
+ .add("Accessible by B", false)
+ .add("Accessible by C", false)
+ .add("Accessible by None", false)))
+ .encode();
+ } else {
+ itemsRequestBytes =
+ cppbor::Map("nameSpaces",
+ cppbor::Map().add("ns", cppbor::Map()
+ .add("Accessible by A", false)
+ .add("Accessible by A or B", false)
+ .add("Accessible by B", false)
+ .add("Accessible by C", false)
+ .add("Accessible by All", false)
+ .add("Accessible by None", false)))
+ .encode();
+ }
+ vector<uint8_t> dataToSign = cppbor::Array()
+ .add("ReaderAuthentication")
+ .add(sessionTranscript.clone())
+ .add(cppbor::Semantic(24, itemsRequestBytes))
+ .encode();
+
+ optional<vector<uint8_t>> readerSignature =
+ support::coseSignEcDsa(readerPrivateKey, // private key for reader
+ {}, // content
+ dataToSign, // detached content
+ support::certificateChainJoin(readerCertChain));
+ ASSERT_TRUE(readerSignature);
+
+ // Generate the key that will be used to sign AuthenticatedData.
+ vector<uint8_t> signingKeyBlob;
+ Certificate signingKeyCertificate;
+ ASSERT_TRUE(c->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
+
+ RequestNamespace rns;
+ rns.namespaceName = "ns";
+ rns.items.push_back(buildRequestDataItem("Accessible by A", 1, {0}));
+ rns.items.push_back(buildRequestDataItem("Accessible by A or B", 1, {0, 1}));
+ rns.items.push_back(buildRequestDataItem("Accessible by B", 1, {1}));
+ rns.items.push_back(buildRequestDataItem("Accessible by C", 1, {2}));
+ rns.items.push_back(buildRequestDataItem("Accessible by All", 1, {3}));
+ rns.items.push_back(buildRequestDataItem("Accessible by None", 1, {}));
+ // OK to fail, not available in v1 HAL
+ c->setRequestedNamespaces({rns}).isOk();
+
+ // It doesn't matter since no user auth is needed in this particular test,
+ // but for good measure, clear out the tokens we pass to the HAL.
+ HardwareAuthToken authToken;
+ VerificationToken verificationToken;
+ authToken.challenge = 0;
+ authToken.userId = 0;
+ authToken.authenticatorId = 0;
+ authToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE;
+ authToken.timestamp.milliSeconds = 0;
+ authToken.mac.clear();
+ verificationToken.challenge = 0;
+ verificationToken.timestamp.milliSeconds = 0;
+ verificationToken.securityLevel = ::android::hardware::keymaster::SecurityLevel::SOFTWARE;
+ verificationToken.mac.clear();
+ // OK to fail, not available in v1 HAL
+ c->setVerificationToken(verificationToken);
+
+ Status status = c->startRetrieval(
+ {sacp0_, sacp1_, sacp2_, sacp3_}, authToken, itemsRequestBytes, signingKeyBlob,
+ sessionTranscriptBytes, readerSignature.value(), {6 /* numDataElementsPerNamespace */});
+ if (expectSuccess) {
+ ASSERT_TRUE(status.isOk());
+ } else {
+ ASSERT_FALSE(status.isOk());
+ return;
+ }
+
+ vector<uint8_t> decrypted;
+
+ status = c->startRetrieveEntryValue("ns", "Accessible by A", 1, {0});
+ if (status.isOk()) {
+ canGetAccessibleByA_ = true;
+ ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByA_, &decrypted).isOk());
+ }
+
+ status = c->startRetrieveEntryValue("ns", "Accessible by A or B", 1, {0, 1});
+ if (status.isOk()) {
+ canGetAccessibleByAorB_ = true;
+ ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByAorB_, &decrypted).isOk());
+ }
+
+ status = c->startRetrieveEntryValue("ns", "Accessible by B", 1, {1});
+ if (status.isOk()) {
+ canGetAccessibleByB_ = true;
+ ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByB_, &decrypted).isOk());
+ }
+
+ status = c->startRetrieveEntryValue("ns", "Accessible by C", 1, {2});
+ if (status.isOk()) {
+ canGetAccessibleByC_ = true;
+ ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByC_, &decrypted).isOk());
+ }
+
+ status = c->startRetrieveEntryValue("ns", "Accessible by All", 1, {3});
+ if (status.isOk()) {
+ canGetAccessibleByAll_ = true;
+ ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByAll_, &decrypted).isOk());
+ }
+
+ status = c->startRetrieveEntryValue("ns", "Accessible by None", 1, {});
+ if (status.isOk()) {
+ canGetAccessibleByNone_ = true;
+ ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByNone_, &decrypted).isOk());
+ }
+
+ vector<uint8_t> mac;
+ vector<uint8_t> deviceNameSpaces;
+ ASSERT_TRUE(c->finishRetrieval(&mac, &deviceNameSpaces).isOk());
+}
+
+TEST_P(ReaderAuthTests, presentingChain_Reader) {
+ provisionData();
+ retrieveData(readerPrivateKey_, {cert_reader_SelfSigned_}, true /* expectSuccess */,
+ false /* leaveOutAccessibleToAllFromRequestMessage */);
+ EXPECT_FALSE(canGetAccessibleByA_);
+ EXPECT_FALSE(canGetAccessibleByAorB_);
+ EXPECT_FALSE(canGetAccessibleByB_);
+ EXPECT_FALSE(canGetAccessibleByC_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+TEST_P(ReaderAuthTests, presentingChain_Reader_A) {
+ provisionData();
+ retrieveData(readerPrivateKey_, {cert_reader_SignedBy_A_, cert_A_SelfSigned_},
+ true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */);
+ EXPECT_TRUE(canGetAccessibleByA_);
+ EXPECT_TRUE(canGetAccessibleByAorB_);
+ EXPECT_FALSE(canGetAccessibleByB_);
+ EXPECT_FALSE(canGetAccessibleByC_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+TEST_P(ReaderAuthTests, presentingChain_Reader_B) {
+ provisionData();
+ retrieveData(readerPrivateKey_, {cert_reader_SignedBy_B_, cert_B_SelfSigned_},
+ true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */);
+ EXPECT_FALSE(canGetAccessibleByA_);
+ EXPECT_TRUE(canGetAccessibleByAorB_);
+ EXPECT_TRUE(canGetAccessibleByB_);
+ EXPECT_FALSE(canGetAccessibleByC_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+// This test proves that for the purpose of determining inclusion of an ACP certificate
+// in a presented reader chain, certificate equality is done by comparing public keys,
+// not bitwise comparison of the certificates.
+//
+// Specifically for this test, the ACP is configured with cert_B_SelfSigned_ and the
+// reader is presenting cert_B_SignedBy_C_. Both certificates have the same public
+// key - intermediateBPublicKey_ - but they are signed by different keys.
+//
+TEST_P(ReaderAuthTests, presentingChain_Reader_B_C) {
+ provisionData();
+ retrieveData(readerPrivateKey_,
+ {cert_reader_SignedBy_B_, cert_B_SignedBy_C_, cert_C_SelfSigned_},
+ true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */);
+ EXPECT_FALSE(canGetAccessibleByA_);
+ EXPECT_TRUE(canGetAccessibleByAorB_);
+ EXPECT_TRUE(canGetAccessibleByB_);
+ EXPECT_TRUE(canGetAccessibleByC_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+// This test presents a reader chain where the chain is invalid because
+// the 2nd certificate in the chain isn't signed by the 3rd one.
+//
+TEST_P(ReaderAuthTests, presentingInvalidChain) {
+ provisionData();
+ retrieveData(readerPrivateKey_,
+ {cert_reader_SignedBy_B_, cert_B_SelfSigned_, cert_C_SelfSigned_},
+ false /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */);
+}
+
+// This tests presents a valid reader chain but where requestMessage isn't
+// signed by the private key corresponding to the public key in the top-level
+// certificate.
+//
+TEST_P(ReaderAuthTests, presentingMessageSignedNotByTopLevel) {
+ provisionData();
+ retrieveData(intermediateBPrivateKey_,
+ {cert_reader_SignedBy_B_, cert_B_SignedBy_C_, cert_C_SelfSigned_},
+ false /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */);
+}
+
+// This test leaves out "Accessible by All" data element from the signed request
+// message (the CBOR from the reader) while still including this data element at
+// the API level. The call on the API level for said element will fail with
+// STATUS_NOT_IN_REQUEST_MESSAGE but this doesn't prevent the other elements
+// from being returned (if authorized, of course).
+//
+// This test verifies that.
+//
+TEST_P(ReaderAuthTests, limitedMessage) {
+ provisionData();
+ retrieveData(readerPrivateKey_, {cert_reader_SelfSigned_}, true /* expectSuccess */,
+ true /* leaveOutAccessibleToAllFromRequestMessage */);
+ EXPECT_FALSE(canGetAccessibleByA_);
+ EXPECT_FALSE(canGetAccessibleByAorB_);
+ EXPECT_FALSE(canGetAccessibleByB_);
+ EXPECT_FALSE(canGetAccessibleByC_);
+ EXPECT_FALSE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+TEST_P(ReaderAuthTests, ephemeralKeyNotInSessionTranscript) {
+ provisionData();
+
+ sp<IIdentityCredential> c;
+ ASSERT_TRUE(credentialStore_
+ ->getCredential(
+ CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+ credentialData_, &c)
+ .isOk());
+
+ optional<vector<uint8_t>> readerEKeyPair = support::createEcKeyPair();
+ optional<vector<uint8_t>> readerEPublicKey =
+ support::ecKeyPairGetPublicKey(readerEKeyPair.value());
+ ASSERT_TRUE(c->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk());
+
+ vector<uint8_t> eKeyPair;
+ ASSERT_TRUE(c->createEphemeralKeyPair(&eKeyPair).isOk());
+ optional<vector<uint8_t>> ePublicKey = support::ecKeyPairGetPublicKey(eKeyPair);
+
+ // Calculate requestData field and sign it with the reader key.
+ auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey.value());
+ ASSERT_TRUE(getXYSuccess);
+ // Instead of include the X and Y coordinates (|ephX| and |ephY|), add NUL bytes instead.
+ vector<uint8_t> nulls(32);
+ cppbor::Map deviceEngagement = cppbor::Map().add("ephX", nulls).add("ephY", nulls);
+ vector<uint8_t> deviceEngagementBytes = deviceEngagement.encode();
+ vector<uint8_t> eReaderPubBytes = cppbor::Tstr("ignored").encode();
+ cppbor::Array sessionTranscript = cppbor::Array()
+ .add(cppbor::Semantic(24, deviceEngagementBytes))
+ .add(cppbor::Semantic(24, eReaderPubBytes));
+ vector<uint8_t> sessionTranscriptBytes = sessionTranscript.encode();
+
+ vector<uint8_t> itemsRequestBytes;
+ itemsRequestBytes =
+ cppbor::Map("nameSpaces",
+ cppbor::Map().add("ns", cppbor::Map()
+ .add("Accessible by A", false)
+ .add("Accessible by A or B", false)
+ .add("Accessible by B", false)
+ .add("Accessible by C", false)
+ .add("Accessible by None", false)))
+ .encode();
+ vector<uint8_t> dataToSign = cppbor::Array()
+ .add("ReaderAuthentication")
+ .add(sessionTranscript.clone())
+ .add(cppbor::Semantic(24, itemsRequestBytes))
+ .encode();
+
+ vector<vector<uint8_t>> readerCertChain = {cert_reader_SelfSigned_};
+ optional<vector<uint8_t>> readerSignature =
+ support::coseSignEcDsa(readerPrivateKey_, // private key for reader
+ {}, // content
+ dataToSign, // detached content
+ support::certificateChainJoin(readerCertChain));
+ ASSERT_TRUE(readerSignature);
+
+ // Generate the key that will be used to sign AuthenticatedData.
+ vector<uint8_t> signingKeyBlob;
+ Certificate signingKeyCertificate;
+ ASSERT_TRUE(c->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
+
+ RequestNamespace rns;
+ rns.namespaceName = "ns";
+ rns.items.push_back(buildRequestDataItem("Accessible by A", 1, {0}));
+ rns.items.push_back(buildRequestDataItem("Accessible by A or B", 1, {0, 1}));
+ rns.items.push_back(buildRequestDataItem("Accessible by B", 1, {1}));
+ rns.items.push_back(buildRequestDataItem("Accessible by C", 1, {2}));
+ rns.items.push_back(buildRequestDataItem("Accessible by All", 1, {3}));
+ rns.items.push_back(buildRequestDataItem("Accessible by None", 1, {}));
+ // OK to fail, not available in v1 HAL
+ c->setRequestedNamespaces({rns}).isOk();
+
+ // It doesn't matter since no user auth is needed in this particular test,
+ // but for good measure, clear out the tokens we pass to the HAL.
+ HardwareAuthToken authToken;
+ VerificationToken verificationToken;
+ authToken.challenge = 0;
+ authToken.userId = 0;
+ authToken.authenticatorId = 0;
+ authToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE;
+ authToken.timestamp.milliSeconds = 0;
+ authToken.mac.clear();
+ verificationToken.challenge = 0;
+ verificationToken.timestamp.milliSeconds = 0;
+ verificationToken.securityLevel =
+ ::android::hardware::keymaster::SecurityLevel::TRUSTED_ENVIRONMENT;
+ verificationToken.mac.clear();
+ // OK to fail, not available in v1 HAL
+ c->setVerificationToken(verificationToken);
+
+ // Finally check that STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND is returned.
+ // This proves that the TA checked for X and Y coordinatets and didn't find
+ // them.
+ Status status = c->startRetrieval(
+ {sacp0_, sacp1_, sacp2_, sacp3_}, authToken, itemsRequestBytes, signingKeyBlob,
+ sessionTranscriptBytes, readerSignature.value(), {6 /* numDataElementsPerNamespace */});
+ ASSERT_FALSE(status.isOk());
+ ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
+ ASSERT_EQ(IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+ status.serviceSpecificErrorCode());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Identity, ReaderAuthTests,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+ android::PrintInstanceNameToString);
+
+} // namespace android::hardware::identity
diff --git a/identity/aidl/vts/UserAuthTests.cpp b/identity/aidl/vts/UserAuthTests.cpp
new file mode 100644
index 0000000..5b4c8f1
--- /dev/null
+++ b/identity/aidl/vts/UserAuthTests.cpp
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "UserAuthTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "VtsIdentityTestUtils.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class UserAuthTests : public testing::TestWithParam<string> {
+ public:
+ virtual void SetUp() override {
+ credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+ String16(GetParam().c_str()));
+ ASSERT_NE(credentialStore_, nullptr);
+ }
+
+ void provisionData();
+ void setupRetrieveData();
+ pair<HardwareAuthToken, VerificationToken> mintTokens(uint64_t challengeForAuthToken,
+ int64_t ageOfAuthTokenMilliSeconds);
+ void retrieveData(HardwareAuthToken authToken, VerificationToken verificationToken,
+ bool expectSuccess, bool useSessionTranscript);
+
+ // Set by provisionData
+ SecureAccessControlProfile sacp0_;
+ SecureAccessControlProfile sacp1_;
+ SecureAccessControlProfile sacp2_;
+
+ vector<uint8_t> encContentUserAuthPerSession_;
+ vector<uint8_t> encContentUserAuthTimeout_;
+ vector<uint8_t> encContentAccessibleByAll_;
+ vector<uint8_t> encContentAccessibleByNone_;
+
+ vector<uint8_t> credentialData_;
+
+ // Set by setupRetrieveData().
+ int64_t authChallenge_;
+ cppbor::Map sessionTranscript_;
+ sp<IIdentityCredential> credential_;
+
+ // Set by retrieveData()
+ bool canGetUserAuthPerSession_;
+ bool canGetUserAuthTimeout_;
+ bool canGetAccessibleByAll_;
+ bool canGetAccessibleByNone_;
+
+ sp<IIdentityCredentialStore> credentialStore_;
+};
+
+void UserAuthTests::provisionData() {
+ string docType = "org.iso.18013-5.2019.mdl";
+ bool testCredential = true;
+ sp<IWritableIdentityCredential> wc;
+ ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
+
+ vector<uint8_t> attestationApplicationId = {};
+ vector<uint8_t> attestationChallenge = {1};
+ vector<Certificate> certChain;
+ ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+ &certChain)
+ .isOk());
+
+ size_t proofOfProvisioningSize = 381;
+ // Not in v1 HAL, may fail
+ wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+ ASSERT_TRUE(wc->startPersonalization(3 /* numAccessControlProfiles */,
+ {4} /* numDataElementsPerNamespace */)
+ .isOk());
+
+ // Access control profile 0: user auth every session (timeout = 0)
+ ASSERT_TRUE(wc->addAccessControlProfile(0, {}, true, 0, 65 /* secureUserId */, &sacp0_).isOk());
+
+ // Access control profile 1: user auth, 60 seconds timeout
+ ASSERT_TRUE(
+ wc->addAccessControlProfile(1, {}, true, 60000, 65 /* secureUserId */, &sacp1_).isOk());
+
+ // Access control profile 2: open access
+ ASSERT_TRUE(wc->addAccessControlProfile(2, {}, false, 0, 0, &sacp2_).isOk());
+
+ // Data Element: "UserAuth Per Session"
+ ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "UserAuth Per Session", 1).isOk());
+ ASSERT_TRUE(wc->addEntryValue({9}, &encContentUserAuthPerSession_).isOk());
+
+ // Data Element: "UserAuth Timeout"
+ ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "UserAuth Timeout", 1).isOk());
+ ASSERT_TRUE(wc->addEntryValue({9}, &encContentUserAuthTimeout_).isOk());
+
+ // Data Element: "Accessible by All"
+ ASSERT_TRUE(wc->beginAddEntry({2}, "ns", "Accessible by All", 1).isOk());
+ ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByAll_).isOk());
+
+ // Data Element: "Accessible by None"
+ ASSERT_TRUE(wc->beginAddEntry({}, "ns", "Accessible by None", 1).isOk());
+ ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByNone_).isOk());
+
+ vector<uint8_t> proofOfProvisioningSignature;
+ Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
+ EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+}
+
+// From ReaderAuthTest.cpp - TODO: consolidate with VtsIdentityTestUtils.h
+pair<vector<uint8_t>, vector<uint8_t>> generateReaderKey();
+vector<uint8_t> generateReaderCert(const vector<uint8_t>& publicKey,
+ const vector<uint8_t>& signingKey);
+RequestDataItem buildRequestDataItem(const string& name, size_t size,
+ vector<int32_t> accessControlProfileIds);
+
+cppbor::Map calcSessionTranscript(const vector<uint8_t>& ePublicKey) {
+ auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey);
+ cppbor::Map deviceEngagement = cppbor::Map().add("ephX", ephX).add("ephY", ephY);
+ vector<uint8_t> deviceEngagementBytes = deviceEngagement.encode();
+ vector<uint8_t> eReaderPubBytes = cppbor::Tstr("ignored").encode();
+ // Let SessionTranscript be a map here (it's an array in EndToEndTest) just
+ // to check that the implementation can deal with either.
+ cppbor::Map sessionTranscript;
+ sessionTranscript.add(42, cppbor::Semantic(24, deviceEngagementBytes));
+ sessionTranscript.add(43, cppbor::Semantic(24, eReaderPubBytes));
+ return sessionTranscript;
+}
+
+void UserAuthTests::setupRetrieveData() {
+ ASSERT_TRUE(credentialStore_
+ ->getCredential(
+ CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+ credentialData_, &credential_)
+ .isOk());
+
+ optional<vector<uint8_t>> readerEKeyPair = support::createEcKeyPair();
+ optional<vector<uint8_t>> readerEPublicKey =
+ support::ecKeyPairGetPublicKey(readerEKeyPair.value());
+ ASSERT_TRUE(credential_->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk());
+
+ vector<uint8_t> eKeyPair;
+ ASSERT_TRUE(credential_->createEphemeralKeyPair(&eKeyPair).isOk());
+ optional<vector<uint8_t>> ePublicKey = support::ecKeyPairGetPublicKey(eKeyPair);
+ sessionTranscript_ = calcSessionTranscript(ePublicKey.value());
+
+ Status status = credential_->createAuthChallenge(&authChallenge_);
+ EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+}
+
+void UserAuthTests::retrieveData(HardwareAuthToken authToken, VerificationToken verificationToken,
+ bool expectSuccess, bool useSessionTranscript) {
+ canGetUserAuthPerSession_ = false;
+ canGetUserAuthTimeout_ = false;
+ canGetAccessibleByAll_ = false;
+ canGetAccessibleByNone_ = false;
+
+ vector<uint8_t> itemsRequestBytes;
+ vector<uint8_t> sessionTranscriptBytes;
+ if (useSessionTranscript) {
+ sessionTranscriptBytes = sessionTranscript_.encode();
+
+ itemsRequestBytes =
+ cppbor::Map("nameSpaces",
+ cppbor::Map().add("ns", cppbor::Map()
+ .add("UserAuth Per Session", false)
+ .add("UserAuth Timeout", false)
+ .add("Accessible by All", false)
+ .add("Accessible by None", false)))
+ .encode();
+ vector<uint8_t> dataToSign = cppbor::Array()
+ .add("ReaderAuthentication")
+ .add(sessionTranscript_.clone())
+ .add(cppbor::Semantic(24, itemsRequestBytes))
+ .encode();
+ }
+
+ // Generate the key that will be used to sign AuthenticatedData.
+ vector<uint8_t> signingKeyBlob;
+ Certificate signingKeyCertificate;
+ ASSERT_TRUE(
+ credential_->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
+
+ RequestNamespace rns;
+ rns.namespaceName = "ns";
+ rns.items.push_back(buildRequestDataItem("UserAuth Per Session", 1, {0}));
+ rns.items.push_back(buildRequestDataItem("UserAuth Timeout", 1, {1}));
+ rns.items.push_back(buildRequestDataItem("Accessible by All", 1, {2}));
+ rns.items.push_back(buildRequestDataItem("Accessible by None", 1, {}));
+ // OK to fail, not available in v1 HAL
+ credential_->setRequestedNamespaces({rns}).isOk();
+
+ // OK to fail, not available in v1 HAL
+ credential_->setVerificationToken(verificationToken);
+
+ Status status = credential_->startRetrieval({sacp0_, sacp1_, sacp2_}, authToken,
+ itemsRequestBytes, signingKeyBlob,
+ sessionTranscriptBytes, {} /* readerSignature */,
+ {4 /* numDataElementsPerNamespace */});
+ if (expectSuccess) {
+ ASSERT_TRUE(status.isOk());
+ } else {
+ ASSERT_FALSE(status.isOk());
+ return;
+ }
+
+ vector<uint8_t> decrypted;
+
+ status = credential_->startRetrieveEntryValue("ns", "UserAuth Per Session", 1, {0});
+ if (status.isOk()) {
+ canGetUserAuthPerSession_ = true;
+ ASSERT_TRUE(
+ credential_->retrieveEntryValue(encContentUserAuthPerSession_, &decrypted).isOk());
+ }
+
+ status = credential_->startRetrieveEntryValue("ns", "UserAuth Timeout", 1, {1});
+ if (status.isOk()) {
+ canGetUserAuthTimeout_ = true;
+ ASSERT_TRUE(credential_->retrieveEntryValue(encContentUserAuthTimeout_, &decrypted).isOk());
+ }
+
+ status = credential_->startRetrieveEntryValue("ns", "Accessible by All", 1, {2});
+ if (status.isOk()) {
+ canGetAccessibleByAll_ = true;
+ ASSERT_TRUE(credential_->retrieveEntryValue(encContentAccessibleByAll_, &decrypted).isOk());
+ }
+
+ status = credential_->startRetrieveEntryValue("ns", "Accessible by None", 1, {});
+ if (status.isOk()) {
+ canGetAccessibleByNone_ = true;
+ ASSERT_TRUE(
+ credential_->retrieveEntryValue(encContentAccessibleByNone_, &decrypted).isOk());
+ }
+
+ vector<uint8_t> mac;
+ vector<uint8_t> deviceNameSpaces;
+ ASSERT_TRUE(credential_->finishRetrieval(&mac, &deviceNameSpaces).isOk());
+}
+
+pair<HardwareAuthToken, VerificationToken> UserAuthTests::mintTokens(
+ uint64_t challengeForAuthToken, int64_t ageOfAuthTokenMilliSeconds) {
+ HardwareAuthToken authToken;
+ VerificationToken verificationToken;
+
+ uint64_t epochMilliseconds = 1000ULL * 1000ULL * 1000ULL * 1000ULL;
+
+ authToken.challenge = challengeForAuthToken;
+ authToken.userId = 65;
+ authToken.authenticatorId = 0;
+ authToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE;
+ authToken.timestamp.milliSeconds = epochMilliseconds - ageOfAuthTokenMilliSeconds;
+ authToken.mac.clear();
+ verificationToken.challenge = authChallenge_;
+ verificationToken.timestamp.milliSeconds = epochMilliseconds;
+ verificationToken.securityLevel =
+ ::android::hardware::keymaster::SecurityLevel::TRUSTED_ENVIRONMENT;
+ verificationToken.mac.clear();
+ return make_pair(authToken, verificationToken);
+}
+
+TEST_P(UserAuthTests, GoodChallenge) {
+ provisionData();
+ setupRetrieveData();
+ auto [authToken, verificationToken] = mintTokens(authChallenge_, // challengeForAuthToken
+ 0); // ageOfAuthTokenMilliSeconds
+ retrieveData(authToken, verificationToken, true /* expectSuccess */,
+ true /* useSessionTranscript */);
+ EXPECT_TRUE(canGetUserAuthPerSession_);
+ EXPECT_TRUE(canGetUserAuthTimeout_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+TEST_P(UserAuthTests, OtherChallenge) {
+ provisionData();
+ setupRetrieveData();
+ uint64_t otherChallenge = authChallenge_ ^ 0x12345678;
+ auto [authToken, verificationToken] = mintTokens(otherChallenge, // challengeForAuthToken
+ 0); // ageOfAuthTokenMilliSeconds
+ retrieveData(authToken, verificationToken, true /* expectSuccess */,
+ true /* useSessionTranscript */);
+ EXPECT_FALSE(canGetUserAuthPerSession_);
+ EXPECT_TRUE(canGetUserAuthTimeout_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+TEST_P(UserAuthTests, NoChallenge) {
+ provisionData();
+ setupRetrieveData();
+ auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
+ 0); // ageOfAuthTokenMilliSeconds
+ retrieveData(authToken, verificationToken, true /* expectSuccess */,
+ true /* useSessionTranscript */);
+ EXPECT_FALSE(canGetUserAuthPerSession_);
+ EXPECT_TRUE(canGetUserAuthTimeout_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+TEST_P(UserAuthTests, AuthTokenAgeZero) {
+ provisionData();
+ setupRetrieveData();
+ auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
+ 0); // ageOfAuthTokenMilliSeconds
+ retrieveData(authToken, verificationToken, true /* expectSuccess */,
+ true /* useSessionTranscript */);
+ EXPECT_FALSE(canGetUserAuthPerSession_);
+ EXPECT_TRUE(canGetUserAuthTimeout_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+TEST_P(UserAuthTests, AuthTokenFromTheFuture) {
+ provisionData();
+ setupRetrieveData();
+ auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
+ -1 * 1000); // ageOfAuthTokenMilliSeconds
+ retrieveData(authToken, verificationToken, true /* expectSuccess */,
+ true /* useSessionTranscript */);
+ EXPECT_FALSE(canGetUserAuthPerSession_);
+ EXPECT_FALSE(canGetUserAuthTimeout_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+TEST_P(UserAuthTests, AuthTokenInsideTimeout) {
+ provisionData();
+ setupRetrieveData();
+ auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
+ 30 * 1000); // ageOfAuthTokenMilliSeconds
+ retrieveData(authToken, verificationToken, true /* expectSuccess */,
+ true /* useSessionTranscript */);
+ EXPECT_FALSE(canGetUserAuthPerSession_);
+ EXPECT_TRUE(canGetUserAuthTimeout_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+TEST_P(UserAuthTests, AuthTokenOutsideTimeout) {
+ provisionData();
+ setupRetrieveData();
+ auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
+ 61 * 1000); // ageOfAuthTokenMilliSeconds
+ retrieveData(authToken, verificationToken, true /* expectSuccess */,
+ true /* useSessionTranscript */);
+ EXPECT_FALSE(canGetUserAuthPerSession_);
+ EXPECT_FALSE(canGetUserAuthTimeout_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+// The API works even when there's no SessionTranscript / itemsRequest.
+// Verify that.
+TEST_P(UserAuthTests, NoSessionTranscript) {
+ provisionData();
+ setupRetrieveData();
+ auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
+ 1 * 1000); // ageOfAuthTokenMilliSeconds
+ retrieveData(authToken, verificationToken, true /* expectSuccess */,
+ false /* useSessionTranscript */);
+ EXPECT_FALSE(canGetUserAuthPerSession_);
+ EXPECT_TRUE(canGetUserAuthTimeout_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+// This test verifies that it's possible to do multiple requests as long
+// as the sessionTranscript doesn't change.
+//
+TEST_P(UserAuthTests, MultipleRequestsSameSessionTranscript) {
+ provisionData();
+ setupRetrieveData();
+
+ // First we try with a stale authToken
+ //
+ auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
+ 61 * 1000); // ageOfAuthTokenMilliSeconds
+ retrieveData(authToken, verificationToken, true /* expectSuccess */,
+ true /* useSessionTranscript */);
+ EXPECT_FALSE(canGetUserAuthPerSession_);
+ EXPECT_FALSE(canGetUserAuthTimeout_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+
+ // Then we get a new authToken and try again.
+ tie(authToken, verificationToken) = mintTokens(0, // challengeForAuthToken
+ 5 * 1000); // ageOfAuthTokenMilliSeconds
+ retrieveData(authToken, verificationToken, true /* expectSuccess */,
+ true /* useSessionTranscript */);
+ EXPECT_FALSE(canGetUserAuthPerSession_);
+ EXPECT_TRUE(canGetUserAuthTimeout_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+}
+
+// Like MultipleRequestsSameSessionTranscript but we change the sessionTranscript
+// between the two calls. This test verifies that change is detected and the
+// second request fails.
+//
+TEST_P(UserAuthTests, MultipleRequestsSessionTranscriptChanges) {
+ provisionData();
+ setupRetrieveData();
+
+ // First we try with a stale authToken
+ //
+ auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken
+ 61 * 1000); // ageOfAuthTokenMilliSeconds
+ retrieveData(authToken, verificationToken, true /* expectSuccess */,
+ true /* useSessionTranscript */);
+ EXPECT_FALSE(canGetUserAuthPerSession_);
+ EXPECT_FALSE(canGetUserAuthTimeout_);
+ EXPECT_TRUE(canGetAccessibleByAll_);
+ EXPECT_FALSE(canGetAccessibleByNone_);
+
+ // Then we get a new authToken and try again.
+ tie(authToken, verificationToken) = mintTokens(0, // challengeForAuthToken
+ 5 * 1000); // ageOfAuthTokenMilliSeconds
+
+ // Change sessionTranscript...
+ optional<vector<uint8_t>> eKeyPairNew = support::createEcKeyPair();
+ optional<vector<uint8_t>> ePublicKeyNew = support::ecKeyPairGetPublicKey(eKeyPairNew.value());
+ sessionTranscript_ = calcSessionTranscript(ePublicKeyNew.value());
+
+ // ... and expect failure.
+ retrieveData(authToken, verificationToken, false /* expectSuccess */,
+ true /* useSessionTranscript */);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Identity, UserAuthTests,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+ android::PrintInstanceNameToString);
+
+} // namespace android::hardware::identity
diff --git a/identity/aidl/vts/VtsAttestationTests.cpp b/identity/aidl/vts/VtsAttestationTests.cpp
index 00b5893..c7cdfc7 100644
--- a/identity/aidl/vts/VtsAttestationTests.cpp
+++ b/identity/aidl/vts/VtsAttestationTests.cpp
@@ -61,51 +61,6 @@
sp<IIdentityCredentialStore> credentialStore_;
};
-TEST_P(VtsAttestationTests, verifyAttestationWithEmptyChallengeEmptyId) {
- Status result;
-
- HardwareInformation hwInfo;
- ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
-
- sp<IWritableIdentityCredential> writableCredential;
- ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
-
- vector<uint8_t> attestationChallenge;
- vector<Certificate> attestationCertificate;
- vector<uint8_t> attestationApplicationId = {};
- result = writableCredential->getAttestationCertificate(
- attestationApplicationId, attestationChallenge, &attestationCertificate);
-
- ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
- << endl;
-
- EXPECT_TRUE(validateAttestationCertificate(attestationCertificate, attestationChallenge,
- attestationApplicationId, hwInfo));
-}
-
-TEST_P(VtsAttestationTests, verifyAttestationWithEmptyChallengeNonemptyId) {
- Status result;
-
- HardwareInformation hwInfo;
- ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
-
- sp<IWritableIdentityCredential> writableCredential;
- ASSERT_TRUE(setupWritableCredential(writableCredential, credentialStore_));
-
- vector<uint8_t> attestationChallenge;
- vector<Certificate> attestationCertificate;
- string applicationId = "Attestation Verification";
- vector<uint8_t> attestationApplicationId = {applicationId.begin(), applicationId.end()};
-
- result = writableCredential->getAttestationCertificate(
- attestationApplicationId, attestationChallenge, &attestationCertificate);
-
- ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
- << endl;
- EXPECT_TRUE(validateAttestationCertificate(attestationCertificate, attestationChallenge,
- attestationApplicationId, hwInfo));
-}
-
TEST_P(VtsAttestationTests, verifyAttestationWithNonemptyChallengeEmptyId) {
Status result;
diff --git a/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
index 464ab0c..a0c4416 100644
--- a/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
+++ b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
@@ -27,15 +27,18 @@
#include <gtest/gtest.h>
#include <future>
#include <map>
+#include <tuple>
#include "VtsIdentityTestUtils.h"
namespace android::hardware::identity {
using std::endl;
+using std::make_tuple;
using std::map;
using std::optional;
using std::string;
+using std::tuple;
using std::vector;
using ::android::sp;
@@ -66,6 +69,61 @@
ASSERT_GE(info.dataChunkSize, 256);
}
+tuple<bool, string, vector<uint8_t>, vector<uint8_t>> extractFromTestCredentialData(
+ const vector<uint8_t>& credentialData) {
+ string docType;
+ vector<uint8_t> storageKey;
+ vector<uint8_t> credentialPrivKey;
+
+ auto [item, _, message] = cppbor::parse(credentialData);
+ if (item == nullptr) {
+ return make_tuple(false, docType, storageKey, credentialPrivKey);
+ }
+
+ const cppbor::Array* arrayItem = item->asArray();
+ if (arrayItem == nullptr || arrayItem->size() != 3) {
+ return make_tuple(false, docType, storageKey, credentialPrivKey);
+ }
+
+ const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
+ const cppbor::Bool* testCredentialItem =
+ ((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
+ : nullptr);
+ const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
+ if (docTypeItem == nullptr || testCredentialItem == nullptr ||
+ encryptedCredentialKeysItem == nullptr) {
+ return make_tuple(false, docType, storageKey, credentialPrivKey);
+ }
+
+ docType = docTypeItem->value();
+
+ vector<uint8_t> hardwareBoundKey = support::getTestHardwareBoundKey();
+ const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
+ const vector<uint8_t> docTypeVec(docType.begin(), docType.end());
+ optional<vector<uint8_t>> decryptedCredentialKeys =
+ support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
+ if (!decryptedCredentialKeys) {
+ return make_tuple(false, docType, storageKey, credentialPrivKey);
+ }
+
+ auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
+ if (dckItem == nullptr) {
+ return make_tuple(false, docType, storageKey, credentialPrivKey);
+ }
+ const cppbor::Array* dckArrayItem = dckItem->asArray();
+ if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
+ return make_tuple(false, docType, storageKey, credentialPrivKey);
+ }
+ const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
+ const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
+ if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
+ return make_tuple(false, docType, storageKey, credentialPrivKey);
+ }
+ storageKey = storageKeyItem->value();
+ credentialPrivKey = credentialPrivKeyItem->value();
+ return make_tuple(true, docType, storageKey, credentialPrivKey);
+}
+
TEST_P(IdentityAidl, createAndRetrieveCredential) {
// First, generate a key-pair for the reader since its public key will be
// part of the request data.
@@ -155,6 +213,7 @@
writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature)
.isOk());
+ // Validate the proofOfProvisioning which was returned
optional<vector<uint8_t>> proofOfProvisioning =
support::coseSignGetPayload(proofOfProvisioningSignature);
ASSERT_TRUE(proofOfProvisioning);
@@ -215,6 +274,22 @@
credentialPubKey.value()));
writableCredential = nullptr;
+ // Extract doctype, storage key, and credentialPrivKey from credentialData... this works
+ // only because we asked for a test-credential meaning that the HBK is all zeroes.
+ auto [exSuccess, exDocType, exStorageKey, exCredentialPrivKey] =
+ extractFromTestCredentialData(credentialData);
+ ASSERT_TRUE(exSuccess);
+ ASSERT_EQ(exDocType, "org.iso.18013-5.2019.mdl");
+ // ... check that the public key derived from the private key matches what was
+ // in the certificate.
+ optional<vector<uint8_t>> exCredentialKeyPair =
+ support::ecPrivateKeyToKeyPair(exCredentialPrivKey);
+ ASSERT_TRUE(exCredentialKeyPair);
+ optional<vector<uint8_t>> exCredentialPubKey =
+ support::ecKeyPairGetPublicKey(exCredentialKeyPair.value());
+ ASSERT_TRUE(exCredentialPubKey);
+ ASSERT_EQ(exCredentialPubKey.value(), credentialPubKey.value());
+
// Now that the credential has been provisioned, read it back and check the
// correct data is returned.
sp<IIdentityCredential> credential;
@@ -287,6 +362,24 @@
vector<uint8_t> signingKeyBlob;
Certificate signingKeyCertificate;
ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
+ optional<vector<uint8_t>> signingPubKey =
+ support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate);
+ EXPECT_TRUE(signingPubKey);
+
+ // Since we're using a test-credential we know storageKey meaning we can get the
+ // private key. Do this, derive the public key from it, and check this matches what
+ // is in the certificate...
+ const vector<uint8_t> exDocTypeVec(exDocType.begin(), exDocType.end());
+ optional<vector<uint8_t>> exSigningPrivKey =
+ support::decryptAes128Gcm(exStorageKey, signingKeyBlob, exDocTypeVec);
+ ASSERT_TRUE(exSigningPrivKey);
+ optional<vector<uint8_t>> exSigningKeyPair =
+ support::ecPrivateKeyToKeyPair(exSigningPrivKey.value());
+ ASSERT_TRUE(exSigningKeyPair);
+ optional<vector<uint8_t>> exSigningPubKey =
+ support::ecKeyPairGetPublicKey(exSigningKeyPair.value());
+ ASSERT_TRUE(exSigningPubKey);
+ ASSERT_EQ(exSigningPubKey.value(), signingPubKey.value());
vector<RequestNamespace> requestedNamespaces = test_utils::buildRequestNamespaces(testEntries);
// OK to fail, not available in v1 HAL
@@ -316,6 +409,9 @@
content.insert(content.end(), chunk.begin(), chunk.end());
}
EXPECT_EQ(content, entry.valueCbor);
+
+ // TODO: also use |exStorageKey| to decrypt data and check it's the same as whatt
+ // the HAL returns...
}
vector<uint8_t> mac;
@@ -346,15 +442,12 @@
deviceAuthentication.add(docType);
deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
vector<uint8_t> encodedDeviceAuthentication = deviceAuthentication.encode();
- optional<vector<uint8_t>> signingPublicKey =
- support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate);
- EXPECT_TRUE(signingPublicKey);
// Derive the key used for MACing.
optional<vector<uint8_t>> readerEphemeralPrivateKey =
support::ecKeyPairGetPrivateKey(readerEphemeralKeyPair.value());
optional<vector<uint8_t>> sharedSecret =
- support::ecdh(signingPublicKey.value(), readerEphemeralPrivateKey.value());
+ support::ecdh(signingPubKey.value(), readerEphemeralPrivateKey.value());
ASSERT_TRUE(sharedSecret);
vector<uint8_t> salt = {0x00};
vector<uint8_t> info = {};
diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
index 8b0c050..b572b0f 100644
--- a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
+++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
@@ -69,11 +69,10 @@
result = writableCredential->getAttestationCertificate(
attestationApplicationId, attestationChallenge, &attestationCertificate);
- EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
- << endl;
-
- EXPECT_TRUE(test_utils::validateAttestationCertificate(
- attestationCertificate, attestationChallenge, attestationApplicationId, hwInfo));
+ EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+ EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ EXPECT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
}
TEST_P(IdentityCredentialTests, verifyAttestationSuccessWithChallenge) {
@@ -130,6 +129,7 @@
// First call should go through
const vector<int32_t> entryCounts = {2, 4};
+ writableCredential->setExpectedProofOfProvisioningSize(123456);
result = writableCredential->startPersonalization(5, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
@@ -151,18 +151,8 @@
// Verify minimal number of profile count and entry count
const vector<int32_t> entryCounts = {1, 1};
- writableCredential->startPersonalization(1, entryCounts);
- EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
- << endl;
-}
-
-TEST_P(IdentityCredentialTests, verifyStartPersonalizationZero) {
- Status result;
- sp<IWritableIdentityCredential> writableCredential;
- ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
-
- const vector<int32_t> entryCounts = {0};
- writableCredential->startPersonalization(0, entryCounts);
+ writableCredential->setExpectedProofOfProvisioningSize(123456);
+ result = writableCredential->startPersonalization(1, entryCounts);
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
}
@@ -174,7 +164,8 @@
// Verify minimal number of profile count and entry count
const vector<int32_t> entryCounts = {1};
- writableCredential->startPersonalization(1, entryCounts);
+ writableCredential->setExpectedProofOfProvisioningSize(123456);
+ result = writableCredential->startPersonalization(1, entryCounts);
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
}
@@ -186,7 +177,8 @@
// Verify set a large number of profile count and entry count is ok
const vector<int32_t> entryCounts = {3000};
- writableCredential->startPersonalization(3500, entryCounts);
+ writableCredential->setExpectedProofOfProvisioningSize(123456);
+ result = writableCredential->startPersonalization(25, entryCounts);
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
}
@@ -198,7 +190,8 @@
// Enter mismatched entry and profile numbers
const vector<int32_t> entryCounts = {5, 6};
- writableCredential->startPersonalization(5, entryCounts);
+ writableCredential->setExpectedProofOfProvisioningSize(123456);
+ result = writableCredential->startPersonalization(5, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
@@ -234,7 +227,8 @@
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
const vector<int32_t> entryCounts = {3, 6};
- writableCredential->startPersonalization(3, entryCounts);
+ writableCredential->setExpectedProofOfProvisioningSize(123456);
+ result = writableCredential->startPersonalization(3, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
@@ -251,9 +245,10 @@
SecureAccessControlProfile profile;
Certificate cert;
cert.encodedCertificate = testProfile.readerCertificate;
+ int64_t secureUserId = testProfile.userAuthenticationRequired ? 66 : 0;
result = writableCredential->addAccessControlProfile(
testProfile.id, cert, testProfile.userAuthenticationRequired,
- testProfile.timeoutMillis, 0, &profile);
+ testProfile.timeoutMillis, secureUserId, &profile);
if (expectOk) {
expectOk = false;
@@ -554,7 +549,7 @@
;
// OK to fail, not available in v1 HAL
writableCredential->setExpectedProofOfProvisioningSize(expectedPoPSize);
- writableCredential->startPersonalization(3, entryCounts);
+ result = writableCredential->startPersonalization(3, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
@@ -608,7 +603,8 @@
// before "Image" and 2 after image, which is not correct. All of same name
// space should occur together. Let's see if this fails.
const vector<int32_t> entryCounts = {2u, 1u, 2u};
- writableCredential->startPersonalization(3, entryCounts);
+ writableCredential->setExpectedProofOfProvisioningSize(123456);
+ result = writableCredential->startPersonalization(3, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
@@ -674,6 +670,7 @@
ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
const vector<int32_t> entryCounts = {1};
+ writableCredential->setExpectedProofOfProvisioningSize(123456);
Status result = writableCredential->startPersonalization(1, entryCounts);
ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
<< endl;
diff --git a/identity/aidl/vts/VtsIdentityTestUtils.cpp b/identity/aidl/vts/VtsIdentityTestUtils.cpp
index aaebcbe..b6ed80f 100644
--- a/identity/aidl/vts/VtsIdentityTestUtils.cpp
+++ b/identity/aidl/vts/VtsIdentityTestUtils.cpp
@@ -96,9 +96,10 @@
SecureAccessControlProfile profile;
Certificate cert;
cert.encodedCertificate = testProfile.readerCertificate;
+ int64_t secureUserId = testProfile.userAuthenticationRequired ? 66 : 0;
result = writableCredential->addAccessControlProfile(
testProfile.id, cert, testProfile.userAuthenticationRequired,
- testProfile.timeoutMillis, 0, &profile);
+ testProfile.timeoutMillis, secureUserId, &profile);
// Don't use assert so all errors can be outputed. Then return
// instead of exit even on errors so caller can decide.
diff --git a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
index 507e914..0f27a72 100644
--- a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
+++ b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
@@ -134,6 +134,11 @@
//
optional<vector<uint8_t>> ecKeyPairGetPrivateKey(const vector<uint8_t>& keyPair);
+// Creates a PKCS#8 encoded key-pair from a private key (which must be uncompressed,
+// e.g. 32 bytes). The public key is derived from the given private key..
+//
+optional<vector<uint8_t>> ecPrivateKeyToKeyPair(const vector<uint8_t>& privateKey);
+
// For an EC key |keyPair| encoded in PKCS#8 format, creates a PKCS#12 structure
// with the key-pair (not using a password to encrypt the data). The public key
// in the created structure is included as a certificate, using the given fields
diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp
index dc49ddc..e9d5d6c 100644
--- a/identity/support/src/IdentityCredentialSupport.cpp
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -1047,6 +1047,42 @@
return privateKey;
}
+optional<vector<uint8_t>> ecPrivateKeyToKeyPair(const vector<uint8_t>& privateKey) {
+ auto bn = BIGNUM_Ptr(BN_bin2bn(privateKey.data(), privateKey.size(), nullptr));
+ if (bn.get() == nullptr) {
+ LOG(ERROR) << "Error creating BIGNUM";
+ return {};
+ }
+
+ auto ecKey = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+ if (EC_KEY_set_private_key(ecKey.get(), bn.get()) != 1) {
+ LOG(ERROR) << "Error setting private key from BIGNUM";
+ return {};
+ }
+
+ auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+ if (pkey.get() == nullptr) {
+ LOG(ERROR) << "Memory allocation failed";
+ return {};
+ }
+
+ if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) {
+ LOG(ERROR) << "Error getting private key";
+ return {};
+ }
+
+ int size = i2d_PrivateKey(pkey.get(), nullptr);
+ if (size == 0) {
+ LOG(ERROR) << "Error generating public key encoding";
+ return {};
+ }
+ vector<uint8_t> keyPair;
+ keyPair.resize(size);
+ unsigned char* p = keyPair.data();
+ i2d_PrivateKey(pkey.get(), &p);
+ return keyPair;
+}
+
optional<vector<uint8_t>> ecKeyPairGetPkcs12(const vector<uint8_t>& keyPair, const string& name,
const string& serialDecimal, const string& issuer,
const string& subject, time_t validityNotBefore,
diff --git a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
index 6cbe4da..aa2de2a 100644
--- a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
+++ b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
@@ -438,10 +438,10 @@
EXPECT_TRUE(device_locked);
}
- // Check that the expected result from VBMeta matches the build type. Only a user build
- // should have AVB reporting the device is locked.
- EXPECT_NE(property_get("ro.build.type", property_value, ""), 0);
- if (!strcmp(property_value, "user")) {
+ // Check that the device is locked if not debuggable, e.g., user build
+ // images in CTS. For VTS, debuggable images are used to allow adb root
+ // and the device is unlocked.
+ if (!property_get_bool("ro.debuggable", false)) {
EXPECT_TRUE(device_locked);
} else {
EXPECT_FALSE(device_locked);
diff --git a/radio/1.1/vts/functional/radio_hidl_hal_api.cpp b/radio/1.1/vts/functional/radio_hidl_hal_api.cpp
index 02dcbab..08121fd 100644
--- a/radio/1.1/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.1/vts/functional/radio_hidl_hal_api.cpp
@@ -49,7 +49,6 @@
}
EXPECT_EQ(CardState::ABSENT, cardStatus.cardState);
}
-#endif
/* Test setSimCardPower power up */
serial = GetRandomSerialNumber();
@@ -60,6 +59,7 @@
ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_1->rspInfo.error,
{RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED,
RadioError::INVALID_ARGUMENTS, RadioError::RADIO_NOT_AVAILABLE}));
+#endif
/**
* If the sim card status for the testing environment is PRESENT,
diff --git a/tv/tuner/1.0/default/Demux.cpp b/tv/tuner/1.0/default/Demux.cpp
index b74f6ec..67eff1b 100644
--- a/tv/tuner/1.0/default/Demux.cpp
+++ b/tv/tuner/1.0/default/Demux.cpp
@@ -48,8 +48,6 @@
return Result::INVALID_STATE;
}
- mFrontendSourceFile = mFrontend->getSourceFile();
-
mTunerService->setFrontendAsDemuxSource(frontendId, mDemuxId);
return Result::SUCCESS;
@@ -62,8 +60,6 @@
uint32_t filterId;
filterId = ++mLastUsedFilterId;
- mUsedFilterIds.insert(filterId);
-
if (cb == nullptr) {
ALOGW("[Demux] callback can't be null");
_hidl_cb(Result::INVALID_ARGUMENT, new Filter());
@@ -82,8 +78,13 @@
mPcrFilterIds.insert(filterId);
}
bool result = true;
- if (mDvr != nullptr && mDvr->getType() == DvrType::PLAYBACK) {
- result = mDvr->addPlaybackFilter(filter);
+ if (!filter->isRecordFilter()) {
+ // Only save non-record filters for now. Record filters are saved when the
+ // IDvr.attacheFilter is called.
+ mPlaybackFilterIds.insert(filterId);
+ if (mDvrPlayback != nullptr) {
+ result = mDvrPlayback->addPlaybackFilter(filterId, filter);
+ }
}
_hidl_cb(result ? Result::SUCCESS : Result::INVALID_ARGUMENT, filter);
@@ -93,9 +94,9 @@
Return<void> Demux::openTimeFilter(openTimeFilter_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
- sp<TimeFilter> timeFilter = new TimeFilter(this);
+ mTimeFilter = new TimeFilter(this);
- _hidl_cb(Result::SUCCESS, timeFilter);
+ _hidl_cb(Result::SUCCESS, mTimeFilter);
return Void();
}
@@ -154,7 +155,13 @@
Return<Result> Demux::close() {
ALOGV("%s", __FUNCTION__);
- mUsedFilterIds.clear();
+ set<uint32_t>::iterator it;
+ for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
+ mDvrPlayback->removePlaybackFilter(*it);
+ }
+ mPlaybackFilterIds.clear();
+ mRecordFilterIds.clear();
+ mFilters.clear();
mLastUsedFilterId = -1;
return Result::SUCCESS;
@@ -170,15 +177,38 @@
return Void();
}
- mDvr = new Dvr(type, bufferSize, cb, this);
+ set<uint32_t>::iterator it;
+ switch (type) {
+ case DvrType::PLAYBACK:
+ mDvrPlayback = new Dvr(type, bufferSize, cb, this);
+ if (!mDvrPlayback->createDvrMQ()) {
+ _hidl_cb(Result::UNKNOWN_ERROR, mDvrPlayback);
+ return Void();
+ }
- if (!mDvr->createDvrMQ()) {
- _hidl_cb(Result::UNKNOWN_ERROR, mDvr);
- return Void();
+ for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
+ if (!mDvrPlayback->addPlaybackFilter(*it, mFilters[*it])) {
+ ALOGE("[Demux] Can't get filter info for DVR playback");
+ _hidl_cb(Result::UNKNOWN_ERROR, mDvrPlayback);
+ return Void();
+ }
+ }
+
+ _hidl_cb(Result::SUCCESS, mDvrPlayback);
+ return Void();
+ case DvrType::RECORD:
+ mDvrRecord = new Dvr(type, bufferSize, cb, this);
+ if (!mDvrRecord->createDvrMQ()) {
+ _hidl_cb(Result::UNKNOWN_ERROR, mDvrRecord);
+ return Void();
+ }
+
+ _hidl_cb(Result::SUCCESS, mDvrRecord);
+ return Void();
+ default:
+ _hidl_cb(Result::INVALID_ARGUMENT, nullptr);
+ return Void();
}
-
- _hidl_cb(Result::SUCCESS, mDvr);
- return Void();
}
Return<Result> Demux::connectCiCam(uint32_t ciCamId) {
@@ -198,8 +228,10 @@
Result Demux::removeFilter(uint32_t filterId) {
ALOGV("%s", __FUNCTION__);
- // resetFilterRecords(filterId);
- mUsedFilterIds.erase(filterId);
+ if (mDvrPlayback != nullptr) {
+ mDvrPlayback->removePlaybackFilter(filterId);
+ }
+ mPlaybackFilterIds.erase(filterId);
mRecordFilterIds.erase(filterId);
mFilters.erase(filterId);
@@ -212,7 +244,7 @@
if (DEBUG_DEMUX) {
ALOGW("[Demux] start ts filter pid: %d", pid);
}
- for (it = mUsedFilterIds.begin(); it != mUsedFilterIds.end(); it++) {
+ for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
if (pid == mFilters[*it]->getTpid()) {
mFilters[*it]->updateFilterOutput(data);
}
@@ -233,7 +265,7 @@
set<uint32_t>::iterator it;
// Handle the output data per filter type
- for (it = mUsedFilterIds.begin(); it != mUsedFilterIds.end(); it++) {
+ for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
if (mFilters[*it]->startFilterHandler() != Result::SUCCESS) {
return false;
}
@@ -280,58 +312,27 @@
void Demux::frontendInputThreadLoop() {
std::lock_guard<std::mutex> lock(mFrontendInputThreadLock);
mFrontendInputThreadRunning = true;
- mKeepFetchingDataFromFrontend = true;
-
- // open the stream and get its length
- std::ifstream inputData(mFrontendSourceFile, std::ifstream::binary);
- // TODO take the packet size from the frontend setting
- int packetSize = 188;
- int writePacketAmount = 6;
- char* buffer = new char[packetSize];
- ALOGW("[Demux] Frontend input thread loop start %s", mFrontendSourceFile.c_str());
- if (!inputData.is_open()) {
- mFrontendInputThreadRunning = false;
- ALOGW("[Demux] Error %s", strerror(errno));
- }
while (mFrontendInputThreadRunning) {
- // move the stream pointer for packet size * 6 every read until the end
- while (mKeepFetchingDataFromFrontend) {
- for (int i = 0; i < writePacketAmount; i++) {
- inputData.read(buffer, packetSize);
- if (!inputData) {
- mKeepFetchingDataFromFrontend = false;
- mFrontendInputThreadRunning = false;
- break;
- }
- // filter and dispatch filter output
- vector<uint8_t> byteBuffer;
- byteBuffer.resize(packetSize);
- for (int index = 0; index < byteBuffer.size(); index++) {
- byteBuffer[index] = static_cast<uint8_t>(buffer[index]);
- }
- if (mIsRecording) {
- // Feed the data into the Dvr recording input
- sendFrontendInputToRecord(byteBuffer);
- } else {
- // Feed the data into the broadcast demux filter
- startBroadcastTsFilter(byteBuffer);
- }
- }
- if (mIsRecording) {
- // Dispatch the data into the broadcasting filters.
- startRecordFilterDispatcher();
- } else {
- // Dispatch the data into the broadcasting filters.
- startBroadcastFilterDispatcher();
- }
- usleep(100);
+ uint32_t efState = 0;
+ status_t status = mDvrPlayback->getDvrEventFlag()->wait(
+ static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY), &efState, WAIT_TIMEOUT,
+ true /* retry on spurious wake */);
+ if (status != OK) {
+ ALOGD("[Demux] wait for data ready on the playback FMQ");
+ continue;
+ }
+ // Our current implementation filter the data and write it into the filter FMQ immediately
+ // after the DATA_READY from the VTS/framework
+ if (!mDvrPlayback->readPlaybackFMQ(true /*isVirtualFrontend*/, mIsRecording) ||
+ !mDvrPlayback->startFilterDispatcher(true /*isVirtualFrontend*/, mIsRecording)) {
+ ALOGE("[Demux] playback data failed to be filtered. Ending thread");
+ break;
}
}
+ mFrontendInputThreadRunning = false;
ALOGW("[Demux] Frontend Input thread end.");
- delete[] buffer;
- inputData.close();
}
void Demux::stopFrontendInput() {
@@ -346,18 +347,19 @@
}
bool Demux::attachRecordFilter(int filterId) {
- if (mFilters[filterId] == nullptr || mDvr == nullptr) {
+ if (mFilters[filterId] == nullptr || mDvrRecord == nullptr ||
+ !mFilters[filterId]->isRecordFilter()) {
return false;
}
mRecordFilterIds.insert(filterId);
- mFilters[filterId]->attachFilterToRecord(mDvr);
+ mFilters[filterId]->attachFilterToRecord(mDvrRecord);
return true;
}
bool Demux::detachRecordFilter(int filterId) {
- if (mFilters[filterId] == nullptr || mDvr == nullptr) {
+ if (mFilters[filterId] == nullptr || mDvrRecord == nullptr) {
return false;
}
diff --git a/tv/tuner/1.0/default/Demux.h b/tv/tuner/1.0/default/Demux.h
index 6c46b0d..7f282b2 100644
--- a/tv/tuner/1.0/default/Demux.h
+++ b/tv/tuner/1.0/default/Demux.h
@@ -91,13 +91,23 @@
void setIsRecording(bool isRecording);
void startFrontendInputLoop();
+ /**
+ * A dispatcher to read and dispatch input data to all the started filters.
+ * Each filter handler handles the data filtering/output writing/filterEvent updating.
+ * Note that recording filters are not included.
+ */
+ bool startBroadcastFilterDispatcher();
+ void startBroadcastTsFilter(vector<uint8_t> data);
+
+ void sendFrontendInputToRecord(vector<uint8_t> data);
+ bool startRecordFilterDispatcher();
+
private:
// Tuner service
sp<Tuner> mTunerService;
// Frontend source
sp<Frontend> mFrontend;
- string mFrontendSourceFile;
// A struct that passes the arguments to a newly created filter thread
struct ThreadArgs {
@@ -117,16 +127,6 @@
*/
void deleteEventFlag();
bool readDataFromMQ();
- /**
- * A dispatcher to read and dispatch input data to all the started filters.
- * Each filter handler handles the data filtering/output writing/filterEvent updating.
- * Note that recording filters are not included.
- */
- bool startBroadcastFilterDispatcher();
- void startBroadcastTsFilter(vector<uint8_t> data);
-
- void sendFrontendInputToRecord(vector<uint8_t> data);
- bool startRecordFilterDispatcher();
uint32_t mDemuxId;
uint32_t mCiCamId;
@@ -137,25 +137,31 @@
*/
uint32_t mLastUsedFilterId = -1;
/**
- * Record all the used filter Ids.
+ * Record all the used playback filter Ids.
* Any removed filter id should be removed from this set.
*/
- set<uint32_t> mUsedFilterIds;
+ set<uint32_t> mPlaybackFilterIds;
/**
* Record all the attached record filter Ids.
* Any removed filter id should be removed from this set.
*/
set<uint32_t> mRecordFilterIds;
/**
- * A list of created FilterMQ ptrs.
+ * A list of created Filter sp.
* The array number is the filter ID.
*/
std::map<uint32_t, sp<Filter>> mFilters;
/**
+ * Local reference to the opened Timer Filter instance.
+ */
+ sp<TimeFilter> mTimeFilter;
+
+ /**
* Local reference to the opened DVR object.
*/
- sp<Dvr> mDvr;
+ sp<Dvr> mDvrPlayback;
+ sp<Dvr> mDvrRecord;
// Thread handlers
pthread_t mFrontendInputThread;
diff --git a/tv/tuner/1.0/default/Dvr.cpp b/tv/tuner/1.0/default/Dvr.cpp
index adb2635..68e175c 100644
--- a/tv/tuner/1.0/default/Dvr.cpp
+++ b/tv/tuner/1.0/default/Dvr.cpp
@@ -70,8 +70,7 @@
return status;
}
- // check if the attached filter is a record filter
- mFilters[filterId] = filter;
+ // TODO check if the attached filter is a record filter
if (!mDemux->attachRecordFilter(filterId)) {
return Result::INVALID_ARGUMENT;
}
@@ -94,19 +93,8 @@
return status;
}
- std::map<uint32_t, sp<IFilter>>::iterator it;
-
- it = mFilters.find(filterId);
- if (it != mFilters.end()) {
- mFilters.erase(filterId);
- if (!mDemux->detachRecordFilter(filterId)) {
- return Result::INVALID_ARGUMENT;
- }
- }
-
- // If all the filters are detached, record can't be started
- if (mFilters.empty()) {
- mIsRecordFilterAttached = false;
+ if (!mDemux->detachRecordFilter(filterId)) {
+ return Result::INVALID_ARGUMENT;
}
return Result::SUCCESS;
@@ -183,6 +171,10 @@
return true;
}
+EventFlag* Dvr::getDvrEventFlag() {
+ return mDvrEventFlag;
+}
+
void* Dvr::__threadLoopPlayback(void* user) {
Dvr* const self = static_cast<Dvr*>(user);
self->playbackThreadLoop();
@@ -205,8 +197,9 @@
}
// Our current implementation filter the data and write it into the filter FMQ immediately
// after the DATA_READY from the VTS/framework
- if (!readPlaybackFMQ() || !startFilterDispatcher()) {
- ALOGD("[Dvr] playback data failed to be filtered. Ending thread");
+ if (!readPlaybackFMQ(false /*isVirtualFrontend*/, false /*isRecording*/) ||
+ !startFilterDispatcher(false /*isVirtualFrontend*/, false /*isRecording*/)) {
+ ALOGE("[Dvr] playback data failed to be filtered. Ending thread");
break;
}
@@ -245,7 +238,7 @@
return mPlaybackStatus;
}
-bool Dvr::readPlaybackFMQ() {
+bool Dvr::readPlaybackFMQ(bool isVirtualFrontend, bool isRecording) {
// Read playback data from the input FMQ
int size = mDvrMQ->availableToRead();
int playbackPacketSize = mDvrSettings.playback().packetSize;
@@ -256,7 +249,15 @@
if (!mDvrMQ->read(dataOutputBuffer.data(), playbackPacketSize)) {
return false;
}
- startTpidFilter(dataOutputBuffer);
+ if (isVirtualFrontend) {
+ if (isRecording) {
+ mDemux->sendFrontendInputToRecord(dataOutputBuffer);
+ } else {
+ mDemux->startBroadcastTsFilter(dataOutputBuffer);
+ }
+ } else {
+ startTpidFilter(dataOutputBuffer);
+ }
}
return true;
@@ -275,7 +276,15 @@
}
}
-bool Dvr::startFilterDispatcher() {
+bool Dvr::startFilterDispatcher(bool isVirtualFrontend, bool isRecording) {
+ if (isVirtualFrontend) {
+ if (isRecording) {
+ return mDemux->startRecordFilterDispatcher();
+ } else {
+ return mDemux->startBroadcastFilterDispatcher();
+ }
+ }
+
std::map<uint32_t, sp<IFilter>>::iterator it;
// Handle the output data per filter type
for (it = mFilters.begin(); it != mFilters.end(); it++) {
@@ -329,27 +338,15 @@
return mRecordStatus;
}
-bool Dvr::addPlaybackFilter(sp<IFilter> filter) {
- uint32_t filterId;
- Result status;
-
- filter->getId([&](Result result, uint32_t id) {
- filterId = id;
- status = result;
- });
-
- if (status != Result::SUCCESS) {
- return false;
- }
-
+bool Dvr::addPlaybackFilter(uint32_t filterId, sp<IFilter> filter) {
mFilters[filterId] = filter;
return true;
}
-DvrType Dvr::getType() {
- return mType;
+bool Dvr::removePlaybackFilter(uint32_t filterId) {
+ mFilters.erase(filterId);
+ return true;
}
-
} // namespace implementation
} // namespace V1_0
} // namespace tuner
diff --git a/tv/tuner/1.0/default/Dvr.h b/tv/tuner/1.0/default/Dvr.h
index 08afd5d..a63a256 100644
--- a/tv/tuner/1.0/default/Dvr.h
+++ b/tv/tuner/1.0/default/Dvr.h
@@ -81,8 +81,11 @@
bool createDvrMQ();
void sendBroadcastInputToDvrRecord(vector<uint8_t> byteBuffer);
bool writeRecordFMQ(const std::vector<uint8_t>& data);
- DvrType getType();
- bool addPlaybackFilter(sp<IFilter> filter);
+ bool addPlaybackFilter(uint32_t filterId, sp<IFilter> filter);
+ bool removePlaybackFilter(uint32_t filterId);
+ bool readPlaybackFMQ(bool isVirtualFrontend, bool isRecording);
+ bool startFilterDispatcher(bool isVirtualFrontend, bool isRecording);
+ EventFlag* getDvrEventFlag();
private:
// Demux service
@@ -105,9 +108,7 @@
* A dispatcher to read and dispatch input data to all the started filters.
* Each filter handler handles the data filtering/output writing/filterEvent updating.
*/
- bool readPlaybackFMQ();
void startTpidFilter(vector<uint8_t> data);
- bool startFilterDispatcher();
static void* __threadLoopPlayback(void* user);
static void* __threadLoopRecord(void* user);
void playbackThreadLoop();
@@ -123,7 +124,6 @@
// Thread handlers
pthread_t mDvrThread;
- pthread_t mBroadcastInputThread;
// FMQ status local records
PlaybackStatus mPlaybackStatus;
@@ -132,7 +132,6 @@
* If a specific filter's writing loop is still running
*/
bool mDvrThreadRunning;
- bool mBroadcastInputThreadRunning;
bool mKeepFetchingDataFromFrontend;
/**
* Lock to protect writes to the FMQs
@@ -143,7 +142,6 @@
*/
std::mutex mPlaybackStatusLock;
std::mutex mRecordStatusLock;
- std::mutex mBroadcastInputThreadLock;
std::mutex mDvrThreadLock;
const bool DEBUG_DVR = false;
@@ -151,7 +149,6 @@
// Booleans to check if recording is running.
// Recording is ready when both of the following are set to true.
bool mIsRecordStarted = false;
- bool mIsRecordFilterAttached = false;
};
} // namespace implementation
diff --git a/tv/tuner/1.0/default/Filter.cpp b/tv/tuner/1.0/default/Filter.cpp
index 8bca70c..30b19c0 100644
--- a/tv/tuner/1.0/default/Filter.cpp
+++ b/tv/tuner/1.0/default/Filter.cpp
@@ -47,12 +47,18 @@
if (mType.subType.tsFilterType() == DemuxTsFilterType::PCR) {
mIsPcrFilter = true;
}
+ if (mType.subType.tsFilterType() == DemuxTsFilterType::RECORD) {
+ mIsRecordFilter = true;
+ }
break;
case DemuxFilterMainType::MMTP:
if (mType.subType.mmtpFilterType() == DemuxMmtpFilterType::AUDIO ||
mType.subType.mmtpFilterType() == DemuxMmtpFilterType::VIDEO) {
mIsMediaFilter = true;
}
+ if (mType.subType.mmtpFilterType() == DemuxMmtpFilterType::RECORD) {
+ mIsRecordFilter = true;
+ }
break;
case DemuxFilterMainType::IP:
break;
@@ -535,12 +541,6 @@
}
Result Filter::startRecordFilterHandler() {
- /*DemuxFilterTsRecordEvent tsRecordEvent;
- tsRecordEvent.pid.tPid(0);
- tsRecordEvent.indexMask.tsIndexMask(0x01);
- mFilterEvent.events.resize(1);
- mFilterEvent.events[0].tsRecord(tsRecordEvent);
-*/
std::lock_guard<std::mutex> lock(mRecordFilterOutputLock);
if (mRecordFilterOutput.empty()) {
return Result::SUCCESS;
@@ -567,7 +567,7 @@
bool Filter::writeSectionsAndCreateEvent(vector<uint8_t> data) {
// TODO check how many sections has been read
- ALOGD("[Filter] section hander");
+ ALOGD("[Filter] section handler");
std::lock_guard<std::mutex> lock(mFilterEventLock);
if (!writeDataToFilterMQ(data)) {
return false;
diff --git a/tv/tuner/1.0/default/Filter.h b/tv/tuner/1.0/default/Filter.h
index 09e9604..9386dca 100644
--- a/tv/tuner/1.0/default/Filter.h
+++ b/tv/tuner/1.0/default/Filter.h
@@ -91,6 +91,7 @@
void freeAvHandle();
bool isMediaFilter() { return mIsMediaFilter; };
bool isPcrFilter() { return mIsPcrFilter; };
+ bool isRecordFilter() { return mIsRecordFilter; };
private:
// Tuner service
@@ -107,6 +108,7 @@
DemuxFilterType mType;
bool mIsMediaFilter = false;
bool mIsPcrFilter = false;
+ bool mIsRecordFilter = false;
DemuxFilterSettings mFilterSettings;
uint16_t mTpid;
diff --git a/tv/tuner/1.0/default/Frontend.cpp b/tv/tuner/1.0/default/Frontend.cpp
index 61bbbf8..8bf0ec5 100644
--- a/tv/tuner/1.0/default/Frontend.cpp
+++ b/tv/tuner/1.0/default/Frontend.cpp
@@ -66,7 +66,7 @@
mTunerService->frontendStartTune(mId);
mCallback->onEvent(FrontendEventType::LOCKED);
- mIsLocked = false;
+ mIsLocked = true;
return Result::SUCCESS;
}
@@ -268,14 +268,14 @@
return mId;
}
-string Frontend::getSourceFile() {
- return FRONTEND_STREAM_FILE;
-}
-
bool Frontend::supportsSatellite() {
return mType == FrontendType::DVBS || mType == FrontendType::ISDBS ||
mType == FrontendType::ISDBS3;
}
+
+bool Frontend::isLocked() {
+ return mIsLocked;
+}
} // namespace implementation
} // namespace V1_0
} // namespace tuner
diff --git a/tv/tuner/1.0/default/Frontend.h b/tv/tuner/1.0/default/Frontend.h
index c0d1613..a529b74 100644
--- a/tv/tuner/1.0/default/Frontend.h
+++ b/tv/tuner/1.0/default/Frontend.h
@@ -68,6 +68,8 @@
string getSourceFile();
+ bool isLocked();
+
private:
virtual ~Frontend();
bool supportsSatellite();
@@ -77,7 +79,6 @@
FrontendId mId = 0;
bool mIsLocked = false;
- const string FRONTEND_STREAM_FILE = "/vendor/etc/segment000000.ts";
std::ifstream mFrontendData;
};
diff --git a/tv/tuner/1.0/default/TimeFilter.cpp b/tv/tuner/1.0/default/TimeFilter.cpp
index 0b1fd1c..cec824f 100644
--- a/tv/tuner/1.0/default/TimeFilter.cpp
+++ b/tv/tuner/1.0/default/TimeFilter.cpp
@@ -34,24 +34,32 @@
TimeFilter::~TimeFilter() {}
-Return<Result> TimeFilter::setTimeStamp(uint64_t /* timeStamp */) {
+Return<Result> TimeFilter::setTimeStamp(uint64_t timeStamp) {
ALOGV("%s", __FUNCTION__);
+ if (timeStamp == INVALID_TIME_STAMP) {
+ return Result::INVALID_ARGUMENT;
+ }
+ mTimeStamp = timeStamp;
+ mBeginTime = time(NULL);
return Result::SUCCESS;
}
Return<Result> TimeFilter::clearTimeStamp() {
ALOGV("%s", __FUNCTION__);
+ mTimeStamp = INVALID_TIME_STAMP;
return Result::SUCCESS;
}
Return<void> TimeFilter::getTimeStamp(getTimeStamp_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
+ if (mTimeStamp == INVALID_TIME_STAMP) {
+ _hidl_cb(Result::INVALID_STATE, mTimeStamp);
+ }
- uint64_t timeStamp = 0;
-
- _hidl_cb(Result::SUCCESS, timeStamp);
+ uint64_t currentTimeStamp = mTimeStamp + difftime(time(NULL), mBeginTime) * 900000;
+ _hidl_cb(Result::SUCCESS, currentTimeStamp);
return Void();
}
@@ -66,6 +74,7 @@
Return<Result> TimeFilter::close() {
ALOGV("%s", __FUNCTION__);
+ mTimeStamp = INVALID_TIME_STAMP;
return Result::SUCCESS;
}
diff --git a/tv/tuner/1.0/default/TimeFilter.h b/tv/tuner/1.0/default/TimeFilter.h
index 7131df8..cb3f29d 100644
--- a/tv/tuner/1.0/default/TimeFilter.h
+++ b/tv/tuner/1.0/default/TimeFilter.h
@@ -19,6 +19,7 @@
#include <android/hardware/tv/tuner/1.0/ITimeFilter.h>
#include "Demux.h"
+#include "time.h"
using namespace std;
@@ -35,6 +36,8 @@
using FilterMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
+#define INVALID_TIME_STAMP -1
+
class Demux;
class TimeFilter : public ITimeFilter {
@@ -57,6 +60,8 @@
private:
sp<Demux> mDemux;
+ uint64_t mTimeStamp = INVALID_TIME_STAMP;
+ time_t mBeginTime;
};
} // namespace implementation
diff --git a/tv/tuner/1.0/default/Tuner.cpp b/tv/tuner/1.0/default/Tuner.cpp
index ae8070c..48ce384 100644
--- a/tv/tuner/1.0/default/Tuner.cpp
+++ b/tv/tuner/1.0/default/Tuner.cpp
@@ -231,6 +231,9 @@
void Tuner::setFrontendAsDemuxSource(uint32_t frontendId, uint32_t demuxId) {
mFrontendToDemux[frontendId] = demuxId;
+ if (mFrontends[frontendId] != nullptr && mFrontends[frontendId]->isLocked()) {
+ mDemuxes[demuxId]->startFrontendInputLoop();
+ }
}
void Tuner::frontendStopTune(uint32_t frontendId) {
diff --git a/tv/tuner/1.0/vts/functional/DvrTests.cpp b/tv/tuner/1.0/vts/functional/DvrTests.cpp
index 7e7f8e6..0dfc032 100644
--- a/tv/tuner/1.0/vts/functional/DvrTests.cpp
+++ b/tv/tuner/1.0/vts/functional/DvrTests.cpp
@@ -49,49 +49,73 @@
EXPECT_TRUE(EventFlag::createEventFlag(mPlaybackMQ->getEventFlagWord(), &playbackMQEventFlag) ==
android::OK);
- // open the stream and get its length
- std::ifstream inputData(mInputDataFile.c_str(), std::ifstream::binary);
- int writeSize = mPlaybackSettings.packetSize * 6;
- char* buffer = new char[writeSize];
- ALOGW("[vts] playback thread loop start %s!", mInputDataFile.c_str());
- if (!inputData.is_open()) {
+ int fd = open(mInputDataFile.c_str(), O_RDONLY | O_LARGEFILE);
+ int readBytes;
+ uint32_t regionSize = 0;
+ uint8_t* buffer;
+ ALOGW("[vts] playback thread loop start %s", mInputDataFile.c_str());
+ if (fd < 0) {
mPlaybackThreadRunning = false;
ALOGW("[vts] Error %s", strerror(errno));
}
while (mPlaybackThreadRunning) {
- // move the stream pointer for packet size * 6 every read until the end
while (mKeepWritingPlaybackFMQ) {
- inputData.read(buffer, writeSize);
- if (!inputData) {
- int leftSize = inputData.gcount();
- if (leftSize == 0) {
- mPlaybackThreadRunning = false;
- break;
- }
- inputData.clear();
- inputData.read(buffer, leftSize);
- // Write the left over of the input data and quit the thread
- if (leftSize > 0) {
- EXPECT_TRUE(mPlaybackMQ->write((unsigned char*)&buffer[0], leftSize));
- playbackMQEventFlag->wake(
- static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
- }
+ int totalWrite = mPlaybackMQ->availableToWrite();
+ if (totalWrite * 4 < mPlaybackMQ->getQuantumCount()) {
+ // Wait for the HAL implementation to read more data then write.
+ continue;
+ }
+ MessageQueue<uint8_t, kSynchronizedReadWrite>::MemTransaction memTx;
+ if (!mPlaybackMQ->beginWrite(totalWrite, &memTx)) {
+ ALOGW("[vts] Fail to write into Playback fmq.");
mPlaybackThreadRunning = false;
break;
}
- // Write input FMQ and notify the Tuner Implementation
- EXPECT_TRUE(mPlaybackMQ->write((unsigned char*)&buffer[0], writeSize));
+ auto first = memTx.getFirstRegion();
+ buffer = first.getAddress();
+ regionSize = first.getLength();
+
+ if (regionSize > 0) {
+ readBytes = read(fd, buffer, regionSize);
+ if (readBytes <= 0) {
+ if (readBytes < 0) {
+ ALOGW("[vts] Read from %s failed.", mInputDataFile.c_str());
+ } else {
+ ALOGW("[vts] playback input EOF.");
+ }
+ mPlaybackThreadRunning = false;
+ break;
+ }
+ }
+ if (regionSize == 0 || (readBytes == regionSize && regionSize < totalWrite)) {
+ auto second = memTx.getSecondRegion();
+ buffer = second.getAddress();
+ regionSize = second.getLength();
+ int ret = read(fd, buffer, regionSize);
+ if (ret <= 0) {
+ if (ret < 0) {
+ ALOGW("[vts] Read from %s failed.", mInputDataFile.c_str());
+ } else {
+ ALOGW("[vts] playback input EOF.");
+ }
+ mPlaybackThreadRunning = false;
+ break;
+ }
+ readBytes += ret;
+ }
+ if (!mPlaybackMQ->commitWrite(readBytes)) {
+ ALOGW("[vts] Failed to commit write playback fmq.");
+ mPlaybackThreadRunning = false;
+ break;
+ }
playbackMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
- inputData.seekg(writeSize, inputData.cur);
- sleep(1);
}
}
+ mPlaybackThreadRunning = false;
ALOGW("[vts] Playback thread end.");
-
- delete[] buffer;
- inputData.close();
+ close(fd);
}
void DvrCallback::testRecordOutput() {
@@ -186,32 +210,65 @@
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
// Create dvr callback
- mDvrCallback = new DvrCallback();
+ if (type == DvrType::PLAYBACK) {
+ mDvrPlaybackCallback = new DvrCallback();
+ mDemux->openDvr(type, bufferSize, mDvrPlaybackCallback,
+ [&](Result result, const sp<IDvr>& dvr) {
+ mDvrPlayback = dvr;
+ status = result;
+ });
+ if (status == Result::SUCCESS) {
+ mDvrPlaybackCallback->setDvr(mDvrPlayback);
+ }
+ }
- mDemux->openDvr(type, bufferSize, mDvrCallback, [&](Result result, const sp<IDvr>& dvr) {
- mDvr = dvr;
+ if (type == DvrType::RECORD) {
+ mDvrRecordCallback = new DvrCallback();
+ mDemux->openDvr(type, bufferSize, mDvrRecordCallback,
+ [&](Result result, const sp<IDvr>& dvr) {
+ mDvrRecord = dvr;
+ status = result;
+ });
+ if (status == Result::SUCCESS) {
+ mDvrRecordCallback->setDvr(mDvrRecord);
+ }
+ }
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+AssertionResult DvrTests::configDvrPlayback(DvrSettings setting) {
+ Result status = mDvrPlayback->configure(setting);
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+AssertionResult DvrTests::configDvrRecord(DvrSettings setting) {
+ Result status = mDvrRecord->configure(setting);
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+AssertionResult DvrTests::getDvrPlaybackMQDescriptor() {
+ Result status;
+ EXPECT_TRUE(mDemux) << "Test with openDemux first.";
+ EXPECT_TRUE(mDvrPlayback) << "Test with openDvr first.";
+
+ mDvrPlayback->getQueueDesc([&](Result result, const MQDesc& dvrMQDesc) {
+ mDvrPlaybackMQDescriptor = dvrMQDesc;
status = result;
});
- if (status == Result::SUCCESS) {
- mDvrCallback->setDvr(mDvr);
- }
return AssertionResult(status == Result::SUCCESS);
}
-AssertionResult DvrTests::configDvr(DvrSettings setting) {
- Result status = mDvr->configure(setting);
-
- return AssertionResult(status == Result::SUCCESS);
-}
-
-AssertionResult DvrTests::getDvrMQDescriptor() {
+AssertionResult DvrTests::getDvrRecordMQDescriptor() {
Result status;
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
- EXPECT_TRUE(mDvr) << "Test with openDvr first.";
+ EXPECT_TRUE(mDvrRecord) << "Test with openDvr first.";
- mDvr->getQueueDesc([&](Result result, const MQDesc& dvrMQDesc) {
- mDvrMQDescriptor = dvrMQDesc;
+ mDvrRecord->getQueueDesc([&](Result result, const MQDesc& dvrMQDesc) {
+ mDvrRecordMQDescriptor = dvrMQDesc;
status = result;
});
@@ -221,9 +278,9 @@
AssertionResult DvrTests::attachFilterToDvr(sp<IFilter> filter) {
Result status;
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
- EXPECT_TRUE(mDvr) << "Test with openDvr first.";
+ EXPECT_TRUE(mDvrRecord) << "Test with openDvr first.";
- status = mDvr->attachFilter(filter);
+ status = mDvrRecord->attachFilter(filter);
return AssertionResult(status == Result::SUCCESS);
}
@@ -231,35 +288,61 @@
AssertionResult DvrTests::detachFilterToDvr(sp<IFilter> filter) {
Result status;
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
- EXPECT_TRUE(mDvr) << "Test with openDvr first.";
+ EXPECT_TRUE(mDvrRecord) << "Test with openDvr first.";
- status = mDvr->detachFilter(filter);
+ status = mDvrRecord->detachFilter(filter);
return AssertionResult(status == Result::SUCCESS);
}
-AssertionResult DvrTests::startDvr() {
+AssertionResult DvrTests::startDvrPlayback() {
Result status;
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
- EXPECT_TRUE(mDvr) << "Test with openDvr first.";
+ EXPECT_TRUE(mDvrPlayback) << "Test with openDvr first.";
- status = mDvr->start();
+ status = mDvrPlayback->start();
return AssertionResult(status == Result::SUCCESS);
}
-AssertionResult DvrTests::stopDvr() {
+AssertionResult DvrTests::stopDvrPlayback() {
Result status;
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
- EXPECT_TRUE(mDvr) << "Test with openDvr first.";
+ EXPECT_TRUE(mDvrPlayback) << "Test with openDvr first.";
- status = mDvr->stop();
+ status = mDvrPlayback->stop();
return AssertionResult(status == Result::SUCCESS);
}
-void DvrTests::closeDvr() {
+void DvrTests::closeDvrPlayback() {
ASSERT_TRUE(mDemux);
- ASSERT_TRUE(mDvr);
- ASSERT_TRUE(mDvr->close() == Result::SUCCESS);
+ ASSERT_TRUE(mDvrPlayback);
+ ASSERT_TRUE(mDvrPlayback->close() == Result::SUCCESS);
+}
+
+AssertionResult DvrTests::startDvrRecord() {
+ Result status;
+ EXPECT_TRUE(mDemux) << "Test with openDemux first.";
+ EXPECT_TRUE(mDvrRecord) << "Test with openDvr first.";
+
+ status = mDvrRecord->start();
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+AssertionResult DvrTests::stopDvrRecord() {
+ Result status;
+ EXPECT_TRUE(mDemux) << "Test with openDemux first.";
+ EXPECT_TRUE(mDvrRecord) << "Test with openDvr first.";
+
+ status = mDvrRecord->stop();
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+void DvrTests::closeDvrRecord() {
+ ASSERT_TRUE(mDemux);
+ ASSERT_TRUE(mDvrRecord);
+ ASSERT_TRUE(mDvrRecord->close() == Result::SUCCESS);
}
diff --git a/tv/tuner/1.0/vts/functional/DvrTests.h b/tv/tuner/1.0/vts/functional/DvrTests.h
index dd00c27..3997839 100644
--- a/tv/tuner/1.0/vts/functional/DvrTests.h
+++ b/tv/tuner/1.0/vts/functional/DvrTests.h
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-#include <VtsHalHidlTargetTestBase.h>
-#include <VtsHalHidlTargetTestEnvBase.h>
#include <android-base/logging.h>
#include <android/hardware/tv/tuner/1.0/IDvr.h>
#include <android/hardware/tv/tuner/1.0/IDvrCallback.h>
#include <android/hardware/tv/tuner/1.0/ITuner.h>
#include <android/hardware/tv/tuner/1.0/types.h>
+#include <fcntl.h>
#include <fmq/MessageQueue.h>
+#include <gtest/gtest.h>
+#include <hidl/HidlSupport.h>
#include <hidl/Status.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
@@ -52,6 +53,8 @@
using android::hardware::tv::tuner::V1_0::RecordStatus;
using android::hardware::tv::tuner::V1_0::Result;
+using namespace std;
+
#define WAIT_TIMEOUT 3000000000
class DvrCallback : public IDvrCallback {
@@ -149,25 +152,31 @@
void setDemux(sp<IDemux> demux) { mDemux = demux; }
void startPlaybackInputThread(string& dataInputFile, PlaybackSettings& settings) {
- mDvrCallback->startPlaybackInputThread(dataInputFile, settings, mDvrMQDescriptor);
+ mDvrPlaybackCallback->startPlaybackInputThread(dataInputFile, settings,
+ mDvrPlaybackMQDescriptor);
};
void startRecordOutputThread(RecordSettings settings) {
- mDvrCallback->startRecordOutputThread(settings, mDvrMQDescriptor);
+ mDvrRecordCallback->startRecordOutputThread(settings, mDvrRecordMQDescriptor);
};
- void stopPlaybackThread() { mDvrCallback->stopPlaybackThread(); }
- void testRecordOutput() { mDvrCallback->testRecordOutput(); }
- void stopRecordThread() { mDvrCallback->stopPlaybackThread(); }
+ void stopPlaybackThread() { mDvrPlaybackCallback->stopPlaybackThread(); }
+ void testRecordOutput() { mDvrRecordCallback->testRecordOutput(); }
+ void stopRecordThread() { mDvrRecordCallback->stopRecordThread(); }
AssertionResult openDvrInDemux(DvrType type, uint32_t bufferSize);
- AssertionResult configDvr(DvrSettings setting);
- AssertionResult getDvrMQDescriptor();
+ AssertionResult configDvrPlayback(DvrSettings setting);
+ AssertionResult configDvrRecord(DvrSettings setting);
+ AssertionResult getDvrPlaybackMQDescriptor();
+ AssertionResult getDvrRecordMQDescriptor();
AssertionResult attachFilterToDvr(sp<IFilter> filter);
AssertionResult detachFilterToDvr(sp<IFilter> filter);
- AssertionResult stopDvr();
- AssertionResult startDvr();
- void closeDvr();
+ AssertionResult stopDvrPlayback();
+ AssertionResult startDvrPlayback();
+ AssertionResult stopDvrRecord();
+ AssertionResult startDvrRecord();
+ void closeDvrPlayback();
+ void closeDvrRecord();
protected:
static AssertionResult failure() { return ::testing::AssertionFailure(); }
@@ -175,11 +184,11 @@
static AssertionResult success() { return ::testing::AssertionSuccess(); }
sp<ITuner> mService;
- sp<IDvr> mDvr;
+ sp<IDvr> mDvrPlayback;
+ sp<IDvr> mDvrRecord;
sp<IDemux> mDemux;
- sp<DvrCallback> mDvrCallback;
- MQDesc mDvrMQDescriptor;
-
- pthread_t mPlaybackshread;
- bool mPlaybackThreadRunning;
+ sp<DvrCallback> mDvrPlaybackCallback;
+ sp<DvrCallback> mDvrRecordCallback;
+ MQDesc mDvrPlaybackMQDescriptor;
+ MQDesc mDvrRecordMQDescriptor;
};
diff --git a/tv/tuner/1.0/vts/functional/FilterTests.cpp b/tv/tuner/1.0/vts/functional/FilterTests.cpp
index ca8bd19..0ecdf73 100644
--- a/tv/tuner/1.0/vts/functional/FilterTests.cpp
+++ b/tv/tuner/1.0/vts/functional/FilterTests.cpp
@@ -149,6 +149,44 @@
return AssertionResult(status == Result::SUCCESS);
}
+AssertionResult FilterTests::openTimeFilterInDemux() {
+ if (!mDemux) {
+ ALOGW("[vts] Test with openDemux first.");
+ return failure();
+ }
+
+ // Add time filter to the local demux
+ Result status;
+ mDemux->openTimeFilter([&](Result result, const sp<ITimeFilter>& filter) {
+ mTimeFilter = filter;
+ status = result;
+ });
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+AssertionResult FilterTests::setTimeStamp(uint64_t timeStamp) {
+ if (!mTimeFilter) {
+ ALOGW("[vts] Test with openTimeFilterInDemux first.");
+ return failure();
+ }
+
+ mBeginTimeStamp = timeStamp;
+ return AssertionResult(mTimeFilter->setTimeStamp(timeStamp) == Result::SUCCESS);
+}
+
+AssertionResult FilterTests::getTimeStamp() {
+ if (!mTimeFilter) {
+ ALOGW("[vts] Test with openTimeFilterInDemux first.");
+ return failure();
+ }
+
+ Result status;
+ mTimeFilter->getTimeStamp([&](Result result, uint64_t /*timeStamp*/) { status = result; });
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
AssertionResult FilterTests::getNewlyOpenedFilterId(uint32_t& filterId) {
Result status;
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
@@ -229,6 +267,15 @@
return AssertionResult(status == Result::SUCCESS);
}
+AssertionResult FilterTests::clearTimeStamp() {
+ if (!mTimeFilter) {
+ ALOGW("[vts] Test with openTimeFilterInDemux first.");
+ return failure();
+ }
+
+ return AssertionResult(mTimeFilter->clearTimeStamp() == Result::SUCCESS);
+}
+
AssertionResult FilterTests::closeFilter(uint32_t filterId) {
EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
Result status = mFilters[filterId]->close();
@@ -244,3 +291,12 @@
}
return AssertionResult(status == Result::SUCCESS);
}
+
+AssertionResult FilterTests::closeTimeFilter() {
+ if (!mTimeFilter) {
+ ALOGW("[vts] Test with openTimeFilterInDemux first.");
+ return failure();
+ }
+
+ return AssertionResult(mTimeFilter->close() == Result::SUCCESS);
+}
diff --git a/tv/tuner/1.0/vts/functional/FilterTests.h b/tv/tuner/1.0/vts/functional/FilterTests.h
index 2aa1b90..a76a6b9 100644
--- a/tv/tuner/1.0/vts/functional/FilterTests.h
+++ b/tv/tuner/1.0/vts/functional/FilterTests.h
@@ -57,6 +57,7 @@
using android::hardware::tv::tuner::V1_0::IDemux;
using android::hardware::tv::tuner::V1_0::IFilter;
using android::hardware::tv::tuner::V1_0::IFilterCallback;
+using android::hardware::tv::tuner::V1_0::ITimeFilter;
using android::hardware::tv::tuner::V1_0::ITuner;
using android::hardware::tv::tuner::V1_0::Result;
@@ -151,14 +152,19 @@
std::map<uint32_t, sp<FilterCallback>> getFilterCallbacks() { return mFilterCallbacks; }
AssertionResult openFilterInDemux(DemuxFilterType type, uint32_t bufferSize);
+ AssertionResult openTimeFilterInDemux();
+ AssertionResult setTimeStamp(uint64_t timeStamp);
+ AssertionResult getTimeStamp();
AssertionResult getNewlyOpenedFilterId(uint32_t& filterId);
AssertionResult configFilter(DemuxFilterSettings setting, uint32_t filterId);
AssertionResult getFilterMQDescriptor(uint32_t filterId);
AssertionResult setFilterDataSource(uint32_t sourceFilterId, uint32_t sinkFilterId);
AssertionResult setFilterDataSourceToDemux(uint32_t filterId);
AssertionResult startFilter(uint32_t filterId);
+ AssertionResult clearTimeStamp();
AssertionResult stopFilter(uint32_t filterId);
AssertionResult closeFilter(uint32_t filterId);
+ AssertionResult closeTimeFilter();
FilterEventType getFilterEventType(DemuxFilterType type) {
FilterEventType eventType = FilterEventType::UNDEFINED;
@@ -214,6 +220,7 @@
sp<ITuner> mService;
sp<IFilter> mFilter;
+ sp<ITimeFilter> mTimeFilter;
sp<IDemux> mDemux;
std::map<uint32_t, sp<IFilter>> mFilters;
std::map<uint32_t, sp<FilterCallback>> mFilterCallbacks;
@@ -223,4 +230,5 @@
vector<uint32_t> mUsedFilterIds;
uint32_t mFilterId = -1;
+ uint64_t mBeginTimeStamp;
};
diff --git a/tv/tuner/1.0/vts/functional/FrontendTests.cpp b/tv/tuner/1.0/vts/functional/FrontendTests.cpp
index d094510..45951d2 100644
--- a/tv/tuner/1.0/vts/functional/FrontendTests.cpp
+++ b/tv/tuner/1.0/vts/functional/FrontendTests.cpp
@@ -360,13 +360,28 @@
ASSERT_TRUE(status == Result::SUCCESS);
}
-AssertionResult FrontendTests::tuneFrontend(FrontendConfig config) {
+AssertionResult FrontendTests::tuneFrontend(FrontendConfig config, bool testWithDemux) {
EXPECT_TRUE(mFrontendCallback)
<< "test with openFrontendById/setFrontendCallback/getFrontendInfo first.";
EXPECT_TRUE(mFrontendInfo.type == config.type)
<< "FrontendConfig does not match the frontend info of the given id.";
+ mIsSoftwareFe = config.isSoftwareFe;
+ bool result = true;
+ if (mIsSoftwareFe && testWithDemux) {
+ DvrConfig dvrConfig;
+ getSoftwareFrontendPlaybackConfig(dvrConfig);
+ result &= mDvrTests.openDvrInDemux(dvrConfig.type, dvrConfig.bufferSize) == success();
+ result &= mDvrTests.configDvrPlayback(dvrConfig.settings) == success();
+ result &= mDvrTests.getDvrPlaybackMQDescriptor() == success();
+ mDvrTests.startPlaybackInputThread(dvrConfig.playbackInputFile,
+ dvrConfig.settings.playback());
+ if (!result) {
+ ALOGW("[vts] Software frontend dvr configure failed.");
+ return failure();
+ }
+ }
mFrontendCallback->tuneTestOnLock(mFrontend, config.settings);
return AssertionResult(true);
}
@@ -379,10 +394,14 @@
return AssertionResult(mFrontend->setLnb(lnbId) == Result::SUCCESS);
}
-AssertionResult FrontendTests::stopTuneFrontend() {
+AssertionResult FrontendTests::stopTuneFrontend(bool testWithDemux) {
EXPECT_TRUE(mFrontend) << "Test with openFrontendById first.";
Result status;
status = mFrontend->stopTune();
+ if (mIsSoftwareFe && testWithDemux) {
+ mDvrTests.stopPlaybackThread();
+ mDvrTests.closeDvrPlayback();
+ }
return AssertionResult(status == Result::SUCCESS);
}
@@ -415,9 +434,9 @@
ASSERT_TRUE(feId != INVALID_ID);
ASSERT_TRUE(openFrontendById(feId));
ASSERT_TRUE(setFrontendCallback());
- ASSERT_TRUE(tuneFrontend(frontendConf));
+ ASSERT_TRUE(tuneFrontend(frontendConf, false /*testWithDemux*/));
verifyFrontendStatus(frontendConf.tuneStatusTypes, frontendConf.expectTuneStatuses);
- ASSERT_TRUE(stopTuneFrontend());
+ ASSERT_TRUE(stopTuneFrontend(false /*testWithDemux*/));
ASSERT_TRUE(closeFrontend());
}
diff --git a/tv/tuner/1.0/vts/functional/FrontendTests.h b/tv/tuner/1.0/vts/functional/FrontendTests.h
index b8b9f47..c536325 100644
--- a/tv/tuner/1.0/vts/functional/FrontendTests.h
+++ b/tv/tuner/1.0/vts/functional/FrontendTests.h
@@ -31,6 +31,7 @@
#include <utils/Mutex.h>
#include <map>
+#include "DvrTests.h"
#include "VtsHalTvTunerV1_0TestConfigurations.h"
#define WAIT_TIMEOUT 3000000000
@@ -100,7 +101,10 @@
public:
sp<ITuner> mService;
- void setService(sp<ITuner> tuner) { mService = tuner; }
+ void setService(sp<ITuner> tuner) {
+ mService = tuner;
+ mDvrTests.setService(tuner);
+ }
AssertionResult getFrontendIds();
AssertionResult getFrontendInfo(uint32_t frontendId);
@@ -108,23 +112,43 @@
AssertionResult setFrontendCallback();
AssertionResult scanFrontend(FrontendConfig config, FrontendScanType type);
AssertionResult stopScanFrontend();
- AssertionResult tuneFrontend(FrontendConfig config);
+ AssertionResult tuneFrontend(FrontendConfig config, bool testWithDemux);
AssertionResult setLnb(uint32_t lnbId);
void verifyFrontendStatus(vector<FrontendStatusType> statusTypes,
vector<FrontendStatus> expectStatuses);
- AssertionResult stopTuneFrontend();
+ AssertionResult stopTuneFrontend(bool testWithDemux);
AssertionResult closeFrontend();
void getFrontendIdByType(FrontendType feType, uint32_t& feId);
void tuneTest(FrontendConfig frontendConf);
void scanTest(FrontendConfig frontend, FrontendScanType type);
+ void setDvrTests(DvrTests dvrTests) { mDvrTests = dvrTests; }
+ void setDemux(sp<IDemux> demux) { mDvrTests.setDemux(demux); }
+
protected:
static AssertionResult failure() { return ::testing::AssertionFailure(); }
static AssertionResult success() { return ::testing::AssertionSuccess(); }
+ void getSoftwareFrontendPlaybackConfig(DvrConfig& dvrConfig) {
+ PlaybackSettings playbackSettings{
+ .statusMask = 0xf,
+ .lowThreshold = 0x1000,
+ .highThreshold = 0x07fff,
+ .dataFormat = DataFormat::TS,
+ .packetSize = 188,
+ };
+ dvrConfig.type = DvrType::PLAYBACK;
+ dvrConfig.playbackInputFile = "/data/local/tmp/segment000000.ts";
+ dvrConfig.bufferSize = FMQ_SIZE_4M;
+ dvrConfig.settings.playback(playbackSettings);
+ }
+
sp<IFrontend> mFrontend;
FrontendInfo mFrontendInfo;
sp<FrontendCallback> mFrontendCallback;
hidl_vec<FrontendId> mFeIds;
+
+ DvrTests mDvrTests;
+ bool mIsSoftwareFe = false;
};
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
index e1284b4..6819659 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
@@ -56,6 +56,23 @@
ASSERT_TRUE(mFrontendTests.closeFrontend());
}
+void TunerFilterHidlTest::testTimeFilter(TimeFilterConfig filterConf) {
+ if (!filterConf.supportTimeFilter) {
+ return;
+ }
+ uint32_t demuxId;
+ sp<IDemux> demux;
+
+ ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
+ mFilterTests.setDemux(demux);
+ ASSERT_TRUE(mFilterTests.openTimeFilterInDemux());
+ ASSERT_TRUE(mFilterTests.setTimeStamp(filterConf.timeStamp));
+ ASSERT_TRUE(mFilterTests.getTimeStamp());
+ ASSERT_TRUE(mFilterTests.clearTimeStamp());
+ ASSERT_TRUE(mFilterTests.closeTimeFilter());
+ ASSERT_TRUE(mDemuxTests.closeDemux());
+}
+
void TunerBroadcastHidlTest::broadcastSingleFilterTest(FilterConfig filterConf,
FrontendConfig frontendConf) {
uint32_t feId;
@@ -77,6 +94,7 @@
}
ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
+ mFrontendTests.setDemux(demux);
mFilterTests.setDemux(demux);
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
@@ -84,9 +102,9 @@
ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
ASSERT_TRUE(mFilterTests.startFilter(filterId));
// tune test
- ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf));
+ ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
ASSERT_TRUE(filterDataOutputTest(goldenOutputFiles));
- ASSERT_TRUE(mFrontendTests.stopTuneFrontend());
+ ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/));
ASSERT_TRUE(mFilterTests.stopFilter(filterId));
ASSERT_TRUE(mFilterTests.closeFilter(filterId));
ASSERT_TRUE(mDemuxTests.closeDemux());
@@ -122,21 +140,21 @@
mFilterTests.setDemux(demux);
mDvrTests.setDemux(demux);
ASSERT_TRUE(mDvrTests.openDvrInDemux(dvrConf.type, dvrConf.bufferSize));
- ASSERT_TRUE(mDvrTests.configDvr(dvrConf.settings));
- ASSERT_TRUE(mDvrTests.getDvrMQDescriptor());
+ ASSERT_TRUE(mDvrTests.configDvrPlayback(dvrConf.settings));
+ ASSERT_TRUE(mDvrTests.getDvrPlaybackMQDescriptor());
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
mDvrTests.startPlaybackInputThread(dvrConf.playbackInputFile, dvrConf.settings.playback());
- ASSERT_TRUE(mDvrTests.startDvr());
+ ASSERT_TRUE(mDvrTests.startDvrPlayback());
ASSERT_TRUE(mFilterTests.startFilter(filterId));
ASSERT_TRUE(filterDataOutputTest(goldenOutputFiles));
mDvrTests.stopPlaybackThread();
ASSERT_TRUE(mFilterTests.stopFilter(filterId));
- ASSERT_TRUE(mDvrTests.stopDvr());
+ ASSERT_TRUE(mDvrTests.stopDvrPlayback());
ASSERT_TRUE(mFilterTests.closeFilter(filterId));
- mDvrTests.closeDvr();
+ mDvrTests.closeDvrPlayback();
ASSERT_TRUE(mDemuxTests.closeDemux());
}
@@ -159,9 +177,10 @@
ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
mFilterTests.setDemux(demux);
mDvrTests.setDemux(demux);
+ mFrontendTests.setDvrTests(mDvrTests);
ASSERT_TRUE(mDvrTests.openDvrInDemux(dvrConf.type, dvrConf.bufferSize));
- ASSERT_TRUE(mDvrTests.configDvr(dvrConf.settings));
- ASSERT_TRUE(mDvrTests.getDvrMQDescriptor());
+ ASSERT_TRUE(mDvrTests.configDvrRecord(dvrConf.settings));
+ ASSERT_TRUE(mDvrTests.getDvrRecordMQDescriptor());
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
@@ -170,17 +189,17 @@
ASSERT_TRUE(filter != nullptr);
mDvrTests.startRecordOutputThread(dvrConf.settings.record());
ASSERT_TRUE(mDvrTests.attachFilterToDvr(filter));
- ASSERT_TRUE(mDvrTests.startDvr());
+ ASSERT_TRUE(mDvrTests.startDvrRecord());
ASSERT_TRUE(mFilterTests.startFilter(filterId));
- ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf));
+ ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
mDvrTests.testRecordOutput();
mDvrTests.stopRecordThread();
- ASSERT_TRUE(mFrontendTests.stopTuneFrontend());
+ ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/));
ASSERT_TRUE(mFilterTests.stopFilter(filterId));
- ASSERT_TRUE(mDvrTests.stopDvr());
+ ASSERT_TRUE(mDvrTests.stopDvrRecord());
ASSERT_TRUE(mDvrTests.detachFilterToDvr(filter));
ASSERT_TRUE(mFilterTests.closeFilter(filterId));
- mDvrTests.closeDvr();
+ mDvrTests.closeDvrRecord();
ASSERT_TRUE(mDemuxTests.closeDemux());
ASSERT_TRUE(mFrontendTests.closeFrontend());
}
@@ -223,8 +242,8 @@
mFilterTests.setDemux(demux);
mDvrTests.setDemux(demux);
ASSERT_TRUE(mDvrTests.openDvrInDemux(dvrConf.type, dvrConf.bufferSize));
- ASSERT_TRUE(mDvrTests.configDvr(dvrConf.settings));
- ASSERT_TRUE(mDvrTests.getDvrMQDescriptor());
+ ASSERT_TRUE(mDvrTests.configDvrRecord(dvrConf.settings));
+ ASSERT_TRUE(mDvrTests.getDvrRecordMQDescriptor());
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
@@ -232,13 +251,13 @@
filter = mFilterTests.getFilterById(filterId);
ASSERT_TRUE(filter != nullptr);
ASSERT_TRUE(mDvrTests.attachFilterToDvr(filter));
- ASSERT_TRUE(mDvrTests.startDvr());
+ ASSERT_TRUE(mDvrTests.startDvrRecord());
ASSERT_TRUE(mFilterTests.startFilter(filterId));
ASSERT_TRUE(mFilterTests.stopFilter(filterId));
- ASSERT_TRUE(mDvrTests.stopDvr());
+ ASSERT_TRUE(mDvrTests.stopDvrRecord());
ASSERT_TRUE(mDvrTests.detachFilterToDvr(filter));
ASSERT_TRUE(mFilterTests.closeFilter(filterId));
- mDvrTests.closeDvr();
+ mDvrTests.closeDvrRecord();
ASSERT_TRUE(mDemuxTests.closeDemux());
ASSERT_TRUE(mFrontendTests.closeFrontend());
}
@@ -266,6 +285,7 @@
ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
mFilterTests.setDemux(demux);
+ mFrontendTests.setDemux(demux);
for (config = mediaFilterConfs.begin(); config != mediaFilterConfs.end(); config++) {
ASSERT_TRUE(mFilterTests.openFilterInDemux((*config).type, (*config).bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
@@ -289,9 +309,9 @@
ASSERT_TRUE(mFilterTests.startFilter(*id));
}
// tune test
- ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf));
+ ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
ASSERT_TRUE(filterDataOutputTest(goldenOutputFiles));
- ASSERT_TRUE(mFrontendTests.stopTuneFrontend());
+ ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/));
for (id = filterIds.begin(); id != filterIds.end(); id++) {
ASSERT_TRUE(mFilterTests.stopFilter(*id));
}
@@ -321,6 +341,17 @@
mFrontendTests.scanTest(frontendScanArray[SCAN_DVBT], FrontendScanType::SCAN_BLIND);
}
+TEST_P(TunerLnbHidlTest, OpenLnbByName) {
+ description("Open and configure an Lnb with name then send a diseqc msg to it.");
+ ASSERT_TRUE(mLnbTests.openLnbByName(lnbArray[LNB_EXTERNAL].name));
+ ASSERT_TRUE(mLnbTests.setLnbCallback());
+ ASSERT_TRUE(mLnbTests.setVoltage(lnbArray[LNB_EXTERNAL].voltage));
+ ASSERT_TRUE(mLnbTests.setTone(lnbArray[LNB_EXTERNAL].tone));
+ ASSERT_TRUE(mLnbTests.setSatellitePosition(lnbArray[LNB_EXTERNAL].position));
+ ASSERT_TRUE(mLnbTests.sendDiseqcMessage(diseqcMsgArray[DISEQC_POWER_ON]));
+ ASSERT_TRUE(mLnbTests.closeLnb());
+}
+
TEST_P(TunerLnbHidlTest, SendDiseqcMessageToLnb) {
description("Open and configure an Lnb with specific settings then send a diseqc msg to it.");
vector<uint32_t> ids;
@@ -424,6 +455,12 @@
ASSERT_TRUE(mDemuxTests.closeDemux());
}
+TEST_P(TunerFilterHidlTest, testTimeFilter) {
+ description("Open a timer filter in Demux and set time stamp.");
+ // TODO use paramterized tests
+ testTimeFilter(timeFilterArray[TIMER0]);
+}
+
TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowVideoFilterTest) {
description("Test Video Filter functionality in Broadcast use case.");
broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendArray[DVBT]);
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h
index d71222b..6804f3c 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h
@@ -16,7 +16,6 @@
#include "DemuxTests.h"
#include "DescramblerTests.h"
-#include "DvrTests.h"
#include "FrontendTests.h"
#include "LnbTests.h"
@@ -34,6 +33,7 @@
initFrontendScanConfig();
initLnbConfig();
initFilterConfig();
+ initTimeFilterConfig();
initDvrConfig();
initDescramblerConfig();
}
@@ -127,6 +127,7 @@
}
void configSingleFilterInDemuxTest(FilterConfig filterConf, FrontendConfig frontendConf);
+ void testTimeFilter(TimeFilterConfig filterConf);
sp<ITuner> mService;
FrontendTests mFrontendTests;
@@ -145,6 +146,7 @@
mDemuxTests.setService(mService);
mFilterTests.setService(mService);
mLnbTests.setService(mService);
+ mDvrTests.setService(mService);
}
protected:
@@ -157,6 +159,7 @@
DemuxTests mDemuxTests;
FilterTests mFilterTests;
LnbTests mLnbTests;
+ DvrTests mDvrTests;
AssertionResult filterDataOutputTest(vector<string> goldenOutputFiles);
@@ -245,6 +248,7 @@
mFrontendTests.setService(mService);
mDemuxTests.setService(mService);
+ mDvrTests.setService(mService);
mDescramblerTests.setService(mService);
mDescramblerTests.setCasService(mCasService);
}
@@ -264,5 +268,6 @@
DemuxTests mDemuxTests;
FilterTests mFilterTests;
DescramblerTests mDescramblerTests;
+ DvrTests mDvrTests;
};
} // namespace
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
index d0f2b9c..6c68e35 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
@@ -86,6 +86,11 @@
} Filter;
typedef enum {
+ TIMER0,
+ TIMER_MAX,
+} TimeFilter;
+
+typedef enum {
SOURCE,
SINK,
LINKAGE_DIR,
@@ -99,6 +104,7 @@
typedef enum {
LNB0,
+ LNB_EXTERNAL,
LNB_MAX,
} Lnb;
@@ -115,6 +121,7 @@
typedef enum {
DVR_RECORD0,
DVR_PLAYBACK0,
+ DVR_SOFTWARE_FE,
DVR_MAX,
} Dvr;
@@ -131,7 +138,13 @@
bool operator<(const FilterConfig& /*c*/) const { return false; }
};
+struct TimeFilterConfig {
+ bool supportTimeFilter;
+ uint64_t timeStamp;
+};
+
struct FrontendConfig {
+ bool isSoftwareFe;
FrontendType type;
FrontendSettings settings;
vector<FrontendStatusType> tuneStatusTypes;
@@ -140,6 +153,7 @@
struct LnbConfig {
bool usingLnb;
+ string name;
LnbVoltage voltage;
LnbTone tone;
LnbPosition position;
@@ -172,6 +186,7 @@
static vector<uint8_t> diseqcMsgArray[DISEQC_MAX];
static ChannelConfig channelArray[FRONTEND_MAX];
static FilterConfig filterArray[FILTER_MAX];
+static TimeFilterConfig timeFilterArray[TIMER_MAX];
static DemuxFilterType filterLinkageTypes[LINKAGE_DIR][FILTER_MAIN_TYPE_BIT_COUNT];
static DvrConfig dvrArray[DVR_MAX];
static DescramblerConfig descramblerArray[DESC_MAX];
@@ -200,7 +215,9 @@
statuses.push_back(status);
frontendArray[DVBT].tuneStatusTypes = types;
frontendArray[DVBT].expectTuneStatuses = statuses;
+ frontendArray[DVBT].isSoftwareFe = true;
frontendArray[DVBS].type = FrontendType::DVBS;
+ frontendArray[DVBS].isSoftwareFe = true;
};
/** Configuration array for the frontend scan test */
@@ -226,6 +243,11 @@
lnbArray[LNB0].voltage = LnbVoltage::VOLTAGE_12V;
lnbArray[LNB0].tone = LnbTone::NONE;
lnbArray[LNB0].position = LnbPosition::UNDEFINED;
+ lnbArray[LNB_EXTERNAL].usingLnb = true;
+ lnbArray[LNB_EXTERNAL].name = "default_lnb_external";
+ lnbArray[LNB_EXTERNAL].voltage = LnbVoltage::VOLTAGE_5V;
+ lnbArray[LNB_EXTERNAL].tone = LnbTone::NONE;
+ lnbArray[LNB_EXTERNAL].position = LnbPosition::UNDEFINED;
};
/** Diseqc messages array for the Lnb test */
@@ -311,6 +333,12 @@
filterLinkageTypes[SINK][4] = filterLinkageTypes[SOURCE][4];
};
+/** Configuration array for the timer filter test */
+inline void initTimeFilterConfig() {
+ timeFilterArray[TIMER0].supportTimeFilter = true;
+ timeFilterArray[TIMER0].timeStamp = 1;
+}
+
/** Configuration array for the dvr test */
inline void initDvrConfig() {
RecordSettings recordSettings{
@@ -331,9 +359,20 @@
.packetSize = 188,
};
dvrArray[DVR_PLAYBACK0].type = DvrType::PLAYBACK;
- dvrArray[DVR_PLAYBACK0].playbackInputFile = "/vendor/etc/segment000000.ts";
+ dvrArray[DVR_PLAYBACK0].playbackInputFile = "/data/local/tmp/segment000000.ts";
dvrArray[DVR_PLAYBACK0].bufferSize = FMQ_SIZE_4M;
dvrArray[DVR_PLAYBACK0].settings.playback(playbackSettings);
+ PlaybackSettings softwareFePlaybackSettings{
+ .statusMask = 0xf,
+ .lowThreshold = 0x1000,
+ .highThreshold = 0x07fff,
+ .dataFormat = DataFormat::TS,
+ .packetSize = 188,
+ };
+ dvrArray[DVR_SOFTWARE_FE].type = DvrType::PLAYBACK;
+ dvrArray[DVR_SOFTWARE_FE].playbackInputFile = "/data/local/tmp/segment000000.ts";
+ dvrArray[DVR_SOFTWARE_FE].bufferSize = FMQ_SIZE_4M;
+ dvrArray[DVR_SOFTWARE_FE].settings.playback(softwareFePlaybackSettings);
};
/** Configuration array for the descrambler test */