diff --git a/CHANGELOG.md b/CHANGELOG.md index 457e6337..ec8b3786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,67 @@ [TOC] +## 15.3.0 (2020-02-11) + +Bugfixes: + - A bug has been fixed that prevented HDCP 2.3 from working in previous + Widevine CE CDM releases, even those that nominally supported HDCP 2.3. All + devices wanting to support HDCP 2.3 *must* update to CE CDM 15.3.0 or later. + - A bug was preventing sessions from being created if Privacy Mode was turned + on and no service certificate had been installed for the licensing service. + This has been fixed, and it should now be possible to create sessions before + installing a service certificate, as required by EME. + - Note, however, that attempting to do a license exchange while in this + state will still return an error. Performing license exchange requires a + service certificate for the licensing service if Privacy Mode is turned + on. + - Previous CE CDM releases erroneously had two errors assigned to the + number 109. One of these errors is now number 110. + - Several OEMCrypto tests were being too stringent about the errors they + allowed to be reported when certain kinds of output protection error were + encountered. These tests have been relaxed to accept the expected error from + either `OEMCrypto_SelectKey()` or `OEMCrypto_DecryptCENC()`. + - Some issues causing incomplete output from failed CDM unit tests have been + fixed. + +Features: + - It is now possible to create a CDM instance that will never write to its + storage. Such a CDM will treat its `IStorage` as read-only. Because such a + CDM instance is impossible to provision, this is only useful for ATSC 3.0, + where there is an `IStorage` instance that is pre-populated with a + certificate and licenses. + - To create such a CDM instance, pass `true` as the final parameter to a new + overload of `Cdm::create()`. + - This feature should be used for instances that use ATSC 3.0 licenses, to + protect the preloaded licenses from being overwritten. + - This feature should *only* be used for instances that use ATSC 3.0 + licenses. All other CDM instances should continue to be created with + writeable storage. + - Partners that use Sandbox IDs in their OEMCrypto implementation can now pass + the Sandbox ID through the CE CDM rather than calling + `OEMCrypto_SetSandbox()` manually. + - The Sandbox ID is passed as a parameter to a new overload of + `Cdm::initialize()`. + - This feature is only useful in combination with an OEMCrypto that uses + Sandbox IDs. Most partners do not use Sandbox IDs and should continue to + use the version of `Cdm::initialize()` that does not have a Sandbox ID + parameter. + - To run the unit tests on a device that uses Sandbox IDs, you can pass a + new `--sandbox_id=` parameter to the unit test binary to + tell it which Sandbox ID to use. + +Documentation: + - Widevine has changed our recommendation for when Privacy Mode should be + turned on, and the documentation has been updated accordingly. Previously, + Widevine recommended the use of Privacy Mode whenever possible. However, + Privacy Mode has no benefit unless the CDM is being used in a web browser. + This is because web browsers visit arbitrary webpages and run untrusted + JavaScript. There is no benefit when running trusted apps, and Privacy Mode + complicates provisioning and licensing. As such, Widevine now only + recommends that Privacy Mode be turned on for web browsers. It should be + turned off for most CE devices. + - This advice applies retroactively to all previous CE CDM releases as well. + ## 15.2.0 (2019-06-28) Features: diff --git a/Widevine_CE_CDM_IntegrationGuide_15.2.0.pdf b/Widevine_CE_CDM_IntegrationGuide_15.2.0.pdf deleted file mode 100644 index f22de354..00000000 Binary files a/Widevine_CE_CDM_IntegrationGuide_15.2.0.pdf and /dev/null differ diff --git a/Widevine_CE_CDM_IntegrationGuide_15.3.0.pdf b/Widevine_CE_CDM_IntegrationGuide_15.3.0.pdf new file mode 100644 index 00000000..1f16a446 Binary files /dev/null and b/Widevine_CE_CDM_IntegrationGuide_15.3.0.pdf differ diff --git a/cdm/include/cdm.h b/cdm/include/cdm.h index cb5eb35e..58b57a9b 100644 --- a/cdm/include/cdm.h +++ b/cdm/include/cdm.h @@ -89,7 +89,8 @@ class CDM_EXPORT Cdm : public ITimerClient { kResourceContention = 107, // Recoverable kSessionStateLost = 108, // Recoverable kSystemStateLost = 109, // Recoverable - kOutputTooLarge = 109, // Recoverable + kOutputTooLarge = 110, // Recoverable + kNeedsServiceCertificate = 111, // This covers errors that we do not expect (see logs for details): kUnexpectedError = 99999, @@ -131,6 +132,7 @@ class CDM_EXPORT Cdm : public ITimerClient { kHdcp2_0 = 1, kHdcp2_1 = 2, kHdcp2_2 = 3, + kHdcp2_3 = 4, } HdcpVersion; // Permissible usages for a key. Returned as a set of flags; multiple @@ -343,6 +345,15 @@ class CDM_EXPORT Cdm : public ITimerClient { const ClientInfo& client_info, IStorage* storage, IClock* clock, ITimer* timer, LogLevel verbosity); + // This is a variant of the above function that allows the caller to pass a + // Sandbox ID. Platforms that use Sandbox IDs should use this initalize() + // function instead of the previous one. Platforms that do not use Sandbox IDs + // should not use this version of initialize(). + static Status initialize(SecureOutputType secure_output_type, + const ClientInfo& client_info, IStorage* storage, + IClock* clock, ITimer* timer, LogLevel verbosity, + const std::string& sandbox_id); + // Query the CDM library version. static const char* version(); @@ -365,6 +376,21 @@ class CDM_EXPORT Cdm : public ITimerClient { static Cdm* create(IEventListener* listener, IStorage* storage, bool privacy_mode); + // This is a variant of the above function that allows the caller to specify + // that the IStorage should be treated as read-only. Passing true for this + // parameter will cause the Widevine CE CDM to prevent attempts to modify any + // data in the IStorage. Note that this is *not* the expected operation mode + // for most clients and will likely lead to playback failures. It should only + // be used in cases where read-only certificates and licenses have been + // pre-loaded on a device, such as the preloaded licenses in ATSC 3. + // + // It is not possible to mix read-only and non-read-only files in the same + // IStorage instance. A separate CDM with a separate IStorage pointing to the + // non-read-only files should be created with the read-only flag omitted or + // set to false. + static Cdm* create(IEventListener* listener, IStorage* storage, + bool privacy_mode, bool storage_is_read_only); + virtual ~Cdm() {} // The following three methods relate to service certificates. A service diff --git a/cdm/include/cdm_version.h b/cdm/include/cdm_version.h index 520edc1d..82e26c58 100644 --- a/cdm/include/cdm_version.h +++ b/cdm/include/cdm_version.h @@ -1,5 +1,5 @@ // Widevine CE CDM Version #ifndef CDM_VERSION -# define CDM_VERSION "15.2.0-non-prod" +# define CDM_VERSION "15.3.0" #endif #define EME_VERSION "https://www.w3.org/TR/2017/REC-encrypted-media-20170918" diff --git a/cdm/include/properties_ce.h b/cdm/include/properties_ce.h index 6ce04730..07bd0b8c 100644 --- a/cdm/include/properties_ce.h +++ b/cdm/include/properties_ce.h @@ -17,6 +17,9 @@ class PropertiesCE { static Cdm::ClientInfo GetClientInfo(); static Cdm::SecureOutputType GetSecureOutputType(); static void SetProvisioningMessagesAreBinary(bool bin_prov); + // If this is set to an empty string, (which is the default) then PropertiesCE + // will report that there is no Sandbox ID in use. + static void SetSandboxId(const std::string& sandbox_id); private: static void SetSecureOutputType(Cdm::SecureOutputType secure_output_type); diff --git a/cdm/src/cdm.cpp b/cdm/src/cdm.cpp index 91fccef9..56861999 100644 --- a/cdm/src/cdm.cpp +++ b/cdm/src/cdm.cpp @@ -38,6 +38,7 @@ using namespace wvcdm; namespace { +constexpr const char* kNoSandboxId = ""; const int64_t kPolicyTimerDurationMilliseconds = 5000; void* const kPolicyTimerContext = nullptr; @@ -108,9 +109,59 @@ class PropertySet : public CdmClientPropertySet { const std::string empty_string_; }; +// A wrapper for another IStorage instance that intercepts attempts to write to +// that storage and blocks them. Acts as a last-ditch safeguard against the CDM +// trying to write erroneously to filesystems where it is critical that we never +// overwrite or delete the files. Specifically, preloaded files on ATSC 3. +// +// TODO(b/148693106): Right now, this wrapper fails writes silently because +// there are known bugs where the CDM will still try to write to it. Once these +// bugs are resolved, we can change this wrapper to return errors on write +// attempts. The wrapper itself will still be necessary, in order to prevent any +// future bugs from corrupting ATSC files. +class ReadOnlyStorage : public Cdm::IStorage { + public: + explicit ReadOnlyStorage(Cdm::IStorage* storage) : storage_(storage) {} + ~ReadOnlyStorage() override {} + + bool read(const std::string& name, std::string* data) override { + return storage_->read(name, data); + } + + bool write(const std::string& name, const std::string& data) override { + // TODO(b/148693106): Once we have resolved the bugs causing the CDM to + // erroneously write to read-only files, change this to an error instead of + // a silent failure. + return true; + } + + bool exists(const std::string& name) override { + return storage_->exists(name); + } + + bool remove(const std::string& name) override { + // TODO(b/148693106): Once we have resolved the bugs causing the CDM to + // erroneously write to read-only files, change this to an error instead of + // a silent failure. + return true; + } + + int32_t size(const std::string& name) override { + return storage_->size(name); + } + + bool list(std::vector* file_names) override { + return storage_->list(file_names); + } + + private: + Cdm::IStorage* storage_; // Lifetime is managed by the caller +}; + class CdmImpl : public Cdm, public WvCdmEventListener { public: - CdmImpl(IEventListener* listener, IStorage* storage, bool privacy_mode); + CdmImpl(IEventListener* listener, IStorage* storage, bool privacy_mode, + const ReadOnlyStorage* owned_storage_object); ~CdmImpl() override; @@ -262,14 +313,23 @@ class CdmImpl : public Cdm, public WvCdmEventListener { }; std::map sessions_; + + // This field will be nullptr unless the create() function had to allocate + // memory for a read-only storage wrapper that needs to be cleaned up when + // this CDM instance is deleted. The storage should always be accessed via + // file_system_ and not via this parameter directly. However, it must be + // stored so it can be cleaned up later. + const std::unique_ptr owned_storage_object_; }; -CdmImpl::CdmImpl(IEventListener* listener, IStorage* storage, bool privacy_mode) +CdmImpl::CdmImpl(IEventListener* listener, IStorage* storage, bool privacy_mode, + const ReadOnlyStorage* owned_storage_object) : listener_(listener), policy_timer_enabled_(false), provisioning_service_certificate_(), file_system_("", storage), - cdm_engine_(CdmEngineFactory::CreateCdmEngine(&file_system_)) { + cdm_engine_(CdmEngineFactory::CreateCdmEngine(&file_system_)), + owned_storage_object_(owned_storage_object) { assert(nullptr != listener_); property_set_.set_use_privacy_mode(privacy_mode); } @@ -292,6 +352,12 @@ Cdm::Status CdmImpl::setServiceCertificate(ServiceRole role, if (role == kLicensingService || role == kAllServices) { property_set_.set_service_certificate(certificate); + + // Update all open sessions with the new certificate. + for (const auto& session_pair : sessions_) { + cdm_engine_->SetSessionServiceCertificate(session_pair.first, + certificate); + } } if (role == kProvisioningService || role == kAllServices) { @@ -680,6 +746,10 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, } else if (result == NEED_PROVISIONING) { LOGE("Device not provisioned"); return kNeedsDeviceCertificate; + } else if (result == PRIVACY_MODE_ERROR_1 || result == PRIVACY_MODE_ERROR_2 || + result == PRIVACY_MODE_ERROR_3) { + LOGE("No licensing service certificate installed"); + return kNeedsServiceCertificate; } else if (result != KEY_MESSAGE) { LOGE("Unexpected error %d", result); return kUnexpectedError; @@ -1501,6 +1571,8 @@ Cdm::Status CdmImpl::ConvertHdcpLevel(const std::string& query_value, *result = kHdcp2_1; } else if (query_value == QUERY_VALUE_HDCP_V2_2) { *result = kHdcp2_2; + } else if (query_value == QUERY_VALUE_HDCP_V2_3) { + *result = kHdcp2_3; } else { return kUnexpectedError; } @@ -1513,6 +1585,15 @@ Cdm::Status CdmImpl::ConvertHdcpLevel(const std::string& query_value, Cdm::Status Cdm::initialize(SecureOutputType secure_output_type, const ClientInfo& client_info, IStorage* storage, IClock* clock, ITimer* timer, LogLevel verbosity) { + return initialize(secure_output_type, client_info, storage, clock, timer, + verbosity, kNoSandboxId); +} + +// static +Cdm::Status Cdm::initialize(SecureOutputType secure_output_type, + const ClientInfo& client_info, IStorage* storage, + IClock* clock, ITimer* timer, LogLevel verbosity, + const std::string& sandbox_id) { // Specify the maximum severity of message that will be output to // the console. See core/include/log.h for the valid priority values. g_cutoff = static_cast(verbosity); @@ -1540,6 +1621,7 @@ Cdm::Status Cdm::initialize(SecureOutputType secure_output_type, PropertiesCE::SetSecureOutputType(secure_output_type); PropertiesCE::SetClientInfo(client_info); + if (sandbox_id != kNoSandboxId) PropertiesCE::SetSandboxId(sandbox_id); Properties::Init(); host.storage = storage; host.clock = clock; @@ -1554,6 +1636,13 @@ const char* Cdm::version() { return CDM_VERSION; } // static Cdm* Cdm::create(IEventListener* listener, IStorage* storage, bool privacy_mode) { + return create(listener, storage, privacy_mode, + false /* storage_is_read_only */); +} + +// static +Cdm* Cdm::create(IEventListener* listener, IStorage* storage, bool privacy_mode, + bool storage_is_read_only) { if (!host.initialized) { LOGE("Not initialized!"); return nullptr; @@ -1567,7 +1656,13 @@ Cdm* Cdm::create(IEventListener* listener, IStorage* storage, return nullptr; } - return new CdmImpl(listener, storage, privacy_mode); + if (storage_is_read_only) { + ReadOnlyStorage* read_only_storage = new ReadOnlyStorage(storage); + return new CdmImpl(listener, read_only_storage, privacy_mode, + read_only_storage); + } else { + return new CdmImpl(listener, storage, privacy_mode, nullptr); + } } } // namespace widevine diff --git a/cdm/src/properties_ce.cpp b/cdm/src/properties_ce.cpp index c514ac78..dd3a972d 100644 --- a/cdm/src/properties_ce.cpp +++ b/cdm/src/properties_ce.cpp @@ -15,6 +15,7 @@ bool use_secure_buffers_ = false; bool use_fifo_ = false; bool use_userspace_buffers_ = true; bool set_provisioning_messages_to_binary_ = false; +std::string sandbox_id_; widevine::Cdm::SecureOutputType secure_output_type_ = widevine::Cdm::kNoSecureOutput; @@ -75,6 +76,11 @@ void PropertiesCE::SetProvisioningMessagesAreBinary(bool new_setting) { set_provisioning_messages_to_binary_ = new_setting; } +// static +void PropertiesCE::SetSandboxId(const std::string& sandbox_id) { + sandbox_id_ = sandbox_id; +} + } // namespace widevine namespace wvcdm { @@ -128,6 +134,7 @@ bool Properties::GetWVCdmVersion(std::string* version) { // static bool Properties::GetDeviceFilesBasePath(CdmSecurityLevel, std::string* base_path) { + if (base_path == nullptr) return false; // A no-op, but successful. base_path->clear(); return true; @@ -146,7 +153,11 @@ bool Properties::GetOEMCryptoPath(std::string*) { } // static -bool Properties::GetSandboxId(std::string* /* sandbox_id */) { return false; } +bool Properties::GetSandboxId(std::string* sandbox_id_ptr) { + if (sandbox_id_.empty() || sandbox_id_ptr == nullptr) return false; + (*sandbox_id_ptr) = sandbox_id_; + return true; +} // static bool Properties::AlwaysUseKeySetIds() { return true; } diff --git a/cdm/test/cdm_test.cpp b/cdm/test/cdm_test.cpp index 1a47cd65..2a097b68 100644 --- a/cdm/test/cdm_test.cpp +++ b/cdm/test/cdm_test.cpp @@ -216,7 +216,7 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { // Reinit the library. Cdm::Status status = Cdm::initialize( Cdm::kNoSecureOutput, PropertiesCE::GetClientInfo(), g_host, g_host, - g_host, static_cast(g_cutoff)); + g_host, static_cast(g_cutoff), g_sandbox_id); ASSERT_EQ(Cdm::kSuccess, status); // Make a fresh CDM. @@ -541,12 +541,11 @@ TEST_F(CdmTest, TestHostTimer) { } TEST_F(CdmTest, Initialize) { - Cdm::Status status; - // Try with an invalid output type. - status = Cdm::initialize(static_cast(-1), - PropertiesCE::GetClientInfo(), g_host, g_host, - g_host, static_cast(g_cutoff)); + Cdm::Status status = + Cdm::initialize(static_cast(-1), + PropertiesCE::GetClientInfo(), g_host, g_host, g_host, + static_cast(g_cutoff), g_sandbox_id); EXPECT_EQ(Cdm::kTypeError, status); // Try with various client info properties missing. @@ -555,77 +554,77 @@ TEST_F(CdmTest, Initialize) { broken_client_info = working_client_info; broken_client_info.product_name.clear(); - status = - Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, g_host, - g_host, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, + g_host, g_host, static_cast(g_cutoff), + g_sandbox_id); EXPECT_EQ(Cdm::kTypeError, status); broken_client_info = working_client_info; broken_client_info.company_name.clear(); - status = - Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, g_host, - g_host, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, + g_host, g_host, static_cast(g_cutoff), + g_sandbox_id); EXPECT_EQ(Cdm::kTypeError, status); broken_client_info = working_client_info; broken_client_info.device_name.clear(); // Not required - status = - Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, g_host, - g_host, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, + g_host, g_host, static_cast(g_cutoff), + g_sandbox_id); EXPECT_EQ(Cdm::kSuccess, status); broken_client_info = working_client_info; broken_client_info.model_name.clear(); - status = - Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, g_host, - g_host, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, + g_host, g_host, static_cast(g_cutoff), + g_sandbox_id); EXPECT_EQ(Cdm::kTypeError, status); broken_client_info = working_client_info; broken_client_info.arch_name.clear(); // Not required - status = - Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, g_host, - g_host, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, + g_host, g_host, static_cast(g_cutoff), + g_sandbox_id); EXPECT_EQ(Cdm::kSuccess, status); broken_client_info = working_client_info; broken_client_info.build_info.clear(); // Not required - status = - Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, g_host, - g_host, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, + g_host, g_host, static_cast(g_cutoff), + g_sandbox_id); EXPECT_EQ(Cdm::kSuccess, status); // Try with various host interfaces missing. - status = - Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, nullptr, - g_host, g_host, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, nullptr, + g_host, g_host, static_cast(g_cutoff), + g_sandbox_id); EXPECT_EQ(Cdm::kTypeError, status); - status = - Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, g_host, - nullptr, g_host, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, g_host, + nullptr, g_host, + static_cast(g_cutoff), g_sandbox_id); EXPECT_EQ(Cdm::kTypeError, status); - status = - Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, g_host, g_host, - nullptr, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, g_host, + g_host, nullptr, + static_cast(g_cutoff), g_sandbox_id); EXPECT_EQ(Cdm::kTypeError, status); // Try all output types. - status = - Cdm::initialize(Cdm::kDirectRender, working_client_info, g_host, g_host, - g_host, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kDirectRender, working_client_info, g_host, + g_host, g_host, static_cast(g_cutoff), + g_sandbox_id); EXPECT_EQ(Cdm::kSuccess, status); - status = - Cdm::initialize(Cdm::kOpaqueHandle, working_client_info, g_host, g_host, - g_host, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kOpaqueHandle, working_client_info, g_host, + g_host, g_host, static_cast(g_cutoff), + g_sandbox_id); EXPECT_EQ(Cdm::kSuccess, status); // One last init with everything correct and working. - status = - Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, g_host, g_host, - g_host, static_cast(g_cutoff)); + status = Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, g_host, + g_host, g_host, static_cast(g_cutoff), + g_sandbox_id); EXPECT_EQ(Cdm::kSuccess, status); } @@ -747,6 +746,34 @@ TEST_F(CdmTest, SetServiceCertificate) { EXPECT_EQ(Cdm::kTypeError, status); } +TEST_F(CdmTest, OpenSessionWithoutServiceCertificate) { + // Create a CDM instance that does not have any service certificates + // installed. + ASSERT_NO_FATAL_FAILURE(CreateAdditionalCdm(true /* privacy_mode */, &cdm_)); + EnsureProvisioned(); + + // Verify that sessions can be opened. + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + + // License request generation, however, should fail. + EXPECT_CALL(*this, onMessage(session_id, _, _)).Times(0); + EXPECT_EQ(Cdm::kNeedsServiceCertificate, + generateRequestWithRetry(session_id, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); + + // Once a service certificate has been set, the existing session should be + // able to generate a request. + ASSERT_EQ(Cdm::kSuccess, + cdm_->setServiceCertificate(Cdm::kLicensingService, + config_.license_service_certificate())); + + EXPECT_CALL(*this, onMessage(session_id, _, _)).Times(AtLeast(1)); + EXPECT_EQ(Cdm::kSuccess, + generateRequestWithRetry(session_id, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); +} + TEST_F(CdmTest, GetRobustnessLevel) { Cdm::RobustnessLevel level; Cdm::Status status = cdm_->getRobustnessLevel(&level); @@ -1981,6 +2008,10 @@ TEST_F(CdmTest, GetStatusForHdcpResolution) { ASSERT_EQ(Cdm::kSuccess, cdm_->getStatusForHdcpVersion(Cdm::kHdcp2_2, &key_status)); EXPECT_THAT(key_status, AnyOf(Cdm::kUsable, Cdm::kOutputRestricted)); + + ASSERT_EQ(Cdm::kSuccess, + cdm_->getStatusForHdcpVersion(Cdm::kHdcp2_3, &key_status)); + EXPECT_THAT(key_status, AnyOf(Cdm::kUsable, Cdm::kOutputRestricted)); } TEST_F(CdmTest, HandlesKeyRotationWithOnlyOneLicenseRequest) { diff --git a/cdm/test/cdm_test_main.cpp b/cdm/test/cdm_test_main.cpp index c8412b65..49667ff9 100644 --- a/cdm/test/cdm_test_main.cpp +++ b/cdm/test/cdm_test_main.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include +#include +#include #if defined(__linux__) #include @@ -19,11 +23,34 @@ using namespace widevine; TestHost* g_host = nullptr; +std::string g_sandbox_id = ""; + +namespace { +constexpr const char kSandboxIdParam[] = "--sandbox_id="; + +// Following the pattern established by help text in test_base.cpp +constexpr const char kExtraHelpText[] = + " --sandbox_id=\n" + " Specifies the Sandbox ID that should be sent to OEMCrypto via\n" + " OEMCrypto_SetSandbox(). On most platforms, since Sandbox IDs are not\n" + " in use, this parameter should be omitted.\n"; +} // namespace int main(int argc, char** argv) { // Init gtest and let it consume arguments. ::testing::InitGoogleTest(&argc, argv); + // Find and filter out the Sandbox ID, if any. + std::vector args(argv, argv + argc); + auto sandbox_id_iter = std::find_if(std::begin(args) + 1, std::end(args), + [](const std::string& elem) -> bool { + return elem.find(kSandboxIdParam) == 0; + }); + if (sandbox_id_iter != std::end(args)) { + g_sandbox_id = sandbox_id_iter->substr(strlen(kSandboxIdParam)); + args.erase(sandbox_id_iter); + } + // Set up a Host and initialize the library. This makes these services // available to the tests. We would do this in the test suite itself, but the // core & OEMCrypto tests don't know they depend on this for storage. @@ -46,15 +73,22 @@ int main(int argc, char** argv) { #endif client_info.build_info = __DATE__; - Cdm::Status status = - Cdm::initialize(Cdm::kNoSecureOutput, client_info, g_host, g_host, g_host, - static_cast(wvcdm::g_cutoff)); + Cdm::Status status = Cdm::initialize( + Cdm::kNoSecureOutput, client_info, g_host, g_host, g_host, + static_cast(wvcdm::g_cutoff), g_sandbox_id); (void)status; // status is now used when assertions are turned off. assert(status == Cdm::kSuccess); - // This must take place after the call to Cdm::initialize() because it may - // make calls that are only valid after the library is initialized. - if (!wvcdm::WvCdmTestBase::Initialize(argc, argv)) return 0; + std::vector new_argv(args.size()); + std::transform( + std::begin(args), std::end(args), std::begin(new_argv), + [](const std::string& arg) -> const char* { return arg.c_str(); }); + // This must take place after the call to Cdm::initialize() because it makes + // calls that are only valid after the library is initialized. + if (!wvcdm::WvCdmTestBase::Initialize(new_argv.size(), new_argv.data(), + kExtraHelpText)) { + return 0; + } return RUN_ALL_TESTS(); } diff --git a/cdm/test/cdm_test_printers.cpp b/cdm/test/cdm_test_printers.cpp index a8b29aa2..874a04b1 100644 --- a/cdm/test/cdm_test_printers.cpp +++ b/cdm/test/cdm_test_printers.cpp @@ -31,18 +31,7 @@ void PrintTo(const Cdm::Status& value, ::std::ostream* os) { case Cdm::kSuccess: *os << "Cdm::kSuccess"; break; - case Cdm::kNeedsDeviceCertificate: - *os << "Cdm::kNeedsDeviceCertificate"; - break; - case Cdm::kSessionNotFound: - *os << "Cdm::kSessionNotFound"; - break; - case Cdm::kDecryptError: - *os << "Cdm::kDecryptError"; - break; - case Cdm::kNoKey: - *os << "Cdm::kNoKey"; - break; + case Cdm::kTypeError: *os << "Cdm::kTypeError"; break; @@ -55,12 +44,45 @@ void PrintTo(const Cdm::Status& value, ::std::ostream* os) { case Cdm::kQuotaExceeded: *os << "Cdm::kQuotaExceeded"; break; + + case Cdm::kNeedsDeviceCertificate: + *os << "Cdm::kNeedsDeviceCertificate"; + break; + case Cdm::kSessionNotFound: + *os << "Cdm::kSessionNotFound"; + break; + case Cdm::kDecryptError: + *os << "Cdm::kDecryptError"; + break; + case Cdm::kNoKey: + *os << "Cdm::kNoKey"; + break; + case Cdm::kKeyUsageBlockedByPolicy: + *os << "Cdm::kKeyUsageBlockedByPolicy"; + break; case Cdm::kRangeError: *os << "Cdm::kRangeError"; break; + case Cdm::kResourceContention: + *os << "Cdm::kResourceContention"; + break; + case Cdm::kSessionStateLost: + *os << "Cdm::kSessionStateLost"; + break; + case Cdm::kSystemStateLost: + *os << "Cdm::kSystemStateLost"; + break; + case Cdm::kOutputTooLarge: + *os << "Cdm::kOutputTooLarge"; + break; + case Cdm::kNeedsServiceCertificate: + *os << "Cdm::kNeedsServiceCertificate"; + break; + case Cdm::kUnexpectedError: *os << "Cdm::kUnexpectedError"; break; + default: *os << "Unknown Cdm::Status value " << value; break; @@ -84,6 +106,9 @@ void PrintTo(const Cdm::KeyStatus& value, ::std::ostream* os) { case Cdm::kInternalError: *os << "Cdm::kInternalError"; break; + case Cdm::kReleased: + *os << "Cdm::kReleased"; + break; default: *os << "Unknown Cdm::KeyStatus value " << value; break; diff --git a/cdm/test/test_host.h b/cdm/test/test_host.h index f1a11adb..f9d2a79a 100644 --- a/cdm/test/test_host.h +++ b/cdm/test/test_host.h @@ -66,5 +66,6 @@ class TestHost : public widevine::Cdm::IStorage, // Owned and managed by the test runner. extern TestHost* g_host; +extern std::string g_sandbox_id; #endif // WVCDM_CDM_TEST_TEST_HOST_H_ diff --git a/core/include/cdm_engine.h b/core/include/cdm_engine.h index 309e72f7..7061cf83 100644 --- a/core/include/cdm_engine.h +++ b/core/include/cdm_engine.h @@ -137,6 +137,10 @@ class CdmEngine { virtual CdmResponseType RenewKey(const CdmSessionId& session_id, const CdmKeyResponse& key_data); + // Change the service certificate installed in a given session. + virtual CdmResponseType SetSessionServiceCertificate( + const CdmSessionId& session_id, const std::string& service_certificate); + // Query system information virtual CdmResponseType QueryStatus(SecurityLevel security_level, const std::string& query_token, diff --git a/core/include/cdm_session.h b/core/include/cdm_session.h index 11d8eee6..2483b98e 100644 --- a/core/include/cdm_session.h +++ b/core/include/cdm_session.h @@ -43,19 +43,23 @@ class CdmSession { bool IsClosed() { return closed_; } // Initializes this instance of CdmSession with the given property set. - // |cdm_client_property_set| MAY be null, is owned by the caller, - // and must remain in scope throughout the scope of this session. + // + // |cdm_client_property_set| is caller owned, may be null, but must be in + // scope as long as the session is in scope. The service certificate field is + // cached at the time Init() is called. virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set); // Initializes this instance of CdmSession with the given parmeters. // All parameters are owned by the caller. - // |service_certificate| is caller owned, cannot be null, and must be in - // scope as long as the session is in scope. - // |cdm_client_property_set| is caller owned, may be null, but must be - // in scope as long as the session is in scope. + // + // |cdm_client_property_set| is caller owned, may be null, but must be in + // scope as long as the session is in scope. The service certificate field is + // cached at the time Init() is called. + // // |forced_session_id| is caller owned and may be null. - // |event_listener| is caller owned, may be null, but must be in scope - // as long as the session is in scope. + // + // |event_listener| is caller owned, may be null, but must be in scope as long + // as the session is in scope. virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set, const CdmSessionId* forced_session_id, WvCdmEventListener* event_listener); @@ -84,6 +88,11 @@ class CdmSession { // AddKey() - Accept license response and extract key info. virtual CdmResponseType AddKey(const CdmKeyResponse& key_response); + // Override the currently-installed service certificate with a new service + // certificate. + virtual CdmResponseType SetServiceCertificate( + const std::string& service_certificate); + // Query session status virtual CdmResponseType QueryStatus(CdmQueryMap* query_response); diff --git a/core/include/license.h b/core/include/license.h index 91b9708d..b641a743 100644 --- a/core/include/license.h +++ b/core/include/license.h @@ -44,6 +44,11 @@ class CdmLicense { const std::string& signed_service_certificate, CryptoSession* session, PolicyEngine* policy_engine); + // Override the currently-installed service certificate with a new service + // certificate. + virtual CdmResponseType SetServiceCertificate( + const std::string& signed_service_certificate); + virtual CdmResponseType PrepareKeyRequest( const InitializationData& init_data, CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request, diff --git a/core/include/wv_cdm_types.h b/core/include/wv_cdm_types.h index 88c26046..b51798d9 100644 --- a/core/include/wv_cdm_types.h +++ b/core/include/wv_cdm_types.h @@ -399,7 +399,11 @@ enum CdmResponseType { REWRAP_DEVICE_RSA_KEY_30_ERROR = 345, INVALID_SRM_LIST = 346, KEYSET_ID_NOT_FOUND_4 = 347, - // Don't forget to add new values to ../test/test_printers.cpp. + SESSION_NOT_FOUND_22 = 348, + // Don't forget to add new values to + // * core/test/test_printers.cpp. + // * android/include/mapErrors-inl.h + // * android/include_hidl/mapErrors-inl.h }; enum CdmKeyStatus { diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp index abea390c..3058efcd 100644 --- a/core/src/cdm_engine.cpp +++ b/core/src/cdm_engine.cpp @@ -523,6 +523,19 @@ CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id, return KEY_ADDED; } +CdmResponseType CdmEngine::SetSessionServiceCertificate( + const CdmSessionId& session_id, const std::string& service_certificate) { + LOGI("Setting service certificate: session_id = %s", session_id.c_str()); + + std::shared_ptr session; + if (!session_map_.FindSession(session_id, &session)) { + LOGE("Session ID not found: %s", session_id.c_str()); + return SESSION_NOT_FOUND_22; + } + + return session->SetServiceCertificate(service_certificate); +} + CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, const std::string& query_token, std::string* query_response) { diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp index 9e3ee8e6..91f20c9f 100644 --- a/core/src/cdm_session.cpp +++ b/core/src/cdm_session.cpp @@ -599,6 +599,11 @@ CdmResponseType CdmSession::QueryStatus(CdmQueryMap* query_response) { return NO_ERROR; } +CdmResponseType CdmSession::SetServiceCertificate( + const std::string& service_certificate) { + return license_parser_->SetServiceCertificate(service_certificate); +} + CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* query_response) { return policy_engine_->Query(query_response); } diff --git a/core/src/license.cpp b/core/src/license.cpp index d7dcd761..12dc1921 100644 --- a/core/src/license.cpp +++ b/core/src/license.cpp @@ -226,16 +226,9 @@ bool CdmLicense::Init(const std::string& client_token, return false; } - if (use_privacy_mode) { - if (!signed_service_certificate.empty()) { - if (service_certificate_.Init(signed_service_certificate) != NO_ERROR) - return false; - } - if (!service_certificate_.has_certificate() && - !Properties::allow_service_certificate_requests()) { - LOGE("CdmLicense::Init: Required service certificate not provided"); - return false; - } + if (use_privacy_mode && !signed_service_certificate.empty() && + service_certificate_.Init(signed_service_certificate) != NO_ERROR) { + return false; } client_token_ = client_token; @@ -248,6 +241,11 @@ bool CdmLicense::Init(const std::string& client_token, return true; } +CdmResponseType CdmLicense::SetServiceCertificate( + const std::string& signed_service_certificate) { + return service_certificate_.Init(signed_service_certificate); +} + CdmResponseType CdmLicense::PrepareKeyRequest( const InitializationData& init_data, CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request, diff --git a/core/test/cdm_engine_test.cpp b/core/test/cdm_engine_test.cpp index 87b09f5c..9ec09e9b 100644 --- a/core/test/cdm_engine_test.cpp +++ b/core/test/cdm_engine_test.cpp @@ -44,6 +44,8 @@ const std::string kWebmMimeType = "video/webm"; const std::string kEmptyString; const std::string kComma = ","; +const std::string kFakeSessionId = "TotallyARealSession123456789"; + } // namespace class WvCdmEnginePreProvTest : public WvCdmTestBase { @@ -317,6 +319,27 @@ TEST_F(WvCdmEnginePreProvTestUat, ProvisioningServiceCertificateInvalidTest) { ASSERT_NE(cdm_engine_.ValidateServiceCertificate(certificate), NO_ERROR); }; +TEST_F(WvCdmEngineTest, SetLicensingServiceValidCertificate) { + ASSERT_EQ(cdm_engine_.SetSessionServiceCertificate( + session_id_, config_.license_service_certificate()), + NO_ERROR); +}; + +TEST_F(WvCdmEngineTest, SetLicensingServiceCertificateUnknownSession) { + ASSERT_EQ(cdm_engine_.SetSessionServiceCertificate( + kFakeSessionId, config_.license_service_certificate()), + SESSION_NOT_FOUND_22); +}; + +TEST_F(WvCdmEngineTest, SetLicensingServiceInvalidCertificate) { + std::string certificate = config_.license_service_certificate(); + // Add four nulls to the beginning of the cert to invalidate it + certificate.insert(0, 4, '\0'); + + ASSERT_NE(cdm_engine_.SetSessionServiceCertificate(session_id_, certificate), + NO_ERROR); +}; + TEST_F(WvCdmEnginePreProvTestStaging, ProvisioningTest) { Provision(); } TEST_F(WvCdmEnginePreProvTestUatBinary, ProvisioningTest) { diff --git a/core/test/license_unittest.cpp b/core/test/license_unittest.cpp index ea387fb1..c17c67fc 100644 --- a/core/test/license_unittest.cpp +++ b/core/test/license_unittest.cpp @@ -282,10 +282,9 @@ TEST_F(CdmLicenseTest, InitWithEmptyServiceCert) { EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); CreateCdmLicense(); - EXPECT_EQ(cdm_license_->Init(kToken, kClientTokenDrmCert, "", true, - kEmptyServiceCertificate, crypto_session_, - policy_engine_), - Properties::allow_service_certificate_requests()); + EXPECT_TRUE(cdm_license_->Init(kToken, kClientTokenDrmCert, "", true, + kEmptyServiceCertificate, crypto_session_, + policy_engine_)); } TEST_F(CdmLicenseTest, InitWithInvalidServiceCert) { diff --git a/core/test/test_base.cpp b/core/test/test_base.cpp index 570bed46..99892cd7 100644 --- a/core/test/test_base.cpp +++ b/core/test/test_base.cpp @@ -30,7 +30,7 @@ using wvcdm::metrics::EngineMetrics; namespace wvcdm { namespace { -void show_menu(char* prog_name) { +void show_menu(const char* prog_name, const std::string& extra_help_text) { std::cout << std::endl; std::cout << "usage: " << prog_name << " [options]" << std::endl << std::endl; std::cout << " enclose multiple arguments in '' when using adb shell" @@ -90,6 +90,8 @@ void show_menu(char* prog_name) { std::cout << " configure the provisioning server url, please include http[s]" << " in the url" << std::endl << std::endl; + + std::cout << extra_help_text << std::endl; } /* @@ -343,7 +345,8 @@ void WvCdmTestBase::EnsureProvisioned() { ASSERT_EQ(NO_ERROR, cdm_engine.CloseSession(session_id)); } -bool WvCdmTestBase::Initialize(int argc, char **argv) { +bool WvCdmTestBase::Initialize(int argc, const char* const argv[], + const std::string& extra_help_text) { Properties::Init(); bool is_cast_receiver = false; bool force_load_test_keybox = false; // TODO(fredgc): obsolete. remove. @@ -403,7 +406,7 @@ bool WvCdmTestBase::Initialize(int argc, char **argv) { } if (show_usage) { - show_menu(argv[0]); + show_menu(argv[0], extra_help_text); return false; } diff --git a/core/test/test_base.h b/core/test/test_base.h index c93c5365..3d53a8c8 100644 --- a/core/test/test_base.h +++ b/core/test/test_base.h @@ -5,6 +5,9 @@ #ifndef WVCDM_CORE_TEST_BASE_H_ #define WVCDM_CORE_TEST_BASE_H_ +#include +#include + #include #include "cdm_engine.h" @@ -26,8 +29,13 @@ class WvCdmTestBase : public ::testing::Test { // Returns true if the test program should continue, if false, the caller // should exit. This should be called by main() to allow the user to pass in - // command line switches. - static bool Initialize(int argc, char **argv); + // command line switches. The |extra_help_text| parameter can be used to + // append platform-specific information to the usage information printed when + // invalid flags are detected. For instance, a platform might add information + // about platform-specific flags that were already parsed before calling + // Initialize(). + static bool Initialize(int argc, const char* const argv[], + const std::string& extra_help_text = std::string()); // Install a test keybox, if appropriate. static void InstallTestRootOfTrust(); diff --git a/core/test/test_printers.cpp b/core/test/test_printers.cpp index c7af0f8c..e312531b 100644 --- a/core/test/test_printers.cpp +++ b/core/test/test_printers.cpp @@ -781,6 +781,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case SESSION_NOT_FOUND_21: *os << "SESSION_NOT_FOUND_21"; break; + case SESSION_NOT_FOUND_22: + *os << "SESSION_NOT_FOUND_22"; + break; case INVALID_DECRYPT_HASH_FORMAT: *os << "INVALID_DECRYPT_HASH_FORMAT"; break; diff --git a/oemcrypto/test/oec_session_util.cpp b/oemcrypto/test/oec_session_util.cpp index b041a034..f0314c4f 100644 --- a/oemcrypto/test/oec_session_util.cpp +++ b/oemcrypto/test/oec_session_util.cpp @@ -823,13 +823,12 @@ void Session::EncryptCTR(const vector& in_buffer, const uint8_t* key, void Session::TestDecryptCTR(bool select_key_first, OEMCryptoResult expected_result, int key_index) { - OEMCryptoResult sts; + OEMCryptoResult select_result = OEMCrypto_SUCCESS; if (select_key_first) { // Select the key (from FillSimpleMessage) - sts = OEMCrypto_SelectKey(session_id(), license_.keys[key_index].key_id, - license_.keys[key_index].key_id_length, - OEMCrypto_CipherMode_CTR); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); + select_result = OEMCrypto_SelectKey( + session_id(), license_.keys[key_index].key_id, + license_.keys[key_index].key_id_length, OEMCrypto_CipherMode_CTR); } vector unencryptedData(256); @@ -853,38 +852,42 @@ void Session::TestDecryptCTR(bool select_key_first, pattern.skip = 0; pattern.offset = 0; // Decrypt the data - sts = OEMCrypto_DecryptCENC( + const OEMCryptoResult decrypt_result = OEMCrypto_DecryptCENC( session_id(), encryptedData.data(), encryptedData.size(), true, encryptionIv.data(), 0, &destBuffer, &pattern, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); // We only have a few errors that we test are reported. + ASSERT_NO_FATAL_FAILURE( + TestDecryptResult(expected_result, select_result, decrypt_result)) + << "Either SelectKey or DecryptCENC should return " << expected_result + << ", but they returned " << select_result << " and " << decrypt_result + << ", respectively."; if (expected_result == OEMCrypto_SUCCESS) { // No error. - ASSERT_EQ(OEMCrypto_SUCCESS, sts); ASSERT_EQ(unencryptedData, outputBuffer); } else { - ASSERT_NO_FATAL_FAILURE(TestDecryptResult(expected_result, sts)); ASSERT_NE(unencryptedData, outputBuffer); } } void Session::TestDecryptResult(OEMCryptoResult expected_result, - OEMCryptoResult actual_result) { - + OEMCryptoResult actual_select_result, + OEMCryptoResult actual_decrypt_result) { + // In most cases, we expect the result to come from either the select key or + // from the decrypt call. if (expected_result == OEMCrypto_SUCCESS) { // No error. - ASSERT_EQ(OEMCrypto_SUCCESS, actual_result); - } else if (expected_result == OEMCrypto_ERROR_KEY_EXPIRED && - global_features.api_version >= 9) { - // Report stale keys, required in v9 and beyond. - ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, actual_result); - } else if (expected_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP) { - // Report HDCP errors. - ASSERT_EQ(OEMCrypto_ERROR_INSUFFICIENT_HDCP, actual_result); - } else if (expected_result == OEMCrypto_ERROR_ANALOG_OUTPUT) { - // Report analog errors. - ASSERT_EQ(OEMCrypto_ERROR_ANALOG_OUTPUT, actual_result); + ASSERT_EQ(OEMCrypto_SUCCESS, actual_select_result); + ASSERT_EQ(OEMCrypto_SUCCESS, actual_decrypt_result); + } else if (expected_result == OEMCrypto_ERROR_KEY_EXPIRED || + expected_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP || + expected_result == OEMCrypto_ERROR_ANALOG_OUTPUT) { + // Key expired or output problems may be reported from select key or + // decrypt, but must be reported. + ASSERT_TRUE(actual_select_result == expected_result || + actual_decrypt_result == expected_result); } else { // OEM's can fine tune other error codes for debugging. - ASSERT_NE(OEMCrypto_SUCCESS, actual_result); + ASSERT_TRUE(actual_select_result != OEMCrypto_SUCCESS || + actual_decrypt_result != OEMCrypto_SUCCESS); } } diff --git a/oemcrypto/test/oec_session_util.h b/oemcrypto/test/oec_session_util.h index f1627601..8ed65055 100644 --- a/oemcrypto/test/oec_session_util.h +++ b/oemcrypto/test/oec_session_util.h @@ -253,10 +253,6 @@ class Session { void TestDecryptCTR(bool select_key_first = true, OEMCryptoResult expected_result = OEMCrypto_SUCCESS, int key_index = 0); - // This compares the actual result with the expected result. If OEMCrypto is - // an older version, we allow it to report an equivalent error code. - void TestDecryptResult(OEMCryptoResult expected_result, - OEMCryptoResult actual_result); // Verify that an attempt to select an expired key either succeeds, or gives // an actionable error code. void TestSelectExpired(unsigned int key_index); @@ -414,6 +410,11 @@ class Session { const uint8_t* encrypted_entitled_message_ptr(); private: + // This compares the actual result with the expected result. If OEMCrypto is + // an older version, we allow it to report an equivalent error code. + void TestDecryptResult(OEMCryptoResult expected_result, + OEMCryptoResult actual_select_result, + OEMCryptoResult actual_decryt_result); // Generate mac and enc keys give the master key. void DeriveKeys(const uint8_t* master_key, const vector& mac_key_context, diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index 480149d9..ec1c4b21 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -4773,8 +4773,7 @@ TEST_F(GenericCryptoTest, KeyDurationEncrypt) { OEMCryptoResult status = OEMCrypto_Generic_Encrypt( session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), iv_, OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data()); - ASSERT_NO_FATAL_FAILURE( - session_.TestDecryptResult(OEMCrypto_ERROR_KEY_EXPIRED, status)); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); ASSERT_NE(encrypted, expected_encrypted); ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); } @@ -4808,8 +4807,7 @@ TEST_F(GenericCryptoTest, KeyDurationDecrypt) { OEMCryptoResult status = OEMCrypto_Generic_Decrypt( session_.session_id(), encrypted.data(), encrypted.size(), iv_, OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); - ASSERT_NO_FATAL_FAILURE( - session_.TestDecryptResult(OEMCrypto_ERROR_KEY_EXPIRED, status)); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); ASSERT_NE(clear_buffer_, resultant); ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); } @@ -4845,8 +4843,7 @@ TEST_F(GenericCryptoTest, KeyDurationSign) { OEMCryptoResult status = OEMCrypto_Generic_Sign( session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), OEMCrypto_HMAC_SHA256, signature.data(), &signature_length); - ASSERT_NO_FATAL_FAILURE( - session_.TestDecryptResult(OEMCrypto_ERROR_KEY_EXPIRED, status)); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); ASSERT_NE(expected_signature, signature); ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); } @@ -4879,8 +4876,7 @@ TEST_F(GenericCryptoTest, KeyDurationVerify) { OEMCryptoResult status = OEMCrypto_Generic_Verify( session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), OEMCrypto_HMAC_SHA256, signature.data(), signature.size()); - ASSERT_NO_FATAL_FAILURE( - session_.TestDecryptResult(OEMCrypto_ERROR_KEY_EXPIRED, status)); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); } diff --git a/third_party/gyp/input.py b/third_party/gyp/input.py index 39bdbbb3..4c128916 100644 --- a/third_party/gyp/input.py +++ b/third_party/gyp/input.py @@ -54,6 +54,23 @@ path_sections = set() per_process_data = {} per_process_aux_data = {} +try: + _str_types = (basestring,) +# There's no basestring in python3. +except NameError: + _str_types = (str,) + +try: + _int_types = (int, long) +# There's no long in python3. +except NameError: + _int_types = (int,) + +# Shortcuts as we use these combos a lot. +_str_int_types = _str_types + _int_types +_str_int_list_types = _str_int_types + (list,) + + def IsPathSection(section): # If section ends in one of the '=+?!' characters, it's applied to a section # without the trailing characters. '/' is notably absent from this list, @@ -85,11 +102,13 @@ def IsPathSection(section): base_non_configuration_keys = [ # Sections that must exist inside targets and not configurations. 'actions', + 'all_dependent_settings', 'configurations', 'copies', 'default_configuration', 'dependencies', 'dependencies_original', + 'direct_dependent_settings', 'libraries', 'postbuilds', 'product_dir', @@ -221,7 +240,7 @@ def LoadOneBuildFile(build_file_path, data, aux_data, includes, return data[build_file_path] if os.path.exists(build_file_path): - build_file_contents = open(build_file_path).read() + build_file_contents = open(build_file_path, 'rb').read().decode('utf-8') else: raise GypError("%s not found (cwd: %s)" % (build_file_path, os.getcwd())) @@ -638,7 +657,7 @@ def IsStrCanonicalInt(string): The canonical form is such that str(int(string)) == string. """ - if type(string) is str: + if isinstance(string, _str_types): # This function is called a lot so for maximum performance, avoid # involving regexps which would otherwise make the code much # shorter. Regexps would need twice the time of this function. @@ -908,7 +927,7 @@ def ExpandVariables(input, phase, variables, build_file): # in python 2.5 and later. raise GypError("Call to '%s' returned exit status %d while in %s." % (contents, p.returncode, build_file)) - replacement = p_stdout.rstrip() + replacement = p_stdout.decode('utf-8').rstrip() cached_command_results[cache_key] = replacement else: @@ -938,7 +957,7 @@ def ExpandVariables(input, phase, variables, build_file): if type(replacement) is list: for item in replacement: - if not contents[-1] == '/' and type(item) not in (str, int): + if not contents[-1] == '/' and not isinstance(item, _str_int_types): raise GypError('Variable ' + contents + ' must expand to a string or list of strings; ' + 'list contains a ' + @@ -948,7 +967,7 @@ def ExpandVariables(input, phase, variables, build_file): # with conditions sections. ProcessVariablesAndConditionsInList(replacement, phase, variables, build_file) - elif type(replacement) not in (str, int): + elif not isinstance(replacement, _str_int_types): raise GypError('Variable ' + str(contents) + ' must expand to a string or list of strings; ' + 'found a ' + replacement.__class__.__name__) @@ -1067,7 +1086,7 @@ def EvalSingleCondition( # use a command expansion directly inside a condition. cond_expr_expanded = ExpandVariables(cond_expr, phase, variables, build_file) - if type(cond_expr_expanded) not in (str, int): + if not isinstance(cond_expr_expanded, _str_int_types): raise ValueError( 'Variable expansion in this context permits str and int ' + \ 'only, found ' + cond_expr_expanded.__class__.__name__) @@ -1143,7 +1162,7 @@ def LoadAutomaticVariablesFromDict(variables, the_dict): # Any keys with plain string values in the_dict become automatic variables. # The variable name is the key name with a "_" character prepended. for key, value in the_dict.items(): - if type(value) in (str, int, list): + if isinstance(value, _str_int_list_types): variables['_' + key] = value @@ -1156,7 +1175,7 @@ def LoadVariablesFromVariablesDict(variables, the_dict, the_dict_key): # (it could be a list or it could be parentless because it is a root dict), # the_dict_key will be None. for key, value in the_dict.get('variables', {}).items(): - if type(value) not in (str, int, list): + if not isinstance(value, _str_int_list_types): continue if key.endswith('%'): @@ -1209,9 +1228,9 @@ def ProcessVariablesAndConditionsInDict(the_dict, phase, variables_in, for key, value in the_dict.items(): # Skip "variables", which was already processed if present. - if key != 'variables' and type(value) is str: + if key != 'variables' and isinstance(value, _str_types): expanded = ExpandVariables(value, phase, variables, build_file) - if type(expanded) not in (str, int): + if not isinstance(expanded, _str_int_types): raise ValueError( 'Variable expansion in this context permits str and int ' + \ 'only, found ' + expanded.__class__.__name__ + ' for ' + key) @@ -1268,7 +1287,7 @@ def ProcessVariablesAndConditionsInDict(the_dict, phase, variables_in, for key, value in the_dict.items(): # Skip "variables" and string values, which were already processed if # present. - if key == 'variables' or type(value) is str: + if key == 'variables' or isinstance(value, _str_types): continue if type(value) is dict: # Pass a copy of the variables dict so that subdicts can't influence @@ -1282,7 +1301,7 @@ def ProcessVariablesAndConditionsInDict(the_dict, phase, variables_in, # copy is necessary here. ProcessVariablesAndConditionsInList(value, phase, variables, build_file) - elif type(value) is not int: + elif not isinstance(value, _int_types): raise TypeError('Unknown type ' + value.__class__.__name__ + \ ' for ' + key) @@ -1299,9 +1318,9 @@ def ProcessVariablesAndConditionsInList(the_list, phase, variables, ProcessVariablesAndConditionsInDict(item, phase, variables, build_file) elif type(item) is list: ProcessVariablesAndConditionsInList(item, phase, variables, build_file) - elif type(item) is str: + elif isinstance(item, _str_types): expanded = ExpandVariables(item, phase, variables, build_file) - if type(expanded) in (str, int): + if isinstance(expanded, _str_int_types): the_list[index] = expanded elif type(expanded) is list: the_list[index:index+1] = expanded @@ -1315,7 +1334,7 @@ def ProcessVariablesAndConditionsInList(the_list, phase, variables, 'Variable expansion in this context permits strings and ' + \ 'lists only, found ' + expanded.__class__.__name__ + ' at ' + \ index) - elif type(item) is not int: + elif not isinstance(item, _int_types): raise TypeError('Unknown type ' + item.__class__.__name__ + \ ' at index ' + index) index = index + 1 @@ -2050,14 +2069,14 @@ def MergeLists(to, fro, to_file, fro_file, is_paths=False, append=True): hashable_to_set = set(x for x in to if is_hashable(x)) for item in fro: singleton = False - if type(item) in (str, int): + if isinstance(item, _str_int_types): # The cheap and easy case. if is_paths: to_item = MakePathRelative(to_file, fro_file, item) else: to_item = item - if not (type(item) is str and item.startswith('-')): + if not (isinstance(item, _str_types) and item.startswith('-')): # Any string that doesn't begin with a "-" is a singleton - it can # only appear once in a list, to be enforced by the list merge append # or prepend. @@ -2114,8 +2133,8 @@ def MergeDicts(to, fro, to_file, fro_file): # modified. if k in to: bad_merge = False - if type(v) in (str, int): - if type(to[k]) not in (str, int): + if isinstance(v, _str_int_types): + if not isinstance(to[k], _str_int_types): bad_merge = True elif type(v) is not type(to[k]): bad_merge = True @@ -2125,7 +2144,7 @@ def MergeDicts(to, fro, to_file, fro_file): 'Attempt to merge dict value of type ' + v.__class__.__name__ + \ ' into incompatible type ' + to[k].__class__.__name__ + \ ' for key ' + k) - if type(v) in (str, int): + if isinstance(v, _str_int_types): # Overwrite the existing value, if any. Cheap and easy. is_path = IsPathSection(k) if is_path: @@ -2600,7 +2619,7 @@ def ValidateRunAsInTarget(target, target_dict, build_file): "must be a list." % (target_name, build_file)) working_directory = run_as.get('working_directory') - if working_directory and type(working_directory) is not str: + if working_directory and not isinstance(working_directory, _str_types): raise GypError("The 'working_directory' for 'run_as' in target %s " "in file %s should be a string." % (target_name, build_file)) @@ -2635,7 +2654,7 @@ def TurnIntIntoStrInDict(the_dict): # Use items instead of iteritems because there's no need to try to look at # reinserted keys and their associated values. for k, v in the_dict.items(): - if type(v) is int: + if isinstance(v, _int_types): v = str(v) the_dict[k] = v elif type(v) is dict: @@ -2643,7 +2662,7 @@ def TurnIntIntoStrInDict(the_dict): elif type(v) is list: TurnIntIntoStrInList(v) - if type(k) is int: + if isinstance(k, _int_types): del the_dict[k] the_dict[str(k)] = v @@ -2652,7 +2671,7 @@ def TurnIntIntoStrInList(the_list): """Given list the_list, recursively converts all integers into strings. """ for index, item in enumerate(the_list): - if type(item) is int: + if isinstance(item, _int_types): the_list[index] = str(item) elif type(item) is dict: TurnIntIntoStrInDict(item) diff --git a/third_party/gyp/xcodeproj_file.py b/third_party/gyp/xcodeproj_file.py index 9220513e..19edcb07 100644 --- a/third_party/gyp/xcodeproj_file.py +++ b/third_party/gyp/xcodeproj_file.py @@ -1533,6 +1533,7 @@ class PBXFileReference(XCFileLikeElement, XCContainerPortal, XCRemoteObject): 'xcdatamodeld':'wrapper.xcdatamodeld', 'xib': 'file.xib', 'y': 'sourcecode.yacc', + 'tbd': 'sourcecode.text-based-dylib-definition', } prop_map = {