From 66a3ec64d23b033ae651bd954e1037a223a7f8f6 Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Thu, 1 Sep 2022 13:48:14 -0700 Subject: [PATCH] Create custom gtest matcher for similar URLs. [ Cherry-pick of http://ag/19893913 ] [ Merge of http://go/wvgerrit/157098 ] Several of the Android integration tests perform direct URL comparisons between fixed URLs and the server URL returned by the CDM. With provisioning 4.0, the CDM will append additional query parameters to the server URL. This updated URL still contains all of the original expected information, but with additional parameters. So long as the URL contains the required fields, any additional parameter should be considered valid. The gtest framework used by the integration tests allow for the creation of custom "matchers", rules that can be used to validate data and create informative failure logs. The CL creates a new matcher for checking that a tested URL is a superset of content of the expected URL. Bug: 244319313 Test: request_license_test on prov 4 device Change-Id: Ie721058fa628b3a4a74dc56f4172a3dfcb1f1ef3 (cherry picked from commit fa8c0a9a625e731f010e661be533b597448fe931) --- .../cdm/test/request_license_test.cpp | 161 ++++++++++++++++-- libwvdrmengine/run_all_unit_tests.sh | 2 +- 2 files changed, 147 insertions(+), 16 deletions(-) diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index c892345b..9288984e 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -1548,6 +1548,118 @@ FourSampleDecryptionInfo kCenc30SwitchCipherData[8] = { }}, }; +// Extracts the scheme, hostname and path from the provided URL. +// Example: +// Input: "https://www.widevine.com/service?id=1234&device=pixel&flag" +// Output: "https://www.widevine.com/service" +std::string GetUrlHostAndPath(const std::string& full_url) { + const auto start_of_params = full_url.find('?'); + if (start_of_params == std::string::npos) return full_url; + return full_url.substr(0, start_of_params); +} + +// Extracts and splits the query parameters from the provided URL. +// Returns an empty list if no parameters are found. +// +// Example: +// Input: "https://www.widevine.com/service?id=1234&device=pixel&flag" +// Output: {"id=1234", "device=pixel", "flag"} +std::vector GetUrlQueryParamPairs(const std::string& full_url) { + const auto start_of_params = full_url.find('?'); + if (start_of_params == std::string::npos) return {}; // No params. + // Remove host and path. + const std::string all_params = full_url.substr(start_of_params + 1); + if (all_params.empty()) return {}; + // Check if there are more than 1 parameters. + auto param_separator = all_params.find('&'); + if (param_separator == std::string::npos) { + return {all_params}; // Only 1 parameter. + } + // Split parameters by '&'. + std::vector params; + params.push_back(all_params.substr(0, param_separator)); + while (param_separator != std::string::npos) { + auto next_param_separator = all_params.find('&', param_separator + 1); + if (next_param_separator == std::string::npos) { + params.push_back(all_params.substr(param_separator + 1)); + } else { + params.push_back(all_params.substr( + param_separator + 1, next_param_separator - param_separator - 1)); + } + param_separator = next_param_separator; + } + return params; +} + +// Extracts and maps the query parameters from the provided URL. +// Creates a map between the parameter keys and values. Parameters +// that are only keys have an empty string value. +// +// Example: +// Input: "https://www.widevine.com/service?id=1234&device=pixel&flag" +// Output: {"id": "1234", "device": "pixel", "flag": ""} +std::map GetUrlQueryParams( + const std::string& full_url) { + const std::vector param_pairs = GetUrlQueryParamPairs(full_url); + std::map params; + if (param_pairs.empty()) return params; + for (const auto& pair : param_pairs) { + const auto value_separator = pair.find('='); + if (value_separator == std::string::npos) { + // Just the key. + params.emplace(pair, ""); + } else { + params.emplace(pair.substr(0, value_separator), + pair.substr(value_separator + 1)); + } + } + return params; +} + +// Checks that the |actual_url| is a super set of information compared +// to the |expected_url|. The scheme, hostname and path must be the +// same. The |actual_url| must contain at all the query parameters of +// the |expected_url|. Order of the query parameters do not matter. +// +// Example A: +// Expected: "https://www.widevine.com/service?key=1234" +// Actual: "https://www.widevine.com/service?retry=true&key=1234" +// Result: true +// +// Example B: +// Expected: "https://www.widevine.com/service?key=1234&retry=true" +// Actual: "https://www.widevine.com/service?key=1234" +// Result: false +// +// Example C: +// Expected: "https://www.widevine.com/service?key=1234" +// Actual: "https://www.widevine.org/service?key=1234" +// Result: false +bool IsUrlSimilar(const std::string& expected_url, + const std::string& actual_url) { + // First check the host and path. + const std::string expected_host_and_path = GetUrlHostAndPath(expected_url); + const std::string actual_host_and_path = GetUrlHostAndPath(actual_url); + if (expected_host_and_path != actual_host_and_path) { + return false; // Bad host and/or path. + } + // Compare query parameters. + const std::map expected_params = + GetUrlQueryParams(expected_url); + const std::map actual_params = + GetUrlQueryParams(actual_url); + if (actual_params.size() < expected_params.size()) { + return false; // Missing params. + } + for (const auto& expected_param : expected_params) { + const auto actual_param = actual_params.find(expected_param.first); + if (actual_param == actual_params.end()) + return false; // Missing particular param. + if (actual_param->second != expected_param.second) + return false; // Incorrect param value. + } + return true; +} } // namespace namespace wvcdm { @@ -1556,6 +1668,10 @@ using video_widevine::ClientIdentification; using video_widevine::ClientIdentification_NameValue; using video_widevine::SignedMessage; +MATCHER_P(IsSimilarUrlTo, expected_url, "") { + return IsUrlSimilar(expected_url, arg); +} + class TestWvCdmClientPropertySet : public CdmClientPropertySet { public: TestWvCdmClientPropertySet() @@ -2058,7 +2174,8 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { &provisioning_server); EXPECT_EQ(wvcdm::NO_ERROR, status); if (NO_ERROR != status) return; - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); CdmProvisioningResponse response = GetCertRequestResponse(config_.provisioning_server()); @@ -2177,7 +2294,8 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningTest) { cert_type, cert_authority, kDefaultCdmIdentifier, kEmptyServiceCertificate, kLevelDefault, &key_msg_, &provisioning_server)); - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); std::string response = GetCertRequestResponse(config_.provisioning_server()); EXPECT_NE(0, static_cast(response.size())); @@ -2202,7 +2320,8 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningTestWithServiceCertificate) { cert_type, cert_authority, kDefaultCdmIdentifier, config_.provisioning_service_certificate(), kLevelDefault, &key_msg_, &provisioning_server)); - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); std::string response = GetCertRequestResponse(config_.provisioning_server()); EXPECT_NE(0, static_cast(response.size())); @@ -2227,7 +2346,8 @@ TEST_F(WvCdmRequestLicenseTest, L3ProvisioningTest) { decryptor_->GetProvisioningRequest( cert_type, cert_authority, kDefaultCdmIdentifier, kEmptyServiceCertificate, kLevel3, &key_msg_, &provisioning_server)); - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); std::string response = GetCertRequestResponse(config_.provisioning_server()); EXPECT_NE(0, static_cast(response.size())); @@ -2330,14 +2450,16 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningInterposedRetryTest) { cert_type, cert_authority, kDefaultCdmIdentifier, kEmptyServiceCertificate, kLevelDefault, &key_msg1, &provisioning_server)); - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); EXPECT_EQ(wvcdm::NO_ERROR, decryptor_->GetProvisioningRequest( cert_type, cert_authority, kDefaultCdmIdentifier, kEmptyServiceCertificate, kLevelDefault, &key_msg2, &provisioning_server)); - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); // Second message should succeed. key_msg_ = key_msg2; @@ -2375,14 +2497,16 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningInterspersedRetryTest) { cert_type, cert_authority, kDefaultCdmIdentifier, kEmptyServiceCertificate, kLevelDefault, &key_msg1, &provisioning_server)); - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); EXPECT_EQ(wvcdm::NO_ERROR, decryptor_->GetProvisioningRequest( cert_type, cert_authority, kDefaultCdmIdentifier, kEmptyServiceCertificate, kLevelDefault, &key_msg2, &provisioning_server)); - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); // First request expected to fail, because only one message may be active. key_msg_ = key_msg1; @@ -2421,7 +2545,8 @@ TEST_F(WvCdmRequestLicenseTest, DISABLED_X509ProvisioningTest) { cert_type, cert_authority, kDefaultCdmIdentifier, kEmptyServiceCertificate, kLevelDefault, &key_msg_, &provisioning_server)); - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); std::string response = GetCertRequestResponse(config_.provisioning_server()); EXPECT_NE(0, static_cast(response.size())); @@ -2512,7 +2637,8 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningRevocationTest) { EXPECT_EQ(wvcdm::NO_ERROR, result); if (NO_ERROR != result) return; - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); if (!wvcdm::Properties::provisioning_messages_are_binary()) { std::vector request = @@ -2550,7 +2676,8 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningRevocationTest) { EXPECT_EQ(wvcdm::NO_ERROR, result); if (NO_ERROR != result) return; - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); ProvisioningResponse provisioning_response; provisioning_response.set_status(status); @@ -2663,7 +2790,8 @@ TEST_F(WvCdmRequestLicenseTest, PropertySetTest) { cert_type, cert_authority, kDefaultCdmIdentifier, kEmptyServiceCertificate, kLevel3, &key_msg_, &provisioning_server)); - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); std::string response = GetCertRequestResponse(config_.provisioning_server()); EXPECT_NE(0, static_cast(response.size())); @@ -2740,7 +2868,8 @@ TEST_F(WvCdmRequestLicenseTest, ForceL3Test) { cert_type, cert_authority, kDefaultCdmIdentifier, kEmptyServiceCertificate, kLevel3, &key_msg_, &provisioning_server)); - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); std::string response = GetCertRequestResponse(config_.provisioning_server()); EXPECT_NE(0, static_cast(response.size())); EXPECT_EQ(NO_ERROR, @@ -3150,7 +3279,8 @@ TEST_F(WvCdmRequestLicenseTest, ReleaseRetryL3OfflineKeyTest) { cert_type, cert_authority, kDefaultCdmIdentifier, kEmptyServiceCertificate, kLevel3, &key_msg_, &provisioning_server)); - EXPECT_EQ(provisioning_server, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); std::string response = GetCertRequestResponse(config_.provisioning_server()); EXPECT_NE(0, static_cast(response.size())); @@ -3220,7 +3350,8 @@ TEST_F(WvCdmRequestLicenseTest, cert_type, cert_authority, kDefaultCdmIdentifier, kEmptyServiceCertificate, kLevel3, &key_msg_, &provisioning_server_url)); - EXPECT_EQ(provisioning_server_url, kDefaultProvisioningServerUrl); + EXPECT_THAT(provisioning_server_url, + IsSimilarUrlTo(kDefaultProvisioningServerUrl)); std::string response = GetCertRequestResponse(config_.provisioning_server()); EXPECT_NE(0, static_cast(response.size())); diff --git a/libwvdrmengine/run_all_unit_tests.sh b/libwvdrmengine/run_all_unit_tests.sh index bda1f1e3..1cd4fe85 100755 --- a/libwvdrmengine/run_all_unit_tests.sh +++ b/libwvdrmengine/run_all_unit_tests.sh @@ -108,7 +108,7 @@ adb_shell_run event_metric_unittest adb_shell_run file_store_unittest adb_shell_run file_utils_unittest adb_shell_run generic_crypto_unittest -adb_shell_run hidl_metrics_adapter_unittest +adb_shell_run hal_metrics_adapter_unittest adb_shell_run http_socket_test adb_shell_run initialization_data_unittest adb_shell_run libwvdrmdrmplugin_hal_test