diff --git a/CHANGELOG.md b/CHANGELOG.md index bb4d4137..97050e12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +## 3.1.0 (2016-07-18) + +Features: + - Updates to conform to EME June 10, 2016 Specification + (http://www.w3.org/TR/2016/WD-encrypted-media-20160610/) + - Add per-origin storage of all persistent data. + - Use EME Direct Individualization to provision devices. + - Add IEventListener::onDirectIndividualizationRequest() callback. + - A "license-release" message is no longer fired on calls to load(). + - Add CDM entry points for generic crypto operations (Cdm::genericEncrypt(), + Cdm::genericDecrypt(), Cdm::genericSign(), Cdm::genericVerify()). + - Add support for CENC 3.0 and decryption of encrypted HLS content. + - Add support for querying allowed usage for a key + (Cdm::getKeyAllowedUsages()). + - Upgrade to OEMCrypto v11. + - Numerous unit test additions and improvements. + - Add jsmn to third\_party/. + +Bugfixes: + - Remove IEventListener::onMessageUrl() callback. + - Don't check/validate crypto mode when Decrypt is called with unencrypted + data. + - Ensure keys are loaded before sending OnKeyStatusChange notifications. + This avoids errors due to prematurely checking key statuses. + - Correctly handle a bad RSA key. + ## 3.0.5 (2015-12-16) Features: diff --git a/README.pdf b/README.pdf index fbd9ebc4..065d8629 100644 Binary files a/README.pdf and b/README.pdf differ diff --git a/cdm/cdm.gyp b/cdm/cdm.gyp index ec62e674..32b37333 100644 --- a/cdm/cdm.gyp +++ b/cdm/cdm.gyp @@ -7,7 +7,7 @@ { 'variables': { # Override if you intend to link against a different OEMCrypto API version. - 'oemcrypto_version%': 10, + 'oemcrypto_version%': 11, # Override if you can't depend on OpenSSL for privacy features. # If set to 'dummy', privacy mode in the CDM will fail. @@ -84,6 +84,7 @@ 'include_dirs': [ '../core/include', '../oemcrypto/include', + '../third_party/jsmn', '../third_party/stringencoders/src', ], 'direct_dependent_settings': { @@ -104,10 +105,10 @@ '../core/include/device_files.h', '../core/include/file_store.h', '../core/include/initialization_data.h', + '../core/include/license_key_status.h', '../core/include/license.h', '../core/include/lock.h', '../core/include/log.h', - '../core/include/max_res_engine.h', '../core/include/oemcrypto_adapter.h', '../core/include/policy_engine.h', '../core/include/privacy_crypto.h', @@ -124,13 +125,18 @@ '../core/src/crypto_session.cpp', '../core/src/device_files.cpp', '../core/src/initialization_data.cpp', + '../core/src/license_key_status.cpp', '../core/src/license.cpp', - '../core/src/max_res_engine.cpp', '../core/src/oemcrypto_adapter_static.cpp', '../core/src/policy_engine.cpp', '../core/src/privacy_crypto_<(privacy_crypto_impl).cpp', '../core/src/properties.cpp', '../core/src/string_conversions.cpp', + '../third_party/jsmn/jsmn.h', + '../third_party/jsmn/jsmn.c', + '../third_party/stringencoders/src/modp_b64_data.h', + '../third_party/stringencoders/src/modp_b64.cpp', + '../third_party/stringencoders/src/modp_b64.h', '../third_party/stringencoders/src/modp_b64w_data.h', '../third_party/stringencoders/src/modp_b64w.cpp', '../third_party/stringencoders/src/modp_b64w.h', @@ -148,6 +154,12 @@ '../core/src/oemcrypto_adapter_static_v10.cpp', ], }], + ['oemcrypto_version < 11', { + 'sources': [ + # Include APIs introduced in v10. + '../core/src/oemcrypto_adapter_static_v11.cpp', + ], + }], ['privacy_crypto_impl=="openssl"', { 'conditions': [ ['openssl_config == "target"', { diff --git a/cdm/core_unittests.gypi b/cdm/core_unittests.gypi index 45ae79a0..d9e85ce0 100644 --- a/cdm/core_unittests.gypi +++ b/cdm/core_unittests.gypi @@ -9,12 +9,14 @@ '../core/test/cdm_session_unittest.cpp', '../core/test/config_test_env.cpp', '../core/test/device_files_unittest.cpp', + '../core/test/generic_crypto_unittest.cpp', '../core/test/http_socket.cpp', '../core/test/initialization_data_unittest.cpp', '../core/test/license_request.cpp', '../core/test/license_unittest.cpp', - '../core/test/max_res_engine_unittest.cpp', + '../core/test/license_keys_unittest.cpp', '../core/test/policy_engine_unittest.cpp', + '../core/test/policy_engine_constraints_unittest.cpp', '../core/test/test_printers.cpp', '../core/test/url_request.cpp', ], diff --git a/cdm/include/cdm.h b/cdm/include/cdm.h index 64600227..aa47bfd8 100644 --- a/cdm/include/cdm.h +++ b/cdm/include/cdm.h @@ -1,6 +1,6 @@ // Copyright 2015 Google Inc. All Rights Reserved. -// Based on the EME draft spec from 2015 November 20. -// https://rawgit.com/w3c/encrypted-media/1dab9e5/index.html +// Based on the EME draft spec from 2016 June 10. +// http://www.w3.org/TR/2016/WD-encrypted-media-20160610/" #ifndef WVCDM_CDM_CDM_H_ #define WVCDM_CDM_CDM_H_ @@ -58,7 +58,8 @@ class CDM_EXPORT Cdm : public ITimerClient { kLicenseRequest = 0, kLicenseRenewal = 1, kLicenseRelease = 2, - kIndividualizationRequest = 3, + kIndividualizationRequest = 3, // Not used. Direct Individualization + // is used instead of App-Assisted } MessageType; typedef enum { @@ -77,6 +78,10 @@ class CDM_EXPORT Cdm : public ITimerClient { kQuotaExceeded = 8, kRangeError = 9, + // The action could not be completed yet but has been scheduled to be done + // later. A call to |event_listener.onDeferredComplete| will be made once + // the action is complete. + kDeferred = 99998, // This covers errors that we do not expect (see logs for details): kUnexpectedError = 99999, } Status; @@ -86,8 +91,18 @@ class CDM_EXPORT Cdm : public ITimerClient { kCenc = 0, kKeyIds = 1, // NOTE: not supported by Widevine at this time kWebM = 2, + + // This type is not defined by EME but is supported by Widevine + kHls = 10000, } InitDataType; + // These are the crypto schemes supported by CENC 3.0. + typedef enum { + kClear = 0, + kAesCtr = 1, // AES-CTR, for use with cenc and cens modes + kAesCbc = 2, // AES-CBC, for use with cbc1 and cbcs modes + } EncryptionScheme; + // These are key statuses defined by EME. typedef enum { kUsable = 0, @@ -99,6 +114,19 @@ class CDM_EXPORT Cdm : public ITimerClient { kReleased = 5, } KeyStatus; + // Permissible usages for a key. Returned as a set of flags; multiple + // flags may be set. The specific settings are defined in the license + // and the OEMCrypto Key Control Block. The CDM uses settings in the + // license to derive these flags. + typedef uint32_t KeyAllowedUsageFlags; + static const KeyAllowedUsageFlags kAllowNone = 0; + static const KeyAllowedUsageFlags kAllowDecryptToClearBuffer = 1; + static const KeyAllowedUsageFlags kAllowDecryptToSecureBuffer = 2; + static const KeyAllowedUsageFlags kAllowGenericEncrypt = 4; + static const KeyAllowedUsageFlags kAllowGenericDecrypt = 8; + static const KeyAllowedUsageFlags kAllowGenericSign = 16; + static const KeyAllowedUsageFlags kAllowGenericSignatureVerify = 32; + // These are defined by Widevine. The CDM can be configured to decrypt in // three modes (dependent on OEMCrypto support). typedef enum { @@ -137,20 +165,6 @@ class CDM_EXPORT Cdm : public ITimerClient { // See Cdm::createSession(). class IEventListener { public: - // A URL to be added to a renewal request message. - // This call will immediately precede the onMessage() call. - // Do not override this call if the URL is not needed. - // - // WARNING: this call exists temporarily to allow interoperation with - // older versions of Chromium and the prefixed EME API. This call will - // be removed in a future release. Therefore: (1) Do not use this call - // unless you are certain that it is needed on your platform for your - // application, and (2) If it is needed, figure how move to a new version - // of Chromium and the unprefixed EME API as soon as possible. - // TODO: Remove this call (see b/24776024). - virtual void onMessageUrl(const std::string& session_id, - const std::string& server_url) {} - // A message (license request, renewal, etc.) to be dispatched to the // application's license server. // The response, if successful, should be provided back to the CDM via a @@ -165,14 +179,30 @@ class CDM_EXPORT Cdm : public ITimerClient { // A remove() operation has been completed. virtual void onRemoveComplete(const std::string& session_id) = 0; + // Called when a deferred action has completed. + virtual void onDeferredComplete(const std::string& session_id, + Status result) = 0; + + // Called when the CDM requires a new device certificate + virtual void onDirectIndividualizationRequest( + const std::string& session_id, const std::string& request) = 0; + protected: IEventListener() {} virtual ~IEventListener() {} }; - // A storage interface provided by the application, independent of CDM - // instances. - // See Cdm::initialize(). + // A storage interface provided by the application. This defines the "origin" + // that the CDM will operate in by the files it can access. Passing different + // IStorage instances to Cdm::create will cause those CDM instances to be in + // different "origins" as defined by the IStorage instance. For example, + // different IStorage instances could be tied to different folders for + // different origins. + // + // It is important for multi-origin hosts to verify the application's origin. + // This ensures that the application does not access files from another + // origin. + // // NOTE: It is important for users of your application to be able to clear // stored data. Also, browsers or other multi-application systems should // store data separately per-app or per-origin. @@ -258,35 +288,8 @@ class CDM_EXPORT Cdm : public ITimerClient { std::string build_info; }; - // Device certificate request information. - // The structure is passed by the application to the library in as an output - // parameter to Cdm::initialize(). - // All fields are filled in by the library to instruct the application to - // handle device certificate requests, if needed. - struct DeviceCertificateRequest { - // If false, the library is ready to create and/or load sessions. - // If true, a device certificate is needed first. - // Sessions cannot be created or loaded until the device certificate has - // been provisioned. - bool needed; - - // If |needed| is true, this string contains the URL that must be used to - // provision a device certificate. The request must be a POST. - std::string url; - - // If |needed| is true, the response from the above-described HTTP POST - // must be provided as an argument to this method. - // Returns kSuccess if the provisioning was successful. - // Any other return value means the provisioning failed and the CDM cannot - // be used yet. - Status acceptReply(const std::string& reply); - }; - // Initialize the CDM library and provide access to platform services. // All platform interfaces are required. - // The |device_certificate_request| parameter will be filled in by - // initialize(). - // See documentation for DeviceCertificateRequest for more information. // Logging is controlled by |verbosity|. // Must be called and must return kSuccess before create() is called. static Status initialize( @@ -295,7 +298,6 @@ class CDM_EXPORT Cdm : public ITimerClient { IStorage* storage, IClock* clock, ITimer* timer, - DeviceCertificateRequest* device_certificate_request, LogLevel verbosity); // Query the CDM library version. @@ -306,6 +308,9 @@ class CDM_EXPORT Cdm : public ITimerClient { // instance may be constructed. // The CDM may notify of events at any time via the provided |listener|, // which may not be NULL. + // |storage| defines the storage to use for this instance. This can be used + // to provide per-origin storage. Passing NULL will use the storage passed + // to initialize(). // If |privacy_mode| is true, server certificates are required and will be // used to encrypt messages to the license server. // By using server certificates to encrypt communication with the license @@ -315,6 +320,7 @@ class CDM_EXPORT Cdm : public ITimerClient { // This is particularly useful for browser environments, but is recommended // for use whenever possible. static Cdm* create(IEventListener* listener, + IStorage* storage, bool privacy_mode); virtual ~Cdm() {} @@ -360,6 +366,18 @@ class CDM_EXPORT Cdm : public ITimerClient { virtual Status getKeyStatuses(const std::string& session_id, KeyStatusMap* key_statuses) = 0; + // Gets the permitted usage for a specific key by ID. + virtual Status getKeyAllowedUsages(const std::string& session_id, + const std::string& key_id, + KeyAllowedUsageFlags* usage_flags) = 0; + + // Gets the permitted usage for a specific key by ID. + // Search for key across all known sessions. If there are keys in separate + // sessions that match the given key_id, return kTypeError unless all such + // keys have identical Allowed Usage settings. + virtual Status getKeyAllowedUsages(const std::string& key_id, + KeyAllowedUsageFlags* usage_flags) = 0; + // Indicates that the application no longer needs the session and the CDM // should release any resources associated with it and close it. // Does not generate release messages for persistent sessions. @@ -373,6 +391,28 @@ class CDM_EXPORT Cdm : public ITimerClient { // session is fully removed. virtual Status remove(const std::string& session_id) = 0; + // Describes a repeating pattern as defined by the CENC 3.0 standard. A + // CENC 3.0 pattern consists of a number of encrypted blocks followed by a + // number of clear blocks, after which it repeats. + struct Pattern { + public: + Pattern() + : encrypted_blocks(0), + clear_blocks(0) {} + + Pattern(uint32_t encrypt, uint32_t clear) + : encrypted_blocks(encrypt), + clear_blocks(clear) {} + + // The number of crypto blocks that are encrypted and therefore need to be + // decrypted. + uint32_t encrypted_blocks; + + // The number of crypto blocks that are not encrypted and therefore should + // be skipped when doing decryption. + uint32_t clear_blocks; + }; + struct InputBuffer { public: InputBuffer() @@ -380,10 +420,11 @@ class CDM_EXPORT Cdm : public ITimerClient { key_id_length(0), iv(NULL), iv_length(0), + pattern(), data(NULL), data_length(0), block_offset(0), - is_encrypted(true), + encryption_scheme(kAesCtr), is_video(true), first_subsample(true), last_subsample(true) {} @@ -395,18 +436,34 @@ class CDM_EXPORT Cdm : public ITimerClient { const uint8_t* iv; uint32_t iv_length; + // Describes the repeating pattern with which the content was encrypted. If + // left at its default value of (0,0), patterns will be disabled. Should + // only be changed for content that uses patterns, such as for CENC 3.0 + // "cens" and "cbcs" content or for HLS content. + Pattern pattern; + + // This pointer and length describe the data to be decrypted. This data + // should be ready to be decrypted with no further processing. If the data + // is coming from a format that requires processing before decryption, that + // processing needs to happen before the data is passed in here. For + // example, content coming from HLS will need to have its extra start code + // emulation prevention removed before it is passed to Widevine. const uint8_t* data; uint32_t data_length; - // |data|'s offset within its 16-byte AES block, used for CENC subsamples. - // Should start at 0 for each sample, then go up by |data_length| (mod 16) - // after the |is_encrypted| part of each subsample. + // |data|'s offset within its 16-byte AES block. Only used for encrypted + // subsamples from content using CENC standards before 3.0 or the + // equivalent mode in CENC 3.0, "cenc" mode. Should always be 0 in CENC 3.0 + // modes "cens," "cbc1," and "cbcs," as well as for HLS content. When used, + // it should start at 0 for each sample, then go up by |data_length| (mod + // 16) after the |is_encrypted| part of each subsample. uint32_t block_offset; - // If false, copies the input data directly to the output buffer. Used for - // secure output types, where the output buffer cannot be directly accessed - // above the CDM. - bool is_encrypted; + // Specifies the encryption scheme, if any, to be used to decrypt the data. + // When set to kClear, decryption will copy the input data directly to the + // output buffer. This is necessary for secure output types, where the + // output buffer cannot be directly accessed above the CDM. + EncryptionScheme encryption_scheme; // Used by secure output type kDirectRender, where the secure hardware must // decode and render the decrypted content: @@ -473,6 +530,44 @@ class CDM_EXPORT Cdm : public ITimerClient { // Clears all the values in the custom app settings. See setAppParameter(). virtual Status clearAppParameters() = 0; + // Generic crypto - functions for applying crypto operations to + // app-level data (outside the content stream). + + typedef enum { + kEncryptionAlgorithmUnknown, + kEncryptionAlgorithmAesCbc128, + } GenericEncryptionAlgorithmType; + + typedef enum { + kSigningAlgorithmUnknown, + kSigningAlgorithmHmacSha256 + } GenericSigningAlgorithmType; + + // Encrypts a buffer of app-level data. + virtual Status genericEncrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + GenericEncryptionAlgorithmType algorithm, std::string* out_buffer) = 0; + + // Decrypts a buffer of app-level data. + virtual Status genericDecrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + GenericEncryptionAlgorithmType algorithm, std::string* out_buffer) = 0; + + // Signs a buffer of app-level data. + virtual Status genericSign( + const std::string& session_id, const std::string& message, + const std::string& key_id, GenericSigningAlgorithmType algorithm, + std::string* signature) = 0; + + // Verifies the signature on a buffer of app-level data. + // Returns kSuccess if signature is verified, otherwise returns kDecryptError. + virtual Status genericVerify( + const std::string& session_id, const std::string& message, + const std::string& key_id, GenericSigningAlgorithmType algorithm, + const std::string& signature) = 0; + protected: Cdm() {} }; diff --git a/cdm/include/cdm_version.h b/cdm/include/cdm_version.h index 74254145..3c7f3f4d 100644 --- a/cdm/include/cdm_version.h +++ b/cdm/include/cdm_version.h @@ -1,2 +1,2 @@ // Widevine CE CDM Version -#define CDM_VERSION "v3.0.5-0-g897db53-ce" +#define CDM_VERSION "v3.1.0-0-g63dfeca-ce" diff --git a/cdm/oemcrypto_unittests.gypi b/cdm/oemcrypto_unittests.gypi index 79244a18..d5f7ff03 100644 --- a/cdm/oemcrypto_unittests.gypi +++ b/cdm/oemcrypto_unittests.gypi @@ -4,6 +4,8 @@ # Does not include the test runner main. { 'sources': [ + '../oemcrypto/test/oec_device_features.cpp', + '../oemcrypto/test/oec_session_util.cpp', '../oemcrypto/test/oemcrypto_test.cpp', ], 'include_dirs': [ diff --git a/cdm/src/cdm.cpp b/cdm/src/cdm.cpp index 85c349f0..c1583593 100644 --- a/cdm/src/cdm.cpp +++ b/cdm/src/cdm.cpp @@ -38,14 +38,14 @@ struct HostType { Cdm::IStorage* storage; Cdm::IClock* clock; Cdm::ITimer* timer; - CdmEngine* provisioning_engine; + FileSystem* file_system; bool initialized; HostType() : storage(NULL), clock(NULL), timer(NULL), - provisioning_engine(NULL), + file_system(NULL), initialized(false) {} } host; @@ -109,8 +109,7 @@ class PropertySet : public CdmClientPropertySet { class CdmImpl : public Cdm, public WvCdmEventListener { public: - CdmImpl(IEventListener* listener, - bool privacy_mode); + CdmImpl(IEventListener* listener, IStorage* storage, bool privacy_mode); virtual ~CdmImpl(); @@ -135,6 +134,13 @@ class CdmImpl : public Cdm, virtual Status getKeyStatuses(const std::string& session_id, KeyStatusMap* key_statuses) OVERRIDE; + virtual Status getKeyAllowedUsages( + const std::string& session_id, const std::string& key_id, + KeyAllowedUsageFlags* usage_flags) OVERRIDE; + + virtual Status getKeyAllowedUsages( + const std::string& key_id, KeyAllowedUsageFlags* usage_flags) OVERRIDE; + virtual Status setAppParameter(const std::string& key, const std::string& value) OVERRIDE; @@ -152,6 +158,25 @@ class CdmImpl : public Cdm, virtual Status decrypt(const InputBuffer& input, const OutputBuffer& output) OVERRIDE; + virtual Status genericEncrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + GenericEncryptionAlgorithmType algorithm, + std::string* out_buffer) OVERRIDE; + virtual Status genericDecrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + GenericEncryptionAlgorithmType algorithm, + std::string* out_buffer) OVERRIDE; + virtual Status genericSign( + const std::string& session_id, const std::string& message, + const std::string& key_id, GenericSigningAlgorithmType algorithm, + std::string* signature) OVERRIDE; + virtual Status genericVerify( + const std::string& session_id, const std::string& message, + const std::string& key_id, GenericSigningAlgorithmType algorithm, + const std::string& signature) OVERRIDE; + // ITimerClient: virtual void onTimerExpired(void* context) OVERRIDE; @@ -166,9 +191,17 @@ class CdmImpl : public Cdm, int64_t new_expiry_time_seconds) OVERRIDE; private: + KeyAllowedUsageFlags KeyAllowedFlags(const CdmKeyAllowedUsage& usages); + bool SendProvisioningRequest(const std::string& session_id); + CdmEncryptionAlgorithm ConvertEncryptionAlgorithm( + GenericEncryptionAlgorithmType algorithm); + CdmSigningAlgorithm ConvertSigningAlgorithm( + GenericSigningAlgorithmType algorithm); + IEventListener* listener_; bool policy_timer_enabled_; + FileSystem file_system_; CdmEngine cdm_engine_; PropertySet property_set_; CdmAppParameterMap app_parameters_; @@ -184,13 +217,33 @@ class CdmImpl : public Cdm, type((SessionType)-1), expiration(0) {} }; + struct UnprovisionedSessionMetadata { + SessionType type; + std::string init_data; + InitDataType init_data_type; + bool has_init_data; + bool is_load; + + UnprovisionedSessionMetadata() + : type((SessionType)-1), + init_data_type((InitDataType)-1), + has_init_data(false), + is_load(false) {} + }; + typedef std::map UnprovisionedMap; + std::map sessions_; + UnprovisionedMap unprovisioned_sessions_; + bool provision_request_sent_; }; -CdmImpl::CdmImpl(IEventListener* listener, - bool privacy_mode) +CdmImpl::CdmImpl(IEventListener* listener, IStorage* storage, bool privacy_mode) : listener_(listener), - policy_timer_enabled_(false) { + policy_timer_enabled_(false), + file_system_("", storage), + cdm_engine_(&file_system_), + provision_request_sent_(false) { + assert(NULL != listener_); property_set_.set_use_privacy_mode(privacy_mode); } @@ -220,7 +273,7 @@ Cdm::Status CdmImpl::setServerCertificate(const std::string& certificate) { Cdm::Status CdmImpl::createSession(SessionType session_type, std::string* session_id) { - if (!session_id) { + if (NULL == session_id) { LOGE("Missing session ID pointer."); return kTypeError; } @@ -238,17 +291,17 @@ Cdm::Status CdmImpl::createSession(SessionType session_type, return kNotSupported; } - std::string empty_origin; CdmResponseType result = cdm_engine_.OpenSession( - "com.widevine.alpha", &property_set_, empty_origin, this, - NULL, session_id); + "com.widevine.alpha", &property_set_, this, session_id); switch (result) { case NO_ERROR: sessions_[*session_id].type = session_type; return kSuccess; case NEED_PROVISIONING: - LOGE("A device certificate is needed."); - return kNeedsDeviceCertificate; + // The session does not exist in |cdm_engine_| yet, will be added in + // generateRequest. + unprovisioned_sessions_[*session_id].type = session_type; + return kSuccess; default: LOGE("Unexpected error %d", result); return kUnexpectedError; @@ -259,8 +312,43 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, InitDataType init_data_type, const std::string& init_data) { if (!cdm_engine_.IsOpenSession(session_id)) { - LOGE("No such session: %s", session_id.c_str()); - return kSessionNotFound; + UnprovisionedMap::iterator session_iter = + unprovisioned_sessions_.find(session_id); + if (session_iter == unprovisioned_sessions_.end()) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + + if (cdm_engine_.IsProvisioned(kSecurityLevelL1)) { + // We are provisioned now, create the session and generate the request + // like normal. + CdmResponseType result = cdm_engine_.OpenSession( + "com.widevine.alpha", &property_set_, session_id, this); + if (result != NO_ERROR) { + LOGE("Unexpected error %d", result); + return kUnexpectedError; + } + sessions_[session_id].type = unprovisioned_sessions_[session_id].type; + unprovisioned_sessions_.erase(session_iter); + } else { + // We are not provisioned yet, save the init data and send a provisioning + // request if needed. + if (unprovisioned_sessions_[session_id].has_init_data) { + LOGE("Request already generated: %s", session_id.c_str()); + return kInvalidState; + } + + unprovisioned_sessions_[session_id].has_init_data = true; + unprovisioned_sessions_[session_id].init_data_type = init_data_type; + unprovisioned_sessions_[session_id].init_data = init_data; + + if (provision_request_sent_) + return kDeferred; + + if (!SendProvisioningRequest(session_id)) + return kUnexpectedError; + return kSuccess; + } } if (sessions_[session_id].callable) { @@ -296,6 +384,9 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, case kWebM: init_data_type_name = WEBM_INIT_DATA_FORMAT; break; + case kHls: + init_data_type_name = HLS_INIT_DATA_FORMAT; + break; default: LOGE("Invalid init data type: %d", init_data_type); return kTypeError; @@ -315,14 +406,11 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, return kNotSupported; } - std::string key_request; - CdmKeyRequestType key_request_type; - std::string ignored_server_url; + CdmKeyRequest key_request; CdmResponseType result = cdm_engine_.GenerateKeyRequest( session_id, session_id, init_data_obj, - license_type, app_parameters_, &key_request, &key_request_type, - &ignored_server_url, NULL); + license_type, app_parameters_, &key_request); if (result != KEY_MESSAGE) { LOGE("Unexpected error %d", result); @@ -330,7 +418,9 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, } sessions_[session_id].callable = true; - assert(key_request_type == kKeyRequestTypeInitial); + assert(key_request.type == kKeyRequestTypeInitial); + + MessageType message_type; if (property_set_.use_privacy_mode() && property_set_.service_certificate().empty()) { // We can deduce that this is a server cert request, even though CdmEgine @@ -339,10 +429,12 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, // The EME editor has clarified that this was a misinterpretation of the // spec, and that this should also be kLicenseRequest. LOGI("A server certificate request has been generated."); + message_type = kLicenseRequest; } else { LOGI("A license request has been generated."); + message_type = kLicenseRequest; } - listener_->onMessage(session_id, kLicenseRequest, key_request); + listener_->onMessage(session_id, message_type, key_request.message); return kSuccess; } @@ -357,23 +449,28 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { return kQuotaExceeded; } - std::string empty_origin; - std::string ignored_session_id_output; CdmResponseType result = cdm_engine_.OpenSession( - "com.widevine.alpha", &property_set_, empty_origin, this, - &session_id, &ignored_session_id_output); + "com.widevine.alpha", &property_set_, session_id, this); switch (result) { case NO_ERROR: break; case NEED_PROVISIONING: - LOGE("A device certificate is needed."); - return kNeedsDeviceCertificate; + unprovisioned_sessions_[session_id].is_load = true; + if (provision_request_sent_) + return kDeferred; + + // Send a provisioning request right away. Then we will load the session + // again in |update|. + if (!SendProvisioningRequest(session_id)) + return kUnexpectedError; + + return kSuccess; default: LOGE("Unexpected error %d", result); return kUnexpectedError; } - DeviceFiles f; + DeviceFiles f(&file_system_); if (!f.Init(kSecurityLevelUnknown)) { LOGE("Unexpected error, failed to init DeviceFiles"); return kUnexpectedError; @@ -381,8 +478,8 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { if (!f.LicenseExists(session_id)) { // This might be a usage record session which needs to be loaded. - CdmKeyMessage release_message; - result = cdm_engine_.LoadUsageSession(session_id, &release_message); + CdmKeyMessage ignored_release_message; + result = cdm_engine_.LoadUsageSession(session_id, &ignored_release_message); if (result == LOAD_USAGE_INFO_MISSING) { LOGE("Unable to load license: %s", session_id.c_str()); cdm_engine_.CloseSession(session_id); @@ -393,10 +490,6 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { return kUnexpectedError; } - LOGI("A usage record release has been generated."); - MessageType message_type = kLicenseRelease; - listener_->onMessage(session_id, message_type, release_message); - sessions_[session_id].type = kPersistentUsageRecord; sessions_[session_id].callable = true; return kSuccess; @@ -405,24 +498,7 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { result = cdm_engine_.RestoreKey(session_id, session_id); if (result == GET_RELEASED_LICENSE_ERROR) { // This was partially removed already. - // The EME spec states that we should send a release message right away. - InitializationData empty_initialization_data; - CdmKeyMessage key_request; - std::string ignored_server_url; - - CdmResponseType result = cdm_engine_.GenerateKeyRequest( - session_id, session_id, empty_initialization_data, - kLicenseTypeRelease, app_parameters_, &key_request, NULL, - &ignored_server_url, NULL); - if (result != KEY_MESSAGE) { - LOGE("Unexpected error %d", result); - cdm_engine_.CloseSession(session_id); - return kUnexpectedError; - } - - LOGI("A license release has been generated."); - MessageType message_type = kLicenseRelease; - listener_->onMessage(session_id, message_type, key_request); + // The EME spec states that we should be able to load it, but not use it. } else if (result != KEY_ADDED) { LOGE("Unexpected error %d", result); return kUnexpectedError; @@ -435,6 +511,64 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { Cdm::Status CdmImpl::update(const std::string& session_id, const std::string& response) { + if (provision_request_sent_) { + std::string ignored_cert; + std::string ignored_wrapped_key; + + provision_request_sent_ = false; + CdmResponseType result = cdm_engine_.HandleProvisioningResponse( + response, &ignored_cert, &ignored_wrapped_key); + if (result != NO_ERROR) { + LOGE("Unexpected error %d", result); + return kUnexpectedError; + } + + // We are now provisioned, we need to recreate the unprovisioned sessions. + Cdm::Status ret = kSuccess; + for (UnprovisionedMap::iterator it = unprovisioned_sessions_.begin(); + it != unprovisioned_sessions_.end();) { + if (it->second.is_load) { + Cdm::Status load_status = load(it->first); + if (it->first != session_id) + listener_->onDeferredComplete(it->first, load_status); + else if (load_status != kSuccess) + ret = load_status; + unprovisioned_sessions_.erase(it++); + continue; + } + + result = cdm_engine_.OpenSession("com.widevine.alpha", &property_set_, + it->first, this); + if (result != NO_ERROR) { + LOGE("Unexpected error %d", result); + if (it->first == session_id) + ret = kUnexpectedError; + else + listener_->onDeferredComplete(it->first, kUnexpectedError); + // We failed to create the session. Keep it in the unprovisioned + // sessions map so that another call to generateRequest will try to + // create the session again. + ++it; + continue; + } + sessions_[it->first].type = it->second.type; + + // Call generate request again for the session. + if (it->second.has_init_data) { + Cdm::Status generate_status = generateRequest( + it->first, it->second.init_data_type, it->second.init_data); + if (it->first != session_id) + listener_->onDeferredComplete(it->first, generate_status); + else if (generate_status != kSuccess) + // Still erase the item because the session exists in |cdm_engine_|. + ret = generate_status; + } + unprovisioned_sessions_.erase(it++); + } + + return ret; + } + if (!cdm_engine_.IsOpenSession(session_id)) { LOGE("No such session: %s", session_id.c_str()); return kSessionNotFound; @@ -467,14 +601,11 @@ Cdm::Status CdmImpl::update(const std::string& session_id, // The underlying session in CdmEngine has stored a copy of the original // init data, so we can use an empty one this time. InitializationData empty_init_data; - std::string key_request; - CdmKeyRequestType key_request_type; - std::string ignored_server_url; + CdmKeyRequest key_request; CdmResponseType result = cdm_engine_.GenerateKeyRequest( session_id, session_id, empty_init_data, kLicenseTypeDeferred, - app_parameters_, &key_request, &key_request_type, - &ignored_server_url, NULL); + app_parameters_, &key_request); if (result != KEY_MESSAGE) { LOGE("Unexpected error %d", result); @@ -482,9 +613,9 @@ Cdm::Status CdmImpl::update(const std::string& session_id, } LOGI("A deferred license request has been generated."); - assert(key_request_type == kKeyRequestTypeInitial); + assert(key_request.type == kKeyRequestTypeInitial); MessageType message_type = kLicenseRequest; - listener_->onMessage(session_id, message_type, key_request); + listener_->onMessage(session_id, message_type, key_request.message); return kSuccess; } else if (result == OFFLINE_LICENSE_PROHIBITED) { LOGE("A temporary session cannot be used for a persistent license."); @@ -518,6 +649,10 @@ Cdm::Status CdmImpl::getExpiration(const std::string& session_id, LOGE("No such session: %s", session_id.c_str()); return kSessionNotFound; } + if (NULL == expiration) { + LOGE("Missing pointer to expiration result."); + return kTypeError; + } *expiration = sessions_[session_id].expiration; return kSuccess; @@ -529,11 +664,69 @@ Cdm::Status CdmImpl::getKeyStatuses(const std::string& session_id, LOGE("No such session: %s", session_id.c_str()); return kSessionNotFound; } + if (NULL == key_statuses) { + LOGE("Missing pointer to KeyStatusMap result."); + return kTypeError; + } *key_statuses = sessions_[session_id].key_statuses; return kSuccess; } +Cdm::Status CdmImpl::getKeyAllowedUsages(const std::string& session_id, + const std::string& key_id, + KeyAllowedUsageFlags* usage_flags) { + if (!cdm_engine_.IsOpenSession(session_id)) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + if (NULL == usage_flags) { + LOGE("Missing pointer to KeyAllowedUsageFlags result."); + return kTypeError; + } + + CdmKeyAllowedUsage usage_for_key; + CdmResponseType result = cdm_engine_.QueryKeyAllowedUsage(session_id, + key_id, + &usage_for_key); + if (result != NO_ERROR) { + if (result == KEY_NOT_FOUND_1) { + return kNoKey; + } else { + LOGE("Unexpected error %d", result); + return kUnexpectedError; + } + } + + *usage_flags = KeyAllowedFlags(usage_for_key); + return kSuccess; +} + +Cdm::Status CdmImpl::getKeyAllowedUsages(const std::string& key_id, + KeyAllowedUsageFlags* usage_flags) { + if (NULL == usage_flags) { + LOGE("Missing pointer to KeyAllowedUsageFlags result."); + return kTypeError; + } + + CdmKeyAllowedUsage usage_for_key; + CdmResponseType result = cdm_engine_.QueryKeyAllowedUsage(key_id, + &usage_for_key); + if (result != NO_ERROR) { + if (result == KEY_NOT_FOUND_1 || result == KEY_NOT_FOUND_2) { + return kNoKey; + } else if (result == KEY_CONFLICT_1) { + return kTypeError; + } else { + LOGE("Unexpected error %d", result); + return kUnexpectedError; + } + } + + *usage_flags = KeyAllowedFlags(usage_for_key); + return kSuccess; +} + Cdm::Status CdmImpl::setAppParameter(const std::string& key, const std::string& value) { if (key.empty()) { @@ -602,8 +795,7 @@ Cdm::Status CdmImpl::remove(const std::string& session_id) { } InitializationData empty_initialization_data; - CdmKeyMessage key_request; - std::string ignored_server_url; + CdmKeyRequest key_request; // Mark all keys as released ahead of generating the release request. // When released, cdm_engine_ will mark all keys as expired, which we will @@ -615,8 +807,7 @@ Cdm::Status CdmImpl::remove(const std::string& session_id) { CdmResponseType result = cdm_engine_.GenerateKeyRequest( session_id, session_id, empty_initialization_data, - kLicenseTypeRelease, app_parameters_, &key_request, NULL, - &ignored_server_url, NULL); + kLicenseTypeRelease, app_parameters_, &key_request); if (result != KEY_MESSAGE) { LOGE("Unexpected error %d", result); cdm_engine_.CloseSession(session_id); @@ -625,13 +816,14 @@ Cdm::Status CdmImpl::remove(const std::string& session_id) { LOGI("A license release has been generated."); MessageType message_type = kLicenseRelease; - listener_->onMessage(session_id, message_type, key_request); + listener_->onMessage(session_id, message_type, key_request.message); return kSuccess; } Cdm::Status CdmImpl::decrypt(const InputBuffer& input, const OutputBuffer& output) { - if (input.is_encrypted && input.iv_length != 16) { + const bool is_encrypted = (input.encryption_scheme != kClear); + if (is_encrypted && input.iv_length != 16) { LOGE("The IV must be 16 bytes long."); return kTypeError; } @@ -646,8 +838,13 @@ Cdm::Status CdmImpl::decrypt(const InputBuffer& input, std::vector iv(input.iv, input.iv + input.iv_length); CdmDecryptionParameters parameters; - parameters.is_encrypted = input.is_encrypted; + parameters.is_encrypted = is_encrypted; parameters.is_secure = output.is_secure; + if (input.encryption_scheme == kAesCtr) { + parameters.cipher_mode = kCipherModeCtr; + } else if (input.encryption_scheme == kAesCbc) { + parameters.cipher_mode = kCipherModeCbc; + } parameters.key_id = &key_id; parameters.encrypt_buffer = input.data; parameters.encrypt_length = input.data_length; @@ -660,6 +857,8 @@ Cdm::Status CdmImpl::decrypt(const InputBuffer& input, (input.first_subsample ? OEMCrypto_FirstSubsample : 0) | (input.last_subsample ? OEMCrypto_LastSubsample : 0); parameters.is_video = input.is_video; + parameters.pattern_descriptor.encrypt_blocks = input.pattern.encrypted_blocks; + parameters.pattern_descriptor.skip_blocks = input.pattern.clear_blocks; CdmSessionId empty_session_id; CdmResponseType result = cdm_engine_.Decrypt(empty_session_id, parameters); @@ -675,6 +874,118 @@ Cdm::Status CdmImpl::decrypt(const InputBuffer& input, return kDecryptError; } +Cdm::Status CdmImpl::genericEncrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + GenericEncryptionAlgorithmType algorithm, std::string* out_buffer) { + + CdmEncryptionAlgorithm cdm_algorithm = ConvertEncryptionAlgorithm(algorithm); + if (cdm_algorithm == wvcdm::kEncryptionAlgorithmUnknown) { + LOGE("Unrecognized encryption algorithm: %d.", cdm_algorithm); + return kNotSupported; + } + + CdmResponseType result = cdm_engine_.GenericEncrypt( + session_id, in_buffer, key_id, iv, cdm_algorithm, out_buffer); + if (result == NO_ERROR) { + return kSuccess; + } + if (result == SESSION_NOT_FOUND_13) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + if (result == KEY_NOT_FOUND_3 || result == KEY_ERROR_1) { + LOGE("Key Error: %s", session_id.c_str()); + return kNoKey; + } + LOGE("Unexpected error %d", result); + return kUnexpectedError; +} + +Cdm::Status CdmImpl::genericDecrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + GenericEncryptionAlgorithmType algorithm, std::string* out_buffer) { + + CdmEncryptionAlgorithm cdm_algorithm = ConvertEncryptionAlgorithm(algorithm); + if (cdm_algorithm == wvcdm::kEncryptionAlgorithmUnknown) { + LOGE("Unrecognized encryption algorithm: %d.", cdm_algorithm); + return kNotSupported; + } + + CdmResponseType result = cdm_engine_.GenericDecrypt( + session_id, in_buffer, key_id, iv, cdm_algorithm, out_buffer); + if (result == NO_ERROR) { + return kSuccess; + } + if (result == SESSION_NOT_FOUND_14) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + if (result == KEY_NOT_FOUND_4 || result == KEY_ERROR_2) { + LOGE("Key Error: %s", session_id.c_str()); + return kNoKey; + } + LOGE("Unexpected error %d", result); + return kUnexpectedError; +} + +Cdm::Status CdmImpl::genericSign( + const std::string& session_id, const std::string& message, + const std::string& key_id, GenericSigningAlgorithmType algorithm, + std::string* signature) { + + CdmSigningAlgorithm cdm_algorithm = ConvertSigningAlgorithm(algorithm); + if (cdm_algorithm == wvcdm::kSigningAlgorithmUnknown) { + LOGE("Unrecognized signing algorithm: %d.", cdm_algorithm); + return kNotSupported; + } + + CdmResponseType result = cdm_engine_.GenericSign( + session_id, message, key_id, cdm_algorithm, signature); + if (result == NO_ERROR) { + return kSuccess; + } + if (result == SESSION_NOT_FOUND_15) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + if (result == KEY_NOT_FOUND_5 || result == KEY_ERROR_3) { + LOGE("Key Error: %s", session_id.c_str()); + return kNoKey; + } + LOGE("Unexpected error %d", result); + return kUnexpectedError; +} + +Cdm::Status CdmImpl::genericVerify( + const std::string& session_id, const std::string& message, + const std::string& key_id, GenericSigningAlgorithmType algorithm, + const std::string& signature) { + + CdmSigningAlgorithm cdm_algorithm = ConvertSigningAlgorithm(algorithm); + if (cdm_algorithm == wvcdm::kSigningAlgorithmUnknown) { + LOGE("Unrecognized signing algorithm: %d.", cdm_algorithm); + return kNotSupported; + } + + CdmResponseType result = cdm_engine_.GenericVerify( + session_id, message, key_id, cdm_algorithm, signature); + if (result == NO_ERROR) { + return kSuccess; + } + if (result == SESSION_NOT_FOUND_16) { + LOGE("No such session: %s", session_id.c_str()); + return kSessionNotFound; + } + if (result == KEY_NOT_FOUND_6 || result == KEY_ERROR_4) { + LOGE("Key Error: %s", session_id.c_str()); + return kNoKey; + } + LOGE("Unexpected error %d", result); + return kUnexpectedError; +} + void CdmImpl::onTimerExpired(void* context) { if (context == kPolicyTimerContext) { if (policy_timer_enabled_) { @@ -687,10 +998,9 @@ void CdmImpl::onTimerExpired(void* context) { } void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) { - CdmKeyMessage message; - std::string server_url; + CdmKeyRequest key_request; CdmResponseType result = - cdm_engine_.GenerateRenewalRequest(session_id, &message, &server_url); + cdm_engine_.GenerateRenewalRequest(session_id, &key_request); if (result != KEY_MESSAGE) { LOGE("Unexpected error %d", result); return; @@ -698,12 +1008,7 @@ void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) { LOGI("A license renewal has been generated."); MessageType message_type = kLicenseRenewal; - - // Post the server_url before providing the message. - // For systems that still require the server URL, - // the listener will add the URL to its renewal request. - listener_->onMessageUrl(session_id, server_url); - listener_->onMessage(session_id, message_type, message); + listener_->onMessage(session_id, message_type, key_request.message); } void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id, @@ -756,6 +1061,55 @@ void CdmImpl::OnExpirationUpdate(const CdmSessionId& session_id, } } +Cdm::KeyAllowedUsageFlags CdmImpl::KeyAllowedFlags( + const CdmKeyAllowedUsage& usages) { + KeyAllowedUsageFlags flags = kAllowNone; + flags |= (usages.decrypt_to_clear_buffer) ? kAllowDecryptToClearBuffer : 0; + flags |= (usages.decrypt_to_secure_buffer) ? kAllowDecryptToSecureBuffer : 0; + flags |= (usages.generic_encrypt) ? kAllowGenericEncrypt : 0; + flags |= (usages.generic_decrypt) ? kAllowGenericDecrypt : 0; + flags |= (usages.generic_sign) ? kAllowGenericSign : 0; + flags |= (usages.generic_verify) ? kAllowGenericSignatureVerify : 0; + return flags; +} + +bool CdmImpl::SendProvisioningRequest(const std::string& session_id) { + // Generate a provisioning request. + std::string empty_authority; + std::string ignored_base_url; + std::string signed_request; + CdmResponseType result = cdm_engine_.GetProvisioningRequest( + kCertificateWidevine, empty_authority, &signed_request, + &ignored_base_url); + if (result != NO_ERROR) { + LOGE("Unexpected error %d", result); + return false; + } + listener_->onDirectIndividualizationRequest(session_id, signed_request); + provision_request_sent_ = true; + + return true; +} + +CdmEncryptionAlgorithm CdmImpl::ConvertEncryptionAlgorithm( + GenericEncryptionAlgorithmType algorithm) { + if (algorithm == Cdm::kEncryptionAlgorithmAesCbc128) { + return wvcdm::kEncryptionAlgorithmAesCbc128; + } else { + LOGW("Unknown encryption algorithm: %d", algorithm); + return wvcdm::kEncryptionAlgorithmUnknown; + } +} + +CdmSigningAlgorithm CdmImpl::ConvertSigningAlgorithm( + GenericSigningAlgorithmType algorithm) { + if (algorithm == Cdm::kSigningAlgorithmHmacSha256) { + return wvcdm::kSigningAlgorithmHmacSha256; + } else { + LOGW("Unknown signing algorithm: %d", algorithm); + return wvcdm::kSigningAlgorithmUnknown; + } +} bool VerifyL1() { CryptoSession cs; @@ -772,7 +1126,6 @@ Cdm::Status Cdm::initialize( IStorage* storage, IClock* clock, ITimer* timer, - DeviceCertificateRequest* device_certificate_request, LogLevel verbosity) { // If you want to direct-render on L3, CryptoSession will pass that request // along to OEMCrypto. But if you want to use an opaque handle on L3, @@ -813,11 +1166,6 @@ Cdm::Status Cdm::initialize( return kTypeError; } - if (!device_certificate_request) { - LOGE("Device certificate request pointer is required!"); - return kTypeError; - } - // Our enum values match those in core/include/log.h g_cutoff = static_cast(verbosity); @@ -827,33 +1175,6 @@ Cdm::Status Cdm::initialize( host.storage = storage; host.clock = clock; host.timer = timer; - - device_certificate_request->needed = false; - - if (!host.provisioning_engine) { - host.provisioning_engine = new CdmEngine(); - } - bool has_cert = host.provisioning_engine->IsProvisioned( - kSecurityLevelL1, "" /* origin */); - - if (!has_cert) { - device_certificate_request->needed = true; - std::string empty_authority; - std::string empty_origin; - std::string base_url; - std::string signed_request; - CdmResponseType result = host.provisioning_engine->GetProvisioningRequest( - kCertificateWidevine, empty_authority, empty_origin, - &signed_request, &base_url); - if (result != NO_ERROR) { - LOGE("Unexpected error %d", result); - return kUnexpectedError; - } - device_certificate_request->url = base_url; - device_certificate_request->url.append("&signedRequest="); - device_certificate_request->url.append(signed_request); - } - host.initialized = true; return kSuccess; } @@ -865,6 +1186,7 @@ const char* Cdm::version() { // static Cdm* Cdm::create(IEventListener* listener, + IStorage* storage, bool privacy_mode) { if (!host.initialized) { LOGE("Not initialized!"); @@ -874,28 +1196,10 @@ Cdm* Cdm::create(IEventListener* listener, LOGE("No listener!"); return NULL; } - return new CdmImpl(listener, privacy_mode); -} + if (!storage) + storage = host.storage; -Cdm::Status Cdm::DeviceCertificateRequest::acceptReply( - const std::string& reply) { - if (!host.provisioning_engine) { - LOGE("Provisioning reply received while not in a provisioning state!"); - return kTypeError; - } - - std::string empty_origin; - std::string ignored_cert; - std::string ignored_wrapped_key; - - CdmResponseType result = - host.provisioning_engine->HandleProvisioningResponse( - empty_origin, reply, &ignored_cert, &ignored_wrapped_key); - if (result != NO_ERROR) { - LOGE("Unexpected error %d", result); - return kUnexpectedError; - } - return kSuccess; + return new CdmImpl(listener, storage, privacy_mode); } } // namespace widevine @@ -911,35 +1215,22 @@ int64_t Clock::GetCurrentTime() { class File::Impl { public: + Cdm::IStorage* storage; std::string name; bool read_only; bool truncate; }; -File::File() : impl_(NULL) {} +File::File(Impl* impl) : impl_(impl) {} -File::~File() { - Close(); -} - -bool File::Open(const std::string& file_path, int flags) { - if (!(flags & kCreate) && !host.storage->exists(file_path)) { - return false; - } - - impl_ = new Impl; - impl_->name = file_path; - impl_->read_only = (flags & kReadOnly); - impl_->truncate = (flags & kTruncate); - return true; -} +File::~File() {} ssize_t File::Read(char* buffer, size_t bytes) { if (!impl_) { return -1; } std::string data; - if (!host.storage->read(impl_->name, &data)) { + if (!impl_->storage->read(impl_->name, &data)) { return -1; } @@ -957,7 +1248,7 @@ ssize_t File::Write(const char* buffer, size_t bytes) { return -1; } std::string data(buffer, bytes); - if (!host.storage->write(impl_->name, data)) { + if (!impl_->storage->write(impl_->name, data)) { return -1; } return bytes; @@ -968,45 +1259,61 @@ void File::Close() { delete impl_; } impl_ = NULL; + delete this; } -bool File::Exists(const std::string& file_path) { - // An empty path is the "base directory" for CE CDM's file storage. - // Therefore, it should always be seen as existing. - // If it ever does not exist, CdmEngine detects this as a "factory reset" - // and wipes out all usage table data. - return file_path.empty() || host.storage->exists(file_path); +class FileSystem::Impl { + public: + widevine::Cdm::IStorage* storage; +}; + +FileSystem::FileSystem() : FileSystem("", NULL) {} + +FileSystem::FileSystem(const std::string& origin, void* extra_data) + : impl_(new Impl), origin_(origin) { + if (extra_data) + impl_->storage = (widevine::Cdm::IStorage*)extra_data; + else + impl_->storage = host.storage; } -bool File::Remove(const std::string& file_path) { - return host.storage->remove(file_path); +FileSystem::~FileSystem() { + delete impl_; + impl_ = NULL; } -bool File::Copy(const std::string& old_path, const std::string& new_path) { - std::string data; - bool read_ok = host.storage->read(old_path, &data); - if (!read_ok) return false; - return host.storage->write(new_path, data); +File* FileSystem::Open(const std::string& file_path, int flags) { + if (!impl_ || (!(flags & kCreate) && !impl_->storage->exists(file_path))) { + return NULL; + } + + File::Impl* file_impl = new File::Impl; + file_impl->storage = impl_->storage; + file_impl->name = file_path; + file_impl->read_only = (flags & kReadOnly); + file_impl->truncate = (flags & kTruncate); + return new File(file_impl); } -bool File::List(const std::string& path, std::vector* files) { - return false; +bool FileSystem::Exists(const std::string& file_path) { + if (!impl_) + return false; + + return file_path.empty() || impl_->storage->exists(file_path); } -bool File::CreateDirectory(const std::string dir_path) { - return true; +bool FileSystem::Remove(const std::string& file_path) { + if (!impl_) + return false; + + return impl_->storage->remove(file_path); } -bool File::IsDirectory(const std::string& dir_path) { - return false; -} +ssize_t FileSystem::FileSize(const std::string& file_path) { + if (!impl_) + return -1; -bool File::IsRegularFile(const std::string& file_path) { - return host.storage->exists(file_path); -} - -ssize_t File::FileSize(const std::string& file_path) { - return host.storage->size(file_path); + return impl_->storage->size(file_path); } } // namespace wvcdm diff --git a/cdm/test/cdm_test.cpp b/cdm/test/cdm_test.cpp index 58a1c030..ff98e7cf 100644 --- a/cdm/test/cdm_test.cpp +++ b/cdm/test/cdm_test.cpp @@ -56,7 +56,15 @@ const std::string kDefaultServerCertificate = a2bs_hex( "7C0011E0F5B38E4E298ED2CB301EB4564965F55C5D79757A250A4EB9C84AB3E6539F6B6FDF" "56899EA29914"); -const std::string kLicenseServer = "http://widevine-proxy.appspot.com/proxy"; +const std::string kProvisioningServerUrl = + "https://www.googleapis.com/" + "certificateprovisioning/v1/devicecertificates/create" + "?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE"; + +const std::string kLicenseServerAppspot = + "http://widevine-proxy.appspot.com/proxy"; +const std::string kLicenseServerUat = "https://proxy.uat.widevine.com/proxy"; + const std::string kCencInitData = a2bs_hex( "00000042" // blob size "70737368" // "pssh" @@ -89,11 +97,35 @@ const std::string kNonWidevineCencInitData = a2bs_hex( const std::string kWebMInitData = a2bs_hex("deadbeefdeadbeefdeadbeefdeadbeef"); const std::string kKeyIdsInitData = "{\"kids\":[\"67ef0gd8pvfd0\",\"77ef0gd8pvfd0\"]}"; +const std::string kHlsInitData = + "#EXT-X-KEY:METHOD=SAMPLE-AES,KEYFORMAT=\"com.widevine\",KEYFORMATVERSIONS=" + "\"1\",URI=\"data:text/plain;base64,ew0KICAgInByb3ZpZGVyIjogIndpZGV2aW5lX3R" + "lc3QiLA0KICAgImNvbnRlbnRfaWQiOiAiWW1sblluVmphMkoxYm01NSIsDQogICAia2V5X2lkc" + "yI6IFsNCiAgICAgICI5Yjc1OTA0MDMyMWE0MDhhNWM3NzY4YjQ1MTEyODdhNiINCiAgIF0NCn0" + "=\",IV=0x75537a79fa41abc7b598ea72aba0c26f"; -// Dummy encrypted data. -const std::vector kKeyId1 = a2b_hex( +// This Key ID must match the key retrieved from kLicenseServerAppspot by +// kCencInitData. +const std::vector kKeyIdCtr = a2b_hex( "371ea35e1a985d75d198a7f41020dc23"); -const std::vector kInput1 = a2b_hex( +// This Key ID must match the key retrieved from kLicenseServerUat by +// kHlsInitData. +const std::vector kKeyIdCbc = a2b_hex( + "9b759040321a408a5c7768b4511287a6"); + +// A default pattern object disables patterns during decryption. +const Cdm::Pattern kPatternNone; +// The recommended pattern from CENC 3.0, which is also the pattern used by +// HLS. Encrypts 1 in every 10 crypto blocks. +const Cdm::Pattern kPatternRecommended(1, 9); +// The recommended pattern for HLS Audio, which should be decrypted in CENC 3.0 +// cbcs mode despite not using patterns. This pattern disables patterned +// decryption by having one encrypted block and no clear blocks. +const Cdm::Pattern kPatternHlsAudio(1, 0); + +// Dummy encrypted data using the CENC 3.0 "cenc" mode. Encrypted using the +// key matching kKeyIdCtr. +const std::vector kInputCenc = a2b_hex( "64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36" "17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d" "7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab" @@ -102,9 +134,9 @@ const std::vector kInput1 = a2b_hex( "9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4" "6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012" "c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685"); -const std::vector kIv1 = a2b_hex( +const std::vector kIvCenc = a2b_hex( "f6f4b1e600a5b67813ed2bded913ba9f"); -const std::vector kOutput1 = a2b_hex( +const std::vector kOutputCenc = a2b_hex( "217ce9bde99bd91e9733a1a00b9b557ac3a433dc92633546156817fae26b6e1c" "942ac20a89ff79f4c2f25fba99d6a44618a8c0420b27d54e3da17b77c9d43cca" "595d259a1e4a8b6d7744cd98c5d3f921adc252eb7d8af6b916044b676a574747" @@ -114,6 +146,75 @@ const std::vector kOutput1 = a2b_hex( "4884604c8da8a53ce33db9ff8f1c5bb6bb97f37b39906bf41596555c1bcce9ed" "08a899cd760ff0899a1170c2f224b9c52997a0785b7fe170805fd3e8b1127659"); +// Dummy encrypted data using the CENC 3.0 "cens" mode. Encrypted using the +// key matching kKeyIdCtr. +const std::vector kInputCens = a2b_hex( + "1660a777a301908b5e8c15b465ed7fa434793f65a8be816278f9479d741a78e0" + "b245e17629d63bbc2b15a5fa98b21daf62bdaf054113604ef19311adc5c3b74c" + "6167dc3160f27c4920d2f9ae4a7f8dfd029dde48bce29b2751f27f12503d369d" + "0ceb8b347e2884f51715f612badf15934aaa39db886e749afb8d8bdd29a18dd6" + "2b0c4355935c4dcc5ec0153307154ace5bfedcdaa2b670052660889f3d64c4b3" + "e363b16dc312d7e20373e873c760fae8b8bb39eccb6fe16e0198f6818ba24c30" + "39dec55ef91ddc47c320ec284e24d1c8cdd62515e8ce5c0cb01bea2fbf36ce99" + "246f5f8a2aca37719524dadffd4926a75a06402779a945d0b2c14a9c3f060a34"); +const std::vector kIvCens = a2b_hex( + "a891b8000af53049d7b24bdc19074839"); +const std::vector kOutputCens = a2b_hex( + "4bc4abcd79205e54188f04f99ea7e02534793f65a8be816278f9479d741a78e0" + "b245e17629d63bbc2b15a5fa98b21daf62bdaf054113604ef19311adc5c3b74c" + "6167dc3160f27c4920d2f9ae4a7f8dfd029dde48bce29b2751f27f12503d369d" + "0ceb8b347e2884f51715f612badf15934aaa39db886e749afb8d8bdd29a18dd6" + "2b0c4355935c4dcc5ec0153307154ace5bfedcdaa2b670052660889f3d64c4b3" + "f6104e15275ecb58324fb8f25ccde60db8bb39eccb6fe16e0198f6818ba24c30" + "39dec55ef91ddc47c320ec284e24d1c8cdd62515e8ce5c0cb01bea2fbf36ce99" + "246f5f8a2aca37719524dadffd4926a75a06402779a945d0b2c14a9c3f060a34"); + +// Dummy encrypted data using the CENC 3.0 "cbc1" mode. Encrypted using the +// key matching kKeyIdCbc. +const std::vector kInputCbc1 = a2b_hex( + "a69c76294ccd7e709fc3d11b1e0ccd4a74c9ffa8ce31ab92437c4da03b85822d" + "6f0da6d7935121cd585950ecc61efc83d2d86be9c32b3091cf546de987d9b480" + "fae8b8c35222f6fb7e2939b1af4c1445b6bd3ac22aeafc06ec016b011d465bf0" + "9d9a3a18865518bca314b1208830f0a18e6922b1d0a451df8f2c09efb416ca1d" + "0bdf93a7610f40da65fd23fc65531bb01373a85658043ed238e79d2b3f3c49e7" + "842ea0488a862932850153849f5ac20ce8181594240d16bb309d7523ffb9a7f0" + "edd976a6dcb0c90bf6895dad90b8f373b22162c397b0d0e3e49041dce4f7a34f" + "1dbe1e2c0f3f6be9d5bbc3e783743a70df89bf488de8dd97106c7fb9fdbbf662"); +const std::vector kIvCbc1 = a2b_hex( + "0111321322793b04f871aab28f6b066e"); +const std::vector kOutputCbc1 = a2b_hex( + "d5c7a71abfbfa2b490916d0e316c7b7e928b2cdaf9768b682b98f4087d664faa" + "c8f05bd97fede1c678dc4320df4ac65674ad63370616df3ee85acc145b4bc7a8" + "9169214197489350faa658ddff36959cf8dc2328bca5b1ccf26da4e1ce717595" + "a11ddf354a9811890afbb2207e90367bf007df42d99c682e6024cf7671273523" + "06d3e68a0fa2914640842759911bfdf90be7fc84742031989bb0b676d93a1904" + "4ba6811a032ddafd9e2d2caa44ec17363794b661d2460aa4517b1e349f0eeb23" + "9c2e83d31584f56a31b1688f89a4c64917e0037ae6aa7e483cd641dec38c3aba" + "195ca7942df98c124d4be96524edbda671aab2a52a2305637101f274e031bbc7"); + +// Dummy encrypted data using the CENC 3.0 "cbcs" mode. Encrypted using the +// key matching kKeyIdCbc. +const std::vector kInputCbcs = a2b_hex( + "7d8665445b3ac25fda29054e81626ed89f528f87315bdb07ba7fdad32835808f" + "6458893d28a247c37ec56c48f89ba6f941757edf22fea1b69980833746526dba" + "a8e9193125a4ac73893df76784aeb954124c59aa9771e5e5f91478ed720d2cfe" + "7421c5f7acbdfb762e75da0b48ba3f7bbf1dda3e8ff8ad1b625622438c100bd7" + "a063a0518b21bcbc6ebc2809478ebfd612e3400b515018b64fc1ba23c5b38b6d" + "45aaa4c0c394c3b75066390b646bf273fc4913f7e20b8c856d20561337fd4383" + "86938f189e0354ee9425974fc7153e61e006c3b472f21e2f3e33a1c94435d747" + "da153cd7f0cb8a7e4d1ee43acbf51027604cfe93808b42a2b6f30b4667d056e4"); +const std::vector kIvCbcs = a2b_hex( + "8e261c9660f5930ebe1734510cb9bc23"); +const std::vector kOutputCbcs = a2b_hex( + "bf7d1e9edc64a1782e884870edde98399f528f87315bdb07ba7fdad32835808f" + "6458893d28a247c37ec56c48f89ba6f941757edf22fea1b69980833746526dba" + "a8e9193125a4ac73893df76784aeb954124c59aa9771e5e5f91478ed720d2cfe" + "7421c5f7acbdfb762e75da0b48ba3f7bbf1dda3e8ff8ad1b625622438c100bd7" + "a063a0518b21bcbc6ebc2809478ebfd612e3400b515018b64fc1ba23c5b38b6d" + "d48cfbfc9e08ff501c62d5e85200dab0fc4913f7e20b8c856d20561337fd4383" + "86938f189e0354ee9425974fc7153e61e006c3b472f21e2f3e33a1c94435d747" + "da153cd7f0cb8a7e4d1ee43acbf51027604cfe93808b42a2b6f30b4667d056e4"); + const std::string kValue = "A Value"; const std::string kNewValue = "A New Value"; @@ -121,8 +222,7 @@ const std::string kParamName = "PARAM"; const std::string kParamName2 = "PARAM2"; -class CdmTest : public Test, - public Cdm::IEventListener { +class CdmTest : public Test, public Cdm::IEventListener { public: CdmTest() {} virtual ~CdmTest() {} @@ -136,6 +236,11 @@ class CdmTest : public Test, void(const std::string& session_id)); MOCK_METHOD1(onRemoveComplete, void(const std::string& session_id)); + MOCK_METHOD2(onDeferredComplete, + void(const std::string& session_id, Cdm::Status error_code)); + MOCK_METHOD2(onDirectIndividualizationRequest, + void(const std::string& session_id, + const std::string& message)); protected: virtual void SetUp() OVERRIDE { @@ -144,15 +249,17 @@ class CdmTest : public Test, // Clear anything stored by OEMCrypto. ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_DeleteUsageTable()); + int result = OEMCrypto_DeleteUsageTable(); + // Don't fault OEMCrypto implementations without usage tables: + if (result != OEMCrypto_ERROR_NOT_IMPLEMENTED) { + EXPECT_EQ(OEMCrypto_SUCCESS, result); + } ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); // Reinit the library. - Cdm::DeviceCertificateRequest cert_request; Cdm::Status status = Cdm::initialize( Cdm::kNoSecureOutput, PropertiesCE::GetClientInfo(), - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); ASSERT_EQ(Cdm::kSuccess, status); // Make a fresh CDM. @@ -172,7 +279,7 @@ class CdmTest : public Test, } void CreateAdditionalCdm(bool privacy_mode, scoped_ptr* cdm) { - cdm->reset(Cdm::create(this, privacy_mode)); + cdm->reset(Cdm::create(this, NULL, privacy_mode)); ASSERT_NE((Cdm*)0, cdm->get()); } @@ -226,10 +333,11 @@ class CdmTest : public Test, if (ok) ASSERT_EQ(kHttpOk, status_code); } - void FetchLicense(const std::string& message, + void FetchLicense(const std::string& license_server, + const std::string& message, std::string* response) { int status_code; - bool ok = Fetch(kLicenseServer, message, response, &status_code); + bool ok = Fetch(license_server, message, response, &status_code); ASSERT_TRUE(ok); if (ok) ASSERT_EQ(kHttpOk, status_code); } @@ -237,12 +345,13 @@ class CdmTest : public Test, void FetchLicenseFailure(const std::string& message, int expected_status_code) { int status_code; - bool ok = Fetch(kLicenseServer, message, NULL, &status_code); + bool ok = Fetch(kLicenseServerAppspot, message, NULL, &status_code); ASSERT_TRUE(ok); if (ok) ASSERT_EQ(expected_status_code, status_code); } void CreateSessionAndGenerateRequest(Cdm::SessionType session_type, + Cdm::InitDataType init_data_type, std::string* session_id, std::string* message) { Cdm::Status status = cdm_->createSession(session_type, session_id); @@ -250,32 +359,50 @@ class CdmTest : public Test, std::string init_data; if (session_type == Cdm::kTemporary) { - init_data = kCencInitData; - } else { - init_data = kCencPersistentInitData; + if (init_data_type == Cdm::kCenc) { + init_data = kCencInitData; + } else if (init_data_type == Cdm::kHls) { + init_data = kHlsInitData; + } + } else if (session_type == Cdm::kPersistentLicense || + session_type == Cdm::kPersistentUsageRecord) { + if (init_data_type == Cdm::kCenc) { + init_data = kCencPersistentInitData; + } } + ASSERT_FALSE(init_data.empty()); EXPECT_CALL(*this, onMessage(*session_id, Cdm::kLicenseRequest, _)). WillOnce(SaveArg<2>(message)); - status = cdm_->generateRequest(*session_id, Cdm::kCenc, init_data); + status = cdm_->generateRequest(*session_id, init_data_type, init_data); ASSERT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); } void CreateSessionAndFetchLicense(Cdm::SessionType session_type, + Cdm::InitDataType init_data_type, std::string* session_id, std::string* response) { std::string message; ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( - session_type, session_id, &message)); - FetchLicense(message, response); + session_type, init_data_type, session_id, &message)); + + std::string license_server; + if (init_data_type == Cdm::kCenc) { + license_server = kLicenseServerAppspot; + } else if (init_data_type == Cdm::kHls) { + license_server = kLicenseServerUat; + } + ASSERT_FALSE(license_server.empty()); + FetchLicense(license_server, message, response); } void CreateSessionAndUpdate(Cdm::SessionType session_type, + Cdm::InitDataType init_data_type, std::string* session_id) { std::string response; ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( - session_type, session_id, &response)); + session_type, init_data_type, session_id, &response)); EXPECT_CALL(*this, onKeyStatusesChange(*session_id)); Cdm::Status status = cdm_->update(*session_id, response); ASSERT_EQ(Cdm::kSuccess, status); @@ -286,7 +413,8 @@ class CdmTest : public Test, const std::string& message) { // Acquire a license. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // This license should be accepted, but the keys are not expected to change. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); @@ -299,6 +427,44 @@ class CdmTest : public Test, }; +struct DecryptParam { + public: + DecryptParam( + const std::string& short_name_param, + Cdm::InitDataType init_data_type_param, + const std::vector& key_id_param, + const std::vector& iv_param, + Cdm::EncryptionScheme scheme_param, + const Cdm::Pattern& pattern_param, + const std::vector& input_param, + const std::vector& output_param) + : short_name(short_name_param), + init_data_type(init_data_type_param), + key_id(&key_id_param), + iv(&iv_param), + scheme(scheme_param), + pattern(&pattern_param), + input(&input_param), + output(&output_param) {} + + const std::string short_name; + const Cdm::InitDataType init_data_type; + const std::vector* const key_id; + const std::vector* const iv; + const Cdm::EncryptionScheme scheme; + const Cdm::Pattern* const pattern; + const std::vector* const input; + const std::vector* const output; +}; + +void PrintTo(const DecryptParam& value, ::std::ostream* os) { + *os << value.short_name << " DecryptParam"; +} + +class CdmTestWithDecryptParam : public CdmTest, + public WithParamInterface {}; + + class MockTimerClient : public Cdm::ITimer::IClient { public: MockTimerClient() {} @@ -335,14 +501,12 @@ TEST_F(CdmTest, TestHostTimer) { } TEST_F(CdmTest, Initialize) { - Cdm::DeviceCertificateRequest cert_request; Cdm::Status status; // Try with an invalid output type. status = Cdm::initialize( static_cast(-1), PropertiesCE::GetClientInfo(), - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kTypeError, status); // Try with various client info properties missing. @@ -353,130 +517,64 @@ TEST_F(CdmTest, Initialize) { broken_client_info.product_name.clear(); status = Cdm::initialize( Cdm::kNoSecureOutput, broken_client_info, - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); 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, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); 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, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); 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, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); 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, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); 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, &cert_request, - static_cast(g_cutoff)); + g_host, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kSuccess, status); // Try with various host interfaces missing. status = Cdm::initialize( Cdm::kNoSecureOutput, working_client_info, - NULL, g_host, g_host, &cert_request, - static_cast(g_cutoff)); + NULL, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kTypeError, status); status = Cdm::initialize( Cdm::kNoSecureOutput, working_client_info, - g_host, NULL, g_host, &cert_request, - static_cast(g_cutoff)); + g_host, NULL, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kTypeError, status); status = Cdm::initialize( Cdm::kNoSecureOutput, working_client_info, - g_host, g_host, NULL, &cert_request, - static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kTypeError, status); - - status = Cdm::initialize( - Cdm::kNoSecureOutput, working_client_info, - g_host, g_host, g_host, NULL, - static_cast(g_cutoff)); + g_host, g_host, NULL, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kTypeError, status); // One last init with everything correct and working. status = Cdm::initialize( Cdm::kNoSecureOutput, working_client_info, - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kSuccess, status); -} - -TEST_F(CdmTest, DeviceCertificateRequest) { - uint32_t nonce = 0; - uint8_t buffer[1]; - size_t size = 0; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); - int result = OEMCrypto_RewrapDeviceRSAKey( - 0, buffer, 0, buffer, 0, &nonce, buffer, 0, buffer, buffer, &size); - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); - if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { - LOGW("WARNING: Skipping DeviceCertificateRequest because the device does " - "not support provisioning. If you are using a baked-in certificate, " - "this is expected. Otherwise, something is wrong."); - return; - } - - // Clear any existing certificates. - g_host->remove("cert.bin"); - - // Reinit the library without a device cert. - Cdm::DeviceCertificateRequest cert_request; - Cdm::Status status = Cdm::initialize( - Cdm::kNoSecureOutput, PropertiesCE::GetClientInfo(), - g_host, g_host, g_host, &cert_request, - static_cast(g_cutoff)); - - // This should succeed, but indicate that we need a cert still. - EXPECT_EQ(Cdm::kSuccess, status); - EXPECT_TRUE(cert_request.needed); - EXPECT_FALSE(cert_request.url.empty()); - - // Creating a session should fail. - std::string session_id; - status = cdm_->createSession(Cdm::kTemporary, &session_id); - EXPECT_EQ(Cdm::kNeedsDeviceCertificate, status); - - // Loading a session should fail. - status = cdm_->load(kBogusSessionId); - EXPECT_EQ(Cdm::kNeedsDeviceCertificate, status); - - // We should be able to provision. - std::string reply; - ASSERT_NO_FATAL_FAILURE(FetchCertificate(cert_request.url, &reply)); - status = cert_request.acceptReply(reply); - ASSERT_EQ(Cdm::kSuccess, status); - - // We should now be able to create a session. - status = cdm_->createSession(Cdm::kTemporary, &session_id); + g_host, g_host, g_host, static_cast(g_cutoff)); EXPECT_EQ(Cdm::kSuccess, status); } @@ -560,6 +658,14 @@ TEST_F(CdmTest, GenerateRequest) { EXPECT_EQ(Cdm::kNotSupported, status); Mock::VerifyAndClear(this); + // Create a new session and generate a license request for HLS. + status = cdm_->createSession(Cdm::kTemporary, &session_id); + ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)); + status = cdm_->generateRequest(session_id, Cdm::kHls, kHlsInitData); + EXPECT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + // Create a new session and try a bogus init data type. status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); @@ -611,11 +717,12 @@ TEST_F(CdmTest, Update) { std::string session_id; std::string message; ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( - Cdm::kTemporary, &session_id, &message)); + Cdm::kTemporary, Cdm::kCenc, &session_id, &message)); // Acquire a license. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -667,7 +774,7 @@ TEST_F(CdmTest, LoadTemporary) { std::string session_id; std::string response; ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( - Cdm::kTemporary, &session_id, &response)); + Cdm::kTemporary, Cdm::kCenc, &session_id, &response)); // Update the temporary session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -688,7 +795,7 @@ TEST_F(CdmTest, LoadPersistent) { std::string session_id; std::string response; ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( - Cdm::kPersistentLicense, &session_id, &response)); + Cdm::kPersistentLicense, Cdm::kCenc, &session_id, &response)); // Update the persistent session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -722,11 +829,44 @@ TEST_F(CdmTest, LoadPersistent) { Mock::VerifyAndClear(this); } +TEST_F(CdmTest, PerOriginLoadPersistent) { + std::string session_id; + std::string response; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + Cdm::kPersistentLicense, Cdm::kCenc, &session_id, &response)); + + // Update and close the persistent session. + Cdm::Status status = cdm_->update(session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + status = cdm_->close(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + + // Should be able to load the session again after recreating the CDM. + ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); + ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate()); + status = cdm_->load(session_id); + EXPECT_EQ(Cdm::kSuccess, status); + + // Create another host to use its storage. This will simulate another origin. + TestHost other_host; + scoped_ptr other_cdm( + Cdm::create(this, &other_host, /* privacy_mode */ true)); + ASSERT_TRUE(other_cdm.get()); + status = other_cdm->setServerCertificate(kDefaultServerCertificate); + ASSERT_EQ(Cdm::kSuccess, status); + + // Should not be able to load from another origin. + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + status = other_cdm->load(session_id); + EXPECT_EQ(Cdm::kSessionNotFound, status); + Mock::VerifyAndClear(this); +} + TEST_F(CdmTest, LoadUsageRecord) { std::string session_id; std::string response; ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( - Cdm::kPersistentUsageRecord, &session_id, &response)); + Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id, &response)); // Update the session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -740,7 +880,7 @@ TEST_F(CdmTest, LoadUsageRecord) { // There should be no usable keys after loading this session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0); status = cdm_->load(session_id); EXPECT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); @@ -749,7 +889,7 @@ TEST_F(CdmTest, LoadUsageRecord) { ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate()); EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0); status = cdm_->load(session_id); EXPECT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); @@ -773,7 +913,8 @@ TEST_F(CdmTest, LoadBogus) { TEST_F(CdmTest, GetKeyStatuses) { std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kTemporary, Cdm::kCenc, &session_id)); // We should be able to query status and see a usable key. Cdm::KeyStatusMap map; @@ -783,7 +924,7 @@ TEST_F(CdmTest, GetKeyStatuses) { // The key ID should be the one we are expecting. const std::string expected_key_id( - reinterpret_cast(kKeyId1.data()), kKeyId1.size()); + reinterpret_cast(kKeyIdCtr.data()), kKeyIdCtr.size()); EXPECT_EQ(expected_key_id, map.begin()->first); // Let the key expire. @@ -807,7 +948,8 @@ TEST_F(CdmTest, GetKeyStatuses) { TEST_F(CdmTest, GetExpiration) { std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kTemporary, Cdm::kCenc, &session_id)); // We should be able to query expiration and get a value in the future. int64_t expiration; @@ -840,7 +982,7 @@ TEST_F(CdmTest, GetExpiration) { TEST_F(CdmTest, Remove) { std::string session_id; ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( - Cdm::kPersistentLicense, &session_id)); + Cdm::kPersistentLicense, Cdm::kCenc, &session_id)); // Remove the session. This causes a release message to be generated. std::string message; @@ -859,7 +1001,8 @@ TEST_F(CdmTest, Remove) { // Post the release message to the license server. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. EXPECT_CALL(*this, onRemoveComplete(session_id)); @@ -884,7 +1027,8 @@ TEST_F(CdmTest, Remove) { EXPECT_EQ(Cdm::kInvalidState, status); // Try a temporary session. - ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kTemporary, Cdm::kCenc, &session_id)); status = cdm_->remove(session_id); EXPECT_EQ(Cdm::kRangeError, status); } @@ -892,7 +1036,7 @@ TEST_F(CdmTest, Remove) { TEST_F(CdmTest, RemoveUsageRecord) { std::string session_id; ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( - Cdm::kPersistentUsageRecord, &session_id)); + Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id)); // Remove the session. This causes a release message to be generated. std::string message; @@ -911,7 +1055,8 @@ TEST_F(CdmTest, RemoveUsageRecord) { // Post the release message to the license server. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. EXPECT_CALL(*this, onRemoveComplete(session_id)); @@ -927,7 +1072,7 @@ TEST_F(CdmTest, RemoveUsageRecord) { TEST_F(CdmTest, RemoveIncomplete) { std::string session_id; ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( - Cdm::kPersistentLicense, &session_id)); + Cdm::kPersistentLicense, Cdm::kCenc, &session_id)); // Remove the session. This causes a release message to be generated. std::string message; @@ -949,15 +1094,11 @@ TEST_F(CdmTest, RemoveIncomplete) { ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate()); - // Load the partially removed session, which will immediately generate a - // release message. - message.clear(); + // Load the partially removed session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce( - SaveArg<2>(&message)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0); status = cdm_->load(session_id); ASSERT_EQ(Cdm::kSuccess, status); - ASSERT_FALSE(message.empty()); Mock::VerifyAndClear(this); // This session has no keys. @@ -965,9 +1106,20 @@ TEST_F(CdmTest, RemoveIncomplete) { ASSERT_EQ(Cdm::kSuccess, status); EXPECT_TRUE(map.empty()); + // Remove the session again to fire the release message. + message.clear(); + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce( + SaveArg<2>(&message)); + status = cdm_->remove(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + ASSERT_FALSE(message.empty()); + Mock::VerifyAndClear(this); + // Post the release message to the license server. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); @@ -984,7 +1136,7 @@ TEST_F(CdmTest, RemoveIncomplete) { TEST_F(CdmTest, RemoveUsageRecordIncomplete) { std::string session_id; ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( - Cdm::kPersistentUsageRecord, &session_id)); + Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id)); // Remove the session. This causes a release message to be generated. std::string message; @@ -1006,13 +1158,19 @@ TEST_F(CdmTest, RemoveUsageRecordIncomplete) { ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate()); - // Load the partially removed session, which will immediately generate a - // release message. + // Load the partially removed session. + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0); + status = cdm_->load(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Remove the session again to fire a release message. message.clear(); EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce( SaveArg<2>(&message)); - status = cdm_->load(session_id); + status = cdm_->remove(session_id); ASSERT_EQ(Cdm::kSuccess, status); ASSERT_FALSE(message.empty()); Mock::VerifyAndClear(this); @@ -1024,7 +1182,8 @@ TEST_F(CdmTest, RemoveUsageRecordIncomplete) { // Post the release message to the license server. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); @@ -1042,7 +1201,7 @@ TEST_F(CdmTest, RemoveNotLoaded) { // Create a persistent session and then close it. std::string session_id; ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( - Cdm::kPersistentLicense, &session_id)); + Cdm::kPersistentLicense, Cdm::kCenc, &session_id)); Cdm::Status status = cdm_->close(session_id); ASSERT_EQ(Cdm::kSuccess, status); @@ -1052,37 +1211,6 @@ TEST_F(CdmTest, RemoveNotLoaded) { EXPECT_EQ(Cdm::kSessionNotFound, status); } -TEST_F(CdmTest, DecryptClear) { - Cdm::InputBuffer input; - Cdm::OutputBuffer output; - - input.key_id = kKeyId1.data(); - input.key_id_length = kKeyId1.size(); - input.iv = kIv1.data(); - input.iv_length = kIv1.size(); - input.data = kInput1.data(); - input.data_length = kInput1.size(); - input.is_encrypted = true; - - std::vector output_buffer(input.data_length); - output.data = &(output_buffer[0]); - output.data_length = output_buffer.size(); - - // Decrypt without keys loaded should fail. - Cdm::Status status = cdm_->decrypt(input, output); - ASSERT_EQ(Cdm::kNoKey, status); - - // Create a session with the right keys. - std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); - - // Decrypt should now succeed. - status = cdm_->decrypt(input, output); - ASSERT_EQ(Cdm::kSuccess, status); - EXPECT_EQ(kOutput1, output_buffer); -} -// TODO: add infrastructure to test secure buffer decrypt for some platforms - TEST_F(CdmTest, RequestPersistentLicenseWithWrongInitData) { // Generate a request for a persistent license without using the correct // persistent content init data. @@ -1118,7 +1246,8 @@ TEST_F(CdmTest, RequestTemporaryLicenseWithWrongInitData) { // Acquire a license. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // This license should not be accepted. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -1129,7 +1258,8 @@ TEST_F(CdmTest, RequestTemporaryLicenseWithWrongInitData) { TEST_F(CdmTest, Renewal) { std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kTemporary, Cdm::kCenc, &session_id)); // We should have a timer. EXPECT_NE(0, g_host->NumTimers()); @@ -1169,15 +1299,16 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { // Expect a license request type message, but this is actually a server cert // provisioning request. std::string message; - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)). - WillOnce(SaveArg<2>(&message)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)) + .WillOnce(SaveArg<2>(&message)); status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData); ASSERT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); // Relay it to the server. std::string response; - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // No keys will change, since this wasn't a license. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); @@ -1196,7 +1327,8 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { ASSERT_TRUE(map.empty()); // Relay the license request to the server. - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Update the session. The keys will change now. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1); @@ -1225,7 +1357,8 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { Mock::VerifyAndClear(this); // Relay it to the server. - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Keys will change, since this was an actual license. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1); @@ -1244,14 +1377,15 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { status = cdm2->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); message.clear(); - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce( - SaveArg<2>(&message)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)) + .WillOnce(SaveArg<2>(&message)); status = cdm2->generateRequest(session_id, Cdm::kCenc, kCencInitData); ASSERT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); // Relay it to the server. - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // No keys will change, since this wasn't a license. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); @@ -1275,7 +1409,8 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { Mock::VerifyAndClear(this); // Relay it to the server. - ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + ASSERT_NO_FATAL_FAILURE(FetchLicense( + kLicenseServerAppspot, message, &response)); // Keys will change, since this was an actual license. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1); @@ -1364,4 +1499,334 @@ TEST_F(CdmTest, SetAppParameters) { ASSERT_EQ(Cdm::kTypeError, status); } +TEST_P(CdmTestWithDecryptParam, DecryptToClearBuffer) { + DecryptParam param = GetParam(); + + Cdm::InputBuffer input; + Cdm::OutputBuffer output; + + input.key_id = param.key_id->data(); + input.key_id_length = param.key_id->size(); + input.iv = param.iv->data(); + input.iv_length = param.iv->size(); + input.data = param.input->data(); + input.data_length = param.input->size(); + input.encryption_scheme = param.scheme; + input.pattern = *param.pattern; + + std::vector output_buffer(input.data_length); + output.data = &(output_buffer[0]); + output.data_length = output_buffer.size(); + + // Decrypt without keys loaded should fail. + Cdm::Status status = cdm_->decrypt(input, output); + ASSERT_EQ(Cdm::kNoKey, status); + + // Create a session with the right keys. + std::string session_id; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kTemporary, param.init_data_type, &session_id)); + + // Decrypt should now succeed. + status = cdm_->decrypt(input, output); + ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_EQ(*param.output, output_buffer); +} + +INSTANTIATE_TEST_CASE_P(CdmDecryptTest, CdmTestWithDecryptParam, Values( + DecryptParam("CENC 3.0 cenc Mode", + Cdm::kCenc, kKeyIdCtr, kIvCenc, Cdm::kAesCtr, + kPatternNone, kInputCenc, kOutputCenc), + DecryptParam("CENC 3.0 cens Mode", + Cdm::kCenc, kKeyIdCtr, kIvCens, Cdm::kAesCtr, + kPatternRecommended, kInputCens, kOutputCens), + DecryptParam("CENC 3.0 cbc1 Mode", + Cdm::kHls, kKeyIdCbc, kIvCbc1, Cdm::kAesCbc, + kPatternNone, kInputCbc1, kOutputCbc1), + DecryptParam("CENC 3.0 cbcs Mode", + Cdm::kHls, kKeyIdCbc, kIvCbcs, Cdm::kAesCbc, + kPatternRecommended, kInputCbcs, kOutputCbcs), + DecryptParam("HLS Audio (CENC 3.0 cbcs Mode Without a Pattern)", + Cdm::kHls, kKeyIdCbc, kIvCbc1, Cdm::kAesCbc, + kPatternHlsAudio, kInputCbc1, kOutputCbc1) + )); + +// TODO: add infrastructure to test secure buffer decrypt for some platforms + +class CdmIndividualizationTest : public CdmTest { + protected: + bool CheckProvisioningSupport() { + uint32_t nonce = 0; + uint8_t buffer[1]; + size_t size = 0; + EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); + int result = OEMCrypto_RewrapDeviceRSAKey(0, buffer, 0, buffer, 0, &nonce, + buffer, 0, buffer, buffer, &size); + EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); + if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + LOGW( + "WARNING: Skipping PerOriginDeviceProvisioning because the device " + "does not support provisioning. If you are using a baked-in " + "certificate, this is expected. Otherwise, something is wrong."); + return false; + } + return true; + } + + std::string GetProvisioningResponse(const std::string& message) { + std::string reply; + std::string uri = kProvisioningServerUrl; + uri += "&signedRequest=" + message; + FetchCertificate(uri, &reply); + if (HasFatalFailure()) + return ""; + return reply; + } +}; + +TEST_F(CdmIndividualizationTest, BasicFlow) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); + EXPECT_EQ(Cdm::kSuccess, status); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData); + EXPECT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Complete the provisioning request. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1); + EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0); + status = cdm_->update(session_id, reply); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, WillNotSendRequestTwice) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); + + // Create a second session. + std::string session_id2; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id2)); + + // Should not get another individualization request. + EXPECT_CALL(*this, onMessage(_, _, _)).Times(0); + ASSERT_EQ(Cdm::kDeferred, + cdm_->generateRequest(session_id2, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); + + // Complete the provisioning request, should generate requests for both + // sessions. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1); + EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(1); + EXPECT_CALL(*this, onDeferredComplete(session_id2, Cdm::kSuccess)).Times(1); + EXPECT_EQ(Cdm::kSuccess, cdm_->update(session_id, reply)); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, + WillNotSendMessageWhenGenerateRequestNotCalled) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); + + // Create a second session, don't call generateRequest for it. + std::string session_id2; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id2)); + + // Complete the provisioning request, should not get calls for the second + // session. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1); + EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(0); + EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0); + EXPECT_EQ(Cdm::kSuccess, cdm_->update(session_id, reply)); + Mock::VerifyAndClear(this); + + // We should get a license message for the second session. + EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(1); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id2, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, PropagatesErrorsInUpdate) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, cdm_->generateRequest(session_id, Cdm::kCenc, + kInvalidCencInitData)); + Mock::VerifyAndClear(this); + + // Complete the provisioning request, should get an error call. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + EXPECT_CALL(*this, onMessage(_, _, _)).Times(0); + EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0); + EXPECT_EQ(Cdm::kNotSupported, cdm_->update(session_id, reply)); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, OnlyPropagatesErrorsForThisSession) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); + + // Create another session that will cause an error. + std::string session_id_2; + ASSERT_EQ(Cdm::kSuccess, + cdm_->createSession(Cdm::kTemporary, &session_id_2)); + ASSERT_EQ(Cdm::kDeferred, cdm_->generateRequest(session_id_2, Cdm::kCenc, + kInvalidCencInitData)); + + // Complete the provisioning request, should succeed, but get an error + // callback. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1); + EXPECT_CALL(*this, onMessage(session_id_2, _, _)).Times(0); + EXPECT_CALL(*this, onDeferredComplete(session_id_2, Cdm::kNotSupported)) + .Times(1); + EXPECT_EQ(Cdm::kSuccess, cdm_->update(session_id, reply)); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, WorksWithLoad) { + if (!CheckProvisioningSupport()) return; + + // Create an offline session to load. + std::string session_id; + ASSERT_NO_FATAL_FAILURE( + CreateSessionAndUpdate(Cdm::kPersistentLicense, Cdm::kCenc, &session_id)); + EXPECT_EQ(Cdm::kSuccess, cdm_->close(session_id)); + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Loading a session should succeed, we should get an individualization + // request right away. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, cdm_->load(session_id)); + Mock::VerifyAndClear(this); + + // Complete the provisioning request. + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + // Because we are now provisioned with a new key, we can't load the session, + // but we will still be provisioned. + EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0); + EXPECT_EQ(Cdm::kUnexpectedError, cdm_->update(session_id, reply)); + Mock::VerifyAndClear(this); + + // Create a second session, we should be previsioned at this point. + std::string session_id2; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id2)); + EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(1); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id2, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); +} + +TEST_F(CdmIndividualizationTest, WillResendOnProvisioningError) { + if (!CheckProvisioningSupport()) return; + + // Clear any existing certificates. + g_host->remove("cert.bin"); + + // Creating a session should succeed. + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + + // Should get an individualization request when we generate request. + std::string message; + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); + + // Fail to provision the device. + EXPECT_CALL(*this, onMessage(_, _, _)).Times(0); + EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0); + EXPECT_EQ(Cdm::kUnexpectedError, cdm_->update(session_id, "")); + Mock::VerifyAndClear(this); + + // Should get another individualization request. + std::string session_id_2; + ASSERT_EQ(Cdm::kSuccess, + cdm_->createSession(Cdm::kTemporary, &session_id_2)); + EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id_2, _)) + .WillOnce(SaveArg<1>(&message)); + ASSERT_EQ(Cdm::kSuccess, + cdm_->generateRequest(session_id_2, Cdm::kCenc, kCencInitData)); + Mock::VerifyAndClear(this); +} + } // namespace widevine diff --git a/cdm/test/cdm_test_main.cpp b/cdm/test/cdm_test_main.cpp index ed68408e..6d10571b 100644 --- a/cdm/test/cdm_test_main.cpp +++ b/cdm/test/cdm_test_main.cpp @@ -15,7 +15,7 @@ #include "test_host.h" #if defined(OEMCRYPTO_TESTS) -# include "oemcrypto_test.h" +# include "oec_device_features.h" #endif using namespace widevine; @@ -68,13 +68,11 @@ int main(int argc, char** argv) { #endif client_info.build_info = __DATE__; - Cdm::DeviceCertificateRequest cert_request; Cdm::Status status = Cdm::initialize( - Cdm::kNoSecureOutput, client_info, g_host, g_host, g_host, &cert_request, + Cdm::kNoSecureOutput, client_info, g_host, g_host, g_host, static_cast(verbosity)); (void)status; // status is now used when assertions are turned off. assert(status == Cdm::kSuccess); - assert(cert_request.needed == false); #if defined(OEMCRYPTO_TESTS) // Set up the OEMCrypto test harness. diff --git a/cdm/test/test_host.cpp b/cdm/test/test_host.cpp index 26faf0e8..deb1af3b 100644 --- a/cdm/test/test_host.cpp +++ b/cdm/test/test_host.cpp @@ -71,6 +71,7 @@ bool TestHost::exists(const std::string& name) { } bool TestHost::remove(const std::string& name) { + LOGD("remove: %s", name.c_str()); files_.erase(name); return true; } diff --git a/core/include/cdm_engine.h b/core/include/cdm_engine.h index 3b77b098..d4582df2 100644 --- a/core/include/cdm_engine.h +++ b/core/include/cdm_engine.h @@ -7,6 +7,7 @@ #include "certificate_provisioning.h" #include "crypto_session.h" +#include "file_store.h" #include "initialization_data.h" #include "lock.h" #include "oemcrypto_adapter.h" @@ -26,22 +27,24 @@ typedef std::map CdmReleaseKeySetMap; class CdmEngine { public: - CdmEngine(); + CdmEngine(FileSystem* file_system); virtual ~CdmEngine(); // Session related methods virtual CdmResponseType OpenSession(const CdmKeySystem& key_system, CdmClientPropertySet* property_set, - const std::string& origin, + const CdmSessionId& forced_session_id, + WvCdmEventListener* event_listener); + virtual CdmResponseType OpenSession(const CdmKeySystem& key_system, + CdmClientPropertySet* property_set, WvCdmEventListener* event_listener, - const CdmSessionId* forced_session_id, CdmSessionId* session_id); virtual CdmResponseType CloseSession(const CdmSessionId& session_id); virtual bool IsOpenSession(const CdmSessionId& session_id); - virtual CdmResponseType OpenKeySetSession( - const CdmKeySetId& key_set_id, CdmClientPropertySet* property_set, - const std::string& origin, WvCdmEventListener* event_listener); + virtual CdmResponseType OpenKeySetSession(const CdmKeySetId& key_set_id, + CdmClientPropertySet* property_set, + WvCdmEventListener* event_listener); virtual CdmResponseType CloseKeySetSession(const CdmKeySetId& key_set_id); // License related methods @@ -58,27 +61,17 @@ class CdmEngine { // app_parameters: Additional, application-specific parameters that factor // into the request generation. This is ignored for release // and renewal requests. - // key_request: This must be non-null and point to a CdmKeyMessage. The buffer - // will have its contents replaced with the key request. - // key_request_type: May be null. If it is non-null, it will be filled with - // key request type, whether it is an initial request, - // renewal request or release request etc. - // server_url: This must be non-null and point to a string. The string will - // have its contents replaced with the default URL (if one is - // known) to send this key request to. - // key_set_id_out: May be null. If it is non-null, the CdmKeySetId pointed to - // will have its contents replaced with the key set ID of the - // session. Note that for non-offline license requests, the - // key set ID is empty, so the CdmKeySetId will be cleared. - // TODO(kqyang): Consider refactor GenerateKeyRequest to reduce the number of - // parameters. + // key_request: This must be non-null and point to a CdmKeyRequest. The + // message field will be filled with the key request, the + // type field will be filled with the key request type, + // whether it is an initial request, renewal request, + // release request, etc. The url field will be filled with + // the default URL (if one is known) to send this key + // request to. virtual CdmResponseType GenerateKeyRequest( const CdmSessionId& session_id, const CdmKeySetId& key_set_id, const InitializationData& init_data, const CdmLicenseType license_type, - CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, - CdmKeyRequestType* key_request_type, std::string* server_url, - CdmKeySetId* key_set_id_out); - + CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request); // Accept license response and extract key info. virtual CdmResponseType AddKey(const CdmSessionId& session_id, const CdmKeyResponse& key_data, @@ -90,9 +83,8 @@ class CdmEngine { virtual CdmResponseType RemoveKeys(const CdmSessionId& session_id); // Construct valid renewal request for the current session keys. - virtual CdmResponseType GenerateRenewalRequest(const CdmSessionId& session_id, - CdmKeyMessage* key_request, - std::string* server_url); + virtual CdmResponseType GenerateRenewalRequest( + const CdmSessionId& session_id, CdmKeyRequest* key_request); // Accept renewal response and update key info. virtual CdmResponseType RenewKey(const CdmSessionId& session_id, @@ -100,38 +92,48 @@ class CdmEngine { // Query system information virtual CdmResponseType QueryStatus(SecurityLevel security_level, - const std::string& key, - std::string* value); + const std::string& query_token, + std::string* query_response); // Query session information virtual CdmResponseType QuerySessionStatus(const CdmSessionId& session_id, - CdmQueryMap* key_info); + CdmQueryMap* query_response); virtual bool IsReleaseSession(const CdmSessionId& session_id); virtual bool IsOfflineSession(const CdmSessionId& session_id); // Query license information virtual CdmResponseType QueryKeyStatus(const CdmSessionId& session_id, - CdmQueryMap* key_info); + CdmQueryMap* query_response); - // Query session control information - virtual CdmResponseType QueryKeyControlInfo(const CdmSessionId& session_id, - CdmQueryMap* key_info); + // Query the types of usage permitted for the specified key. + virtual CdmResponseType QueryKeyAllowedUsage(const CdmSessionId& session_id, + const std::string& key_id, + CdmKeyAllowedUsage* key_usage); + + // Query the types of usage permitted for the specified key. + // Apply the query across all sessions. If the key is found in more than + // one session, return the allowed usage settings only if the usage settings + // are identical for each instance of the key. Otherwise, clear the settings + // and return KEY_CONFLICT_1. + virtual CdmResponseType QueryKeyAllowedUsage(const std::string& key_id, + CdmKeyAllowedUsage* key_usage); + + // Query OEMCrypto session ID + virtual CdmResponseType QueryOemCryptoSessionId( + const CdmSessionId& session_id, CdmQueryMap* query_response); // Provisioning related methods virtual CdmResponseType GetProvisioningRequest( CdmCertificateType cert_type, const std::string& cert_authority, - const std::string& origin, CdmProvisioningRequest* request, - std::string* default_url); + CdmProvisioningRequest* request, std::string* default_url); virtual CdmResponseType HandleProvisioningResponse( - const std::string& origin, const CdmProvisioningResponse& response, - std::string* cert, std::string* wrapped_key); + const CdmProvisioningResponse& response, std::string* cert, + std::string* wrapped_key); - virtual bool IsProvisioned(CdmSecurityLevel security_level, - const std::string& origin); + virtual bool IsProvisioned(CdmSecurityLevel security_level); - virtual CdmResponseType Unprovision(CdmSecurityLevel security_level, - const std::string& origin); + virtual CdmResponseType Unprovision(CdmSecurityLevel security_level); // Usage related methods for streaming licenses // Retrieve a random usage info from the list of all usage infos for this app @@ -154,6 +156,35 @@ class CdmEngine { virtual CdmResponseType Decrypt(const CdmSessionId& session_id, const CdmDecryptionParameters& parameters); + // Generic crypto operations - provides basic crypto operations that an + // application can use outside of content stream processing + + // Encrypts a buffer of app-level data. + virtual CdmResponseType GenericEncrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + CdmEncryptionAlgorithm algorithm, std::string* out_buffer); + + // Decrypts a buffer of app-level data. + virtual CdmResponseType GenericDecrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + CdmEncryptionAlgorithm algorithm, std::string* out_buffer); + + // Computes the signature for a message. + virtual CdmResponseType GenericSign(const std::string& session_id, + const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + std::string* signature); + + // Verifies the signature on a buffer of app-level data. + virtual CdmResponseType GenericVerify(const std::string& session_id, + const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + const std::string& signature); + virtual size_t SessionSize() const { return sessions_.size(); } // Is the key known to any session? @@ -175,6 +206,12 @@ class CdmEngine { private: // private methods + CdmResponseType OpenSession(const CdmKeySystem& key_system, + CdmClientPropertySet* property_set, + WvCdmEventListener* event_listener, + const CdmSessionId* forced_session_id, + CdmSessionId* session_id); + void DeleteAllUsageReportsUponFactoryReset(); bool ValidateKeySystem(const CdmKeySystem& key_system); CdmResponseType GetUsageInfo(const std::string& app_id, @@ -190,6 +227,7 @@ class CdmEngine { CdmReleaseKeySetMap release_key_sets_; scoped_ptr cert_provisioning_; SecurityLevel cert_provisioning_requested_security_level_; + FileSystem* file_system_; static bool seeded_; diff --git a/core/include/cdm_session.h b/core/include/cdm_session.h index d473d16b..63c1908d 100644 --- a/core/include/cdm_session.h +++ b/core/include/cdm_session.h @@ -8,6 +8,7 @@ #include "crypto_session.h" #include "device_files.h" +#include "file_store.h" #include "initialization_data.h" #include "license.h" #include "oemcrypto_adapter.h" @@ -22,12 +23,13 @@ class WvCdmEventListener; class CdmSession { public: - CdmSession(CdmClientPropertySet* cdm_client_property_set, - const std::string& origin, WvCdmEventListener* event_listener, - const CdmSessionId* forced_session_id); + CdmSession(FileSystem* file_system); virtual ~CdmSession(); - virtual CdmResponseType Init(); + virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set); + virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set, + const CdmSessionId* forced_session_id, + WvCdmEventListener* event_listener); virtual CdmResponseType RestoreOfflineSession( const CdmKeySetId& key_set_id, const CdmLicenseType license_type); @@ -35,25 +37,27 @@ class CdmSession { const CdmKeyMessage& key_request, const CdmKeyResponse& key_response); virtual const CdmSessionId& session_id() { return session_id_; } + virtual const CdmKeySetId& key_set_id() { return key_set_id_; } virtual CdmResponseType GenerateKeyRequest( const InitializationData& init_data, CdmLicenseType license_type, - const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, - CdmKeyRequestType* key_request_type, std::string* server_url, - CdmKeySetId* key_set_id); + const CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request); // AddKey() - Accept license response and extract key info. - virtual CdmResponseType AddKey(const CdmKeyResponse& key_response, - CdmKeySetId* key_set_id); + virtual CdmResponseType AddKey(const CdmKeyResponse& key_response); // Query session status - virtual CdmResponseType QueryStatus(CdmQueryMap* key_info); + virtual CdmResponseType QueryStatus(CdmQueryMap* query_response); // Query license information - virtual CdmResponseType QueryKeyStatus(CdmQueryMap* key_info); + virtual CdmResponseType QueryKeyStatus(CdmQueryMap* query_response); - // Query session control info - virtual CdmResponseType QueryKeyControlInfo(CdmQueryMap* key_info); + // Query allowed usages for key + virtual CdmResponseType QueryKeyAllowedUsage(const std::string& key_id, + CdmKeyAllowedUsage* key_usage); + + // Query OEMCrypto session ID + virtual CdmResponseType QueryOemCryptoSessionId(CdmQueryMap* query_response); // Decrypt() - Accept encrypted buffer and return decrypted data. virtual CdmResponseType Decrypt(const CdmDecryptionParameters& parameters); @@ -61,8 +65,7 @@ class CdmSession { // License renewal // GenerateRenewalRequest() - Construct valid renewal request for the current // session keys. - virtual CdmResponseType GenerateRenewalRequest(CdmKeyMessage* key_request, - std::string* server_url); + virtual CdmResponseType GenerateRenewalRequest(CdmKeyRequest* key_request); // RenewKey() - Accept renewal response and update key info. virtual CdmResponseType RenewKey(const CdmKeyResponse& key_response); @@ -70,13 +73,13 @@ class CdmSession { // License release // GenerateReleaseRequest() - Construct valid release request for the current // session keys. - virtual CdmResponseType GenerateReleaseRequest(CdmKeyMessage* key_request, - std::string* server_url); + virtual CdmResponseType GenerateReleaseRequest(CdmKeyRequest* key_request); // ReleaseKey() - Accept response and release key. virtual CdmResponseType ReleaseKey(const CdmKeyResponse& key_response); virtual bool IsKeyLoaded(const KeyId& key_id); + virtual int64_t GetDurationRemaining(); // Used for notifying the Policy Engine of resolution changes virtual void NotifyResolution(uint32_t width, uint32_t height); @@ -116,11 +119,41 @@ class CdmSession { bool DeleteLicense(); + // Generate unique ID for each new session. + CdmSessionId GenerateSessionId(); + + // Generic crypto operations - provides basic crypto operations that an + // application can use outside of content stream processing + + // Encrypts a buffer of app-level data. + virtual CdmResponseType GenericEncrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer); + + // Decrypts a buffer of app-level data. + virtual CdmResponseType GenericDecrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer); + + // Computes the signature for a message. + virtual CdmResponseType GenericSign(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + std::string* signature); + + // Verifies the signature on a buffer of app-level data. + virtual CdmResponseType GenericVerify(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + const std::string& signature); + private: friend class CdmSessionTest; - // Generate unique ID for each new session. - CdmSessionId GenerateSessionId(); bool GenerateKeySetId(CdmKeySetId* key_set_id); CdmResponseType StoreLicense(); @@ -135,7 +168,6 @@ class CdmSession { // instance variables bool initialized_; CdmSessionId session_id_; - const std::string origin_; scoped_ptr license_parser_; scoped_ptr crypto_session_; scoped_ptr policy_engine_; @@ -167,6 +199,9 @@ class CdmSession { // license type release and offline related information CdmKeySetId key_set_id_; + bool mock_license_parser_in_use_; + bool mock_policy_engine_in_use_; + CORE_DISALLOW_COPY_AND_ASSIGN(CdmSession); }; diff --git a/core/include/certificate_provisioning.h b/core/include/certificate_provisioning.h index dd222828..22460c21 100644 --- a/core/include/certificate_provisioning.h +++ b/core/include/certificate_provisioning.h @@ -12,6 +12,7 @@ namespace wvcdm { class CdmSession; +class FileSystem; class CertificateProvisioning { public: @@ -26,7 +27,7 @@ class CertificateProvisioning { CdmProvisioningRequest* request, std::string* default_url); CdmResponseType HandleProvisioningResponse( - const std::string& origin, + FileSystem* file_system, const CdmProvisioningResponse& response, std::string* cert, std::string* wrapped_key); diff --git a/core/include/crypto_key.h b/core/include/crypto_key.h index d973a021..921517e6 100644 --- a/core/include/crypto_key.h +++ b/core/include/crypto_key.h @@ -17,6 +17,7 @@ class CryptoKey { const std::string& key_data_iv() const { return key_data_iv_; } const std::string& key_control() const { return key_control_; } const std::string& key_control_iv() const { return key_control_iv_; } + CdmCipherMode cipher_mode() const { return cipher_mode_; } void set_key_id(const std::string& key_id) { key_id_ = key_id; } void set_key_data(const std::string& key_data) { key_data_ = key_data; } void set_key_data_iv(const std::string& iv) { key_data_iv_ = iv; } @@ -24,6 +25,9 @@ class CryptoKey { void set_key_control_iv(const std::string& ctl_iv) { key_control_iv_ = ctl_iv; } + void set_cipher_mode(CdmCipherMode cipher_mode) { + cipher_mode_ = cipher_mode; + } bool HasKeyControl() const { return key_control_.size() >= 16; } @@ -33,6 +37,7 @@ class CryptoKey { std::string key_data_; std::string key_control_; std::string key_control_iv_; + CdmCipherMode cipher_mode_; }; } // namespace wvcdm diff --git a/core/include/crypto_session.h b/core/include/crypto_session.h index 1e8d52c2..9f1893c8 100644 --- a/core/include/crypto_session.h +++ b/core/include/crypto_session.h @@ -35,6 +35,7 @@ class CryptoSession { virtual bool GetApiVersion(uint32_t* version); virtual bool GetSystemId(uint32_t* system_id); virtual bool GetProvisioningId(std::string* provisioning_id); + virtual uint8_t GetSecurityPatchLevel(); virtual CdmResponseType Open() { return Open(kLevelDefault); } virtual CdmResponseType Open(SecurityLevel requested_security_level); @@ -102,6 +103,25 @@ class CryptoSession { virtual bool GetNumberOfOpenSessions(size_t* count); virtual bool GetMaxNumberOfSessions(size_t* max); + virtual CdmResponseType GenericEncrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer); + virtual CdmResponseType GenericDecrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer); + virtual CdmResponseType GenericSign(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + std::string* signature); + virtual CdmResponseType GenericVerify(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + const std::string& signature); + private: void Init(); void Terminate(); @@ -113,9 +133,17 @@ class CryptoSession { bool GenerateRsaSignature(const std::string& message, std::string* signature); size_t GetOffset(std::string message, std::string field); bool SetDestinationBufferType(); - bool SelectKey(const std::string& key_id); + static const OEMCrypto_Algorithm kInvalidAlgorithm = + static_cast(-1); + + OEMCrypto_Algorithm GenericSigningAlgorithm(CdmSigningAlgorithm algorithm); + OEMCrypto_Algorithm GenericEncryptionAlgorithm( + CdmEncryptionAlgorithm algorithm); + size_t GenericEncryptionBlockSize(CdmEncryptionAlgorithm algorithm); + + static const size_t kAes128BlockSize = 16; // Block size for AES_CBC_128 static const size_t kSignatureSize = 32; // size for HMAC-SHA256 signature static Lock crypto_lock_; static bool initialized_; @@ -129,11 +157,13 @@ class CryptoSession { bool is_destination_buffer_type_valid_; SecurityLevel requested_security_level_; - KeyId key_id_; + KeyId cached_key_id_; uint64_t request_id_base_; static uint64_t request_id_index_; + CdmCipherMode cipher_mode_; + CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession); }; diff --git a/core/include/device_files.h b/core/include/device_files.h index 88dabae0..6eef4ee7 100644 --- a/core/include/device_files.h +++ b/core/include/device_files.h @@ -18,7 +18,7 @@ namespace wvcdm { -class File; +class FileSystem; class DeviceFiles { public: @@ -28,7 +28,7 @@ class DeviceFiles { kLicenseStateUnknown, } LicenseState; - DeviceFiles(); + DeviceFiles(FileSystem*); virtual ~DeviceFiles(); virtual bool Init(CdmSecurityLevel security_level); @@ -36,14 +36,12 @@ class DeviceFiles { return Init(security_level); } - virtual bool StoreCertificate(const std::string& origin, - const std::string& certificate, + virtual bool StoreCertificate(const std::string& certificate, const std::string& wrapped_private_key); - virtual bool RetrieveCertificate(const std::string& origin, - std::string* certificate, + virtual bool RetrieveCertificate(std::string* certificate, std::string* wrapped_private_key); - virtual bool HasCertificate(const std::string& origin); - virtual bool RemoveCertificate(const std::string& origin); + virtual bool HasCertificate(); + virtual bool RemoveCertificate(); virtual bool StoreLicense(const std::string& key_set_id, const LicenseState state, @@ -100,6 +98,13 @@ class DeviceFiles { CdmKeyMessage* license_request, CdmKeyResponse* license_response); + virtual bool StoreHlsAttributes(const std::string& key_set_id, + const CdmHlsMethod method, + const std::vector& media_segment_iv); + virtual bool RetrieveHlsAttributes(const std::string& key_set_id, + CdmHlsMethod* method, + std::vector* media_segment_iv); + virtual bool DeleteHlsAttributes(const std::string& key_set_id); private: // Helpers that wrap the File interface and automatically handle hashing, as // well as adding the device files base path to to the file name. @@ -113,29 +118,25 @@ class DeviceFiles { bool RemoveFile(const std::string& name); ssize_t GetFileSize(const std::string& name); - // Certificate and offline licenses are now stored in security - // level specific directories. In an earlier version they were - // stored in a common directory and need to be copied over. - virtual void SecurityLevelPathBackwardCompatibility(); - - static std::string GetCertificateFileName(const std::string& origin); + static std::string GetCertificateFileName(); + static std::string GetHlsAttributesFileNameExtension(); static std::string GetLicenseFileNameExtension(); static std::string GetUsageInfoFileName(const std::string& app_id); static std::string GetFileNameSafeHash(const std::string& input); - // For testing only: - void SetTestFile(File* file); #if defined(UNIT_TEST) FRIEND_TEST(DeviceFilesSecurityLevelTest, SecurityLevel); FRIEND_TEST(DeviceCertificateStoreTest, StoreCertificate); FRIEND_TEST(DeviceCertificateTest, ReadCertificate); FRIEND_TEST(DeviceCertificateTest, HasCertificate); FRIEND_TEST(DeviceFilesStoreTest, StoreLicense); + FRIEND_TEST(DeviceFilesHlsAttributesTest, Delete); + FRIEND_TEST(DeviceFilesHlsAttributesTest, Read); + FRIEND_TEST(DeviceFilesHlsAttributesTest, Store); FRIEND_TEST(DeviceFilesTest, DeleteLicense); FRIEND_TEST(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem); FRIEND_TEST(DeviceFilesTest, RetrieveLicenses); FRIEND_TEST(DeviceFilesTest, AppParametersBackwardCompatibility); - FRIEND_TEST(DeviceFilesTest, SecurityLevelPathBackwardCompatibility); FRIEND_TEST(DeviceFilesTest, StoreLicenses); FRIEND_TEST(DeviceFilesTest, UpdateLicenseState); FRIEND_TEST(DeviceFilesUsageInfoTest, Delete); @@ -153,12 +154,10 @@ class DeviceFiles { static std::set reserved_license_ids_; - scoped_ptr file_; + FileSystem* file_system_; CdmSecurityLevel security_level_; bool initialized_; - bool test_file_; - CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles); }; diff --git a/core/include/file_store.h b/core/include/file_store.h index 8e77b4f1..6e28990f 100644 --- a/core/include/file_store.h +++ b/core/include/file_store.h @@ -15,39 +15,54 @@ namespace wvcdm { // File class. The implementation is platform dependent. class File { + public: + virtual ssize_t Read(char* buffer, size_t bytes); + virtual ssize_t Write(const char* buffer, size_t bytes); + virtual void Close(); + + protected: + class Impl; + + File(Impl*); + virtual ~File(); + + private: + Impl* impl_; + + friend class FileSystem; + CORE_DISALLOW_COPY_AND_ASSIGN(File); +}; + +class FileSystem { public: class Impl; // defines as bit flag enum OpenFlags { kNoFlags = 0, - kBinary = 1, - kCreate = 2, - kReadOnly = 4, // defaults to read and write access - kTruncate = 8 + kCreate = 1, + kReadOnly = 2, // defaults to read and write access + kTruncate = 4 }; - File(); - virtual ~File(); + FileSystem(); + FileSystem(const std::string& origin, void* extra_data); + virtual ~FileSystem(); - virtual bool Open(const std::string& file_path, int flags); - virtual ssize_t Read(char* buffer, size_t bytes); - virtual ssize_t Write(const char* buffer, size_t bytes); - virtual void Close(); + virtual File* Open(const std::string& file_path, int flags); virtual bool Exists(const std::string& file_path); virtual bool Remove(const std::string& file_path); - virtual bool Copy(const std::string& old_path, const std::string& new_path); - virtual bool List(const std::string& path, std::vector* files); - virtual bool CreateDirectory(const std::string dir_path); - virtual bool IsDirectory(const std::string& dir_path); - virtual bool IsRegularFile(const std::string& file_path); virtual ssize_t FileSize(const std::string& file_path); + const std::string& origin() const { return origin_; } + void SetOrigin(const std::string& origin); + private: Impl* impl_; + std::string origin_; - CORE_DISALLOW_COPY_AND_ASSIGN(File); + CORE_DISALLOW_COPY_AND_ASSIGN(FileSystem); }; } // namespace wvcdm diff --git a/core/include/initialization_data.h b/core/include/initialization_data.h index 80803c7b..c4edbd06 100644 --- a/core/include/initialization_data.h +++ b/core/include/initialization_data.h @@ -16,24 +16,62 @@ class InitializationData { InitializationData(const std::string& type = std::string(), const CdmInitData& data = CdmInitData()); - bool is_supported() const { return is_cenc_ || is_webm_; } + bool is_supported() const { return is_cenc_ || is_webm_ || is_hls_; } bool is_cenc() const { return is_cenc_; } + bool is_hls() const { return is_hls_; } bool is_webm() const { return is_webm_; } bool IsEmpty() const { return data_.empty(); } const std::string& type() const { return type_; } const CdmInitData& data() const { return data_; } + std::vector hls_iv() const { return hls_iv_; } + CdmHlsMethod hls_method() const { return hls_method_; } private: // Parse a blob of multiple concatenated PSSH atoms to extract the first // Widevine PSSH. bool ExtractWidevinePssh(const CdmInitData& init_data, CdmInitData* output); + bool ExtractHlsAttributes(const std::string& attribute_list, + CdmHlsMethod* method, std::vector* iv, + std::string* uri); + static bool ConstructWidevineInitData(CdmHlsMethod method, + const std::string& uri, + CdmInitData* output); + static bool ExtractQuotedAttribute(const std::string& attribute_list, + const std::string& key, + std::string* value); + static bool ExtractHexAttribute(const std::string& attribute_list, + const std::string& key, + std::vector* value); + static bool ExtractAttribute(const std::string& attribute_list, + const std::string& key, std::string* value); + + static std::vector ExtractKeyFormatVersions( + const std::string& key_format_versions); + +// For testing only: +#if defined(UNIT_TEST) + FRIEND_TEST(HlsAttributeExtractionTest, ExtractAttribute); + FRIEND_TEST(HlsConstructionTest, InitData); + FRIEND_TEST(HlsInitDataConstructionTest, InvalidUriDataFormat); + FRIEND_TEST(HlsInitDataConstructionTest, InvalidUriBase64Encode); + FRIEND_TEST(HlsHexAttributeExtractionTest, ExtractHexAttribute); + FRIEND_TEST(HlsKeyFormatVersionsExtractionTest, ExtractKeyFormatVersions); + FRIEND_TEST(HlsParseTest, Parse); + FRIEND_TEST(HlsQuotedAttributeExtractionTest, ExtractQuotedAttribute); + FRIEND_TEST(HlsTest, ExtractHlsAttributes); +#endif + std::string type_; CdmInitData data_; bool is_cenc_; + bool is_hls_; bool is_webm_; + + std::vector hls_iv_; + CdmHlsMethod hls_method_; }; } // namespace wvcdm diff --git a/core/include/license_key_status.h b/core/include/license_key_status.h new file mode 100644 index 00000000..cc81dd5a --- /dev/null +++ b/core/include/license_key_status.h @@ -0,0 +1,139 @@ +// Copyright 2016 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_LICENSE_KEY_STATUS_H_ +#define WVCDM_CORE_LICENSE_KEY_STATUS_H_ + +#include + +#include "crypto_session.h" +#include "license_protocol.pb.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class LicenseKeyStatus; + +// Holds all content and operator session keys for a session. +class LicenseKeys { + public: + LicenseKeys() {} + virtual ~LicenseKeys() { Clear(); } + + virtual bool Empty() { return keys_.empty(); } + + // Returns true if the key is a content key (not an operator session key) + virtual bool IsContentKey(const KeyId& key_id); + + // Returns true if the key is currently usable for content decryption. + virtual bool CanDecryptContent(const KeyId& key_id); + + // Returns the allowed usages for a key. + virtual bool GetAllowedUsage(const KeyId& key_id, + CdmKeyAllowedUsage* allowed_usage); + + // Applies a new status to each content key. + // Returns true if any statuses changed, and sets new_usable_keys to + // true if the status changes resulted in keys becoming usable. + virtual bool ApplyStatusChange(CdmKeyStatus new_status, + bool* new_usable_keys); + + // Populates the CdmKeyStatusMap with the current content keys. + virtual void ExtractKeyStatuses(CdmKeyStatusMap* content_keys); + + // Determines whether the specified key can be used under the current + // resolution and/or hdcp constraints. If no constraints have been applied + // to the key, returns true. + virtual bool MeetsConstraints(const KeyId& key_id); + + // Applies a resolution and/or hdcp change to each key, updating their + // useability under their constraints. + virtual void ApplyConstraints(uint32_t new_resolution, + CryptoSession::HdcpCapability new_hdcp_level); + + // Extracts the keys from a license and makes them available for + // querying usage and constraint settings. + virtual void SetFromLicense( + const video_widevine_server::sdk::License& license); + + private: + typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; + typedef std::map::const_iterator + LicenseKeyStatusIterator; + + void Clear(); + + bool is_initialized_; + std::map keys_; + + CORE_DISALLOW_COPY_AND_ASSIGN(LicenseKeys); +}; + +// Holds the current license status of a key. +class LicenseKeyStatus { + friend class LicenseKeys; + + public: + // Returns true if the key is a content key (not an operator session key) + virtual bool IsContentKey() { return is_content_key_; } + + // Returns true if the key is currently usable for content decryption + virtual bool CanDecryptContent(); + + // Returns the usages allowed for this key. + virtual bool GetAllowedUsage(CdmKeyAllowedUsage* allowed_usage); + + // Returns the current status of the key. + virtual CdmKeyStatus GetKeyStatus() const { return key_status_; } + + // Applies a new status to this key. + // Returns true if the status changed, and sets new_usable_keys to + // true if the status changes resulted in the key becoming usable. + virtual bool ApplyStatusChange(CdmKeyStatus new_status, + bool* new_usable_keys); + + // Returns the current constraint status of this key. The result + // may change due to calls to ApplyConstraints(). + // Note: this will return true until the first call to ApplyConstraints(). + virtual bool MeetsConstraints() const { return meets_constraints_; } + + // Applies the given changes in resolution or HDCP settings. + virtual void ApplyConstraints( + uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level); + + protected: + typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; + typedef KeyContainer::OperatorSessionKeyPermissions + OperatorSessionKeyPermissions; + typedef KeyContainer::OutputProtection OutputProtection; + typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint; + typedef ::google::protobuf::RepeatedPtrField + ConstraintList; + + LicenseKeyStatus(const KeyContainer& key); + + virtual ~LicenseKeyStatus() {} + + private: + + void ParseContentKey(const KeyContainer& key); + void ParseOperatorSessionKey(const KeyContainer& key); + + bool HasConstraints() { + return is_content_key_ && constraints_.size() != 0; + } + + void SetConstraints(const ConstraintList& constraints); + + bool is_content_key_; + CdmKeyStatus key_status_; + bool meets_constraints_; + CdmKeyAllowedUsage allowed_usage_; + CryptoSession::HdcpCapability default_hdcp_level_; + ConstraintList constraints_; + + CORE_DISALLOW_COPY_AND_ASSIGN(LicenseKeyStatus); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_LICENSE_KEY_STATUS_H_ diff --git a/core/include/max_res_engine.h b/core/include/max_res_engine.h deleted file mode 100644 index 9a28d140..00000000 --- a/core/include/max_res_engine.h +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2014 Google Inc. All Rights Reserved. - -#ifndef WVCDM_CORE_MAX_RES_ENGINE_H_ -#define WVCDM_CORE_MAX_RES_ENGINE_H_ - -#include - -#include "crypto_session.h" -#include "license_protocol.pb.h" -#include "lock.h" -#include "scoped_ptr.h" -#include "wv_cdm_types.h" - -namespace wvcdm { - -class Clock; -class MaxResEngineTest; - -// Similar to the Policy Engine, this acts as an oracle that basically says -// "Yes(true) you may still decrypt or no(false) you may not decrypt this data -// anymore." -class MaxResEngine { - public: - explicit MaxResEngine(CryptoSession* crypto_session); - virtual ~MaxResEngine(); - - // The value returned is computed during the last call to SetLicense/ - // SetResolution/OnTimerEvent and may be out of sync depending on the amount - // of time elapsed. The current decryption status is not calculated when this - // function is called to avoid overhead in the decryption path. - virtual bool CanDecrypt(const KeyId& key_id); - - // SetLicense is used in handling the initial license response. It stores - // an exact copy of the key constraints from the license. - virtual void SetLicense(const video_widevine_server::sdk::License& license); - - // SetResolution is called when the current output resolution is updated by - // the decoder. The max-res engine will recalculate the current resolution - // constraints, (if any) which may affect the results for CanDecrypt(). - virtual void SetResolution(uint32_t width, uint32_t height); - - // OnTimerEvent is called when a timer fires. The max-res engine may check the - // current HDCP level using the crypto session, which may affect the results - // for CanDecrypt(). - virtual void OnTimerEvent(); - - private: - typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; - typedef ::video_widevine_server::sdk::License::KeyContainer::OutputProtection - OutputProtection; - typedef ::video_widevine_server::sdk::License::KeyContainer:: - VideoResolutionConstraint VideoResolutionConstraint; - typedef ::google::protobuf::RepeatedPtrField - ConstraintList; - - class KeyStatus { - public: - explicit KeyStatus(const ConstraintList& constraints); - KeyStatus(const ConstraintList& constraints, - const OutputProtection::HDCP& default_hdcp_level); - - bool can_decrypt() const { return can_decrypt_; } - - void Update(uint32_t res, - CryptoSession::HdcpCapability current_hdcp_level); - - private: - void Init(const ConstraintList& constraints); - - VideoResolutionConstraint* GetConstraintForRes(uint32_t res); - - static CryptoSession::HdcpCapability ProtobufHdcpToOemCryptoHdcp( - const OutputProtection::HDCP& input); - - bool can_decrypt_; - - CryptoSession::HdcpCapability default_hdcp_level_; - ConstraintList constraints_; - }; - - typedef std::map::const_iterator KeyIterator; - - void Init(CryptoSession* crypto_session, Clock* clock); - - void DeleteAllKeys(); - - Lock status_lock_; - std::map keys_; - uint32_t current_resolution_; - int64_t next_check_time_; - - scoped_ptr clock_; - CryptoSession* crypto_session_; - - // For testing - friend class MaxResEngineTest; - MaxResEngine(CryptoSession* crypto_session, Clock* clock); - - CORE_DISALLOW_COPY_AND_ASSIGN(MaxResEngine); -}; - -} // wvcdm - -#endif // WVCDM_CORE_MAX_RES_ENGINE_H_ diff --git a/core/include/oemcrypto_adapter.h b/core/include/oemcrypto_adapter.h index 5d7a648c..944cb66d 100644 --- a/core/include/oemcrypto_adapter.h +++ b/core/include/oemcrypto_adapter.h @@ -34,6 +34,7 @@ OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(SecurityLevel level, size_t* count); OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(SecurityLevel level, size_t* maximum); +uint8_t OEMCrypto_Security_Patch_Level(SecurityLevel level); } // namespace wvcdm #endif // WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ diff --git a/core/include/policy_engine.h b/core/include/policy_engine.h index aefe3c1e..ccef771d 100644 --- a/core/include/policy_engine.h +++ b/core/include/policy_engine.h @@ -6,8 +6,8 @@ #include #include +#include "license_key_status.h" #include "license_protocol.pb.h" -#include "max_res_engine.h" #include "scoped_ptr.h" #include "wv_cdm_types.h" @@ -67,7 +67,10 @@ class PolicyEngine { virtual void NotifySessionExpiration(); - virtual CdmResponseType Query(CdmQueryMap* key_info); + virtual CdmResponseType Query(CdmQueryMap* query_response); + + virtual CdmResponseType QueryKeyAllowedUsage(const KeyId& key_id, + CdmKeyAllowedUsage* key_usage); virtual const LicenseIdentification& license_id() { return license_id_; } @@ -84,11 +87,20 @@ class PolicyEngine { bool IsPlaybackStarted() { return playback_start_time_ > 0; } bool IsLicenseOrPlaybackDurationExpired(int64_t current_time); + int64_t GetLicenseOrPlaybackDurationRemaining(); bool CanRenew() { return policy_.can_renew(); } private: friend class PolicyEngineTest; + friend class PolicyEngineConstraintsTest; + + void InitDevice(CryptoSession* crypto_session); + void CheckDevice(int64_t current_time); + + void SetDeviceResolution(uint32_t width, uint32_t height) { + current_resolution_ = width * height; + } typedef enum { kLicenseStateInitial, @@ -119,9 +131,9 @@ class PolicyEngine { // expiry time changes. void NotifyExpirationUpdate(); - // These setters are for testing only. Takes ownership of the pointers. + // set_clock() is for testing only. It alters ownership of the + // passed-in pointer. void set_clock(Clock* clock); - void set_max_res_engine(MaxResEngine* max_res_engine); LicenseState license_state_; @@ -153,9 +165,14 @@ class PolicyEngine { CdmSessionId session_id_; WvCdmEventListener* event_listener_; - scoped_ptr max_res_engine_; + // Keys associated with license - holds allowed usage, usage constraints, + // and current status (CdmKeyStatus) + scoped_ptr license_keys_; - std::map keys_status_; + // Device checks + int64_t next_device_check_; + uint32_t current_resolution_; + CryptoSession* crypto_session_; scoped_ptr clock_; diff --git a/core/include/string_conversions.h b/core/include/string_conversions.h index f1f2ecb4..d261c804 100644 --- a/core/include/string_conversions.h +++ b/core/include/string_conversions.h @@ -15,12 +15,13 @@ std::vector a2b_hex(const std::string& label, const std::string& b); std::string a2bs_hex(const std::string& b); std::string b2a_hex(const std::vector& b); std::string b2a_hex(const std::string& b); +std::string Base64Encode(const std::vector& bin_input); +std::vector Base64Decode(const std::string& bin_input); std::string Base64SafeEncode(const std::vector& bin_input); std::string Base64SafeEncodeNoPad(const std::vector& bin_input); std::vector Base64SafeDecode(const std::string& bin_input); std::string HexEncode(const uint8_t* bytes, unsigned size); std::string IntToString(int value); -std::string UintToString(unsigned int value); int64_t htonll64(int64_t x); inline int64_t ntohll64(int64_t x) { return htonll64(x); } diff --git a/core/include/wv_cdm_constants.h b/core/include/wv_cdm_constants.h index c576d8a4..ba782ff8 100644 --- a/core/include/wv_cdm_constants.h +++ b/core/include/wv_cdm_constants.h @@ -80,8 +80,19 @@ static const std::string ISO_BMFF_AUDIO_MIME_TYPE = "audio/mp4"; static const std::string WEBM_VIDEO_MIME_TYPE = "video/webm"; static const std::string WEBM_AUDIO_MIME_TYPE = "audio/webm"; static const std::string CENC_INIT_DATA_FORMAT = "cenc"; +static const std::string HLS_INIT_DATA_FORMAT = "hls"; static const std::string WEBM_INIT_DATA_FORMAT = "webm"; +static const std::string HLS_KEYFORMAT_ATTRIBUTE = "KEYFORMAT"; +static const std::string HLS_KEYFORMAT_VERSIONS_ATTRIBUTE = "KEYFORMATVERSIONS"; +static const std::string HLS_KEYFORMAT_VERSION_VALUE_1 = "1"; +static const std::string HLS_METHOD_ATTRIBUTE = "METHOD"; +static const std::string HLS_METHOD_AES_128 = "AES-128"; +static const std::string HLS_METHOD_NONE = "NONE"; +static const std::string HLS_METHOD_SAMPLE_AES = "SAMPLE-AES"; +static const std::string HLS_IV_ATTRIBUTE = "IV"; +static const std::string HLS_URI_ATTRIBUTE = "URI"; + static const char EMPTY_ORIGIN[] = ""; } // namespace wvcdm diff --git a/core/include/wv_cdm_types.h b/core/include/wv_cdm_types.h index 06180447..c0ca5fd9 100644 --- a/core/include/wv_cdm_types.h +++ b/core/include/wv_cdm_types.h @@ -149,9 +149,9 @@ enum CdmResponseType { RENEW_KEY_ERROR_1, RENEW_KEY_ERROR_2, LICENSE_RENEWAL_SIGNING_ERROR, - RESTORE_OFFLINE_LICENSE_ERROR_1, + UNUSED_4, /* previously RESTORE_OFFLINE_LICENSE_ERROR_1 */ RESTORE_OFFLINE_LICENSE_ERROR_2, - SESSION_INIT_ERROR_1, + UNUSED_5, /* SESSION_INIT_ERROR_1 */ SESSION_INIT_ERROR_2, SESSION_INIT_GET_KEYBOX_ERROR, SESSION_NOT_FOUND_1, @@ -169,7 +169,7 @@ enum CdmResponseType { SIGNATURE_NOT_FOUND, STORE_LICENSE_ERROR_1, STORE_LICENSE_ERROR_2, - STORE_LICENSE_ERROR_3, + UNUSED_6, /* previously STORE_LICENSE_ERROR_3 */ STORE_USAGE_INFO_ERROR, UNPROVISION_ERROR_1, UNPROVISION_ERROR_2, @@ -213,6 +213,38 @@ enum CdmResponseType { SESSION_NOT_FOUND_11, LOAD_USAGE_INFO_FILE_ERROR, LOAD_USAGE_INFO_MISSING, + SESSION_FILE_HANDLE_INIT_ERROR, + INCORRECT_CRYPTO_MODE, + INVALID_PARAMETERS_ENG_5, + DECRYPT_ERROR, + INSUFFICIENT_OUTPUT_PROTECTION, + SESSION_NOT_FOUND_12, + KEY_NOT_FOUND_1, + KEY_NOT_FOUND_2, + KEY_CONFLICT_1, + INVALID_PARAMETERS_ENG_6, + INVALID_PARAMETERS_ENG_7, + INVALID_PARAMETERS_ENG_8, + INVALID_PARAMETERS_ENG_9, + INVALID_PARAMETERS_ENG_10, + INVALID_PARAMETERS_ENG_11, + INVALID_PARAMETERS_ENG_12, + SESSION_NOT_FOUND_13, + SESSION_NOT_FOUND_14, + SESSION_NOT_FOUND_15, + SESSION_NOT_FOUND_16, + KEY_NOT_FOUND_3, + KEY_NOT_FOUND_4, + KEY_NOT_FOUND_5, + KEY_NOT_FOUND_6, + KEY_ERROR_1, + KEY_ERROR_2, + KEY_ERROR_3, + KEY_ERROR_4, + INVALID_PARAMETERS_ENG_13, + INVALID_PARAMETERS_ENG_14, + INVALID_PARAMETERS_ENG_15, + INVALID_PARAMETERS_ENG_16, }; enum CdmKeyStatus { @@ -258,9 +290,89 @@ enum CdmCertificateType { kCertificateX509, }; +enum CdmHlsMethod { + kHlsMethodNone, + kHlsMethodAes128, + kHlsMethodSampleAes, +}; + +enum CdmCipherMode { + kCipherModeCtr, + kCipherModeCbc, +}; + +enum CdmEncryptionAlgorithm { + kEncryptionAlgorithmUnknown, + kEncryptionAlgorithmAesCbc128 +}; + +enum CdmSigningAlgorithm { + kSigningAlgorithmUnknown, + kSigningAlgorithmHmacSha256 +}; + +class CdmKeyAllowedUsage { + public: + CdmKeyAllowedUsage() { + Clear(); + } + + bool Valid() const { return valid_; } + void SetValid() { valid_ = true; } + + void Clear() { + decrypt_to_clear_buffer = false; + decrypt_to_secure_buffer = false; + generic_encrypt = false; + generic_decrypt = false; + generic_sign = false; + generic_verify = false; + valid_ = false; + } + + bool Equals(const CdmKeyAllowedUsage& other) { + if (!valid_ || !other.Valid() || + decrypt_to_clear_buffer != other.decrypt_to_clear_buffer || + decrypt_to_secure_buffer != other.decrypt_to_secure_buffer || + generic_encrypt != other.generic_encrypt || + generic_decrypt != other.generic_decrypt || + generic_sign != other.generic_sign || + generic_verify != other.generic_verify) { + return false; + } + return true; + } + + bool decrypt_to_clear_buffer; + bool decrypt_to_secure_buffer; + bool generic_encrypt; + bool generic_decrypt; + bool generic_sign; + bool generic_verify; + + private: + bool valid_; +}; + +// For schemes that do not use pattern encryption (cenc and cbc1), encrypt +// and skip should be set to 0. For those that do (cens and cbcs), it is +// recommended that encrypt+skip bytes sum to 10 and for cbcs that a 1:9 +// encrypt:skip ratio be used. See ISO/IEC DIS 23001-7, section 10.4.2 for +// more information. +struct CdmCencPatternEncryptionDescriptor { + size_t encrypt_blocks; // number of 16 byte blocks to decrypt + size_t skip_blocks; // number of 16 byte blocks to leave in clear + size_t offset_blocks; // offset into the pattern for this call, in blocks + CdmCencPatternEncryptionDescriptor() + : encrypt_blocks(0), + skip_blocks(0), + offset_blocks(0) {} +}; + struct CdmDecryptionParameters { bool is_encrypted; bool is_secure; + CdmCipherMode cipher_mode; const KeyId* key_id; const uint8_t* encrypt_buffer; size_t encrypt_length; @@ -271,9 +383,11 @@ struct CdmDecryptionParameters { size_t decrypt_buffer_offset; uint8_t subsample_flags; bool is_video; + CdmCencPatternEncryptionDescriptor pattern_descriptor; CdmDecryptionParameters() : is_encrypted(true), is_secure(true), + cipher_mode(kCipherModeCtr), key_id(NULL), encrypt_buffer(NULL), encrypt_length(0), @@ -290,6 +404,7 @@ struct CdmDecryptionParameters { size_t offset, void* decrypted_buffer) : is_encrypted(true), is_secure(true), + cipher_mode(kCipherModeCtr), key_id(key), encrypt_buffer(encrypted_buffer), encrypt_length(encrypted_length), @@ -302,6 +417,12 @@ struct CdmDecryptionParameters { is_video(true) {} }; +struct CdmKeyRequest { + CdmKeyMessage message; + CdmKeyRequestType type; + std::string url; +}; + // forward class references class KeyMessage; class Request; diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp index 18bcdeb4..ba346cbe 100644 --- a/core/src/cdm_engine.cpp +++ b/core/src/cdm_engine.cpp @@ -15,7 +15,6 @@ #include "license_protocol.pb.h" #include "log.h" #include "properties.h" -#include "scoped_ptr.h" #include "string_conversions.h" #include "wv_cdm_constants.h" #include "wv_cdm_event_listener.h" @@ -55,11 +54,13 @@ class UsagePropertySet : public CdmClientPropertySet { bool CdmEngine::seeded_ = false; -CdmEngine::CdmEngine() +CdmEngine::CdmEngine(FileSystem* file_system) : cert_provisioning_(NULL), cert_provisioning_requested_security_level_(kLevelDefault), + file_system_(file_system), usage_session_(NULL), last_usage_information_update_time_(0) { + assert(file_system); Properties::Init(); if (!seeded_) { Clock clock; @@ -79,7 +80,22 @@ CdmEngine::~CdmEngine() { CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, CdmClientPropertySet* property_set, - const std::string& origin, + const CdmSessionId& forced_session_id, + WvCdmEventListener* event_listener) { + return OpenSession(key_system, property_set, event_listener, + &forced_session_id, NULL); +} + +CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, + CdmClientPropertySet* property_set, + WvCdmEventListener* event_listener, + CdmSessionId* session_id) { + return OpenSession(key_system, property_set, event_listener, NULL, + session_id); +} + +CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, + CdmClientPropertySet* property_set, WvCdmEventListener* event_listener, const CdmSessionId* forced_session_id, CdmSessionId* session_id) { @@ -90,8 +106,8 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, return INVALID_KEY_SYSTEM; } - if (!session_id) { - LOGE("CdmEngine::OpenSession: no session ID destination provided"); + if (!session_id && !forced_session_id) { + LOGE("CdmEngine::OpenSession: no (forced/)session ID destination provided"); return INVALID_PARAMETERS_ENG_1; } @@ -101,32 +117,33 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, } } - scoped_ptr new_session( - new CdmSession(property_set, origin, event_listener, forced_session_id)); - if (new_session->session_id().empty()) { - LOGE("CdmEngine::OpenSession: failure to generate session ID"); - return EMPTY_SESSION_ID; - } + scoped_ptr new_session(new CdmSession(file_system_)); - CdmResponseType sts = new_session->Init(); + CdmResponseType sts = new_session->Init(property_set, forced_session_id, + event_listener); if (sts != NO_ERROR) { if (sts == NEED_PROVISIONING) { cert_provisioning_requested_security_level_ = new_session->GetRequestedSecurityLevel(); + // Reserve a session ID so the CDM can return success. + if (session_id) + *session_id = new_session->GenerateSessionId(); } else { LOGE("CdmEngine::OpenSession: bad session init: %d", sts); } return sts; } - *session_id = new_session->session_id(); + CdmSessionId id = new_session->session_id(); + AutoLock lock(session_list_lock_); - sessions_[*session_id] = new_session.release(); + sessions_[id] = new_session.release(); + if (session_id) *session_id = id; return NO_ERROR; } CdmResponseType CdmEngine::OpenKeySetSession( const CdmKeySetId& key_set_id, CdmClientPropertySet* property_set, - const std::string& origin, WvCdmEventListener* event_listener) { + WvCdmEventListener* event_listener) { LOGI("CdmEngine::OpenKeySetSession"); if (key_set_id.empty()) { @@ -135,9 +152,8 @@ CdmResponseType CdmEngine::OpenKeySetSession( } CdmSessionId session_id; - CdmResponseType sts = - OpenSession(KEY_SYSTEM, property_set, origin, event_listener, - NULL /* forced_session_id */, &session_id); + CdmResponseType sts = OpenSession(KEY_SYSTEM, property_set, event_listener, + NULL /* forced_session_id */, &session_id); if (sts != NO_ERROR) return sts; @@ -182,9 +198,7 @@ bool CdmEngine::IsOpenSession(const CdmSessionId& session_id) { CdmResponseType CdmEngine::GenerateKeyRequest( const CdmSessionId& session_id, const CdmKeySetId& key_set_id, const InitializationData& init_data, const CdmLicenseType license_type, - CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, - CdmKeyRequestType* key_request_type, std::string* server_url, - CdmKeySetId* key_set_id_out) { + CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request) { LOGI("CdmEngine::GenerateKeyRequest"); CdmSessionId id = session_id; @@ -223,11 +237,11 @@ CdmResponseType CdmEngine::GenerateKeyRequest( } if (!key_request) { - LOGE("CdmEngine::GenerateKeyRequest: no key request destination provided"); + LOGE("CdmEngine::GenerateKeyRequest: output destination provided"); return INVALID_PARAMETERS_ENG_2; } - key_request->clear(); + key_request->message.clear(); if (license_type == kLicenseTypeRelease && !iter->second->license_received()) { @@ -240,8 +254,7 @@ CdmResponseType CdmEngine::GenerateKeyRequest( } sts = iter->second->GenerateKeyRequest( - init_data, license_type, app_parameters, key_request, key_request_type, - server_url, key_set_id_out); + init_data, license_type, app_parameters, key_request); if (KEY_MESSAGE != sts) { if (sts == NEED_PROVISIONING) { @@ -300,7 +313,10 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id, return EMPTY_KEY_DATA_1; } - CdmResponseType sts = iter->second->AddKey(key_data, key_set_id); + CdmResponseType sts = iter->second->AddKey(key_data); + if (key_set_id) { + *key_set_id = iter->second->key_set_id(); + } switch (sts) { case KEY_ADDED: @@ -359,8 +375,7 @@ CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) { } CdmResponseType CdmEngine::GenerateRenewalRequest( - const CdmSessionId& session_id, CdmKeyMessage* key_request, - std::string* server_url) { + const CdmSessionId& session_id, CdmKeyRequest* key_request) { LOGI("CdmEngine::GenerateRenewalRequest"); CdmSessionMap::iterator iter = sessions_.find(session_id); @@ -371,14 +386,13 @@ CdmResponseType CdmEngine::GenerateRenewalRequest( } if (!key_request) { - LOGE("CdmEngine::GenerateRenewalRequest: no key request destination"); + LOGE("CdmEngine::GenerateRenewalRequest: no request destination"); return INVALID_PARAMETERS_ENG_4; } - key_request->clear(); + key_request->message.clear(); - CdmResponseType sts = - iter->second->GenerateRenewalRequest(key_request, server_url); + CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request); if (KEY_MESSAGE != sts) { LOGE("CdmEngine::GenerateRenewalRequest: key request gen. failed, sts=%d", @@ -414,8 +428,8 @@ CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id, } CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, - const std::string& key, - std::string* value) { + const std::string& query_token, + std::string* query_response) { LOGI("CdmEngine::QueryStatus"); CryptoSession crypto_session; if (security_level == kLevel3) { @@ -423,36 +437,41 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, if (NO_ERROR != status) return INVALID_QUERY_STATUS; } - if (key == QUERY_KEY_SECURITY_LEVEL) { + if (!query_response) { + LOGE("CdmEngine::QueryStatus: no query response destination"); + return INVALID_PARAMETERS_ENG_6; + } + + if (query_token == QUERY_KEY_SECURITY_LEVEL) { CdmSecurityLevel security_level = crypto_session.GetSecurityLevel(); switch (security_level) { case kSecurityLevelL1: - *value = QUERY_VALUE_SECURITY_LEVEL_L1; + *query_response = QUERY_VALUE_SECURITY_LEVEL_L1; break; case kSecurityLevelL2: - *value = QUERY_VALUE_SECURITY_LEVEL_L2; + *query_response = QUERY_VALUE_SECURITY_LEVEL_L2; break; case kSecurityLevelL3: - *value = QUERY_VALUE_SECURITY_LEVEL_L3; + *query_response = QUERY_VALUE_SECURITY_LEVEL_L3; break; case kSecurityLevelUninitialized: case kSecurityLevelUnknown: - *value = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN; + *query_response = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN; break; default: LOGW("CdmEngine::QueryStatus: Unknown security level: %d", security_level); return UNKNOWN_ERROR; } - } else if (key == QUERY_KEY_DEVICE_ID) { + } else if (query_token == QUERY_KEY_DEVICE_ID) { std::string deviceId; if (!crypto_session.GetDeviceUniqueId(&deviceId)) { LOGW("CdmEngine::QueryStatus: GetDeviceUniqueId failed"); return UNKNOWN_ERROR; } - *value = deviceId; - } else if (key == QUERY_KEY_SYSTEM_ID) { + *query_response = deviceId; + } else if (query_token == QUERY_KEY_SYSTEM_ID) { uint32_t system_id; if (!crypto_session.GetSystemId(&system_id)) { LOGW("CdmEngine::QueryStatus: GetSystemId failed"); @@ -461,35 +480,36 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, std::ostringstream system_id_stream; system_id_stream << system_id; - *value = system_id_stream.str(); - } else if (key == QUERY_KEY_PROVISIONING_ID) { + *query_response = system_id_stream.str(); + } else if (query_token == QUERY_KEY_PROVISIONING_ID) { std::string provisioning_id; if (!crypto_session.GetProvisioningId(&provisioning_id)) { LOGW("CdmEngine::QueryStatus: GetProvisioningId failed"); return UNKNOWN_ERROR; } - *value = provisioning_id; - } else if (key == QUERY_KEY_CURRENT_HDCP_LEVEL || - key == QUERY_KEY_MAX_HDCP_LEVEL) { + *query_response = provisioning_id; + } else if (query_token == QUERY_KEY_CURRENT_HDCP_LEVEL || + query_token == QUERY_KEY_MAX_HDCP_LEVEL) { CryptoSession::HdcpCapability current_hdcp; CryptoSession::HdcpCapability max_hdcp; if (!crypto_session.GetHdcpCapabilities(¤t_hdcp, &max_hdcp)) { LOGW("CdmEngine::QueryStatus: GetHdcpCapabilities failed"); return UNKNOWN_ERROR; } - - *value = MapHdcpVersion(key == QUERY_KEY_CURRENT_HDCP_LEVEL ? current_hdcp - : max_hdcp); - } else if (key == QUERY_KEY_USAGE_SUPPORT) { + *query_response = + MapHdcpVersion(query_token == QUERY_KEY_CURRENT_HDCP_LEVEL ? + current_hdcp : max_hdcp); + } else if (query_token == QUERY_KEY_USAGE_SUPPORT) { bool supports_usage_reporting; if (!crypto_session.UsageInformationSupport(&supports_usage_reporting)) { LOGW("CdmEngine::QueryStatus: UsageInformationSupport failed"); return UNKNOWN_ERROR; } - *value = supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; - } else if (key == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) { + *query_response = + supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; + } else if (query_token == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) { size_t number_of_open_sessions; if (!crypto_session.GetNumberOfOpenSessions(&number_of_open_sessions)) { LOGW("CdmEngine::QueryStatus: GetNumberOfOpenSessions failed"); @@ -498,8 +518,8 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, std::ostringstream open_sessions_stream; open_sessions_stream << number_of_open_sessions; - *value = open_sessions_stream.str(); - } else if (key == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) { + *query_response = open_sessions_stream.str(); + } else if (query_token == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) { size_t maximum_number_of_sessions; if (!crypto_session.GetMaxNumberOfSessions(&maximum_number_of_sessions)) { LOGW("CdmEngine::QueryStatus: GetMaxNumberOfOpenSessions failed"); @@ -508,8 +528,8 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, std::ostringstream max_sessions_stream; max_sessions_stream << maximum_number_of_sessions; - *value = max_sessions_stream.str(); - } else if (key == QUERY_KEY_OEMCRYPTO_API_VERSION) { + *query_response = max_sessions_stream.str(); + } else if (query_token == QUERY_KEY_OEMCRYPTO_API_VERSION) { uint32_t api_version; if (!crypto_session.GetApiVersion(&api_version)) { LOGW("CdmEngine::QueryStatus: GetApiVersion failed"); @@ -518,10 +538,10 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, std::ostringstream api_version_stream; api_version_stream << api_version; - *value = api_version_stream.str(); + *query_response = api_version_stream.str(); } else { - LOGW("CdmEngine::QueryStatus: Unknown status requested, key = %s", - key.c_str()); + LOGW("CdmEngine::QueryStatus: Unknown status requested, token = %s", + query_token.c_str()); return INVALID_QUERY_KEY; } @@ -529,7 +549,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, } CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id, - CdmQueryMap* key_info) { + CdmQueryMap* query_response) { LOGI("CdmEngine::QuerySessionStatus"); CdmSessionMap::iterator iter = sessions_.find(session_id); if (iter == sessions_.end()) { @@ -537,7 +557,7 @@ CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id, session_id.c_str()); return SESSION_NOT_FOUND_8; } - return iter->second->QueryStatus(key_info); + return iter->second->QueryStatus(query_response); } bool CdmEngine::IsReleaseSession(const CdmSessionId& session_id) { @@ -563,7 +583,7 @@ bool CdmEngine::IsOfflineSession(const CdmSessionId& session_id) { } CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id, - CdmQueryMap* key_info) { + CdmQueryMap* query_response) { LOGI("CdmEngine::QueryKeyStatus"); CdmSessionMap::iterator iter = sessions_.find(session_id); if (iter == sessions_.end()) { @@ -571,19 +591,72 @@ CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id, session_id.c_str()); return SESSION_NOT_FOUND_9; } - return iter->second->QueryKeyStatus(key_info); + return iter->second->QueryKeyStatus(query_response); } -CdmResponseType CdmEngine::QueryKeyControlInfo(const CdmSessionId& session_id, - CdmQueryMap* key_info) { - LOGI("CdmEngine::QueryKeyControlInfo"); +CdmResponseType CdmEngine::QueryKeyAllowedUsage(const CdmSessionId& session_id, + const std::string& key_id, + CdmKeyAllowedUsage* key_usage) { + LOGI("CdmEngine::QueryKeyAllowedUsage"); + if (!key_usage) { + LOGE("CdmEngine::QueryKeyAllowedUsage: no response destination"); + return INVALID_PARAMETERS_ENG_12; + } CdmSessionMap::iterator iter = sessions_.find(session_id); if (iter == sessions_.end()) { - LOGE("CdmEngine::QueryKeyControlInfo: session_id not found = %s", + LOGE("CdmEngine::QueryKeyAllowedUsage: session_id not found = %s", + session_id.c_str()); + return SESSION_NOT_FOUND_12; + } + return iter->second->QueryKeyAllowedUsage(key_id, key_usage); +} + +CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id, + CdmKeyAllowedUsage* key_usage) { + LOGI("CdmEngine::QueryKeyAllowedUsage (all sessions)"); + CdmResponseType session_sts; + CdmKeyAllowedUsage found_in_this_session; + bool found = false; + if (!key_usage) { + LOGE("CdmEngine::QueryKeyAllowedUsage: no response destination"); + return INVALID_PARAMETERS_ENG_7; + } + key_usage->Clear(); + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { + session_sts = iter->second->QueryKeyAllowedUsage(key_id, + &found_in_this_session); + if (session_sts == NO_ERROR) { + if (found) { + // Found another key. If usage settings do not match, fail. + if (!key_usage->Equals(found_in_this_session)) { + key_usage->Clear(); + return KEY_CONFLICT_1; + } + } else { + *key_usage = found_in_this_session; + found = true; + } + } else if (session_sts != KEY_NOT_FOUND_1) { + LOGE("CdmEngine::QueryKeyAllowedUsage (all sessions) FAILED = %d", + session_sts); + key_usage->Clear(); + return session_sts; + } + } + return (found) ? NO_ERROR : KEY_NOT_FOUND_2; +} + +CdmResponseType CdmEngine::QueryOemCryptoSessionId( + const CdmSessionId& session_id, CdmQueryMap* query_response) { + LOGI("CdmEngine::QueryOemCryptoSessionId"); + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::QueryOemCryptoSessionId: session_id not found = %s", session_id.c_str()); return SESSION_NOT_FOUND_10; } - return iter->second->QueryKeyControlInfo(key_info); + return iter->second->QueryOemCryptoSessionId(query_response); } /* @@ -595,8 +668,7 @@ CdmResponseType CdmEngine::QueryKeyControlInfo(const CdmSessionId& session_id, */ CdmResponseType CdmEngine::GetProvisioningRequest( CdmCertificateType cert_type, const std::string& cert_authority, - const std::string& origin, CdmProvisioningRequest* request, - std::string* default_url) { + CdmProvisioningRequest* request, std::string* default_url) { if (!request) { LOGE("CdmEngine::GetProvisioningRequest: invalid output parameters"); return INVALID_PROVISIONING_REQUEST_PARAM_1; @@ -613,7 +685,7 @@ CdmResponseType CdmEngine::GetProvisioningRequest( } CdmResponseType ret = cert_provisioning_->GetProvisioningRequest( cert_provisioning_requested_security_level_, cert_type, cert_authority, - origin, request, default_url); + file_system_->origin(), request, default_url); if (ret != NO_ERROR) { cert_provisioning_.reset(NULL); // Release resources. } @@ -628,8 +700,8 @@ CdmResponseType CdmEngine::GetProvisioningRequest( * Returns NO_ERROR for success and CdmResponseType error code if fails. */ CdmResponseType CdmEngine::HandleProvisioningResponse( - const std::string& origin, const CdmProvisioningResponse& response, - std::string* cert, std::string* wrapped_key) { + const CdmProvisioningResponse& response, std::string* cert, + std::string* wrapped_key) { if (response.empty()) { LOGE("CdmEngine::HandleProvisioningResponse: Empty provisioning response."); cert_provisioning_.reset(NULL); @@ -661,7 +733,7 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( return EMPTY_PROVISIONING_CERTIFICATE_2; } CdmSecurityLevel security_level = crypto_session.GetSecurityLevel(); - if (!IsProvisioned(security_level, origin)) { + if (!IsProvisioned(security_level)) { LOGE( "CdmEngine::HandleProvisioningResponse: provisioning object " "missing."); @@ -671,7 +743,7 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( } CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse( - origin, response, cert, wrapped_key); + file_system_, response, cert, wrapped_key); // Release resources only on success. It is possible that a provisioning // attempt was made after this one was requested but before the response was // received, which will cause this attempt to fail. Not releasing will @@ -680,28 +752,25 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( return ret; } -bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level, - const std::string& origin) { - DeviceFiles handle; +bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) { + DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("CdmEngine::IsProvisioned: unable to initialize device files"); return false; } - return handle.HasCertificate(origin); + return handle.HasCertificate(); } -CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level, - const std::string& origin) { - DeviceFiles handle; +CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { + DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("CdmEngine::Unprovision: unable to initialize device files"); return UNPROVISION_ERROR_1; } - if (origin != EMPTY_ORIGIN) { - if (!handle.RemoveCertificate(origin)) { - LOGE("CdmEngine::Unprovision: unable to delete certificate for origin %s", - origin.c_str()); + if (!file_system_->origin().empty()) { + if (!handle.RemoveCertificate()) { + LOGE("CdmEngine::Unprovision: unable to delete certificate"); return UNPROVISION_ERROR_2; } return NO_ERROR; @@ -732,16 +801,19 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, if (NULL == usage_property_set_.get()) { usage_property_set_.reset(new UsagePropertySet()); } + if (!usage_info) { + LOGE("CdmEngine::GetUsageInfo: no usage info destination"); + return INVALID_PARAMETERS_ENG_8; + } usage_property_set_->set_security_level(kLevelDefault); usage_property_set_->set_app_id(app_id); - usage_session_.reset( - new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL)); - CdmResponseType status = usage_session_->Init(); + usage_session_.reset(new CdmSession(file_system_)); + CdmResponseType status = usage_session_->Init(usage_property_set_.get()); if (NO_ERROR != status) { LOGE("CdmEngine::GetUsageInfo: session init error"); return status; } - DeviceFiles handle; + DeviceFiles handle(file_system_); if (!handle.Init(usage_session_->GetSecurityLevel())) { LOGE("CdmEngine::GetUsageInfo: device file init error"); return GET_USAGE_INFO_ERROR_1; @@ -753,9 +825,8 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, &license_response)) { usage_property_set_->set_security_level(kLevel3); usage_property_set_->set_app_id(app_id); - usage_session_.reset( - new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL)); - status = usage_session_->Init(); + usage_session_.reset(new CdmSession(file_system_)); + status = usage_session_->Init(usage_property_set_.get()); if (NO_ERROR != status) { LOGE("CdmEngine::GetUsageInfo: session init error"); return status; @@ -771,18 +842,20 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, } } - std::string server_url; - usage_info->resize(1); status = - usage_session_->RestoreUsageSession(license_request, license_response); + usage_session_->RestoreUsageSession(license_request,license_response); + if (KEY_ADDED != status) { LOGE("CdmEngine::GetUsageInfo: restore usage session error %d", status); usage_info->clear(); return status; } - status = - usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url); + CdmKeyRequest request; + status = usage_session_->GenerateReleaseRequest(&request); + + usage_info->clear(); + usage_info->push_back(request.message); if (KEY_MESSAGE != status) { LOGE("CdmEngine::GetUsageInfo: generate release request error: %d", status); @@ -797,6 +870,10 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, // Return a random usage report from a random security level SecurityLevel security_level = ((rand() % 2) == 0) ? kLevelDefault : kLevel3; CdmResponseType status = UNKNOWN_ERROR; + if (!usage_info) { + LOGE("CdmEngine::GetUsageInfo: no usage info destination"); + return INVALID_PARAMETERS_ENG_9; + } do { status = GetUsageInfo(app_id, security_level, usage_info); @@ -822,16 +899,15 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, usage_property_set_->set_security_level(requested_security_level); usage_property_set_->set_app_id(app_id); - usage_session_.reset( - new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL)); + usage_session_.reset(new CdmSession(file_system_)); - CdmResponseType status = usage_session_->Init(); + CdmResponseType status = usage_session_->Init(usage_property_set_.get()); if (NO_ERROR != status) { LOGE("CdmEngine::GetUsageInfo: session init error"); return status; } - DeviceFiles handle; + DeviceFiles handle(file_system_); if (!handle.Init(usage_session_->GetSecurityLevel())) { LOGE("CdmEngine::GetUsageInfo: unable to initialize device files"); return GET_USAGE_INFO_ERROR_3; @@ -843,12 +919,15 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, return GET_USAGE_INFO_ERROR_4; } + if (!usage_info) { + LOGE("CdmEngine::GetUsageInfo: no usage info destination"); + return INVALID_PARAMETERS_ENG_10; + } if (0 == license_info.size()) { usage_info->resize(0); return NO_ERROR; } - std::string server_url; usage_info->resize(kUsageReportsPerRequest); uint32_t index = rand() % license_info.size(); @@ -861,8 +940,11 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, return status; } - status = - usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url); + CdmKeyRequest request; + status = usage_session_->GenerateReleaseRequest(&request); + + usage_info->clear(); + usage_info->push_back(request.message); switch (status) { case KEY_MESSAGE: @@ -888,7 +970,7 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) { CdmResponseType status = NO_ERROR; for (int j = kSecurityLevelL1; j < kSecurityLevelUnknown; ++j) { - DeviceFiles handle; + DeviceFiles handle(file_system_); if (handle.Init(static_cast(j))) { std::vector provider_session_tokens; if (!handle.DeleteAllUsageInfoForApp(app_id, &provider_session_tokens)) { @@ -901,9 +983,8 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) { ? kLevel3 : kLevelDefault; usage_property_set_->set_security_level(security_level); - usage_session_.reset( - new CdmSession(usage_property_set_.get(), - EMPTY_ORIGIN, NULL, NULL)); + usage_session_.reset(new CdmSession(file_system_)); + usage_session_->Init(usage_property_set_.get()); CdmResponseType status2 = usage_session_-> DeleteMultipleUsageInformation(provider_session_tokens); if (status2 != NO_ERROR) { @@ -954,7 +1035,12 @@ CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id, return SESSION_NOT_FOUND_11; } - DeviceFiles handle; + if (!release_message) { + LOGE("CdmEngine::LoadUsageSession: no release message destination"); + return INVALID_PARAMETERS_ENG_11; + } + + DeviceFiles handle(file_system_); if (!handle.Init(iter->second->GetSecurityLevel())) { LOGE("CdmEngine::LoadUsageSession: unable to initialize device files"); return LOAD_USAGE_INFO_FILE_ERROR; @@ -978,8 +1064,9 @@ CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id, return status; } - std::string server_url; - status = iter->second->GenerateReleaseRequest(release_message, &server_url); + CdmKeyRequest request; + status = iter->second->GenerateReleaseRequest(&request); + *release_message = request.message; switch (status) { case KEY_MESSAGE: @@ -1021,24 +1108,86 @@ CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id, // else we must be level 1 direct and we don't need to return a buffer. } - CdmSessionMap::iterator iter; + CdmSessionMap::iterator session_iter = sessions_.end(); if (session_id.empty()) { - // Loop through the sessions to find the session containing the key_id. - for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) { + // Loop through the sessions to find the session containing the key_id + // with the longest remaining license validity. + int64_t seconds_remaining = 0; + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { if (iter->second->IsKeyLoaded(*parameters.key_id)) { - break; + int64_t duration = iter->second->GetDurationRemaining(); + if (duration > seconds_remaining) { + session_iter = iter; + seconds_remaining = duration; + } } } } else { - iter = sessions_.find(session_id); + session_iter = sessions_.find(session_id); } - if (iter == sessions_.end()) { + if (session_iter == sessions_.end()) { LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d", session_id.c_str(), session_id.size()); return SESSION_NOT_FOUND_FOR_DECRYPT; } - return iter->second->Decrypt(parameters); + return session_iter->second->Decrypt(parameters); +} + +CdmResponseType CdmEngine::GenericEncrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + CdmEncryptionAlgorithm algorithm, std::string* out_buffer) { + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::GenericEncrypt: session_id not found = %s ", + session_id.c_str()); + return SESSION_NOT_FOUND_13; + } + return iter->second->GenericEncrypt(in_buffer, key_id, iv, algorithm, + out_buffer); +} + +CdmResponseType CdmEngine::GenericDecrypt( + const std::string& session_id, const std::string& in_buffer, + const std::string& key_id, const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer) { + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::GenericDecrypt: session_id not found = %s ", + session_id.c_str()); + return SESSION_NOT_FOUND_14; + } + return iter->second->GenericDecrypt(in_buffer, key_id, iv, algorithm, + out_buffer); +} + +CdmResponseType CdmEngine::GenericSign( + const std::string& session_id, const std::string& message, + const std::string& key_id, CdmSigningAlgorithm algorithm, + std::string* signature) { + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::GenericSign: session_id not found = %s ", + session_id.c_str()); + return SESSION_NOT_FOUND_15; + } + return iter->second->GenericSign(message, key_id, algorithm, signature); +} + +CdmResponseType CdmEngine::GenericVerify( + const std::string& session_id, const std::string& message, + const std::string& key_id, CdmSigningAlgorithm algorithm, + const std::string& signature) { + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::GenericVerify: session_id not found = %s ", + session_id.c_str()); + return SESSION_NOT_FOUND_16; + } + return iter->second->GenericVerify(message, key_id, algorithm, signature); } bool CdmEngine::IsKeyLoaded(const KeyId& key_id) { @@ -1058,25 +1207,30 @@ bool CdmEngine::FindSessionForKey(const KeyId& key_id, return false; } - CdmSessionMap::iterator iter = sessions_.find(*session_id); - if (iter != sessions_.end()) { - if (iter->second->IsKeyLoaded(key_id)) { - return true; - } - } uint32_t session_sharing_id = Properties::GetSessionSharingId(*session_id); - for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) { + CdmSessionMap::iterator session_iter = sessions_.end(); + int64_t seconds_remaining = 0; + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { CdmSessionId local_session_id = iter->second->session_id(); if (Properties::GetSessionSharingId(local_session_id) == session_sharing_id) { if (iter->second->IsKeyLoaded(key_id)) { - *session_id = local_session_id; - return true; + int64_t duration = iter->second->GetDurationRemaining(); + if (duration > seconds_remaining) { + session_iter = iter; + seconds_remaining = duration; + } } } } + + if (session_iter != sessions_.end()) { + *session_id = session_iter->second->session_id(); + return true; + } return false; } @@ -1172,9 +1326,8 @@ void CdmEngine::DeleteAllUsageReportsUponFactoryReset() { Properties::GetDeviceFilesBasePath(kSecurityLevelL3, &device_base_path_level3); - File file; - if (!file.Exists(device_base_path_level1) && - !file.Exists(device_base_path_level3)) { + if (!file_system_->Exists(device_base_path_level1) && + !file_system_->Exists(device_base_path_level3)) { scoped_ptr crypto_session(new CryptoSession()); CdmResponseType status = crypto_session->Open( cert_provisioning_requested_security_level_); diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp index 46c0767b..6d3b8b80 100644 --- a/core/src/cdm_session.cpp +++ b/core/src/cdm_session.cpp @@ -10,8 +10,6 @@ #include "cdm_engine.h" #include "clock.h" -#include "crypto_session.h" -#include "device_files.h" #include "file_store.h" #include "log.h" #include "properties.h" @@ -25,47 +23,22 @@ const size_t kKeySetIdLength = 14; namespace wvcdm { -CdmSession::CdmSession(CdmClientPropertySet* cdm_client_property_set, - const std::string& origin, - WvCdmEventListener* event_listener, - const CdmSessionId* forced_session_id) - : initialized_(false), - session_id_(GenerateSessionId()), - origin_(origin), - crypto_session_(new CryptoSession), - file_handle_(new DeviceFiles), - license_received_(false), - is_offline_(false), - is_release_(false), - is_temporary_(false), - security_level_(kSecurityLevelUninitialized), - requested_security_level_(kLevelDefault), - is_initial_decryption_(true), - has_decrypted_since_last_report_(false), - is_initial_usage_update_(true), - is_usage_update_needed_(false) { - if (Properties::AlwaysUseKeySetIds()) { - if (forced_session_id) { - key_set_id_ = *forced_session_id; - } else { - bool ok = GenerateKeySetId(&key_set_id_); - (void)ok; // ok is now used when assertions are turned off. - assert(ok); - } - session_id_ = key_set_id_; - } - license_parser_.reset(new CdmLicense(session_id_)); - policy_engine_.reset(new PolicyEngine( - session_id_, event_listener, crypto_session_.get())); - if (cdm_client_property_set) { - if (cdm_client_property_set->security_level() == - QUERY_VALUE_SECURITY_LEVEL_L3) { - requested_security_level_ = kLevel3; - security_level_ = kSecurityLevelL3; - } - Properties::AddSessionPropertySet(session_id_, cdm_client_property_set); - } -} +CdmSession::CdmSession(FileSystem* file_system) : + initialized_(false), + crypto_session_(new CryptoSession), + file_handle_(new DeviceFiles(file_system)), + license_received_(false), + is_offline_(false), + is_release_(false), + is_temporary_(false), + security_level_(kSecurityLevelUninitialized), + requested_security_level_(kLevelDefault), + is_initial_decryption_(true), + has_decrypted_since_last_report_(false), + is_initial_usage_update_(true), + is_usage_update_needed_(false), + mock_license_parser_in_use_(false), + mock_policy_engine_in_use_(false) {} CdmSession::~CdmSession() { if (!key_set_id_.empty()) { @@ -75,24 +48,38 @@ CdmSession::~CdmSession() { Properties::RemoveSessionPropertySet(session_id_); } -CdmResponseType CdmSession::Init() { - if (session_id_.empty()) { - LOGE("CdmSession::Init: Failed, session not properly constructed"); - return SESSION_INIT_ERROR_1; - } +CdmResponseType CdmSession::Init( + CdmClientPropertySet* cdm_client_property_set) { + return Init(cdm_client_property_set, NULL, NULL); +} + +CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, + const CdmSessionId* forced_session_id, + WvCdmEventListener* event_listener) { if (initialized_) { LOGE("CdmSession::Init: Failed due to previous initialization"); return SESSION_INIT_ERROR_2; } + + if (cdm_client_property_set && + cdm_client_property_set->security_level() == + QUERY_VALUE_SECURITY_LEVEL_L3) { + requested_security_level_ = kLevel3; + security_level_ = kSecurityLevelL3; + } CdmResponseType sts = crypto_session_->Open(requested_security_level_); if (NO_ERROR != sts) return sts; security_level_ = crypto_session_->GetSecurityLevel(); + if (!file_handle_->Init(security_level_)) { + LOGE("CdmSession::Init: Unable to initialize file handle"); + return SESSION_FILE_HANDLE_INIT_ERROR; + } + std::string token; if (Properties::use_certificates_as_identification()) { std::string wrapped_key; - if (!file_handle_->Init(security_level_) || - !file_handle_->RetrieveCertificate(origin_, &token, &wrapped_key) || + if (!file_handle_->RetrieveCertificate(&token, &wrapped_key) || !crypto_session_->LoadCertificatePrivateKey(wrapped_key)) { return NEED_PROVISIONING; } @@ -101,6 +88,30 @@ CdmResponseType CdmSession::Init() { return SESSION_INIT_GET_KEYBOX_ERROR; } + if (forced_session_id) { + key_set_id_ = *forced_session_id; + } else { + bool ok = GenerateKeySetId(&key_set_id_); + (void)ok; // ok is now used when assertions are turned off. + assert(ok); + } + + session_id_ = + Properties::AlwaysUseKeySetIds() ? key_set_id_ : GenerateSessionId(); + + if (session_id_.empty()) { + LOGE("CdmSession::Init: empty session ID"); + return EMPTY_SESSION_ID; + } + if (cdm_client_property_set) + Properties::AddSessionPropertySet(session_id_, cdm_client_property_set); + + if (!mock_license_parser_in_use_) + license_parser_.reset(new CdmLicense(session_id_)); + if (!mock_policy_engine_in_use_) + policy_engine_.reset(new PolicyEngine( + session_id_, event_listener, crypto_session_.get())); + if (!license_parser_->Init(token, crypto_session_.get(), policy_engine_.get())) return LICENSE_PARSER_INIT_ERROR; @@ -115,10 +126,6 @@ CdmResponseType CdmSession::RestoreOfflineSession( const CdmKeySetId& key_set_id, const CdmLicenseType license_type) { key_set_id_ = key_set_id; - // Retrieve license information from persistent store - if (!file_handle_->Reset(security_level_)) - return RESTORE_OFFLINE_LICENSE_ERROR_1; - DeviceFiles::LicenseState license_state; int64_t playback_start_time; int64_t last_playback_time; @@ -177,9 +184,9 @@ CdmResponseType CdmSession::RestoreUsageSession( CdmResponseType CdmSession::GenerateKeyRequest( const InitializationData& init_data, CdmLicenseType license_type, - const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, - CdmKeyRequestType* key_request_type, std::string* server_url, - CdmKeySetId* key_set_id) { + const CdmAppParameterMap& app_parameters, + CdmKeyRequest* key_request) { + if (crypto_session_.get() == NULL) { LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session"); return INVALID_CRYPTO_SESSION_1; @@ -190,6 +197,11 @@ CdmResponseType CdmSession::GenerateKeyRequest( return CRYPTO_SESSION_OPEN_ERROR_1; } + if (!key_request) { + LOGE("CdmSession::GenerateKeyRequest: No output destination provided"); + return INVALID_PARAMETERS_ENG_5; + } + switch (license_type) { case kLicenseTypeTemporary: is_temporary_ = true; @@ -230,13 +242,12 @@ CdmResponseType CdmSession::GenerateKeyRequest( } if (is_release_) { - if (key_request_type) *key_request_type = kKeyRequestTypeRelease; - return GenerateReleaseRequest(key_request, server_url); + return GenerateReleaseRequest(key_request); } else if (license_received_) { // renewal - if (key_request_type) *key_request_type = kKeyRequestTypeRenewal; - return GenerateRenewalRequest(key_request, server_url); + return GenerateRenewalRequest(key_request); } else { - if (key_request_type) *key_request_type = kKeyRequestTypeInitial; + key_request->type = kKeyRequestTypeInitial; + if (!license_parser_->HasInitData()) { if (!init_data.is_supported()) { LOGW("CdmSession::GenerateKeyRequest: unsupported init data type (%s)", @@ -248,8 +259,7 @@ CdmResponseType CdmSession::GenerateKeyRequest( return INIT_DATA_NOT_FOUND; } } - if (is_offline_ && key_set_id_.empty() && - !GenerateKeySetId(&key_set_id_)) { + if (is_offline_ && key_set_id_.empty()) { LOGE("CdmSession::GenerateKeyRequest: Unable to generate key set ID"); return KEY_REQUEST_ERROR_1; } @@ -257,24 +267,24 @@ CdmResponseType CdmSession::GenerateKeyRequest( app_parameters_ = app_parameters; CdmResponseType status = license_parser_->PrepareKeyRequest( init_data, license_type, - app_parameters, key_request, server_url); + app_parameters, &key_request->message, + &key_request->url); if (KEY_MESSAGE != status) return status; - key_request_ = *key_request; + key_request_ = key_request->message; if (is_offline_) { offline_init_data_ = init_data.data(); - offline_release_server_url_ = *server_url; + offline_release_server_url_ = key_request->url; + } - if (key_set_id) *key_set_id = key_set_id_; return KEY_MESSAGE; } } // AddKey() - Accept license response and extract key info. -CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response, - CdmKeySetId* key_set_id) { +CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) { if (crypto_session_.get() == NULL) { LOGW("CdmSession::AddKey: Invalid crypto session"); return INVALID_CRYPTO_SESSION_2; @@ -303,12 +313,11 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response, if (sts != NO_ERROR) return sts; } - if (key_set_id) *key_set_id = key_set_id_; return KEY_ADDED; } } -CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) { +CdmResponseType CdmSession::QueryStatus(CdmQueryMap* query_response) { if (crypto_session_.get() == NULL) { LOGE("CdmSession::QueryStatus: Invalid crypto session"); return INVALID_CRYPTO_SESSION_3; @@ -321,17 +330,20 @@ CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) { switch (security_level_) { case kSecurityLevelL1: - (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1; + (*query_response)[QUERY_KEY_SECURITY_LEVEL] = + QUERY_VALUE_SECURITY_LEVEL_L1; break; case kSecurityLevelL2: - (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L2; + (*query_response)[QUERY_KEY_SECURITY_LEVEL] = + QUERY_VALUE_SECURITY_LEVEL_L2; break; case kSecurityLevelL3: - (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3; + (*query_response)[QUERY_KEY_SECURITY_LEVEL] = + QUERY_VALUE_SECURITY_LEVEL_L3; break; case kSecurityLevelUninitialized: case kSecurityLevelUnknown: - (*key_info)[QUERY_KEY_SECURITY_LEVEL] = + (*query_response)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN; break; default: @@ -340,24 +352,30 @@ CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) { return NO_ERROR; } -CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* key_info) { - return policy_engine_->Query(key_info); +CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* query_response) { + return policy_engine_->Query(query_response); } -CdmResponseType CdmSession::QueryKeyControlInfo(CdmQueryMap* key_info) { +CdmResponseType CdmSession::QueryKeyAllowedUsage( + const std::string& key_id, CdmKeyAllowedUsage* key_usage) { + return policy_engine_->QueryKeyAllowedUsage(key_id, key_usage); +} + +CdmResponseType CdmSession::QueryOemCryptoSessionId( + CdmQueryMap* query_response) { if (crypto_session_.get() == NULL) { - LOGW("CdmSession::QueryKeyControlInfo: Invalid crypto session"); + LOGW("CdmSession::QueryOemCryptoSessionId: Invalid crypto session"); return INVALID_CRYPTO_SESSION_4; } if (!crypto_session_->IsOpen()) { - LOGW("CdmSession::QueryKeyControlInfo: Crypto session not open"); + LOGW("CdmSession::QueryOemCryptoSessionId: Crypto session not open"); return CRYPTO_SESSION_OPEN_ERROR_4; } std::stringstream ss; ss << crypto_session_->oec_session_id(); - (*key_info)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = ss.str(); + (*query_response)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = ss.str(); return NO_ERROR; } @@ -405,15 +423,17 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { // License renewal // GenerateRenewalRequest() - Construct valid renewal request for the current // session keys. -CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request, - std::string* server_url) { +CdmResponseType CdmSession::GenerateRenewalRequest( + CdmKeyRequest* key_request) { CdmResponseType status = license_parser_->PrepareKeyUpdateRequest( - true, app_parameters_, key_request, server_url); + true, app_parameters_, &key_request->message, &key_request->url); + + key_request->type = kKeyRequestTypeRenewal; if (KEY_MESSAGE != status) return status; if (is_offline_) { - offline_key_renewal_request_ = *key_request; + offline_key_renewal_request_ = key_request->message; } return KEY_MESSAGE; } @@ -432,11 +452,14 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { return KEY_ADDED; } -CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request, - std::string* server_url) { +CdmResponseType CdmSession::GenerateReleaseRequest( + CdmKeyRequest* key_request) { is_release_ = true; CdmResponseType status = license_parser_->PrepareKeyUpdateRequest( - false, app_parameters_, key_request, server_url); + false, app_parameters_, &key_request->message, + &key_request->url); + + key_request->type = kKeyRequestTypeRelease; if (KEY_MESSAGE != status) return status; @@ -463,6 +486,11 @@ bool CdmSession::IsKeyLoaded(const KeyId& key_id) { return license_parser_->IsKeyLoaded(key_id); } +int64_t CdmSession::GetDurationRemaining() { + if (policy_engine_->IsLicenseForFuture()) return 0; + return policy_engine_->GetLicenseOrPlaybackDurationRemaining(); +} + CdmSessionId CdmSession::GenerateSessionId() { static int session_num = 1; return SESSION_ID_PREFIX + IntToString(++session_num); @@ -477,8 +505,6 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) { std::vector random_data( (kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2, 0); - if (!file_handle_->Reset(security_level_)) return false; - while (key_set_id->empty()) { if (!crypto_session_->GetRandom(random_data.size(), &random_data[0])) return false; @@ -514,13 +540,6 @@ CdmResponseType CdmSession::StoreLicense() { if (!StoreLicense(DeviceFiles::kLicenseStateActive)) { LOGE("CdmSession::StoreLicense: Unable to store license"); - CdmResponseType sts = Init(); - if (sts != NO_ERROR) { - LOGW("CdmSession::StoreLicense: Reinitialization failed"); - return sts; - } - - key_set_id_.clear(); return STORE_LICENSE_ERROR_1; } return NO_ERROR; @@ -533,11 +552,6 @@ CdmResponseType CdmSession::StoreLicense() { return STORE_LICENSE_ERROR_2; } - if (!file_handle_->Reset(security_level_)) { - LOGE("CdmSession::StoreLicense: Unable to initialize device files"); - return STORE_LICENSE_ERROR_3; - } - std::string app_id; GetApplicationId(&app_id); if (!file_handle_->StoreUsageInfo(provider_session_token, key_request_, @@ -549,8 +563,6 @@ CdmResponseType CdmSession::StoreLicense() { } bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) { - if (!file_handle_->Reset(security_level_)) return false; - return file_handle_->StoreLicense( key_set_id_, state, offline_init_data_, key_request_, key_response_, offline_key_renewal_request_, offline_key_renewal_response_, @@ -558,14 +570,15 @@ bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) { policy_engine_->GetLastPlaybackTime(), app_parameters_); } +CdmResponseType CdmSession::ReleaseCrypto() { + crypto_session_->Close(); + return NO_ERROR; +} + bool CdmSession::DeleteLicense() { if (!is_offline_ && license_parser_->provider_session_token().empty()) return false; - if (!file_handle_->Reset(security_level_)) { - LOGE("CdmSession::DeleteLicense: Unable to initialize device files"); - return false; - } if (is_offline_) { return file_handle_->DeleteLicense(key_set_id_); } else { @@ -613,13 +626,55 @@ CdmResponseType CdmSession::UpdateUsageInformation() { return crypto_session_->UpdateUsageInformation(); } -CdmResponseType CdmSession::ReleaseCrypto() { - crypto_session_->Close(); - return NO_ERROR; +CdmResponseType CdmSession::GenericEncrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer) { + if (!out_buffer) { + LOGE("CdmSession::GenericEncrypt: No output destination provided"); + return INVALID_PARAMETERS_ENG_6; + } + return crypto_session_->GenericEncrypt(in_buffer, key_id, iv, algorithm, + out_buffer); } +CdmResponseType CdmSession::GenericDecrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer) { + if (!out_buffer) { + LOGE("CdmSession::GenericDecrypt: No output destination provided"); + return INVALID_PARAMETERS_ENG_7; + } + return crypto_session_->GenericDecrypt(in_buffer, key_id, iv, algorithm, + out_buffer); +} + +CdmResponseType CdmSession::GenericSign(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + std::string* signature) { + if (!signature) { + LOGE("CdmSession::GenericSign: No output destination provided"); + return INVALID_PARAMETERS_ENG_8; + } + return crypto_session_->GenericSign(message, key_id, algorithm, signature); +} + +CdmResponseType CdmSession::GenericVerify(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + const std::string& signature) { + return crypto_session_->GenericVerify(message, key_id, algorithm, signature); +} + +// For testing only - takes ownership of pointers + void CdmSession::set_license_parser(CdmLicense* license_parser) { license_parser_.reset(license_parser); + mock_license_parser_in_use_ = true; } void CdmSession::set_crypto_session(CryptoSession* crypto_session) { @@ -628,6 +683,7 @@ void CdmSession::set_crypto_session(CryptoSession* crypto_session) { void CdmSession::set_policy_engine(PolicyEngine* policy_engine) { policy_engine_.reset(policy_engine); + mock_policy_engine_in_use_ = true; } void CdmSession::set_file_handle(DeviceFiles* file_handle) { diff --git a/core/src/certificate_provisioning.cpp b/core/src/certificate_provisioning.cpp index 61c27077..a6b2c10a 100644 --- a/core/src/certificate_provisioning.cpp +++ b/core/src/certificate_provisioning.cpp @@ -189,7 +189,7 @@ bool CertificateProvisioning::ParseJsonResponse( * Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails. */ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( - const std::string& origin, const CdmProvisioningResponse& response, + FileSystem* file_system, const CdmProvisioningResponse& response, std::string* cert, std::string* wrapped_key) { // Extracts signed response from JSON string, decodes base64 signed response const std::string kMessageStart = "\"signedResponse\": \""; @@ -259,12 +259,12 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( const std::string& device_certificate = provisioning_response.device_certificate(); - DeviceFiles handle; + DeviceFiles handle(file_system); if (!handle.Init(crypto_session_.GetSecurityLevel())) { LOGE("HandleProvisioningResponse: failed to init DeviceFiles"); return CERT_PROVISIONING_RESPONSE_ERROR_7; } - if (!handle.StoreCertificate(origin, device_certificate, wrapped_rsa_key)) { + if (!handle.StoreCertificate(device_certificate, wrapped_rsa_key)) { LOGE("HandleProvisioningResponse: failed to save provisioning certificate"); return CERT_PROVISIONING_RESPONSE_ERROR_8; } diff --git a/core/src/crypto_session.cpp b/core/src/crypto_session.cpp index ec255d20..cf5e96c3 100644 --- a/core/src/crypto_session.cpp +++ b/core/src/crypto_session.cpp @@ -40,7 +40,8 @@ CryptoSession::CryptoSession() update_usage_table_after_close_session_(false), is_destination_buffer_type_valid_(false), requested_security_level_(kLevelDefault), - request_id_base_(0) { + request_id_base_(0), + cipher_mode_(kCipherModeCtr) { Init(); } @@ -55,17 +56,19 @@ void CryptoSession::Init() { LOGV("CryptoSession::Init"); AutoLock auto_lock(crypto_lock_); session_count_ += 1; - if (initialized_) return; - OEMCryptoResult sts = OEMCrypto_Initialize(); - if (OEMCrypto_SUCCESS != sts) { - LOGE("OEMCrypto_Initialize failed: %d", sts); - return; + if (!initialized_) { + OEMCryptoResult sts = OEMCrypto_Initialize(); + if (OEMCrypto_SUCCESS != sts) { + LOGE("OEMCrypto_Initialize failed: %d", sts); + return; + } + initialized_ = true; } - initialized_ = true; } void CryptoSession::Terminate() { - LOGV("CryptoSession::Terminate"); + LOGE("CryptoSession::Terminate: initialized_=%d, session_count_=%d", + initialized_, session_count_); AutoLock auto_lock(crypto_lock_); if (session_count_ > 0) { session_count_ -= 1; @@ -233,6 +236,10 @@ bool CryptoSession::GetProvisioningId(std::string* provisioning_id) { return true; } +uint8_t CryptoSession::GetSecurityPatchLevel() { + return OEMCrypto_Security_Patch_Level(requested_security_level_); +} + CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { LOGV("CryptoSession::Open: Lock"); AutoLock auto_lock(crypto_lock_); @@ -247,16 +254,15 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { LOGV("OpenSession: id= %ld", (uint32_t)oec_session_id_); open_ = true; } else if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) { - LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", sts, - session_count_, (int)initialized_); + LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", + sts, session_count_, (int)initialized_); return INSUFFICIENT_CRYPTO_RESOURCES; } if (!open_) { - LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", sts, - session_count_, (int)initialized_); + LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", + sts, session_count_, (int)initialized_); return UNKNOWN_ERROR; } - OEMCrypto_GetRandom(reinterpret_cast(&request_id_base_), sizeof(request_id_base_)); ++request_id_index_; @@ -412,6 +418,10 @@ CdmResponseType CryptoSession::LoadKeys( ko->key_control_iv = NULL; ko->key_control = NULL; } + ko->cipher_mode = ki->cipher_mode() == kCipherModeCbc + ? OEMCrypto_CipherMode_CBC + : OEMCrypto_CipherMode_CTR; + cipher_mode_ = ki->cipher_mode(); } uint8_t* pst = NULL; if (!provider_session_token.empty()) { @@ -498,12 +508,22 @@ bool CryptoSession::RefreshKeys(const std::string& message, } bool CryptoSession::SelectKey(const std::string& key_id) { + // Crypto session lock already locked. + if (!cached_key_id_.empty() && cached_key_id_ == key_id) { + // Already using the desired key. + return true; + } + + cached_key_id_ = key_id; + const uint8_t* key_id_string = - reinterpret_cast(key_id.data()); + reinterpret_cast(cached_key_id_.data()); OEMCryptoResult sts = - OEMCrypto_SelectKey(oec_session_id_, key_id_string, key_id.size()); + OEMCrypto_SelectKey(oec_session_id_, key_id_string, + cached_key_id_.size()); if (OEMCrypto_SUCCESS != sts) { + cached_key_id_.clear(); return false; } return true; @@ -558,78 +578,76 @@ bool CryptoSession::GenerateDerivedKeys(const std::string& message, bool CryptoSession::GenerateSignature(const std::string& message, std::string* signature) { LOGV("GenerateSignature: id=%ld", (uint32_t)oec_session_id_); - if (!signature) return false; + if (!signature) { + LOGE("GenerateSignature: null signature string"); + return false; + } + OEMCryptoResult sts; size_t length = signature->size(); - OEMCryptoResult sts = OEMCrypto_GenerateSignature( - oec_session_id_, reinterpret_cast(message.data()), - message.size(), - reinterpret_cast(const_cast(signature->data())), - &length); - if (OEMCrypto_SUCCESS != sts) { - if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { - LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); - return false; - } - - // Retry with proper-sized signature buffer - signature->resize(length); + // At most two attempts. + // The first attempt may fail due to buffer too short + for (int i = 0; i < 2; ++i) { sts = OEMCrypto_GenerateSignature( oec_session_id_, reinterpret_cast(message.data()), message.size(), reinterpret_cast(const_cast(signature->data())), &length); - if (OEMCrypto_SUCCESS != sts) { - LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); - return false; + if (OEMCrypto_SUCCESS == sts) { + // Trim signature buffer and done + signature->resize(length); + return true; } + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + break; + } + + // Retry with proper-sized signature buffer + signature->resize(length); } - // Trim signature buffer - signature->resize(length); - - return true; + LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); + return false; } bool CryptoSession::GenerateRsaSignature(const std::string& message, std::string* signature) { LOGV("GenerateRsaSignature: id=%ld", (uint32_t)oec_session_id_); - if (!signature) return false; + if (!signature) { + LOGE("GenerateRsaSignature: null signature string"); + return false; + } + OEMCryptoResult sts; signature->resize(kRsaSignatureLength); size_t length = signature->size(); - OEMCryptoResult sts = OEMCrypto_GenerateRSASignature( - oec_session_id_, reinterpret_cast(message.data()), - message.size(), - reinterpret_cast(const_cast(signature->data())), &length, - kSign_RSASSA_PSS); - if (OEMCrypto_SUCCESS != sts) { - if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { - LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts); - return false; - } - - // Retry with proper-sized signature buffer - signature->resize(length); + // At most two attempts. + // The first attempt may fail due to buffer too short + for (int i = 0; i < 2; ++i) { sts = OEMCrypto_GenerateRSASignature( oec_session_id_, reinterpret_cast(message.data()), message.size(), reinterpret_cast(const_cast(signature->data())), &length, kSign_RSASSA_PSS); - if (OEMCrypto_SUCCESS != sts) { - LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts); - return false; + if (OEMCrypto_SUCCESS == sts) { + // Trim signature buffer and done + signature->resize(length); + return true; } + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + break; + } + + // Retry with proper-sized signature buffer + signature->resize(length); } - // Trim signature buffer - signature->resize(length); - - return true; + LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts); + return false; } CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { @@ -666,27 +684,32 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { } OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED; - if (!params.is_encrypted) { + if (!params.is_encrypted && + params.subsample_flags == + (OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)) { sts = OEMCrypto_CopyBuffer(requested_security_level_, params.encrypt_buffer, params.encrypt_length, &buffer_descriptor, params.subsample_flags); } + if (params.is_encrypted && params.cipher_mode != cipher_mode_) { + return INCORRECT_CRYPTO_MODE; + } if (params.is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + OEMCrypto_CENCEncryptPatternDesc pattern_descriptor; + pattern_descriptor.encrypt = params.pattern_descriptor.encrypt_blocks; + pattern_descriptor.skip = params.pattern_descriptor.skip_blocks; + pattern_descriptor.offset = params.pattern_descriptor.offset_blocks; AutoLock auto_lock(crypto_lock_); // Check if key needs to be selected if (params.is_encrypted) { - if (key_id_ != *params.key_id) { - if (SelectKey(*params.key_id)) { - key_id_ = *params.key_id; - } else { - return NEED_KEY; - } + if (!SelectKey(*params.key_id)) { + return NEED_KEY; } } - sts = OEMCrypto_DecryptCTR( + sts = OEMCrypto_DecryptCENC( oec_session_id_, params.encrypt_buffer, params.encrypt_length, params.is_encrypted, &(*params.iv).front(), params.block_offset, - &buffer_descriptor, params.subsample_flags); + &buffer_descriptor, &pattern_descriptor, params.subsample_flags); } switch (sts) { @@ -696,6 +719,13 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { return INSUFFICIENT_CRYPTO_RESOURCES; case OEMCrypto_ERROR_KEY_EXPIRED: return NEED_KEY; + case OEMCrypto_ERROR_INVALID_SESSION: + return SESSION_NOT_FOUND_FOR_DECRYPT; + case OEMCrypto_ERROR_DECRYPT_FAILED: + case OEMCrypto_ERROR_UNKNOWN_FAILURE: + return DECRYPT_ERROR; + case OEMCrypto_ERROR_INSUFFICIENT_HDCP: + return INSUFFICIENT_OUTPUT_PROTECTION; default: return UNKNOWN_ERROR; } @@ -1076,4 +1106,200 @@ bool CryptoSession::GetMaxNumberOfSessions(size_t* max) { return true; } +CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer) { + LOGV("GenericEncrypt: id=%ld", (uint32_t)oec_session_id_); + if (!out_buffer) return INVALID_PARAMETERS_ENG_9; + + OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm); + if (iv.size() != GenericEncryptionBlockSize(algorithm) || + oec_algorithm == kInvalidAlgorithm) { + return INVALID_PARAMETERS_ENG_13; + } + + if (out_buffer->size() < in_buffer.size()) { + out_buffer->resize(in_buffer.size()); + } + + AutoLock auto_lock(crypto_lock_); + if (!SelectKey(key_id)) { + return KEY_ERROR_1; + } + + OEMCryptoResult sts = OEMCrypto_Generic_Encrypt( + oec_session_id_, reinterpret_cast(in_buffer.data()), + in_buffer.size(), reinterpret_cast(iv.data()), + oec_algorithm, + reinterpret_cast(const_cast(out_buffer->data()))); + + if (OEMCrypto_SUCCESS != sts) { + LOGE("GenericEncrypt: OEMCrypto_Generic_Encrypt err=%d", sts); + if (OEMCrypto_ERROR_KEY_EXPIRED == sts || + OEMCrypto_ERROR_NO_CONTENT_KEY == sts) { + return KEY_NOT_FOUND_3; + } else { + return UNKNOWN_ERROR; + } + } + return NO_ERROR; +} + +CdmResponseType CryptoSession::GenericDecrypt(const std::string& in_buffer, + const std::string& key_id, + const std::string& iv, + CdmEncryptionAlgorithm algorithm, + std::string* out_buffer) { + LOGV("GenericDecrypt: id=%ld", (uint32_t)oec_session_id_); + if (!out_buffer) return INVALID_PARAMETERS_ENG_10; + + OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm); + if (iv.size() != GenericEncryptionBlockSize(algorithm) || + oec_algorithm == kInvalidAlgorithm) { + return INVALID_PARAMETERS_ENG_14; + } + + if (out_buffer->size() < in_buffer.size()) { + out_buffer->resize(in_buffer.size()); + } + + AutoLock auto_lock(crypto_lock_); + if (!SelectKey(key_id)) { + return KEY_ERROR_2; + } + + OEMCryptoResult sts = OEMCrypto_Generic_Decrypt( + oec_session_id_, reinterpret_cast(in_buffer.data()), + in_buffer.size(), reinterpret_cast(iv.data()), + oec_algorithm, + reinterpret_cast(const_cast(out_buffer->data()))); + + if (OEMCrypto_SUCCESS != sts) { + LOGE("GenericDecrypt: OEMCrypto_Generic_Decrypt err=%d", sts); + if (OEMCrypto_ERROR_KEY_EXPIRED == sts || + OEMCrypto_ERROR_NO_CONTENT_KEY == sts) { + return KEY_NOT_FOUND_4; + } else { + return UNKNOWN_ERROR; + } + } + return NO_ERROR; +} + +CdmResponseType CryptoSession::GenericSign(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + std::string* signature) { + LOGV("GenericSign: id=%ld", (uint32_t)oec_session_id_); + if (!signature) { + LOGE("GenerateSign: null signature string"); + return INVALID_PARAMETERS_ENG_11; + } + + OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm); + if (oec_algorithm == kInvalidAlgorithm) { + return INVALID_PARAMETERS_ENG_15; + } + + OEMCryptoResult sts; + size_t length = signature->size(); + + AutoLock auto_lock(crypto_lock_); + if (!SelectKey(key_id)) { + return KEY_ERROR_3; + } + + // At most two attempts. + // The first attempt may fail due to buffer too short + for (int i = 0; i < 2; ++i) { + sts = OEMCrypto_Generic_Sign( + oec_session_id_, reinterpret_cast(message.data()), + message.size(), oec_algorithm, + reinterpret_cast(const_cast(signature->data())), + &length); + + if (OEMCrypto_SUCCESS == sts) { + // Trim signature buffer and done + signature->resize(length); + return NO_ERROR; + } + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + break; + } + + // Retry with proper-sized return buffer + signature->resize(length); + } + + LOGE("GenericSign: OEMCrypto_Generic_Sign err=%d", sts); + if (OEMCrypto_ERROR_KEY_EXPIRED == sts || + OEMCrypto_ERROR_NO_CONTENT_KEY == sts) { + return KEY_NOT_FOUND_5; + } else { + return UNKNOWN_ERROR; + } +} + +CdmResponseType CryptoSession::GenericVerify(const std::string& message, + const std::string& key_id, + CdmSigningAlgorithm algorithm, + const std::string& signature) { + LOGV("GenericVerify: id=%ld", (uint32_t)oec_session_id_); + + OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm); + if (oec_algorithm == kInvalidAlgorithm) { + return INVALID_PARAMETERS_ENG_16; + } + + AutoLock auto_lock(crypto_lock_); + if (!SelectKey(key_id)) { + return KEY_ERROR_4; + } + + OEMCryptoResult sts = OEMCrypto_Generic_Verify( + oec_session_id_, reinterpret_cast(message.data()), + message.size(), oec_algorithm, + reinterpret_cast(signature.data()), signature.size()); + + if (OEMCrypto_SUCCESS != sts) { + LOGE("GenericVerify: OEMCrypto_Generic_Verify err=%d", sts); + if (OEMCrypto_ERROR_KEY_EXPIRED == sts || + OEMCrypto_ERROR_NO_CONTENT_KEY == sts) { + return KEY_NOT_FOUND_6; + } else { + return UNKNOWN_ERROR; + } + } + return NO_ERROR; +} + +OEMCrypto_Algorithm CryptoSession::GenericSigningAlgorithm( + CdmSigningAlgorithm algorithm) { + if (kSigningAlgorithmHmacSha256 == algorithm) { + return OEMCrypto_HMAC_SHA256; + } else { + return kInvalidAlgorithm; + } +} + +OEMCrypto_Algorithm CryptoSession::GenericEncryptionAlgorithm( + CdmEncryptionAlgorithm algorithm) { + if (kEncryptionAlgorithmAesCbc128 == algorithm) { + return OEMCrypto_AES_CBC_128_NO_PADDING; + } else { + return kInvalidAlgorithm; + } +} + +size_t CryptoSession::GenericEncryptionBlockSize( + CdmEncryptionAlgorithm algorithm) { + if (kEncryptionAlgorithmAesCbc128 == algorithm) { + return kAes128BlockSize; + } else { + return 0; + } +} + } // namespace wvcdm diff --git a/core/src/device_files.cpp b/core/src/device_files.cpp index b2bdb210..ffebc70b 100644 --- a/core/src/device_files.cpp +++ b/core/src/device_files.cpp @@ -18,13 +18,16 @@ #define MD5 CC_MD5 #define MD5_DIGEST_LENGTH CC_MD5_DIGEST_LENGTH #else -#include #include +#include #endif // Protobuf generated classes. using video_widevine_client::sdk::DeviceCertificate; using video_widevine_client::sdk::HashedFile; +using video_widevine_client::sdk::HlsAttributes; +using video_widevine_client::sdk::HlsAttributes_Method_AES_128; +using video_widevine_client::sdk::HlsAttributes_Method_SAMPLE_AES; using video_widevine_client::sdk::License; using video_widevine_client::sdk::License_LicenseState_ACTIVE; using video_widevine_client::sdk::License_LicenseState_RELEASING; @@ -34,19 +37,13 @@ using video_widevine_client::sdk::UsageInfo_ProviderSession; namespace { -const char kCertificateFileNamePrefix[] = "cert"; -const char kCertificateFileNameExt[] = ".bin"; +const char kCertificateFileName[] = "cert.bin"; +const char kHlsAttributesFileNameExt[] = ".hal"; const char kUsageInfoFileNamePrefix[] = "usage"; const char kUsageInfoFileNameExt[] = ".bin"; const char kLicenseFileNameExt[] = ".lic"; const char kEmptyFileName[] = ""; const char kWildcard[] = "*"; -const char kDirectoryDelimiter = '/'; -const char* kSecurityLevelPathCompatibilityExclusionList[] = { - "ay64.dat", "ay64.dat2", "ay64.dat3"}; -size_t kSecurityLevelPathCompatibilityExclusionListSize = - sizeof(kSecurityLevelPathCompatibilityExclusionList) / - sizeof(*kSecurityLevelPathCompatibilityExclusionList); bool Hash(const std::string& data, std::string* hash) { if (!hash) return false; @@ -66,30 +63,30 @@ namespace wvcdm { // static std::set DeviceFiles::reserved_license_ids_; -DeviceFiles::DeviceFiles() - : file_(NULL), +DeviceFiles::DeviceFiles(FileSystem* file_system) + : file_system_(file_system), security_level_(kSecurityLevelUninitialized), - initialized_(false), - test_file_(false) {} + initialized_(false) {} -DeviceFiles::~DeviceFiles() { - if (test_file_) file_.release(); -} +DeviceFiles::~DeviceFiles() {} bool DeviceFiles::Init(CdmSecurityLevel security_level) { + if (!file_system_) { + LOGD("DeviceFiles::Init: Invalid FileSystem given."); + return false; + } + std::string path; if (!Properties::GetDeviceFilesBasePath(security_level, &path)) { LOGW("DeviceFiles::Init: Unsupported security level %d", security_level); return false; } - if (!test_file_) file_.reset(new File()); security_level_ = security_level; initialized_ = true; return true; } -bool DeviceFiles::StoreCertificate(const std::string& origin, - const std::string& certificate, +bool DeviceFiles::StoreCertificate(const std::string& certificate, const std::string& wrapped_private_key) { if (!initialized_) { LOGW("DeviceFiles::StoreCertificate: not initialized"); @@ -109,23 +106,18 @@ bool DeviceFiles::StoreCertificate(const std::string& origin, std::string serialized_file; file.SerializeToString(&serialized_file); - return StoreFileWithHash(GetCertificateFileName(origin), serialized_file); + return StoreFileWithHash(GetCertificateFileName(), serialized_file); } -bool DeviceFiles::RetrieveCertificate(const std::string& origin, - std::string* certificate, +bool DeviceFiles::RetrieveCertificate(std::string* certificate, std::string* wrapped_private_key) { if (!initialized_) { LOGW("DeviceFiles::RetrieveCertificate: not initialized"); return false; } - if (Properties::security_level_path_backward_compatibility_support()) { - SecurityLevelPathBackwardCompatibility(); - } - video_widevine_client::sdk::File file; - if (!RetrieveHashedFile(GetCertificateFileName(origin), &file)) { + if (!RetrieveHashedFile(GetCertificateFileName(), &file)) { return false; } @@ -151,22 +143,22 @@ bool DeviceFiles::RetrieveCertificate(const std::string& origin, return true; } -bool DeviceFiles::HasCertificate(const std::string& origin) { +bool DeviceFiles::HasCertificate() { if (!initialized_) { LOGW("DeviceFiles::HasCertificate: not initialized"); return false; } - return FileExists(GetCertificateFileName(origin)); + return FileExists(GetCertificateFileName()); } -bool DeviceFiles::RemoveCertificate(const std::string& origin) { +bool DeviceFiles::RemoveCertificate() { if (!initialized_) { LOGW("DeviceFiles::RemoveCertificate: not initialized"); return false; } - return RemoveFile(GetCertificateFileName(origin)); + return RemoveFile(GetCertificateFileName()); } bool DeviceFiles::StoreLicense( @@ -534,13 +526,105 @@ bool DeviceFiles::RetrieveUsageInfoByKeySetId( return false; } -bool DeviceFiles::StoreFileWithHash(const std::string& name, - const std::string& serialized_file) { - if (!file_.get()) { - LOGW("DeviceFiles::StoreFileWithHash: Invalid file handle"); +bool DeviceFiles::StoreHlsAttributes( + const std::string& key_set_id, const CdmHlsMethod method, + const std::vector& media_segment_iv) { + if (!initialized_) { + LOGW("DeviceFiles::StoreHlsAttributes: not initialized"); return false; } + // Fill in file information + video_widevine_client::sdk::File file; + + file.set_type(video_widevine_client::sdk::File::HLS_ATTRIBUTES); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + + HlsAttributes* hls_attributes = file.mutable_hls_attributes(); + switch (method) { + case kHlsMethodAes128: + hls_attributes->set_method(HlsAttributes_Method_AES_128); + break; + case kHlsMethodSampleAes: + hls_attributes->set_method(HlsAttributes_Method_SAMPLE_AES); + break; + case kHlsMethodNone: + default: + LOGW("DeviceFiles::StoreHlsAttributeInfo: Unknown HLS method: %u", + method); + return false; + break; + } + hls_attributes->set_media_segment_iv(&media_segment_iv[0], + media_segment_iv.size()); + + std::string serialized_file; + file.SerializeToString(&serialized_file); + + return StoreFileWithHash(key_set_id + kHlsAttributesFileNameExt, + serialized_file); +} + +bool DeviceFiles::RetrieveHlsAttributes( + const std::string& key_set_id, CdmHlsMethod* method, + std::vector* media_segment_iv) { + if (!initialized_) { + LOGW("DeviceFiles::RetrieveHlsAttributes: not initialized"); + return false; + } + + video_widevine_client::sdk::File file; + if (!RetrieveHashedFile(key_set_id + kHlsAttributesFileNameExt, &file)) { + return false; + } + + if (file.type() != video_widevine_client::sdk::File::HLS_ATTRIBUTES) { + LOGW("DeviceFiles::RetrieveHlsAttributes: Incorrect file type: %u", + file.type()); + return false; + } + + if (file.version() != video_widevine_client::sdk::File::VERSION_1) { + LOGW("DeviceFiles::RetrieveHlsAttributes: Incorrect file version: %u", + file.version()); + return false; + } + + if (!file.has_hls_attributes()) { + LOGW("DeviceFiles::RetrieveHlsAttributes: HLS attributes not present"); + return false; + } + + HlsAttributes attributes = file.hls_attributes(); + + switch (attributes.method()) { + case HlsAttributes_Method_AES_128: + *method = kHlsMethodAes128; + break; + case HlsAttributes_Method_SAMPLE_AES: + *method = kHlsMethodSampleAes; + break; + default: + LOGW("DeviceFiles::RetrieveHlsAttributes: Unrecognized HLS method: %u", + attributes.method()); + *method = kHlsMethodNone; + break; + } + media_segment_iv->assign(attributes.media_segment_iv().begin(), + attributes.media_segment_iv().end()); + return true; +} + +bool DeviceFiles::DeleteHlsAttributes(const std::string& key_set_id) { + if (!initialized_) { + LOGW("DeviceFiles::DeleteHlsAttributes: not initialized"); + return false; + } + return RemoveFile(key_set_id + kHlsAttributesFileNameExt); +} + +bool DeviceFiles::StoreFileWithHash(const std::string& name, + const std::string& serialized_file) { // calculate SHA hash std::string hash; if (!Hash(serialized_file, &hash)) { @@ -561,30 +645,23 @@ bool DeviceFiles::StoreFileWithHash(const std::string& name, bool DeviceFiles::StoreFileRaw(const std::string& name, const std::string& serialized_file) { - if (!file_.get()) { - LOGW("DeviceFiles::StoreFileRaw: Invalid file handle"); - return false; - } - std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::StoreFileRaw: Unable to get base path"); return false; } - if (!file_->IsDirectory(path)) { - if (!file_->CreateDirectory(path)) return false; - } - path += name; - if (!file_->Open(path, File::kCreate | File::kTruncate | File::kBinary)) { + File* file = + file_system_->Open(path, FileSystem::kCreate | FileSystem::kTruncate); + if (!file) { LOGW("DeviceFiles::StoreFileRaw: File open failed: %s", path.c_str()); return false; } - ssize_t bytes = file_->Write(serialized_file.data(), serialized_file.size()); - file_->Close(); + ssize_t bytes = file->Write(serialized_file.data(), serialized_file.size()); + file->Close(); if (bytes != static_cast(serialized_file.size())) { LOGW( @@ -599,16 +676,12 @@ bool DeviceFiles::StoreFileRaw(const std::string& name, return true; } -bool DeviceFiles::RetrieveHashedFile(const std::string& name, - video_widevine_client::sdk::File* file) { +bool DeviceFiles::RetrieveHashedFile( + const std::string& name, + video_widevine_client::sdk::File* deserialized_file) { std::string serialized_file; - if (!file_.get()) { - LOGW("DeviceFiles::RetrieveHashedFile: Invalid file handle"); - return false; - } - - if (!file) { + if (!deserialized_file) { LOGW("DeviceFiles::RetrieveHashedFile: Unspecified file parameter"); return false; } @@ -621,29 +694,30 @@ bool DeviceFiles::RetrieveHashedFile(const std::string& name, path += name; - if (!file_->Exists(path)) { + if (!file_system_->Exists(path)) { LOGW("DeviceFiles::RetrieveHashedFile: %s does not exist", path.c_str()); return false; } - ssize_t bytes = file_->FileSize(path); + ssize_t bytes = file_system_->FileSize(path); if (bytes <= 0) { LOGW("DeviceFiles::RetrieveHashedFile: File size invalid: %s", path.c_str()); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. - file_->Remove(path); + file_system_->Remove(path); return false; } - if (!file_->Open(path, File::kReadOnly | File::kBinary)) { + File* file = file_system_->Open(path, FileSystem::kReadOnly); + if (!file) { return false; } std::string serialized_hash_file; serialized_hash_file.resize(bytes); - bytes = file_->Read(&serialized_hash_file[0], serialized_hash_file.size()); - file_->Close(); + bytes = file->Read(&serialized_hash_file[0], serialized_hash_file.size()); + file->Close(); if (bytes != static_cast(serialized_hash_file.size())) { LOGW("DeviceFiles::RetrieveHashedFile: read failed"); @@ -669,11 +743,11 @@ bool DeviceFiles::RetrieveHashedFile(const std::string& name, LOGW("DeviceFiles::RetrieveHashedFile: Hash mismatch"); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. - file_->Remove(path); + file_system_->Remove(path); return false; } - if (!file->ParseFromString(hash_file.file())) { + if (!deserialized_file->ParseFromString(hash_file.file())) { LOGW("DeviceFiles::RetrieveHashedFile: Unable to parse file"); return false; } @@ -681,11 +755,6 @@ bool DeviceFiles::RetrieveHashedFile(const std::string& name, } bool DeviceFiles::FileExists(const std::string& name) { - if (!file_.get()) { - LOGW("DeviceFiles::FileExists: Invalid file handle"); - return false; - } - std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::FileExists: Unable to get base path"); @@ -693,15 +762,10 @@ bool DeviceFiles::FileExists(const std::string& name) { } path += name; - return file_->Exists(path); + return file_system_->Exists(path); } bool DeviceFiles::RemoveFile(const std::string& name) { - if (!file_.get()) { - LOGW("DeviceFiles::RemoveFile: Invalid file handle"); - return false; - } - std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::RemoveFile: Unable to get base path"); @@ -709,15 +773,10 @@ bool DeviceFiles::RemoveFile(const std::string& name) { } path += name; - return file_->Remove(path); + return file_system_->Remove(path); } ssize_t DeviceFiles::GetFileSize(const std::string& name) { - if (!file_.get()) { - LOGW("DeviceFiles::GetFileSize: Invalid file handle"); - return -1; - } - std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::GetFileSize: Unable to get base path"); @@ -725,79 +784,15 @@ ssize_t DeviceFiles::GetFileSize(const std::string& name) { } path += name; - return file_->FileSize(path); + return file_system_->FileSize(path); } -void DeviceFiles::SecurityLevelPathBackwardCompatibility() { - std::string path; - if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { - LOGW( - "DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to " - "get base path"); - return; - } - - std::vector security_dirs; - if (!Properties::GetSecurityLevelDirectories(&security_dirs)) { - LOGW( - "DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to " - "get security directories"); - return; - } - - size_t pos = std::string::npos; - for (size_t i = 0; i < security_dirs.size(); ++i) { - pos = path.find(security_dirs[i]); - if (pos != std::string::npos && pos > 0 && - pos == path.size() - security_dirs[i].size() && - path[pos - 1] == kDirectoryDelimiter) { - break; - } - } - - if (pos == std::string::npos) { - LOGV( - "DeviceFiles::SecurityLevelPathBackwardCompatibility: Security level " - "specific path not found. Check properties?"); - return; - } - - std::string from_dir(path, 0, pos); - - std::vector files; - if (!file_->List(from_dir, &files)) { - return; - } - - for (size_t i = 0; i < files.size(); ++i) { - std::string from = from_dir + files[i]; - bool exclude = false; - for (size_t j = 0; j < kSecurityLevelPathCompatibilityExclusionListSize; - ++j) { - if (files[i] == kSecurityLevelPathCompatibilityExclusionList[j]) { - exclude = true; - break; - } - } - if (exclude) continue; - if (!file_->IsRegularFile(from)) continue; - - for (size_t j = 0; j < security_dirs.size(); ++j) { - std::string to_dir = from_dir + security_dirs[j]; - if (!file_->Exists(to_dir)) file_->CreateDirectory(to_dir); - std::string to = to_dir + files[i]; - file_->Copy(from, to); - } - file_->Remove(from); - } +std::string DeviceFiles::GetCertificateFileName() { + return kCertificateFileName; } -std::string DeviceFiles::GetCertificateFileName(const std::string& origin) { - std::string hash; - if (origin != EMPTY_ORIGIN) { - hash = GetFileNameSafeHash(origin); - } - return kCertificateFileNamePrefix + hash + kCertificateFileNameExt; +std::string DeviceFiles::GetHlsAttributesFileNameExtension() { + return kHlsAttributesFileNameExt; } std::string DeviceFiles::GetLicenseFileNameExtension() { @@ -820,9 +815,4 @@ std::string DeviceFiles::GetFileNameSafeHash(const std::string& input) { return wvcdm::Base64SafeEncode(hash); } -void DeviceFiles::SetTestFile(File* file) { - file_.reset(file); - test_file_ = true; -} - } // namespace wvcdm diff --git a/core/src/device_files.proto b/core/src/device_files.proto index e9b793fd..89bd0c5e 100644 --- a/core/src/device_files.proto +++ b/core/src/device_files.proto @@ -52,11 +52,21 @@ message UsageInfo { repeated ProviderSession sessions = 1; } +message HlsAttributes { + enum Method { + AES_128 = 1; + SAMPLE_AES = 2; + } + optional Method method = 1; + optional bytes media_segment_iv = 2; +} + message File { enum FileType { DEVICE_CERTIFICATE = 1; LICENSE = 2; USAGE_INFO = 3; + HLS_ATTRIBUTES = 4; } enum FileVersion { @@ -68,6 +78,7 @@ message File { optional DeviceCertificate device_certificate = 3; optional License license = 4; optional UsageInfo usage_info = 5; + optional HlsAttributes hls_attributes = 6; } message HashedFile { diff --git a/core/src/initialization_data.cpp b/core/src/initialization_data.cpp index a8378586..8227a42c 100644 --- a/core/src/initialization_data.cpp +++ b/core/src/initialization_data.cpp @@ -2,31 +2,70 @@ #include "initialization_data.h" +#include #include #include "buffer_reader.h" +#include "jsmn.h" +#include "license_protocol.pb.h" #include "log.h" #include "properties.h" +#include "string_conversions.h" #include "wv_cdm_constants.h" +namespace { +const char kKeyFormatVersionsSeparator = '/'; +const char kColon = ':'; +const char kDoubleQuote = '\"'; +const char kLeftBracket = '['; +const char kRightBracket = ']'; +const std::string kBase64String = "base64,"; +const uint32_t kFourCcCbc1 = 0x63626331; +const uint32_t kFourCcCbcs = 0x63626373; + +// json init data key values +const std::string kProvider = "provider"; +const std::string kContentId = "content_id"; +const std::string kKeyIds = "key_ids"; + +// Being conservative, usually we expect 6 + number of Key Ids +const int kDefaultNumJsonTokens = 128; +} // namespace + namespace wvcdm { +// Protobuf generated classes. +using video_widevine_server::sdk::WidevineCencHeader; +using video_widevine_server::sdk::WidevineCencHeader_Algorithm; +using video_widevine_server::sdk::WidevineCencHeader_Algorithm_AESCTR; + InitializationData::InitializationData(const std::string& type, const CdmInitData& data) - : type_(type), is_cenc_(false), is_webm_(false) { + : type_(type), + is_cenc_(false), + is_hls_(false), + is_webm_(false), + hls_method_(kHlsMethodNone) { if (type == ISO_BMFF_VIDEO_MIME_TYPE || type == ISO_BMFF_AUDIO_MIME_TYPE || type == CENC_INIT_DATA_FORMAT) { is_cenc_ = true; } else if (type == WEBM_VIDEO_MIME_TYPE || type == WEBM_AUDIO_MIME_TYPE || type == WEBM_INIT_DATA_FORMAT) { is_webm_ = true; + } else if (type == HLS_INIT_DATA_FORMAT) { + is_hls_ = true; } if (is_supported()) { if (is_cenc()) { ExtractWidevinePssh(data, &data_); - } else { + } else if (is_webm()) { data_ = data; + } else if (is_hls()) { + std::string uri; + if (ExtractHlsAttributes(data, &hls_method_, &hls_iv_, &uri)) { + ConstructWidevineInitData(hls_method_, uri, &data_); + } } } } @@ -61,19 +100,22 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // atom size, used for skipping. uint64_t size; if (!reader.Read4Into8(&size)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read atom size."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read atom size."); return false; } std::vector atom_type; if (!reader.ReadVec(&atom_type, 4)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read atom type."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read atom type."); return false; } if (size == 1) { if (!reader.Read8(&size)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read 64-bit atom " - "size."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read 64-bit " + "atom size."); return false; } } else if (size == 0) { @@ -82,10 +124,12 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // "pssh" if (memcmp(&atom_type[0], "pssh", 4)) { - LOGV("CdmEngine::ExtractWidevinePssh: PSSH literal not present."); + LOGV( + "InitializationData::ExtractWidevinePssh: PSSH literal not present."); if (!reader.SkipBytes(size - (reader.pos() - start_pos))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of " - "the atom."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the rest " + "of the atom."); return false; } continue; @@ -94,15 +138,18 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // version uint8_t version; if (!reader.Read1(&version)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH version."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read PSSH " + "version."); return false; } if (version > 1) { // unrecognized version - skip. if (!reader.SkipBytes(size - (reader.pos() - start_pos))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of " - "the atom."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the rest " + "of the atom."); return false; } continue; @@ -110,25 +157,31 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // flags if (!reader.SkipBytes(3)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the PSSH flags."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the PSSH " + "flags."); return false; } // system id std::vector system_id; if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read system ID."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read system ID."); return false; } if (memcmp(&system_id[0], kWidevineSystemId, sizeof(kWidevineSystemId))) { // skip non-Widevine PSSH boxes. if (!reader.SkipBytes(size - (reader.pos() - start_pos))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of " - "the atom."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the rest " + "of the atom."); return false; } - LOGV("CdmEngine::ExtractWidevinePssh: Skipping non-Widevine PSSH."); + LOGV( + "InitializationData::ExtractWidevinePssh: Skipping non-Widevine " + "PSSH."); continue; } @@ -136,11 +189,14 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // v1 has additional fields for key IDs. We can skip them. uint32_t num_key_ids; if (!reader.Read4(&num_key_ids)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read num key IDs."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read num key " + "IDs."); return false; } if (!reader.SkipBytes(num_key_ids * 16)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip key IDs."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip key IDs."); return false; } } @@ -148,13 +204,16 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // size of PSSH data uint32_t data_length; if (!reader.Read4(&data_length)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data size."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read PSSH data " + "size."); return false; } output->clear(); if (!reader.ReadString(output, data_length)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read PSSH data."); return false; } @@ -165,4 +224,357 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, return false; } +// Parse an EXT-X-KEY tag attribute list. Verify that Widevine supports it +// by validating KEYFORMAT and KEYFORMATVERSION attributes. Extract out +// method, IV, URI and WV init data. +// +// An example of a widevine supported attribute list from an HLS media playlist +// is, +// "EXT-X-KEY: METHOD=SAMPLE-AES, \" +// "URI=”data:text/plain;base64,eyANCiAgICJwcm92aWRlciI6Im1sYmFtaGJvIiwNCiAg" +// "ICJjb250ZW50X2lkIjoiMjAxNV9UZWFycyIsDQogICAia2V5X2lkcyI6DQogICBbDQo" +// "gICAgICAiMzcxZTEzNWUxYTk4NWQ3NWQxOThhN2Y0MTAyMGRjMjMiDQogICBdDQp9DQ" +// "o=, \" +// "IV=0x6df49213a781e338628d0e9c812d328e, \" +// "KEYFORMAT=”com.widevine”, \" +// "KEYFORMATVERSIONS=”1”" +bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list, + CdmHlsMethod* method, + std::vector* iv, + std::string* uri) { + std::string value; + if (!ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_ATTRIBUTE, + &value)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS " + "keyformat value"); + return false; + } + + if (value.compare(0, sizeof(KEY_SYSTEM) - 1, KEY_SYSTEM) != 0) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: unrecognized HLS " + "keyformat value: %s", + value.c_str()); + return false; + } + + // KEYFORMATVERSIONS is an optional parameter. If absent its + // value defaults to "1" + if (ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, + &value)) { + std::vector versions = ExtractKeyFormatVersions(value); + bool supported = false; + for (size_t i = 0; i < versions.size(); ++i) { + if (versions[i].compare(HLS_KEYFORMAT_VERSION_VALUE_1) == 0) { + supported = true; + break; + } + } + if (!supported) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS keyformat " + "version is not supported: %s", + value.c_str()); + return false; + } + } + + if (!ExtractAttribute(attribute_list, HLS_METHOD_ATTRIBUTE, &value)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS " + "method"); + return false; + } + + if (value.compare(HLS_METHOD_AES_128) == 0) { + *method = kHlsMethodAes128; + } else if (value.compare(HLS_METHOD_SAMPLE_AES) == 0) { + *method = kHlsMethodSampleAes; + } else if (value.compare(HLS_METHOD_NONE) == 0) { + *method = kHlsMethodNone; + } else { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS method " + "unrecognized: %s", + value.c_str()); + return false; + } + + if (!ExtractHexAttribute(attribute_list, HLS_IV_ATTRIBUTE, iv)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS IV attribute " + "not present"); + return false; + } + + if (!ExtractQuotedAttribute(attribute_list, HLS_URI_ATTRIBUTE, uri)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS URI attribute " + "not present"); + return false; + } + + return true; +} + +// Extracts a base64 encoded string from URI data. This is then base64 decoded +// and the Json formatted init data is then parsed. The information is used +// to generate a Widevine init data protobuf (WidevineCencHeader). +// +// An example of a widevine supported json formatted init data string is, +// +// { +// "provider":"mlbamhbo", +// "content_id":"MjAxNV9UZWFycw==", +// "key_ids": +// [ +// "371e135e1a985d75d198a7f41020dc23" +// ] +// } +bool InitializationData::ConstructWidevineInitData( + CdmHlsMethod method, const std::string& uri, CdmInitData* init_data_proto) { + if (!init_data_proto) { + LOGV("InitializationData::ConstructWidevineInitData: Invalid parameter"); + return false; + } + if (method != kHlsMethodAes128 && method != kHlsMethodSampleAes) { + LOGV("InitializationData::ConstructWidevineInitData: Invalid method" + " parameter"); + return false; + } + + size_t pos = uri.find(kBase64String); + if (pos == std::string::npos) { + LOGV( + "InitializationData::ConstructWidevineInitData: URI attribute " + "unexpected format: %s", + uri.c_str()); + return false; + } + + std::vector json_init_data = + Base64Decode(uri.substr(pos + kBase64String.size())); + if (json_init_data.size() == 0) { + LOGV( + "InitializationData::ConstructWidevineInitData: Base64 decode of json " + "data failed"); + return false; + } + std::string json_string((const char*)(&json_init_data[0]), + json_init_data.size()); + + // Parse the Json string using jsmn + jsmn_parser parser; + jsmntok_t tokens[kDefaultNumJsonTokens]; + jsmn_init(&parser); + int num_of_tokens = + jsmn_parse(&parser, json_string.c_str(), json_string.size(), tokens, + kDefaultNumJsonTokens); + + if (num_of_tokens <= 0) { + LOGV( + "InitializationData::ConstructWidevineInitData: Json parsing failed: " + "%d", + num_of_tokens); + return false; + } + + std::string provider; + std::string content_id; + std::vector key_ids; + + enum JsmnParserState { + kParseState, + kProviderState, + kContentIdState, + kKeyIdsState, + } state = kParseState; + + int number_of_key_ids = 0; + + // Extract the provider, content_id and key_ids + for (int i = 0; i < num_of_tokens; ++i) { + if (tokens[i].start < 0 || tokens[i].end < 0) { + LOGV( + "InitializationData::ConstructWidevineInitData: Invalid start or end " + "of token"); + return false; + } + + switch (state) { + case kParseState: + if (tokens[i].type == JSMN_STRING) { + std::string token(json_string, tokens[i].start, + tokens[i].end - tokens[i].start); + if (token == kProvider) { + state = kProviderState; + } else if (token == kContentId) { + state = kContentIdState; + } else if (token == kKeyIds) { + state = kKeyIdsState; + } + } + break; + case kProviderState: + if (tokens[i].type == JSMN_STRING) { + provider.assign(json_string, tokens[i].start, + tokens[i].end - tokens[i].start); + } + state = kParseState; + break; + case kContentIdState: + if (tokens[i].type == JSMN_STRING) { + std::string base64_content_id(json_string, tokens[i].start, + tokens[i].end - tokens[i].start); + std::vector content_id_data = + Base64Decode(base64_content_id); + content_id.assign(reinterpret_cast(&content_id_data[0]), + content_id_data.size()); + } + state = kParseState; + break; + case kKeyIdsState: + if (tokens[i].type == JSMN_ARRAY) { + number_of_key_ids = tokens[i].size; + } else if (tokens[i].type == JSMN_STRING) { + std::string key_id(a2bs_hex(json_string.substr( + tokens[i].start, tokens[i].end - tokens[i].start))); + if (key_id.size() == 16) key_ids.push_back(key_id); + --number_of_key_ids; + } else { + state = kParseState; + } + if (number_of_key_ids <= 0) state = kParseState; + break; + } + } + + if (provider.size() == 0) { + LOGV("InitializationData::ConstructWidevineInitData: Invalid provider"); + return false; + } + + if (content_id.size() == 0) { + LOGV("InitializationData::ConstructWidevineInitData: Invalid content_id"); + return false; + } + + if (key_ids.size() == 0) { + LOGV("InitializationData::ConstructWidevineInitData: No key_ids present"); + return false; + } + + // Now format as Widevine init data protobuf + WidevineCencHeader cenc_header; + // TODO(rfrias): The algorithm is a deprecated field, but proto changes + // have not yet been pushed to production. Set until then. + cenc_header.set_algorithm(WidevineCencHeader_Algorithm_AESCTR); + for (size_t i = 0; i < key_ids.size(); ++i) { + cenc_header.add_key_id(key_ids[i]); + } + cenc_header.set_provider(provider); + cenc_header.set_content_id(content_id); + if (method == kHlsMethodAes128) + cenc_header.set_protection_scheme(htonl(kFourCcCbc1)); + else + cenc_header.set_protection_scheme(htonl(kFourCcCbcs)); + cenc_header.SerializeToString(init_data_proto); + return true; +} + +bool InitializationData::ExtractQuotedAttribute( + const std::string& attribute_list, const std::string& key, + std::string* value) { + bool result = ExtractAttribute(attribute_list, key, value); + if (value->size() < 2 || value->at(0) != '\"' || + value->at(value->size() - 1) != '\"') + return false; + *value = value->substr(1, value->size() - 2); + if (value->find('\"') != std::string::npos) return false; + return result; +} + +bool InitializationData::ExtractHexAttribute(const std::string& attribute_list, + const std::string& key, + std::vector* value) { + std::string val; + bool result = ExtractAttribute(attribute_list, key, &val); + if (!result || val.size() <= 2 || val.size() % 2 != 0 || val[0] != '0' || + ((val[1] != 'x') && (val[1] != 'X'))) + return false; + for (size_t i = 2; i < val.size(); ++i) { + if (!isxdigit(val[i])) return false; + } + *value = a2b_hex(val.substr(2, val.size() - 2)); + return result; +} + +bool InitializationData::ExtractAttribute(const std::string& attribute_list, + const std::string& key, + std::string* value) { + // validate the key + for (size_t i = 0; i < key.size(); ++i) + if (!isupper(key[i]) && !isdigit(key[i]) && key[i] != '-') return false; + + bool found = false; + size_t pos = 0; + // Find the key followed by '=' + while (!found) { + pos = attribute_list.find(key, pos); + if (pos == std::string::npos) return false; + pos += key.size(); + if (attribute_list[pos] != '=') continue; + found = true; + } + + if (attribute_list.size() <= ++pos) return false; + + size_t end_pos = pos; + found = false; + // Find the next comma outside the quote or end of string + while (!found) { + end_pos = attribute_list.find(',', end_pos); + if (end_pos != std::string::npos && attribute_list[pos] == '\"' && + attribute_list[end_pos - 1] != '\"') { + ++end_pos; + continue; + } + + if (end_pos == std::string::npos) + end_pos = attribute_list.size() - 1; + else + --end_pos; + found = true; + } + + *value = attribute_list.substr(pos, end_pos - pos + 1); + + // validate the value + for (size_t i = 0; i < value->size(); ++i) + if (!isgraph(value->at(i))) return false; + + return true; +} + +// Key format versions are individual values or multiple versions +// separated by '/'. "1" or "1/2/5" +std::vector InitializationData::ExtractKeyFormatVersions( + const std::string& key_format_versions) { + std::vector versions; + size_t pos = 0; + while (pos < key_format_versions.size()) { + size_t next_pos = + key_format_versions.find(kKeyFormatVersionsSeparator, pos); + if (next_pos == std::string::npos) { + versions.push_back(key_format_versions.substr(pos)); + break; + } else { + versions.push_back(key_format_versions.substr(pos, next_pos - pos)); + pos = next_pos + 1; + } + } + return versions; +} + } // namespace wvcdm diff --git a/core/src/license.cpp b/core/src/license.cpp index 72af4db7..04603769 100644 --- a/core/src/license.cpp +++ b/core/src/license.cpp @@ -2,6 +2,8 @@ #include "license.h" +#include +#include #include #include "clock.h" @@ -24,6 +26,7 @@ std::string kProductNameKey = "product_name"; std::string kBuildInfoKey = "build_info"; std::string kDeviceIdKey = "device_id"; std::string kWVCdmVersionKey = "widevine_cdm_version"; +std::string kOemCryptoSecurityPatchLevelKey = "oem_crypto_security_patch_level"; const unsigned char kServiceCertificateCAPublicKey[] = { 0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01, 0x81, 0x00, 0xb4, 0xfe, 0x39, 0xc3, 0x65, 0x90, 0x03, 0xdb, 0x3c, 0x11, 0x97, 0x09, 0xe8, 0x68, 0xcd, @@ -60,6 +63,10 @@ const unsigned char kServiceCertificateCAPublicKey[] = { 0x78, 0xb4, 0x64, 0x82, 0x50, 0xd2, 0x33, 0x5f, 0x91, 0x02, 0x03, 0x01, 0x00, 0x01}; } +const uint32_t kFourCcCbc1 = 0x63626331; +const uint32_t kFourCcCbcs = 0x63626373; +const uint32_t kFourCcCenc = 0x63656e63; +const uint32_t kFourCcCens = 0x63656e73; namespace wvcdm { @@ -91,7 +98,7 @@ static std::vector ExtractContentKeys(const License& license) { size_t length; switch (license.key(i).type()) { case License_KeyContainer::CONTENT: - case License_KeyContainer::OPERATOR_SESSION: + case License_KeyContainer::OPERATOR_SESSION: { key.set_key_id(license.key(i).id()); // Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes, // the padding will always be 16 bytes. @@ -106,8 +113,17 @@ static std::vector ExtractContentKeys(const License& license) { key.set_key_control(license.key(i).key_control().key_control_block()); key.set_key_control_iv(license.key(i).key_control().iv()); } + uint32_t four_cc = kFourCcCenc; + if (license.has_protection_scheme()) { + four_cc = ntohl(license.protection_scheme()); + } + if (four_cc == kFourCcCbc1 || four_cc == kFourCcCbcs) + key.set_cipher_mode(kCipherModeCbc); + else + key.set_cipher_mode(kCipherModeCtr); key_array.push_back(key); break; + } case License_KeyContainer::KEY_CONTROL: if (license.key(i).has_key_control()) { key.set_key_control(license.key(i).key_control().key_control_block()); @@ -232,7 +248,7 @@ CdmResponseType CdmLicense::PrepareKeyRequest( LicenseRequest_ContentIdentification* content_id = license_request.mutable_content_id(); - if (init_data.is_cenc()) { + if (init_data.is_cenc() || init_data.is_hls()) { LicenseRequest_ContentIdentification_CENC* cenc_content_id = content_id->mutable_cenc_id(); @@ -535,8 +551,6 @@ CdmResponseType CdmLicense::HandleKeyResponse( server_url_ = license.policy().renewal_server_url(); } - policy_engine_->SetLicense(license); - if (license.policy().has_renew_with_client_id()) { renew_with_client_id_ = license.policy().renew_with_client_id(); } @@ -551,6 +565,7 @@ CdmResponseType CdmLicense::HandleKeyResponse( it != key_array.end(); ++it) { loaded_keys_.insert(it->key_id()); } + policy_engine_->SetLicense(license); } return resp; } @@ -612,8 +627,6 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( return LICENSE_ID_NOT_FOUND; } - policy_engine_->UpdateLicense(license); - if (license.policy().has_renew_with_client_id()) { renew_with_client_id_ = license.policy().renew_with_client_id(); } @@ -637,6 +650,8 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( if (session_->RefreshKeys(signed_response.msg(), signed_response.signature(), key_array.size(), &key_array[0])) { + policy_engine_->UpdateLicense(license); + return KEY_ADDED; } else { return REFRESH_KEYS_ERROR; @@ -981,6 +996,11 @@ CdmResponseType CdmLicense::PrepareClientId( client_info->set_name(kWVCdmVersionKey); client_info->set_value(value); } + client_info = client_id->add_client_info(); + client_info->set_name(kOemCryptoSecurityPatchLevelKey); + std::stringstream ss; + ss << (uint32_t)session_->GetSecurityPatchLevel(); + client_info->set_value(ss.str()); ClientIdentification_ClientCapabilities* client_capabilities = client_id->mutable_client_capabilities(); diff --git a/core/src/license_key_status.cpp b/core/src/license_key_status.cpp new file mode 100644 index 00000000..d467687f --- /dev/null +++ b/core/src/license_key_status.cpp @@ -0,0 +1,285 @@ +// Copyright 2016 Google Inc. All Rights Reserved. + +#include "license_key_status.h" + +#include + +#include "log.h" + +namespace { +// License protocol aliases +typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; +typedef KeyContainer::OutputProtection OutputProtection; +typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint; +typedef ::google::protobuf::RepeatedPtrField + ConstraintList; + +// Map the HDCP protection associated with a key in the license to +// an equivalent OEMCrypto HDCP protection level +wvcdm::CryptoSession::HdcpCapability ProtobufHdcpToOemCryptoHdcp( + const OutputProtection::HDCP& input) { + switch (input) { + case OutputProtection::HDCP_NONE: + return HDCP_NONE; + case OutputProtection::HDCP_V1: + return HDCP_V1; + case OutputProtection::HDCP_V2: + return HDCP_V2; + case OutputProtection::HDCP_V2_1: + return HDCP_V2_1; + case OutputProtection::HDCP_V2_2: + return HDCP_V2_2; + case OutputProtection::HDCP_NO_DIGITAL_OUTPUT: + return HDCP_NO_DIGITAL_OUTPUT; + default: + LOGE("ContentKeyStatus::ProtobufHdcpToOemCryptoHdcp: " + "Unknown HDCP Level: input=%d, returning HDCP_NO_DIGITAL_OUTPUT", + input); + return HDCP_NO_DIGITAL_OUTPUT; + } +} + +// Returns the constraint from a set of constraints that matches the +// specified resolution, or null if none match +VideoResolutionConstraint* GetConstraintForRes( + uint32_t res, ConstraintList& constraints_from_key) { + typedef ConstraintList::pointer_iterator Iterator; + for (Iterator i = constraints_from_key.pointer_begin(); + i != constraints_from_key.pointer_end(); ++i) { + VideoResolutionConstraint* constraint = *i; + if (constraint->has_min_resolution_pixels() && + constraint->has_max_resolution_pixels() && + res >= constraint->min_resolution_pixels() && + res <= constraint->max_resolution_pixels()) { + return constraint; + } + } + return NULL; +} + +} // namespace + +namespace wvcdm { + +bool LicenseKeys::IsContentKey(const std::string& key_id) { + if (keys_.count(key_id) > 0) { + return keys_[key_id]->IsContentKey(); + } else { + return false; + } +} + +bool LicenseKeys::CanDecryptContent(const std::string& key_id) { + if (keys_.count(key_id) > 0) { + return keys_[key_id]->CanDecryptContent(); + } else { + return false; + } +} + +bool LicenseKeys::GetAllowedUsage(const KeyId& key_id, + CdmKeyAllowedUsage* allowed_usage) { + if (keys_.count(key_id) > 0) { + return keys_[key_id]->GetAllowedUsage(allowed_usage); + } else { + return false; + } +} + +bool LicenseKeys::ApplyStatusChange(CdmKeyStatus new_status, + bool* new_usable_keys) { + bool keys_changed = false; + bool newly_usable = false; + *new_usable_keys = false; + for (LicenseKeyStatusIterator it = keys_.begin(); it != keys_.end(); ++it) { + bool usable; + if (it->second->ApplyStatusChange(new_status, &usable)) { + newly_usable |= usable; + keys_changed = true; + } + } + *new_usable_keys = newly_usable; + return keys_changed; +} + +void LicenseKeys::ExtractKeyStatuses(CdmKeyStatusMap* content_keys) { + for (LicenseKeyStatusIterator it = keys_.begin(); it != keys_.end(); ++it) { + if (it->second->IsContentKey()) { + const KeyId key_id = it->first; + CdmKeyStatus key_status = it->second->GetKeyStatus(); + (*content_keys)[key_id] = key_status; + } + } +} + +bool LicenseKeys::MeetsConstraints(const KeyId& key_id) { + if (keys_.count(key_id) > 0) { + return keys_[key_id]->MeetsConstraints(); + } else { + // If a Key ID is unknown to us, we don't know of any constraints for it, + // so never block decryption. + return true; + } +} + +void LicenseKeys::ApplyConstraints( + uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level) { + for (LicenseKeyStatusIterator i = keys_.begin(); i != keys_.end(); ++i) { + i->second->ApplyConstraints(new_resolution, new_hdcp_level); + } +} + +void LicenseKeys::SetFromLicense( + const video_widevine_server::sdk::License& license) { + this->Clear(); + for (int32_t key_index = 0; key_index < license.key_size(); ++key_index) { + const KeyContainer& key = license.key(key_index); + if (key.has_id() && (key.type() == KeyContainer::CONTENT || + key.type() == KeyContainer::OPERATOR_SESSION)) { + const KeyId& key_id = key.id(); + keys_[key_id] = new LicenseKeyStatus(key); + } + } +} + +LicenseKeyStatus::LicenseKeyStatus(const KeyContainer& key) : + is_content_key_(false), + key_status_(kKeyStatusInternalError), + meets_constraints_(true), + default_hdcp_level_(HDCP_NONE) { + + allowed_usage_.Clear(); + constraints_.Clear(); + + if (key.type() == KeyContainer::CONTENT) { + ParseContentKey(key); + } else if (key.type() == KeyContainer::OPERATOR_SESSION) { + ParseOperatorSessionKey(key); + } +} + +void LicenseKeyStatus::ParseContentKey(const KeyContainer& key) { + is_content_key_ = true; + if (key.has_level() && + ((key.level() == KeyContainer::HW_SECURE_DECODE) || + (key.level() == KeyContainer::HW_SECURE_ALL))) { + allowed_usage_.decrypt_to_clear_buffer = false; + allowed_usage_.decrypt_to_secure_buffer = true; + } else { + allowed_usage_.decrypt_to_clear_buffer = true; + allowed_usage_.decrypt_to_secure_buffer = true; + } + allowed_usage_.SetValid(); + + if (key.video_resolution_constraints_size() > 0) { + SetConstraints(key.video_resolution_constraints()); + } + + if (key.has_required_protection()) { + default_hdcp_level_ = + ProtobufHdcpToOemCryptoHdcp(key.required_protection().hdcp()); + } +} + +void LicenseKeyStatus::ParseOperatorSessionKey(const KeyContainer& key) { + is_content_key_ = false; + if (key.has_operator_session_key_permissions()) { + OperatorSessionKeyPermissions permissions = + key.operator_session_key_permissions(); + if (permissions.has_allow_encrypt()) + allowed_usage_.generic_encrypt = permissions.allow_encrypt(); + if (permissions.has_allow_decrypt()) + allowed_usage_.generic_decrypt = permissions.allow_decrypt(); + if (permissions.has_allow_sign()) + allowed_usage_.generic_sign = permissions.allow_sign(); + if (permissions.has_allow_signature_verify()) + allowed_usage_.generic_verify = permissions.allow_signature_verify(); + } else { + allowed_usage_.generic_encrypt = false; + allowed_usage_.generic_decrypt = false; + allowed_usage_.generic_sign = false; + allowed_usage_.generic_verify = false; + } + allowed_usage_.SetValid(); +} + +void LicenseKeys::Clear() { + for (LicenseKeyStatusIterator i = keys_.begin(); i != keys_.end(); ++i) { + delete i->second; + } + keys_.clear(); +} + +bool LicenseKeyStatus::CanDecryptContent() { + return is_content_key_ && key_status_ == kKeyStatusUsable; +} + +bool LicenseKeyStatus::GetAllowedUsage(CdmKeyAllowedUsage* allowed_usage) { + if (NULL == allowed_usage) + return false; + *allowed_usage = allowed_usage_; + return true; +} + +bool LicenseKeyStatus::ApplyStatusChange(CdmKeyStatus new_status, + bool* new_usable_key) { + *new_usable_key = false; + if (!is_content_key_) { + return false; + } + CdmKeyStatus updated_status = new_status; + if (updated_status == kKeyStatusUsable) { + if (!MeetsConstraints()) { + updated_status = kKeyStatusOutputNotAllowed; + } + } + if (key_status_ != updated_status) { + key_status_ = updated_status; + if (updated_status == kKeyStatusUsable) { + *new_usable_key = true; + } + return true; + } + return false; +} + +// If the key has constraints, find the constraint that applies. +// If none found, then the constraint test fails. +// If a constraint is found, verify that the device's current HDCP +// level is sufficient. If the constraint has an HDCP setting, use it, +// If the key has no constraints, or if the constraint has no HDCP +// requirement, use the key's default HDCP setting to check against the +// device's current HDCP level. +void LicenseKeyStatus::ApplyConstraints( + uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level) { + + VideoResolutionConstraint* current_constraint = NULL; + if (HasConstraints()) { + current_constraint = GetConstraintForRes(new_resolution, constraints_); + if (NULL == current_constraint) { + meets_constraints_ = false; + return; + } + } + + CryptoSession::HdcpCapability desired_hdcp_level; + if (current_constraint && current_constraint->has_required_protection()) { + desired_hdcp_level = ProtobufHdcpToOemCryptoHdcp( + current_constraint->required_protection().hdcp()); + } else { + desired_hdcp_level = default_hdcp_level_; + } + + meets_constraints_ = (new_hdcp_level >= desired_hdcp_level); +} + +void LicenseKeyStatus::SetConstraints(const ConstraintList& constraints) { + if (!is_content_key_) { + return; + } + constraints_.Clear(); + constraints_.MergeFrom(constraints); + meets_constraints_ = true; +} + +} // namespace wvcdm diff --git a/core/src/license_protocol.proto b/core/src/license_protocol.proto index 9079594e..f411b6b6 100644 --- a/core/src/license_protocol.proto +++ b/core/src/license_protocol.proto @@ -196,6 +196,10 @@ message License { optional bool remote_attestation_verified = 5 [default = false]; // Client token generated by the content provider. Optional. optional bytes provider_client_token = 6; + // Protection scheme identifying the encryption algorithm. Represented as one + // of the following 4CC values: 'cenc' (AES-CTR), 'cbc1' (AES-CBC), + // 'cens' (AES-CTR subsample), 'cbcs' (AES-CBC subsample). + optional uint32 protection_scheme = 7; } enum ProtocolVersion { @@ -268,6 +272,28 @@ message LicenseError { optional Error error_code = 1; } +message MetricData { + enum MetricType { + // The time spent in the 'stage', specified in microseconds. + LATENCY = 1; + // The UNIX epoch timestamp at which the 'stage' was first accessed in + // microseconds. + TIMESTAMP = 2; + } + + message TypeValue { + optional MetricType type = 1; + // The value associated with 'type'. For example if type == LATENCY, the + // value would be the time in microseconds spent in this 'stage'. + optional int64 value = 2 [default = 0]; + } + + // 'stage' that is currently processing the SignedMessage. Required. + optional string stage_name = 1; + // metric and associated value. + repeated TypeValue metric_data = 2; +} + message RemoteAttestation { // Encrypted ClientIdentification message containing the device remote // attestation certificate. Required. @@ -296,6 +322,14 @@ message SignedMessage { // request for ChromeOS client devices operating in verified mode. Remote // attestation challenge data is |msg| field above. Optional. optional RemoteAttestation remote_attestation = 5; + repeated MetricData metric_data = 6; +} + +message GroupKeys { + repeated License.KeyContainer key = 1; + // Byte string that identifies the group to which this license material + // belongs. + optional bytes group_id = 2; } // ---------------------------------------------------------------------------- @@ -427,7 +461,7 @@ message EncryptedClientIdentification { optional bytes encrypted_client_id = 3; // Initialization vector needed to decrypt encrypted_client_id. optional bytes encrypted_client_id_iv = 4; - // AES-128 privacy key, encrytped with the service public public key using + // AES-128 privacy key, encrypted with the service public public key using // RSA-OAEP. optional bytes encrypted_privacy_key = 5; } @@ -547,3 +581,48 @@ message SignedCertificateStatusList { // key using RSASSA-PSS. Required. optional bytes signature = 2; } + +// ---------------------------------------------------------------------------- +// widevine_header.proto +// ---------------------------------------------------------------------------- +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Description: +// Public protocol buffer definitions for Widevine Cenc Header +// protocol. +message WidevineCencHeader { + enum Algorithm { + UNENCRYPTED = 0; + AESCTR = 1; + }; + // Replaced with protection_scheme. + optional Algorithm algorithm = 1 [deprecated=true]; + repeated bytes key_id = 2; + + // Content provider name. + optional string provider = 3; + + // A content identifier, specified by content provider. + optional bytes content_id = 4; + + // Track type. Acceptable values are SD, HD and AUDIO. Used to differentiate + // content keys used by an asset. + // No longer adding track_type to the PSSH since the Widevine license server + // will return keys for all allowed track types in a single license. + optional string track_type_deprecated = 5; + + // The name of a registered policy to be used for this asset. + optional string policy = 6 [deprecated=true]; + + // Crypto period index, for media using key rotation. + optional uint32 crypto_period_index = 7; + + // Optional protected context for group content. The grouped_license is a + // serialized SignedMessage. + optional bytes grouped_license = 8; + + // Protection scheme identifying the encryption algorithm. Represented as one + // of the following 4CC values: 'cenc' (AES-CTR), 'cbc1' (AES-CBC), + // 'cens' (AES-CTR subsample), 'cbcs' (AES-CBC subsample). + optional uint32 protection_scheme = 9; +} diff --git a/core/src/max_res_engine.cpp b/core/src/max_res_engine.cpp deleted file mode 100644 index fbc28a81..00000000 --- a/core/src/max_res_engine.cpp +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright 2014 Google Inc. All Rights Reserved. - -#include "max_res_engine.h" - -#include "clock.h" -#include "log.h" - -namespace { - -const int64_t kHdcpCheckInterval = 10; -const uint32_t kNoResolution = 0; - -} // namespace - -namespace wvcdm { - -MaxResEngine::MaxResEngine(CryptoSession* crypto_session) { - Init(crypto_session, new Clock()); -} - -MaxResEngine::MaxResEngine(CryptoSession* crypto_session, Clock* clock) { - Init(crypto_session, clock); -} - -MaxResEngine::~MaxResEngine() { - AutoLock lock(status_lock_); - DeleteAllKeys(); -} - -bool MaxResEngine::CanDecrypt(const KeyId& key_id) { - AutoLock lock(status_lock_); - if (keys_.count(key_id) > 0) { - return keys_[key_id]->can_decrypt(); - } else { - // If a Key ID is unknown to us, we don't know of any constraints for it, - // so never block decryption. - return true; - } -} - -void MaxResEngine::Init(CryptoSession* crypto_session, Clock* clock) { - AutoLock lock(status_lock_); - current_resolution_ = kNoResolution; - clock_.reset(clock); - next_check_time_ = clock_->GetCurrentTime(); - crypto_session_ = crypto_session; -} - -void MaxResEngine::SetLicense( - const video_widevine_server::sdk::License& license) { - AutoLock lock(status_lock_); - DeleteAllKeys(); - for (int32_t key_index = 0; key_index < license.key_size(); ++key_index) { - const KeyContainer& key = license.key(key_index); - if (key.type() == KeyContainer::CONTENT && key.has_id() && - key.video_resolution_constraints_size() > 0) { - const ConstraintList& constraints = key.video_resolution_constraints(); - const KeyId& key_id = key.id(); - if (key.has_required_protection()) { - keys_[key_id] = - new KeyStatus(constraints, key.required_protection().hdcp()); - } else { - keys_[key_id] = new KeyStatus(constraints); - } - } - } -} - -void MaxResEngine::SetResolution(uint32_t width, uint32_t height) { - AutoLock lock(status_lock_); - current_resolution_ = width * height; -} - -void MaxResEngine::OnTimerEvent() { - AutoLock lock(status_lock_); - int64_t current_time = clock_->GetCurrentTime(); - if (!keys_.empty() && current_resolution_ != kNoResolution && - current_time >= next_check_time_) { - CryptoSession::HdcpCapability current_hdcp_level; - CryptoSession::HdcpCapability ignored; - if (!crypto_session_->GetHdcpCapabilities(¤t_hdcp_level, &ignored)) { - current_hdcp_level = HDCP_NONE; - } - for (KeyIterator i = keys_.begin(); i != keys_.end(); ++i) { - i->second->Update(current_resolution_, current_hdcp_level); - } - next_check_time_ = current_time + kHdcpCheckInterval; - } -} - -void MaxResEngine::DeleteAllKeys() { - // This helper method assumes that status_lock_ is already held. - for (KeyIterator i = keys_.begin(); i != keys_.end(); ++i) delete i->second; - keys_.clear(); -} - -MaxResEngine::KeyStatus::KeyStatus(const ConstraintList& constraints) - : default_hdcp_level_(HDCP_NONE) { - Init(constraints); -} - -MaxResEngine::KeyStatus::KeyStatus( - const ConstraintList& constraints, - const OutputProtection::HDCP& default_hdcp_level) - : default_hdcp_level_(ProtobufHdcpToOemCryptoHdcp(default_hdcp_level)) { - Init(constraints); -} - -void MaxResEngine::KeyStatus::Init(const ConstraintList& constraints) { - constraints_.Clear(); - constraints_.MergeFrom(constraints); - can_decrypt_ = true; -} - -void MaxResEngine::KeyStatus::Update( - uint32_t res, CryptoSession::HdcpCapability current_hdcp_level) { - VideoResolutionConstraint* current_constraint = GetConstraintForRes(res); - - if (current_constraint == NULL) { - can_decrypt_ = false; - return; - } - - CryptoSession::HdcpCapability desired_hdcp_level; - if (current_constraint->has_required_protection()) { - desired_hdcp_level = ProtobufHdcpToOemCryptoHdcp( - current_constraint->required_protection().hdcp()); - } else { - desired_hdcp_level = default_hdcp_level_; - } - can_decrypt_ = (current_hdcp_level >= desired_hdcp_level); -} - -MaxResEngine::VideoResolutionConstraint* -MaxResEngine::KeyStatus::GetConstraintForRes(uint32_t res) { - typedef ConstraintList::pointer_iterator Iterator; - for (Iterator i = constraints_.pointer_begin(); - i != constraints_.pointer_end(); ++i) { - VideoResolutionConstraint* constraint = *i; - if (constraint->has_min_resolution_pixels() && - constraint->has_max_resolution_pixels() && - res >= constraint->min_resolution_pixels() && - res <= constraint->max_resolution_pixels()) { - return constraint; - } - } - return NULL; -} - -CryptoSession::HdcpCapability -MaxResEngine::KeyStatus::ProtobufHdcpToOemCryptoHdcp( - const OutputProtection::HDCP& input) { - switch (input) { - case OutputProtection::HDCP_NONE: - return HDCP_NONE; - case OutputProtection::HDCP_V1: - return HDCP_V1; - case OutputProtection::HDCP_V2: - return HDCP_V2; - case OutputProtection::HDCP_V2_1: - return HDCP_V2_1; - case OutputProtection::HDCP_V2_2: - return HDCP_V2_2; - case OutputProtection::HDCP_NO_DIGITAL_OUTPUT: - return HDCP_NO_DIGITAL_OUTPUT; - default: - LOGE("MaxResEngine::KeyStatus::ProtobufHdcpToOemCryptoHdcp: " - "Unknown HDCP Level"); - return HDCP_NO_DIGITAL_OUTPUT; - } -} - -} // wvcdm diff --git a/core/src/oemcrypto_adapter_static.cpp b/core/src/oemcrypto_adapter_static.cpp index f26ae6c6..eba92969 100644 --- a/core/src/oemcrypto_adapter_static.cpp +++ b/core/src/oemcrypto_adapter_static.cpp @@ -38,6 +38,10 @@ uint32_t OEMCrypto_APIVersion(SecurityLevel level) { return ::OEMCrypto_APIVersion(); } +uint8_t OEMCrypto_Security_Patch_Level(SecurityLevel level) { + return ::OEMCrypto_Security_Patch_Level(); +} + const char* OEMCrypto_SecurityLevel(SecurityLevel level) { return ::OEMCrypto_SecurityLevel(); } diff --git a/core/src/oemcrypto_adapter_static_v11.cpp b/core/src/oemcrypto_adapter_static_v11.cpp new file mode 100644 index 00000000..9cad8a1e --- /dev/null +++ b/core/src/oemcrypto_adapter_static_v11.cpp @@ -0,0 +1,67 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Wrapper of OEMCrypto APIs for platforms that support Level 1 only. +// This should be used when liboemcrypto.so is linked with the CDM code at +// compile time. +// +// Defines APIs introduced in newer version (v11) which is not available in v10 +// to allow an older oemcrypto implementation to be linked with CDM. + +#include "OEMCryptoCENC.h" + +extern "C" OEMCryptoResult OEMCrypto_DecryptCTR_V10( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + bool is_encrypted, const uint8_t* iv, size_t block_offset, + const OEMCrypto_DestBufferDesc* out_buffer, uint8_t subsample_flags); + +typedef struct { + const uint8_t* key_id; + size_t key_id_length; + const uint8_t* key_data_iv; + const uint8_t* key_data; + size_t key_data_length; + const uint8_t* key_control_iv; + const uint8_t* key_control; +} OEMCrypto_KeyObject_V9; + +extern "C" OEMCryptoResult OEMCrypto_LoadKeys_V9_or_V10( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + const uint8_t* enc_mac_keys_iv, const uint8_t* enc_mac_keys, + size_t num_keys, const OEMCrypto_KeyObject_V9* key_array, + const uint8_t* pst, size_t pst_length); + +extern "C" uint8_t OEMCrypto_Security_Patch_Level() { return 0; } + +extern "C" OEMCryptoResult OEMCrypto_DecryptCENC( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + bool is_encrypted, const uint8_t* iv, size_t block_offset, + OEMCrypto_DestBufferDesc* out_buffer, + const OEMCrypto_CENCEncryptPatternDesc* pattern, uint8_t subsample_flags) { + return OEMCrypto_DecryptCTR_V10(session, data_addr, data_length, is_encrypted, + iv, block_offset, out_buffer, + subsample_flags); +} + +extern "C" OEMCryptoResult OEMCrypto_LoadKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + const uint8_t* enc_mac_keys_iv, const uint8_t* enc_mac_keys, + size_t num_keys, const OEMCrypto_KeyObject* key_array, const uint8_t* pst, + size_t pst_length) { + OEMCrypto_KeyObject_V9 key_array_v9[num_keys]; + for (size_t key_index = 0; key_index < num_keys; key_index++) { + key_array_v9[key_index].key_id = key_array[key_index].key_id; + key_array_v9[key_index].key_id_length = key_array[key_index].key_id_length; + key_array_v9[key_index].key_data_iv = key_array[key_index].key_data_iv; + key_array_v9[key_index].key_data = key_array[key_index].key_data; + key_array_v9[key_index].key_data_length = + key_array[key_index].key_data_length; + key_array_v9[key_index].key_control_iv = + key_array[key_index].key_control_iv; + key_array_v9[key_index].key_control = key_array[key_index].key_control; + } + return OEMCrypto_LoadKeys_V9_or_V10( + session, message, message_length, signature, signature_length, + enc_mac_keys_iv, enc_mac_keys, num_keys, key_array_v9, pst, pst_length); +} diff --git a/core/src/policy_engine.cpp b/core/src/policy_engine.cpp index 98a803a3..6d885a21 100644 --- a/core/src/policy_engine.cpp +++ b/core/src/policy_engine.cpp @@ -3,10 +3,7 @@ #include "policy_engine.h" #include - #include -#include -#include #include "clock.h" #include "log.h" @@ -17,6 +14,13 @@ using video_widevine_server::sdk::License; +namespace { + +const int64_t kHdcpCheckInterval = 10; +const uint32_t kNoResolution = 0; + +} // namespace + namespace wvcdm { PolicyEngine::PolicyEngine(CdmSessionId session_id, @@ -32,18 +36,43 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id, policy_max_duration_seconds_(0), session_id_(session_id), event_listener_(event_listener), - max_res_engine_(new MaxResEngine(crypto_session)), - clock_(new Clock) {} + license_keys_(new LicenseKeys), + clock_(new Clock) { + InitDevice(crypto_session); +} PolicyEngine::~PolicyEngine() {} bool PolicyEngine::CanDecrypt(const KeyId& key_id) { - if (keys_status_.find(key_id) == keys_status_.end()) { + if (license_keys_->IsContentKey(key_id)) { + return license_keys_->CanDecryptContent(key_id); + } else { LOGE("PolicyEngine::CanDecrypt Key '%s' not in license.", b2a_hex(key_id).c_str()); return false; } - return keys_status_[key_id] == kKeyStatusUsable; +} + +void PolicyEngine::InitDevice(CryptoSession* crypto_session) { + current_resolution_ = kNoResolution; + next_device_check_ = 0; + crypto_session_ = crypto_session; +} + +void PolicyEngine::CheckDevice(int64_t current_time) { + if (current_time < next_device_check_) { + return; + } + + if (!license_keys_->Empty() && current_resolution_ != kNoResolution) { + CryptoSession::HdcpCapability current_hdcp_level; + CryptoSession::HdcpCapability ignored; + if (!crypto_session_->GetHdcpCapabilities(¤t_hdcp_level, &ignored)) { + current_hdcp_level = HDCP_NONE; + } + license_keys_->ApplyConstraints(current_resolution_, current_hdcp_level); + next_device_check_ = current_time + kHdcpCheckInterval; + } } void PolicyEngine::OnTimerEvent() { @@ -57,7 +86,8 @@ void PolicyEngine::OnTimerEvent() { return; } - max_res_engine_->OnTimerEvent(); + // Check device conditions that affect playability (HDCP, resolution) + CheckDevice(current_time); bool renewal_needed = false; @@ -110,17 +140,8 @@ void PolicyEngine::SetLicense(const License& license) { license_id_.Clear(); license_id_.CopyFrom(license.id()); policy_.Clear(); - - // Extract content key ids. - keys_status_.clear(); - for (int key_index = 0; key_index < license.key_size(); ++key_index) { - const License::KeyContainer& key = license.key(key_index); - if (key.type() == License::KeyContainer::CONTENT && key.has_id()) - keys_status_[key.id()] = kKeyStatusInternalError; - } - + license_keys_->SetFromLicense(license); UpdateLicense(license); - max_res_engine_->SetLicense(license); } void PolicyEngine::SetLicenseForRelease(const License& license) { @@ -130,7 +151,6 @@ void PolicyEngine::SetLicenseForRelease(const License& license) { // Expire any old keys. NotifyKeysChange(kKeyStatusExpired); - UpdateLicense(license); } @@ -220,7 +240,7 @@ void PolicyEngine::DecryptionEvent() { } void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) { - max_res_engine_->SetResolution(width, height); + SetDeviceResolution(width, height); } void PolicyEngine::NotifySessionExpiration() { @@ -228,35 +248,46 @@ void PolicyEngine::NotifySessionExpiration() { NotifyKeysChange(kKeyStatusExpired); } -CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) { +CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) { std::stringstream ss; int64_t current_time = clock_->GetCurrentTime(); if (license_state_ == kLicenseStateInitial) { - key_info->clear(); + query_response->clear(); return NO_ERROR; } - (*key_info)[QUERY_KEY_LICENSE_TYPE] = + (*query_response)[QUERY_KEY_LICENSE_TYPE] = license_id_.type() == video_widevine_server::sdk::STREAMING ? QUERY_VALUE_STREAMING : QUERY_VALUE_OFFLINE; - (*key_info)[QUERY_KEY_PLAY_ALLOWED] = + (*query_response)[QUERY_KEY_PLAY_ALLOWED] = policy_.can_play() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; - (*key_info)[QUERY_KEY_PERSIST_ALLOWED] = + (*query_response)[QUERY_KEY_PERSIST_ALLOWED] = policy_.can_persist() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; - (*key_info)[QUERY_KEY_RENEW_ALLOWED] = + (*query_response)[QUERY_KEY_RENEW_ALLOWED] = policy_.can_renew() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; ss << GetLicenseDurationRemaining(current_time); - (*key_info)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str(); + (*query_response)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str(); ss.str(""); ss << GetPlaybackDurationRemaining(current_time); - (*key_info)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str(); - (*key_info)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url(); + (*query_response)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str(); + (*query_response)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url(); return NO_ERROR; } +CdmResponseType PolicyEngine::QueryKeyAllowedUsage( + const KeyId& key_id, CdmKeyAllowedUsage* key_usage) { + if (NULL == key_usage) { + return INVALID_PARAMETERS_ENG_12; + } + if (license_keys_->GetAllowedUsage(key_id, key_usage)) { + return NO_ERROR; + } + return KEY_NOT_FOUND_1; +} + bool PolicyEngine::GetSecondsSinceStarted(int64_t* seconds_since_started) { if (playback_start_time_ == 0) return false; @@ -272,6 +303,12 @@ bool PolicyEngine::GetSecondsSinceLastPlayed( return (*seconds_since_last_played >= 0) ? true : false; } +int64_t PolicyEngine::GetLicenseOrPlaybackDurationRemaining() { + int64_t current_time = clock_->GetCurrentTime(); + return std::min(GetLicenseDurationRemaining(current_time), + GetPlaybackDurationRemaining(current_time)); +} + void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time, int64_t last_playback_time) { playback_start_time_ = (playback_start_time > 0) ? playback_start_time : 0; @@ -345,25 +382,14 @@ bool PolicyEngine::IsRenewalRetryIntervalExpired(int64_t current_time) { } void PolicyEngine::NotifyKeysChange(CdmKeyStatus new_status) { - bool keys_changed = false; + bool keys_changed; bool has_new_usable_key = false; - for (std::map::iterator it = keys_status_.begin(); - it != keys_status_.end(); ++it) { - const KeyId key_id = it->first; - CdmKeyStatus& key_status = it->second; - CdmKeyStatus updated_status = new_status; - if (updated_status == kKeyStatusUsable) { - if (!max_res_engine_->CanDecrypt(key_id)) - updated_status = kKeyStatusOutputNotAllowed; - } - if (key_status != updated_status) { - key_status = updated_status; - if (updated_status == kKeyStatusUsable) has_new_usable_key = true; - keys_changed = true; - } - } - if (keys_changed && event_listener_) { - event_listener_->OnSessionKeysChange(session_id_, keys_status_, + keys_changed = license_keys_->ApplyStatusChange(new_status, + &has_new_usable_key); + if (event_listener_ && keys_changed) { + CdmKeyStatusMap content_keys; + license_keys_->ExtractKeyStatuses(&content_keys); + event_listener_->OnSessionKeysChange(session_id_, content_keys, has_new_usable_key); } } @@ -381,8 +407,4 @@ void PolicyEngine::NotifyExpirationUpdate() { void PolicyEngine::set_clock(Clock* clock) { clock_.reset(clock); } -void PolicyEngine::set_max_res_engine(MaxResEngine* max_res_engine) { - max_res_engine_.reset(max_res_engine); -} - -} // wvcdm +} // namespace wvcdm diff --git a/core/src/string_conversions.cpp b/core/src/string_conversions.cpp index 39541ff6..4b825d2a 100644 --- a/core/src/string_conversions.cpp +++ b/core/src/string_conversions.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include "log.h" @@ -78,6 +79,26 @@ std::string b2a_hex(const std::string& byte) { byte.length()); } +// Encode for standard base64 encoding (RFC4648). +std::string Base64Encode(const std::vector& bin_input) { + if (bin_input.empty()) { + return std::string(); + } + + int in_size = bin_input.size(); + std::string b64_output(modp_b64_encode_len(in_size), 0); + + int out_size = modp_b64_encode( + &b64_output[0], reinterpret_cast(&bin_input[0]), in_size); + if (out_size == -1) { + LOGE("Base64Encode failed"); + return std::string(); + } + + b64_output.resize(out_size); + return b64_output; +} + // Filename-friendly base64 encoding (RFC4648), commonly referred to // as Base64WebSafeEncode. // @@ -111,6 +132,25 @@ std::string Base64SafeEncodeNoPad(const std::vector& bin_input) { return b64_output; } +// Decode for standard base64 encoding (RFC4648). +std::vector Base64Decode(const std::string& b64_input) { + if (b64_input.empty()) { + return std::vector(); + } + + int in_size = b64_input.size(); + std::vector bin_output(modp_b64_decode_len(in_size), 0); + int out_size = modp_b64_decode(reinterpret_cast(&bin_output[0]), + b64_input.data(), in_size); + if (out_size == -1) { + LOGE("Base64Decode failed"); + return std::vector(0); + } + + bin_output.resize(out_size); + return bin_output; +} + // Decode for Filename-friendly base64 encoding (RFC4648), commonly referred // as Base64WebSafeDecode. std::vector Base64SafeDecode(const std::string& b64_input) { @@ -157,18 +197,6 @@ std::string IntToString(int value) { return out_string; } -std::string UintToString(unsigned int value) { - // log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4. - // So round up to allocate 3 output characters per byte. - const int kOutputBufSize = 3 * sizeof(unsigned int); - char buffer[kOutputBufSize]; - memset(buffer, 0, kOutputBufSize); - snprintf(buffer, kOutputBufSize, "%u", value); - - std::string out_string(buffer); - return out_string; -} - int64_t htonll64(int64_t x) { // Convert to big endian (network-byte-order) union { uint32_t array[2]; diff --git a/core/test/base64_test.cpp b/core/test/base64_test.cpp index fed4e3b7..44cefac4 100644 --- a/core/test/base64_test.cpp +++ b/core/test/base64_test.cpp @@ -38,34 +38,55 @@ const std::string kTestData = const std::string kMultipleOf24BitsB64Data("R29vZCBkYXkh"); const std::string kOneByteOverB64Data("SGVsbG8gRnJpZW5kIQ=="); const std::string kTwoBytesOverB64Data("SGVsbG8gRnJpZW5kISE="); -const std::string kB64TestData = "GPFc9rc-INmI8FwtyTrUrv6xnKHWZNZ_5uaT21nFjNg="; +const std::string kB64TestData = "GPFc9rc+INmI8FwtyTrUrv6xnKHWZNZ/5uaT21nFjNg="; -const std::pair kBase64TestVectors[] = { - make_pair(&kNullString, &kNullString), - make_pair(&kf, &kfB64), - make_pair(&kfo, &kfoB64), - make_pair(&kfoo, &kfooB64), - make_pair(&kfoob, &kfoobB64), - make_pair(&kfooba, &kfoobaB64), - make_pair(&kfoobar, &kfoobarB64), - make_pair(&kMultipleOf24BitsData, &kMultipleOf24BitsB64Data), - make_pair(&kOneByteOverData, &kOneByteOverB64Data), - make_pair(&kTwoBytesOverData, &kTwoBytesOverB64Data), - make_pair(&kTestData, &kB64TestData)}; +const std::pair kBase64TestVectors[] = + {make_pair(&kNullString, &kNullString), + make_pair(&kf, &kfB64), + make_pair(&kfo, &kfoB64), + make_pair(&kfoo, &kfooB64), + make_pair(&kfoob, &kfoobB64), + make_pair(&kfooba, &kfoobaB64), + make_pair(&kfoobar, &kfoobarB64), + make_pair(&kMultipleOf24BitsData, &kMultipleOf24BitsB64Data), + make_pair(&kOneByteOverData, &kOneByteOverB64Data), + make_pair(&kTwoBytesOverData, &kTwoBytesOverB64Data), + make_pair(&kTestData, &kB64TestData)}; + +std::string ConvertToBase64WebSafe(const std::string &std_base64_string) { + std::string str(std_base64_string); + for (size_t i = 0; i < str.size(); ++i) { + if (str[i] == '+') + str[i] = '-'; + else if (str[i] == '/') + str[i] = '_'; + } + return str; +} } // namespace class Base64EncodeDecodeTest : public ::testing::TestWithParam< - std::pair > {}; + std::pair > {}; TEST_P(Base64EncodeDecodeTest, EncodeDecodeTest) { std::pair values = GetParam(); - std::vector decoded_vector = Base64SafeDecode(values.second->data()); + std::vector decoded_vector = Base64Decode(values.second->data()); + std::string decoded_string(decoded_vector.begin(), decoded_vector.end()); + EXPECT_STREQ(values.first->data(), decoded_string.data()); + std::string b64_string = Base64Encode(decoded_vector); + EXPECT_STREQ(values.second->data(), b64_string.data()); +} + +TEST_P(Base64EncodeDecodeTest, WebSafeEncodeDecodeTest) { + std::pair values = GetParam(); + std::string encoded_string = ConvertToBase64WebSafe(*(values.second)); + std::vector decoded_vector = Base64SafeDecode(encoded_string); std::string decoded_string(decoded_vector.begin(), decoded_vector.end()); EXPECT_STREQ(values.first->data(), decoded_string.data()); std::string b64_string = Base64SafeEncode(decoded_vector); - EXPECT_STREQ(values.second->data(), b64_string.data()); + EXPECT_STREQ(encoded_string.data(), b64_string.data()); } INSTANTIATE_TEST_CASE_P(ExecutesBase64Test, Base64EncodeDecodeTest, diff --git a/core/test/cdm_engine_test.cpp b/core/test/cdm_engine_test.cpp index 84e6e71b..aa44c262 100644 --- a/core/test/cdm_engine_test.cpp +++ b/core/test/cdm_engine_test.cpp @@ -4,6 +4,7 @@ // This is because we need a valid RSA certificate, and will attempt to connect // to the provisioning server to request one if we don't. +#include #include #include @@ -43,6 +44,8 @@ const std::string kWebmMimeType = "video/webm"; class WvCdmEngineTest : public testing::Test { public: + WvCdmEngineTest() : cdm_engine_(&file_system_) {} + static void SetUpTestCase() { ConfigTestEnv config(kContentProtectionUatServer); g_client_auth.assign(config.client_auth()); @@ -52,20 +55,16 @@ class WvCdmEngineTest : public testing::Test { g_key_id_pssh.assign(a2bs_hex(config.key_id())); // Extract the key ID from the PSSH box. - InitializationData extractor(CENC_INIT_DATA_FORMAT, - g_key_id_pssh); + InitializationData extractor(CENC_INIT_DATA_FORMAT, g_key_id_pssh); g_key_id_unwrapped = extractor.data(); } virtual void SetUp() { CdmResponseType status = - cdm_engine_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL, - NULL /* forced_session_id */, &session_id_); + cdm_engine_.OpenSession(g_key_system, NULL, NULL, &session_id_); if (status == NEED_PROVISIONING) { Provision(); - status = cdm_engine_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL, - NULL /* forced_session_id */, - &session_id_); + status = cdm_engine_.OpenSession(g_key_system, NULL, NULL, &session_id_); } ASSERT_EQ(NO_ERROR, status); ASSERT_NE("", session_id_) << "Could not open CDM session."; @@ -82,37 +81,43 @@ class WvCdmEngineTest : public testing::Test { std::string cert_authority; std::string cert, wrapped_key; ASSERT_EQ(NO_ERROR, cdm_engine_.GetProvisioningRequest( - cert_type, cert_authority, EMPTY_ORIGIN, - &prov_request, &provisioning_server_url)); + cert_type, cert_authority, &prov_request, + &provisioning_server_url)); UrlRequest url_request(provisioning_server_url); + EXPECT_TRUE(url_request.is_connected()); url_request.PostCertRequestInQueryString(prov_request); std::string message; bool ok = url_request.GetResponse(&message); EXPECT_TRUE(ok); - ASSERT_EQ(NO_ERROR, cdm_engine_.HandleProvisioningResponse(EMPTY_ORIGIN, - message, &cert, + ASSERT_EQ(NO_ERROR, cdm_engine_.HandleProvisioningResponse(message, &cert, &wrapped_key)); } void GenerateKeyRequest(const std::string& key_id, const std::string& init_data_type_string) { CdmAppParameterMap app_parameters; - std::string server_url; CdmKeySetId key_set_id; InitializationData init_data(init_data_type_string, key_id); - CdmKeyRequestType key_request_type; + CdmKeyRequest key_request; + EXPECT_EQ(KEY_MESSAGE, cdm_engine_.GenerateKeyRequest( session_id_, key_set_id, init_data, - kLicenseTypeStreaming, app_parameters, &key_msg_, - &key_request_type, &server_url, NULL)); - EXPECT_EQ(kKeyRequestTypeInitial, key_request_type); + kLicenseTypeStreaming, app_parameters, + &key_request)); + + key_msg_ = key_request.message; + EXPECT_EQ(kKeyRequestTypeInitial, key_request.type); } void GenerateRenewalRequest() { - EXPECT_EQ(KEY_MESSAGE, cdm_engine_.GenerateRenewalRequest( - session_id_, &key_msg_, &server_url_)); + CdmKeyRequest request; + EXPECT_EQ(KEY_MESSAGE, + cdm_engine_.GenerateRenewalRequest(session_id_, &request)); + + key_msg_ = request.message; + server_url_ = request.url; } std::string GetKeyRequestResponse(const std::string& server_url, @@ -171,6 +176,7 @@ class WvCdmEngineTest : public testing::Test { EXPECT_EQ(KEY_ADDED, cdm_engine_.RenewKey(session_id_, resp)); } + FileSystem file_system_; CdmEngine cdm_engine_; std::string key_msg_; std::string session_id_; diff --git a/core/test/cdm_session_unittest.cpp b/core/test/cdm_session_unittest.cpp index 682be2a4..0a7390b9 100644 --- a/core/test/cdm_session_unittest.cpp +++ b/core/test/cdm_session_unittest.cpp @@ -88,13 +88,12 @@ const std::string kWrappedKey = a2bs_hex( "E74C92B44F9205D22262FB47948654229DE1920F8EDF96A19A88A1CA1552F8856FB4CBF83B" "AA3348419159D207F65FCE9C1A500C6818"); -const std::string kTestOrigin = "com.google"; - class MockDeviceFiles : public DeviceFiles { public: + MockDeviceFiles() : DeviceFiles(NULL) {} + MOCK_METHOD1(Init, bool(CdmSecurityLevel)); - MOCK_METHOD3(RetrieveCertificate, bool(const std::string&, std::string*, - std::string*)); + MOCK_METHOD2(RetrieveCertificate, bool(std::string*, std::string*)); }; class MockCryptoSession : public CryptoSession { @@ -136,7 +135,7 @@ using ::testing::StrEq; class CdmSessionTest : public ::testing::Test { protected: virtual void SetUp() { - cdm_session_.reset(new CdmSession(NULL, kTestOrigin, NULL, NULL)); + cdm_session_.reset(new CdmSession(NULL)); // Inject testing mocks. license_parser_ = new MockCdmLicense(cdm_session_->session_id()); cdm_session_->set_license_parser(license_parser_); @@ -165,9 +164,8 @@ TEST_F(CdmSessionTest, InitWithCertificate) { .InSequence(crypto_session_seq) .WillOnce(Return(level)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); - EXPECT_CALL(*file_handle_, RetrieveCertificate(StrEq(kTestOrigin), NotNull(), - NotNull())) - .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), + EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey), Return(true))); EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) .InSequence(crypto_session_seq) @@ -178,7 +176,7 @@ TEST_F(CdmSessionTest, InitWithCertificate) { Properties::set_use_certificates_as_identification(true); - ASSERT_EQ(NO_ERROR, cdm_session_->Init()); + ASSERT_EQ(NO_ERROR, cdm_session_->Init(NULL)); } TEST_F(CdmSessionTest, InitWithKeybox) { @@ -193,13 +191,14 @@ TEST_F(CdmSessionTest, InitWithKeybox) { EXPECT_CALL(*crypto_session_, GetToken(NotNull())) .InSequence(crypto_session_seq) .WillOnce(DoAll(SetArgPointee<0>(kToken), Return(true))); + EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); EXPECT_CALL(*license_parser_, Init(Eq(kToken), Eq(crypto_session_), Eq(policy_engine_))) .WillOnce(Return(true)); Properties::set_use_certificates_as_identification(false); - ASSERT_EQ(NO_ERROR, cdm_session_->Init()); + ASSERT_EQ(NO_ERROR, cdm_session_->Init(NULL)); } TEST_F(CdmSessionTest, ReInitFail) { @@ -212,9 +211,8 @@ TEST_F(CdmSessionTest, ReInitFail) { .InSequence(crypto_session_seq) .WillOnce(Return(level)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); - EXPECT_CALL(*file_handle_, RetrieveCertificate(StrEq(kTestOrigin), NotNull(), - NotNull())) - .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), + EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey), Return(true))); EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) .InSequence(crypto_session_seq) @@ -225,8 +223,8 @@ TEST_F(CdmSessionTest, ReInitFail) { Properties::set_use_certificates_as_identification(true); - ASSERT_EQ(NO_ERROR, cdm_session_->Init()); - ASSERT_NE(NO_ERROR, cdm_session_->Init()); + ASSERT_EQ(NO_ERROR, cdm_session_->Init(NULL)); + ASSERT_NE(NO_ERROR, cdm_session_->Init(NULL)); } TEST_F(CdmSessionTest, InitFailCryptoError) { @@ -235,7 +233,7 @@ TEST_F(CdmSessionTest, InitFailCryptoError) { Properties::set_use_certificates_as_identification(true); - ASSERT_EQ(UNKNOWN_ERROR, cdm_session_->Init()); + ASSERT_EQ(UNKNOWN_ERROR, cdm_session_->Init(NULL)); } TEST_F(CdmSessionTest, InitNeedsProvisioning) { @@ -248,13 +246,12 @@ TEST_F(CdmSessionTest, InitNeedsProvisioning) { .InSequence(crypto_session_seq) .WillOnce(Return(level)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); - EXPECT_CALL(*file_handle_, RetrieveCertificate(StrEq(kTestOrigin), NotNull(), - NotNull())) + EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull())) .WillOnce(Return(false)); Properties::set_use_certificates_as_identification(true); - ASSERT_EQ(NEED_PROVISIONING, cdm_session_->Init()); + ASSERT_EQ(NEED_PROVISIONING, cdm_session_->Init(NULL)); } } // namespace wvcdm diff --git a/core/test/device_files_unittest.cpp b/core/test/device_files_unittest.cpp index d246e13f..dacf0882 100644 --- a/core/test/device_files_unittest.cpp +++ b/core/test/device_files_unittest.cpp @@ -1408,23 +1408,49 @@ UsageInfo kUsageInfoTestData[] = { "0E12202FF1FBA9926A24A1F79970EC427DDF87B4421488F7952499BC33CEB282D9E48" "A")}}; -const std::string kTestOrigin = "com.google"; +struct HlsAttributesInfo { + std::string key_set_id; + CdmHlsMethod method; + std::string media_segment_iv; + std::string file_data; +}; + +HlsAttributesInfo kHlsAttributesTestData[] = { + { + "ksidC8EAA2579A282EB0", kHlsMethodAes128, // hls attributes 0 + a2bs_hex("F7C4D15BD466BF285E241A4E58638543"), + a2bs_hex("0A1A08041001321408011210F7C4D15BD466BF285E241A4E5863854312201" + "39114B0372FF80FADF92614106E27BE8BD1588B4CAE6E1AEFB7F9C34EA52E" + "CC"), + }, + { + "ksidE8C37662C88DC673", kHlsMethodSampleAes, // hls attributes 1 + a2bs_hex("16413F038088438B5D4CD99F03EBB3D8"), + a2bs_hex("0A1A0804100132140802121016413F038088438B5D4CD99F03EBB3D812205" + "9EA13188B75C55D1EB78B3A65DB3EA3F43BD1B16642266D988E3543943C5F" + "41"), + }}; class MockFile : public File { public: - MOCK_METHOD2(Open, bool(const std::string&, int flags)); - MOCK_METHOD0(Close, void()); + MockFile() : File(NULL) {} + ~MockFile() {} MOCK_METHOD2(Read, ssize_t(char*, size_t)); MOCK_METHOD2(Write, ssize_t(const char*, size_t)); + MOCK_METHOD0(Close, void()); +}; + +class MockFileSystem : public FileSystem { + public: + MockFileSystem() {} + ~MockFileSystem() {} + + MOCK_METHOD2(Open, File*(const std::string&, int flags)); + MOCK_METHOD0(IsFactoryReset, bool()); MOCK_METHOD1(Exists, bool(const std::string&)); MOCK_METHOD1(Remove, bool(const std::string&)); - MOCK_METHOD2(Copy, bool(const std::string&, const std::string&)); - MOCK_METHOD2(List, bool(const std::string&, std::vector*)); - MOCK_METHOD1(CreateDirectory, bool(const std::string)); - MOCK_METHOD1(IsDirectory, bool(const std::string&)); - MOCK_METHOD1(IsRegularFile, bool(const std::string&)); MOCK_METHOD1(FileSize, ssize_t(const std::string&)); }; @@ -1490,21 +1516,9 @@ class DeviceFilesTest : public ::testing::Test { class DeviceFilesStoreTest : public DeviceFilesTest, public ::testing::WithParamInterface {}; -struct CertStorageVariant { - CertStorageVariant(bool dir_exists_value, const std::string& origin_value) - : dir_exists(dir_exists_value), origin(origin_value) {} +class DeviceCertificateStoreTest : public DeviceFilesTest {}; - const bool dir_exists; - const std::string origin; -}; - -class DeviceCertificateStoreTest - : public DeviceFilesTest, - public ::testing::WithParamInterface {}; - -class DeviceCertificateTest - : public DeviceFilesTest, - public ::testing::WithParamInterface {}; +class DeviceCertificateTest : public DeviceFilesTest {}; class DeviceFilesSecurityLevelTest : public DeviceFilesTest, @@ -1513,13 +1527,22 @@ class DeviceFilesSecurityLevelTest class DeviceFilesUsageInfoTest : public DeviceFilesTest, public ::testing::WithParamInterface {}; -MATCHER(IsCreateFileFlagSet, "") { return File::kCreate & arg; } -MATCHER(IsBinaryFileFlagSet, "") { return File::kBinary & arg; } +class DeviceFilesHlsAttributesTest + : public DeviceFilesTest, + public ::testing::WithParamInterface {}; + +MATCHER(IsCreateFileFlagSet, "") { return FileSystem::kCreate & arg; } MATCHER_P(IsStrEq, str, "") { // Estimating the length of data. We can have gmock provide length // as well as pointer to data but that will introduce a dependency on tr1 return memcmp(arg, str.c_str(), str.size()) == 0; } +MATCHER_P2(Contains, str1, size, "") { + // Estimating the length of data. We can have gmock provide length + // as well as pointer to data but that will introduce a dependency on tr1 + std::string data(arg, size + str1.size() + kProtobufEstimatedOverhead); + return (data.find(str1) != std::string::npos); +} MATCHER_P3(Contains, str1, str2, size, "") { // Estimating the length of data. We can have gmock provide length // as well as pointer to data but that will introduce a dependency on tr1 @@ -1586,105 +1609,78 @@ MATCHER_P7(Contains, str1, str2, str3, str4, str5, str6, map7, "") { data.find(str6) != std::string::npos && map7_entries_present); } -TEST_P(DeviceCertificateStoreTest, StoreCertificate) { - MockFile file; - CertStorageVariant params = GetParam(); +TEST_F(DeviceCertificateStoreTest, StoreCertificate) { + MockFileSystem file_system; std::string certificate(GenerateRandomData(kCertificateLen)); std::string wrapped_private_key(GenerateRandomData(kWrappedKeyLen)); std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(params.origin); + device_base_path_ + DeviceFiles::GetCertificateFileName(); - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .WillOnce(Return(params.dir_exists)); - if (params.dir_exists) { - EXPECT_CALL(file, CreateDirectory(_)).Times(0); - } else { - EXPECT_CALL(file, CreateDirectory(StrEq(device_base_path_))) - .WillOnce(Return(true)); - } - - EXPECT_CALL(file, Open(StrEq(device_certificate_path), - AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) - .WillOnce(Return(true)); + MockFile file; + EXPECT_CALL(file_system, + Open(StrEq(device_certificate_path), IsCreateFileFlagSet())) + .WillOnce(Return(&file)); EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key, 0), Gt(certificate.size() + wrapped_private_key.size()))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Read(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); - EXPECT_TRUE(device_files.StoreCertificate(params.origin, certificate, - wrapped_private_key)); + EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); } -INSTANTIATE_TEST_CASE_P( - StoreCertificate, DeviceCertificateStoreTest, - ::testing::Values(CertStorageVariant(true, EMPTY_ORIGIN), - CertStorageVariant(true, kTestOrigin), - CertStorageVariant(false, EMPTY_ORIGIN), - CertStorageVariant(false, kTestOrigin))); - -TEST_P(DeviceCertificateTest, ReadCertificate) { - MockFile file; - std::string origin = GetParam(); +TEST_F(DeviceCertificateTest, ReadCertificate) { + MockFileSystem file_system; std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(origin); + device_base_path_ + DeviceFiles::GetCertificateFileName(); std::string data = a2bs_hex(kTestCertificateFileData); - EXPECT_CALL(file, Exists(StrEq(device_certificate_path))) + MockFile file; + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) .WillOnce(Return(true)); - EXPECT_CALL(file, FileSize(StrEq(device_certificate_path))) + EXPECT_CALL(file_system, FileSize(StrEq(device_certificate_path))) .WillOnce(Return(data.size())); - EXPECT_CALL(file, Open(StrEq(device_certificate_path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, Open(StrEq(device_certificate_path), _)) + .WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Write(_, _)).Times(0); - if (Properties::security_level_path_backward_compatibility_support()) { - EXPECT_CALL(file, List(_, NotNull())).WillOnce(Return(false)); - } - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); std::string certificate, wrapped_private_key; - ASSERT_TRUE(device_files.RetrieveCertificate(origin, &certificate, - &wrapped_private_key)); + ASSERT_TRUE( + device_files.RetrieveCertificate(&certificate, &wrapped_private_key)); EXPECT_EQ(kTestCertificate, b2a_hex(certificate)); EXPECT_EQ(kTestWrappedPrivateKey, b2a_hex(wrapped_private_key)); } -TEST_P(DeviceCertificateTest, HasCertificate) { - MockFile file; - std::string origin = GetParam(); +TEST_F(DeviceCertificateTest, HasCertificate) { + MockFileSystem file_system; std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(origin); + device_base_path_ + DeviceFiles::GetCertificateFileName(); - EXPECT_CALL(file, Exists(StrEq(device_certificate_path))) + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) .WillOnce(Return(false)) .WillOnce(Return(true)); - EXPECT_CALL(file, Open(_, _)).Times(0); + EXPECT_CALL(file_system, Open(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); // MockFile returns false. - EXPECT_FALSE(device_files.HasCertificate(origin)); + EXPECT_FALSE(device_files.HasCertificate()); // MockFile returns true. - EXPECT_TRUE(device_files.HasCertificate(origin)); + EXPECT_TRUE(device_files.HasCertificate()); } -INSTANTIATE_TEST_CASE_P(CertificateUseTests, DeviceCertificateTest, - ::testing::Values(EMPTY_ORIGIN, kTestOrigin)); - TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) { - MockFile file; + MockFileSystem file_system; std::string certificate(GenerateRandomData(kCertificateLen)); std::string wrapped_private_key(GenerateRandomData(kWrappedKeyLen)); @@ -1693,55 +1689,39 @@ TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) { ASSERT_TRUE( Properties::GetDeviceFilesBasePath(security_level, &device_base_path)); std::string device_certificate_path = - device_base_path + DeviceFiles::GetCertificateFileName(EMPTY_ORIGIN); + device_base_path + DeviceFiles::GetCertificateFileName(); - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path))) - .WillOnce(Return(false)); - EXPECT_CALL(file, CreateDirectory(StrEq(device_base_path))) - .WillOnce(Return(true)); - - EXPECT_CALL(file, Open(StrEq(device_certificate_path), - AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) - .WillOnce(Return(true)); + MockFile file; + EXPECT_CALL(file_system, + Open(StrEq(device_certificate_path), IsCreateFileFlagSet())) + .WillOnce(Return(&file)); EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key, 0), Gt(certificate.size() + wrapped_private_key.size()))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Read(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(security_level)); - device_files.SetTestFile(&file); - EXPECT_TRUE(device_files.StoreCertificate(EMPTY_ORIGIN, certificate, - wrapped_private_key)); + EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); } INSTANTIATE_TEST_CASE_P(SecurityLevel, DeviceFilesSecurityLevelTest, ::testing::Values(kSecurityLevelL1, kSecurityLevelL3)); TEST_P(DeviceFilesStoreTest, StoreLicense) { - MockFile file; + MockFileSystem file_system; size_t license_num = 0; std::string license_path = device_base_path_ + license_test_data[license_num].key_set_id + DeviceFiles::GetLicenseFileNameExtension(); - bool dir_exists = GetParam(); - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .WillOnce(Return(dir_exists)); - if (dir_exists) { - EXPECT_CALL(file, CreateDirectory(_)).Times(0); - } else { - EXPECT_CALL(file, CreateDirectory(StrEq(device_base_path_))) - .WillOnce(Return(true)); - } - CdmAppParameterMap app_parameters = GetAppParameters(license_test_data[license_num].app_parameters); - EXPECT_CALL(file, Open(StrEq(license_path), - AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) - .WillOnce(Return(true)); + MockFile file; + EXPECT_CALL(file_system, Open(StrEq(license_path), IsCreateFileFlagSet())) + .WillOnce(Return(&file)); EXPECT_CALL( file, Write(Contains(license_test_data[license_num].pssh_data, license_test_data[license_num].key_request, @@ -1755,9 +1735,8 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) { EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Read(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); EXPECT_TRUE(device_files.StoreLicense( license_test_data[license_num].key_set_id, license_test_data[license_num].license_state, @@ -1774,11 +1753,8 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) { INSTANTIATE_TEST_CASE_P(StoreLicense, DeviceFilesStoreTest, ::testing::Bool()); TEST_F(DeviceFilesTest, StoreLicenses) { + MockFileSystem file_system; MockFile file; - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .Times(kNumberOfLicenses) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); for (size_t i = 0; i < kNumberOfLicenses; ++i) { std::string license_path = device_base_path_ + @@ -1788,9 +1764,8 @@ TEST_F(DeviceFilesTest, StoreLicenses) { CdmAppParameterMap app_parameters = GetAppParameters(license_test_data[i].app_parameters); - EXPECT_CALL(file, Open(StrEq(license_path), - AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, Open(StrEq(license_path), IsCreateFileFlagSet())) + .WillOnce(Return(&file)); EXPECT_CALL(file, Write(Contains(license_test_data[i].pssh_data, license_test_data[i].key_request, @@ -1805,9 +1780,8 @@ TEST_F(DeviceFilesTest, StoreLicenses) { EXPECT_CALL(file, Close()).Times(kNumberOfLicenses); EXPECT_CALL(file, Read(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); for (size_t i = 0; i < kNumberOfLicenses; i++) { CdmAppParameterMap app_parameters = GetAppParameters(license_test_data[i].app_parameters); @@ -1825,6 +1799,7 @@ TEST_F(DeviceFilesTest, StoreLicenses) { } TEST_F(DeviceFilesTest, RetrieveLicenses) { + MockFileSystem file_system; MockFile file; for (size_t i = 0; i < kNumberOfLicenses; ++i) { @@ -1834,10 +1809,12 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) { size_t size = license_test_data[i].file_data.size(); - EXPECT_CALL(file, Exists(StrEq(license_path))).WillOnce(Return(true)); - EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size)); - EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet())) + EXPECT_CALL(file_system, Exists(StrEq(license_path))) .WillOnce(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(license_path))) + .WillOnce(Return(size)); + EXPECT_CALL(file_system, Open(StrEq(license_path), _)) + .WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(size))) .WillOnce( DoAll(SetArrayArgument<0>(license_test_data[i].file_data.begin(), @@ -1847,9 +1824,8 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) { EXPECT_CALL(file, Close()).Times(kNumberOfLicenses); EXPECT_CALL(file, Write(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); CdmInitData pssh_data; CdmKeyMessage key_request; CdmKeyResponse key_response; @@ -1886,7 +1862,7 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) { } TEST_F(DeviceFilesTest, AppParametersBackwardCompatibility) { - MockFile file; + MockFileSystem file_system; LicenseInfo* test_data = &license_app_parameters_backwards_compatibility_test_data; @@ -1895,10 +1871,12 @@ TEST_F(DeviceFilesTest, AppParametersBackwardCompatibility) { size_t size = test_data->file_data.size(); - EXPECT_CALL(file, Exists(StrEq(license_path))).WillOnce(Return(true)); - EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size)); - EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + MockFile file; + EXPECT_CALL(file_system, Exists(StrEq(license_path))).WillOnce(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(license_path))) + .WillOnce(Return(size)); + EXPECT_CALL(file_system, Open(StrEq(license_path), _)) + .WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(size))) .WillOnce(DoAll(SetArrayArgument<0>(test_data->file_data.begin(), test_data->file_data.end()), @@ -1907,9 +1885,8 @@ TEST_F(DeviceFilesTest, AppParametersBackwardCompatibility) { EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Write(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); DeviceFiles::LicenseState license_state; CdmInitData pssh_data; CdmKeyMessage key_request; @@ -1936,102 +1913,16 @@ TEST_F(DeviceFilesTest, AppParametersBackwardCompatibility) { EXPECT_EQ(0u, app_parameters.size()); } -TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) { - if (!Properties::security_level_path_backward_compatibility_support()) { - return; - } - - MockFile file; - std::vector security_dirs; - EXPECT_TRUE(Properties::GetSecurityLevelDirectories(&security_dirs)); - - size_t pos = std::string::npos; - for (size_t i = 0; i < security_dirs.size(); ++i) { - pos = device_base_path_.rfind(security_dirs[i]); - if (std::string::npos != pos) break; - } - - EXPECT_NE(std::string::npos, pos); - - std::string base_path(device_base_path_, 0, pos); - std::vector old_files; - std::string new_path; - for (size_t i = 0; i < security_dirs.size(); ++i) { - old_files.push_back(security_dirs[i]); - new_path = base_path + security_dirs[i]; - EXPECT_CALL(file, IsRegularFile(StrEq(new_path))).WillOnce(Return(false)); - EXPECT_CALL(file, Exists(StrEq(new_path))) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(StrEq(new_path))).WillOnce(Return(true)); - } - - std::string old_path = - base_path + DeviceFiles::GetCertificateFileName(EMPTY_ORIGIN); - old_files.push_back(DeviceFiles::GetCertificateFileName(EMPTY_ORIGIN)); - EXPECT_CALL(file, IsRegularFile(StrEq(old_path))).WillOnce(Return(true)); - EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true)); - for (size_t i = 0; i < security_dirs.size(); ++i) { - new_path = base_path + security_dirs[i] + - DeviceFiles::GetCertificateFileName(EMPTY_ORIGIN); - EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path))) - .WillOnce(Return(true)); - } - - for (size_t j = 0; j < kNumberOfLicenses; ++j) { - std::string file_name = license_test_data[j].key_set_id + - DeviceFiles::GetLicenseFileNameExtension(); - old_path = base_path + file_name; - old_files.push_back(file_name); - EXPECT_CALL(file, IsRegularFile(StrEq(old_path))).WillOnce(Return(true)); - EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true)); - for (size_t i = 0; i < security_dirs.size(); ++i) { - new_path = base_path + security_dirs[i] + file_name; - EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path))) - .WillOnce(Return(true)); - } - } - - EXPECT_CALL(file, List(StrEq(base_path), NotNull())) - .WillOnce(DoAll(SetArgPointee<1>(old_files), Return(true))); - - std::string data = a2bs_hex(kTestCertificateFileData); - - new_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(EMPTY_ORIGIN); - EXPECT_CALL(file, Exists(StrEq(new_path))).WillOnce(Return(true)); - EXPECT_CALL(file, FileSize(_)).WillOnce(Return(data.size())); - EXPECT_CALL(file, Open(_, _)).WillOnce(Return(true)); - EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))) - .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), - Return(data.size()))); - EXPECT_CALL(file, Close()).Times(1); - EXPECT_CALL(file, Write(_, _)).Times(0); - - DeviceFiles device_files; - EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); - - Properties::Init(); - std::string certificate, wrapped_private_key; - ASSERT_TRUE(device_files.RetrieveCertificate(EMPTY_ORIGIN, &certificate, - &wrapped_private_key)); -} - TEST_F(DeviceFilesTest, UpdateLicenseState) { - MockFile file; + MockFileSystem file_system; std::string license_path = device_base_path_ + license_update_test_data[0].key_set_id + DeviceFiles::GetLicenseFileNameExtension(); - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) + MockFile file; + EXPECT_CALL(file_system, Open(StrEq(license_path), IsCreateFileFlagSet())) .Times(2) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); - EXPECT_CALL(file, Open(StrEq(license_path), - AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) - .Times(2) - .WillRepeatedly(Return(true)); + .WillRepeatedly(Return(&file)); EXPECT_CALL(file, Write(IsStrEq(license_update_test_data[0].file_data), Eq(license_update_test_data[0].file_data.size()))) .WillOnce(ReturnArg<1>()); @@ -2041,9 +1932,8 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) { EXPECT_CALL(file, Close()).Times(2); EXPECT_CALL(file, Read(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); EXPECT_TRUE(device_files.StoreLicense( license_update_test_data[0].key_set_id, license_update_test_data[0].license_state, @@ -2072,32 +1962,33 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) { } TEST_F(DeviceFilesTest, DeleteLicense) { - MockFile file; + MockFileSystem file_system; std::string license_path = device_base_path_ + license_test_data[0].key_set_id + DeviceFiles::GetLicenseFileNameExtension(); size_t size = license_test_data[0].file_data.size(); - EXPECT_CALL(file, Exists(StrEq(license_path))) + MockFile file; + EXPECT_CALL(file_system, Exists(StrEq(license_path))) .Times(2) .WillOnce(Return(true)) .WillOnce(Return(false)); - EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size)); - EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(license_path))) + .WillOnce(Return(size)); + EXPECT_CALL(file_system, Open(StrEq(license_path), _)) + .WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(size))) .WillOnce( DoAll(SetArrayArgument<0>(license_test_data[0].file_data.begin(), license_test_data[0].file_data.end()), Return(size))); - EXPECT_CALL(file, Remove(StrEq(license_path))).WillOnce(Return(true)); + EXPECT_CALL(file_system, Remove(StrEq(license_path))).WillOnce(Return(true)); EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Write(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); DeviceFiles::LicenseState license_state; CdmInitData pssh_data; CdmKeyMessage key_request; @@ -2135,17 +2026,11 @@ TEST_F(DeviceFilesTest, DeleteLicense) { TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) { // Validate that ReserveLicenseIds does not touch the file system. - MockFile file; - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))).Times(0); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); - EXPECT_CALL(file, Open(_, _)).Times(0); - EXPECT_CALL(file, Write(_, _)).Times(0); - EXPECT_CALL(file, Close()).Times(0); - EXPECT_CALL(file, Read(_, _)).Times(0); + MockFileSystem file_system; + EXPECT_CALL(file_system, Open(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); for (size_t i = 0; i < kNumberOfLicenses; i++) { EXPECT_TRUE(device_files.ReserveLicenseId(license_test_data[i].key_set_id)); // Validate that the license IDs are actually reserved. @@ -2156,6 +2041,7 @@ TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) { } TEST_P(DeviceFilesUsageInfoTest, Read) { + MockFileSystem file_system; MockFile file; std::string app_id; // TODO(fredgc): add tests with multiple app_ids. std::string path = @@ -2167,28 +2053,24 @@ TEST_P(DeviceFilesUsageInfoTest, Read) { data = kUsageInfoTestData[index].file_data; } if (index >= 0) { - EXPECT_CALL(file, Exists(StrEq(path))).WillRepeatedly(Return(true)); - EXPECT_CALL(file, FileSize(StrEq(path))) + EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(path))) .WillRepeatedly(Return(data.size())); - EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); EXPECT_CALL(file, Close()).Times(1); } else { - EXPECT_CALL(file, Exists(StrEq(path))) - .WillRepeatedly(Return(false)); - EXPECT_CALL(file, FileSize(_)).Times(0); - EXPECT_CALL(file, Open(_, _)).Times(0); - EXPECT_CALL(file, Close()).Times(0); + EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(false)); + EXPECT_CALL(file_system, FileSize(_)).Times(0); + EXPECT_CALL(file_system, Open(_, _)).Times(0); } EXPECT_CALL(file, Write(_, _)).Times(0); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); std::vector > license_info; ASSERT_TRUE(device_files.RetrieveUsageInfo(app_id, &license_info)); @@ -2212,7 +2094,7 @@ TEST_P(DeviceFilesUsageInfoTest, Read) { } TEST_P(DeviceFilesUsageInfoTest, Store) { - MockFile file; + MockFileSystem file_system; std::string app_id; // TODO(fredgc): multiple app ids. std::string pst(GenerateRandomData(kProviderSessionTokenLen)); std::string license_request(GenerateRandomData(kLicenseRequestLen)); @@ -2226,24 +2108,24 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { if (index >= 0) { data = kUsageInfoTestData[index].file_data; } - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); - EXPECT_CALL(file, Exists(StrEq(path))).WillRepeatedly(Return(index >= 0)); + MockFile file; + EXPECT_CALL(file_system, Exists(StrEq(path))) + .WillRepeatedly(Return(index >= 0)); if (index >= 0) { - EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size())); - EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) + EXPECT_CALL(file_system, FileSize(StrEq(path))) + .WillOnce(Return(data.size())); + EXPECT_CALL(file_system, Open(StrEq(path), _)) .Times(2) - .WillRepeatedly(Return(true)); + .WillRepeatedly(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); EXPECT_CALL(file, Close()).Times(2); } else { - EXPECT_CALL(file, FileSize(_)).Times(0); - EXPECT_CALL(file, Open(_, _)).Times(1).WillOnce(Return(true)); - EXPECT_CALL(file, Close()).Times(1); + EXPECT_CALL(file_system, FileSize(_)).Times(0); + EXPECT_CALL(file_system, Open(_, _)).Times(1).WillOnce(Return(&file)); + EXPECT_CALL(file, Close()); } EXPECT_CALL(file, @@ -2253,9 +2135,8 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { key_set_id.size()))) .WillOnce(ReturnArg<1>()); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); ASSERT_TRUE( device_files.StoreUsageInfo(pst, license_request, license, app_id, @@ -2263,6 +2144,7 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { } TEST_P(DeviceFilesUsageInfoTest, Delete) { + MockFileSystem file_system; MockFile file; std::string app_id; // TODO(fredgc): expand tests. std::string path = @@ -2282,24 +2164,19 @@ TEST_P(DeviceFilesUsageInfoTest, Delete) { } } - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); + EXPECT_CALL(file_system, Exists(StrEq(path))).WillOnce(Return(index >= 0)); - EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(index >= 0)); - - EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size())); + EXPECT_CALL(file_system, FileSize(StrEq(path))).WillOnce(Return(data.size())); if (index >= 1) { - EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) + EXPECT_CALL(file_system, Open(StrEq(path), _)) .Times(2) - .WillRepeatedly(Return(true)); + .WillRepeatedly(Return(&file)); EXPECT_CALL(file, Write(Contains(prev_pst, prev_license, prev_data.size()), Gt(prev_pst.size() + prev_license.size()))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(file, Close()).Times(2); } else { - EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); EXPECT_CALL(file, Write(_, _)).Times(0); EXPECT_CALL(file, Close()).Times(1); } @@ -2307,9 +2184,8 @@ TEST_P(DeviceFilesUsageInfoTest, Delete) { .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); if (index >= 1) { ASSERT_TRUE(device_files.DeleteUsageInfo(app_id, pst)); @@ -2319,36 +2195,33 @@ TEST_P(DeviceFilesUsageInfoTest, Delete) { } TEST_P(DeviceFilesUsageInfoTest, DeleteAll) { + MockFileSystem file_system; MockFile file; std::string app_id; // TODO(fredgc): expand tests. std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName(app_id); int index = GetParam(); - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .WillRepeatedly(Return(true)); - EXPECT_CALL(file, CreateDirectory(_)).Times(0); EXPECT_CALL(file, Write(_, _)).Times(0); std::string data; if (index < 0) { - EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(false)); + EXPECT_CALL(file_system, Exists(StrEq(path))).WillOnce(Return(false)); } else { data = kUsageInfoTestData[index].file_data; - EXPECT_CALL(file, Exists(StrEq(path))).WillRepeatedly(Return(true)); - EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size())); - EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) - .WillOnce(Return(true)); + EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(path))) + .WillOnce(Return(data.size())); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); EXPECT_CALL(file, Close()).Times(1); - EXPECT_CALL(file, Remove(StrEq(path))).WillOnce(Return(true)); + EXPECT_CALL(file_system, Remove(StrEq(path))).WillOnce(Return(true)); } - DeviceFiles device_files; + DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - device_files.SetTestFile(&file); std::vector psts; ASSERT_TRUE(device_files.DeleteAllUsageInfoForApp(app_id, &psts)); @@ -2367,4 +2240,75 @@ TEST_P(DeviceFilesUsageInfoTest, DeleteAll) { INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageInfoTest, ::testing::Range(-1, 4)); +TEST_P(DeviceFilesHlsAttributesTest, Read) { + MockFileSystem file_system; + MockFile file; + HlsAttributesInfo* param = GetParam(); + std::string path = device_base_path_ + param->key_set_id + + DeviceFiles::GetHlsAttributesFileNameExtension(); + + EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(path))) + .WillRepeatedly(Return(param->file_data.size())); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); + EXPECT_CALL(file, Read(NotNull(), Eq(param->file_data.size()))) + .WillOnce(DoAll( + SetArrayArgument<0>(param->file_data.begin(), param->file_data.end()), + Return(param->file_data.size()))); + EXPECT_CALL(file, Close()).Times(1); + + EXPECT_CALL(file, Write(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + CdmHlsMethod method; + std::vector media_segment_iv; + ASSERT_TRUE(device_files.RetrieveHlsAttributes(param->key_set_id, &method, + &media_segment_iv)); + EXPECT_EQ(param->method, method); + EXPECT_EQ(b2a_hex(param->media_segment_iv), b2a_hex(media_segment_iv)); +} + +TEST_P(DeviceFilesHlsAttributesTest, Store) { + MockFileSystem file_system; + MockFile file; + HlsAttributesInfo* param = GetParam(); + std::string path = device_base_path_ + param->key_set_id + + DeviceFiles::GetHlsAttributesFileNameExtension(); + + EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file)); + EXPECT_CALL(file, Write(Contains(param->media_segment_iv, 0), + Gt(param->media_segment_iv.size()))) + .WillOnce(ReturnArg<1>()); + EXPECT_CALL(file, Read(_, _)).Times(0); + EXPECT_CALL(file, Close()).Times(1); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + std::vector iv(param->media_segment_iv.begin(), + param->media_segment_iv.end()); + ASSERT_TRUE( + device_files.StoreHlsAttributes(param->key_set_id, param->method, iv)); +} + +TEST_P(DeviceFilesHlsAttributesTest, Delete) { + MockFileSystem file_system; + MockFile file; + HlsAttributesInfo* param = GetParam(); + std::string path = device_base_path_ + param->key_set_id + + DeviceFiles::GetHlsAttributesFileNameExtension(); + + EXPECT_CALL(file_system, Remove(StrEq(path))).WillOnce(Return(true)); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + ASSERT_TRUE(device_files.DeleteHlsAttributes(param->key_set_id)); +} + +INSTANTIATE_TEST_CASE_P(HlsAttributes, DeviceFilesHlsAttributesTest, + ::testing::Range(&kHlsAttributesTestData[0], + &kHlsAttributesTestData[2])); + } // namespace wvcdm diff --git a/core/test/file_store_unittest.cpp b/core/test/file_store_unittest.cpp index 3eee8251..48bfcd17 100644 --- a/core/test/file_store_unittest.cpp +++ b/core/test/file_store_unittest.cpp @@ -1,9 +1,8 @@ // Copyright 2013 Google Inc. All Rights Reserved. #include -#include "device_files.h" + #include "file_store.h" -#include "properties.h" #include "test_vectors.h" namespace wvcdm { @@ -18,20 +17,12 @@ const std::string kWildcard = "*"; class FileTest : public testing::Test { protected: - virtual void SetUp() { CreateTestDir(); } + FileTest() {} + virtual void TearDown() { RemoveTestDir(); } - void CreateTestDir() { - File file; - if (!file.Exists(test_vectors::kTestDir)) { - EXPECT_TRUE(file.CreateDirectory(test_vectors::kTestDir)); - } - EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); - } - void RemoveTestDir() { - File file; - EXPECT_TRUE(file.Remove(test_vectors::kTestDir)); + EXPECT_TRUE(file_system.Remove(test_vectors::kTestDir)); } std::string GenerateRandomData(uint32_t len) { @@ -41,64 +32,52 @@ class FileTest : public testing::Test { } return data; } + + FileSystem file_system; }; TEST_F(FileTest, FileExists) { - File file; - EXPECT_TRUE(file.Exists(test_vectors::kExistentFile)); - EXPECT_TRUE(file.Exists(test_vectors::kExistentDir)); - EXPECT_FALSE(file.Exists(test_vectors::kNonExistentFile)); - EXPECT_FALSE(file.Exists(test_vectors::kNonExistentDir)); -} - -TEST_F(FileTest, CreateDirectory) { - File file; - std::string dir_wo_delimiter = - test_vectors::kTestDir.substr(0, test_vectors::kTestDir.size() - 1); - if (file.Exists(dir_wo_delimiter)) EXPECT_TRUE(file.Remove(dir_wo_delimiter)); - EXPECT_FALSE(file.Exists(dir_wo_delimiter)); - EXPECT_TRUE(file.CreateDirectory(dir_wo_delimiter)); - EXPECT_TRUE(file.Exists(dir_wo_delimiter)); - EXPECT_TRUE(file.Remove(dir_wo_delimiter)); - EXPECT_TRUE(file.CreateDirectory(test_vectors::kTestDir)); - EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); - EXPECT_TRUE(file.Remove(test_vectors::kTestDir)); + EXPECT_TRUE(file_system.Exists(test_vectors::kExistentFile)); + EXPECT_TRUE(file_system.Exists(test_vectors::kExistentDir)); + EXPECT_FALSE(file_system.Exists(test_vectors::kNonExistentFile)); + EXPECT_FALSE(file_system.Exists(test_vectors::kNonExistentDir)); } TEST_F(FileTest, RemoveDir) { - File file; - EXPECT_TRUE(file.Remove(test_vectors::kTestDir)); - EXPECT_FALSE(file.Exists(test_vectors::kTestDir)); + EXPECT_TRUE(file_system.Remove(test_vectors::kTestDir)); + EXPECT_FALSE(file_system.Exists(test_vectors::kTestDir)); } TEST_F(FileTest, OpenFile) { std::string path = test_vectors::kTestDir + kTestFileName; - File handle; - EXPECT_TRUE(handle.Remove(path)); + EXPECT_TRUE(file_system.Remove(path)); - File file; - EXPECT_TRUE(file.Open(path, File::kCreate)); - file.Close(); + File* file = file_system.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file); + file->Close(); - EXPECT_TRUE(handle.Exists(path)); + EXPECT_TRUE(file_system.Exists(path)); } TEST_F(FileTest, RemoveDirAndFile) { std::string path = test_vectors::kTestDir + kTestFileName; - File file; - EXPECT_TRUE(file.Open(path, File::kCreate)); - file.Close(); - EXPECT_TRUE(file.Exists(path)); - EXPECT_TRUE(file.Remove(path)); - EXPECT_FALSE(file.Exists(path)); + File* file = file_system.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file); + file->Close(); - EXPECT_TRUE(file.Open(path, File::kCreate)); - file.Close(); - EXPECT_TRUE(file.Exists(path)); + EXPECT_TRUE(file_system.Exists(path)); + EXPECT_TRUE(file_system.Remove(path)); + EXPECT_FALSE(file_system.Exists(path)); + + file = file_system.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file); + file->Close(); + + EXPECT_TRUE(file_system.Exists(path)); RemoveTestDir(); - EXPECT_FALSE(file.Exists(test_vectors::kTestDir)); - EXPECT_FALSE(file.Exists(path)); + EXPECT_FALSE(file_system.Exists(test_vectors::kTestDir)); + EXPECT_FALSE(file_system.Exists(path)); } TEST_F(FileTest, RemoveWildcardFiles) { @@ -107,159 +86,53 @@ TEST_F(FileTest, RemoveWildcardFiles) { std::string wildcard_path = test_vectors::kTestDir + kWildcard + kTestFileNameExt; - File file; - EXPECT_TRUE(file.Open(path1, File::kCreate)); - file.Close(); - EXPECT_TRUE(file.Open(path2, File::kCreate)); - file.Close(); - EXPECT_TRUE(file.Exists(path1)); - EXPECT_TRUE(file.Exists(path2)); - EXPECT_TRUE(file.Remove(wildcard_path)); - EXPECT_FALSE(file.Exists(path1)); - EXPECT_FALSE(file.Exists(path2)); -} + File* file = file_system.Open(path1, FileSystem::kCreate); + ASSERT_TRUE(file); + file->Close(); + file = file_system.Open(path2, FileSystem::kCreate); + ASSERT_TRUE(file); + file->Close(); -TEST_F(FileTest, IsDir) { - std::string path = test_vectors::kTestDir + kTestFileName; - File file; - EXPECT_TRUE(file.Open(path, File::kCreate)); - file.Close(); - - EXPECT_TRUE(file.Exists(path)); - EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); - EXPECT_FALSE(file.IsDirectory(path)); - EXPECT_TRUE(file.IsDirectory(test_vectors::kTestDir)); -} - -TEST_F(FileTest, IsRegularFile) { - std::string path = test_vectors::kTestDir + kTestFileName; - File file; - EXPECT_TRUE(file.Open(path, File::kCreate)); - file.Close(); - - EXPECT_TRUE(file.Exists(path)); - EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); - EXPECT_TRUE(file.IsRegularFile(path)); - EXPECT_FALSE(file.IsRegularFile(test_vectors::kTestDir)); + EXPECT_TRUE(file_system.Exists(path1)); + EXPECT_TRUE(file_system.Exists(path2)); + EXPECT_TRUE(file_system.Remove(wildcard_path)); + EXPECT_FALSE(file_system.Exists(path1)); + EXPECT_FALSE(file_system.Exists(path2)); } TEST_F(FileTest, FileSize) { std::string path = test_vectors::kTestDir + kTestFileName; - File file; - file.Remove(path); + file_system.Remove(path); std::string write_data = GenerateRandomData(600); - File wr_file; - EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary)); - EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); - wr_file.Close(); - EXPECT_TRUE(file.Exists(path)); + File* file = file_system.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file); + EXPECT_TRUE(file->Write(write_data.data(), write_data.size())); + file->Close(); + EXPECT_TRUE(file_system.Exists(path)); - EXPECT_EQ(static_cast(write_data.size()), file.FileSize(path)); -} - -TEST_F(FileTest, WriteReadTextFile) { - std::string path = test_vectors::kTestDir + kTestFileName; - File file; - file.Remove(path); - - std::string write_data = "This is a test"; - File wr_file; - EXPECT_TRUE(wr_file.Open(path, File::kCreate)); - EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); - wr_file.Close(); - EXPECT_TRUE(file.Exists(path)); - - std::string read_data; - read_data.resize(file.FileSize(path)); - File rd_file; - EXPECT_TRUE(rd_file.Open(path, File::kReadOnly)); - EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size())); - rd_file.Close(); - EXPECT_EQ(write_data, read_data); + EXPECT_EQ(static_cast(write_data.size()), + file_system.FileSize(path)); } TEST_F(FileTest, WriteReadBinaryFile) { std::string path = test_vectors::kTestDir + kTestFileName; - File file; - file.Remove(path); + file_system.Remove(path); std::string write_data = GenerateRandomData(600); - File wr_file; - EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary)); - EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); - wr_file.Close(); - EXPECT_TRUE(file.Exists(path)); + File* file = file_system.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file); + EXPECT_TRUE(file->Write(write_data.data(), write_data.size())); + file->Close(); + EXPECT_TRUE(file_system.Exists(path)); std::string read_data; - read_data.resize(file.FileSize(path)); - File rd_file; - EXPECT_TRUE(rd_file.Open(path, File::kReadOnly)); - EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size())); - rd_file.Close(); + read_data.resize(file_system.FileSize(path)); + file = file_system.Open(path, FileSystem::kReadOnly); + ASSERT_TRUE(file); + EXPECT_TRUE(file->Read(&read_data[0], read_data.size())); + file->Close(); EXPECT_EQ(write_data, read_data); } -TEST_F(FileTest, CopyFile) { - std::string path = test_vectors::kTestDir + kTestFileName; - File file; - file.Remove(path); - - std::string write_data = GenerateRandomData(600); - File wr_file; - EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary)); - EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); - wr_file.Close(); - ASSERT_TRUE(file.Exists(path)); - - std::string path_copy = test_vectors::kTestDir + kTestFileName2; - EXPECT_FALSE(file.Exists(path_copy)); - EXPECT_TRUE(file.Copy(path, path_copy)); - - std::string read_data; - read_data.resize(file.FileSize(path_copy)); - File rd_file; - EXPECT_TRUE(rd_file.Open(path_copy, File::kReadOnly)); - EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size())); - rd_file.Close(); - EXPECT_EQ(write_data, read_data); - EXPECT_EQ(file.FileSize(path), file.FileSize(path_copy)); -} - -TEST_F(FileTest, ListEmptyDirectory) { - std::vector files; - File file; - EXPECT_TRUE(file.List(test_vectors::kTestDir, &files)); - EXPECT_EQ(0u, files.size()); -} - -TEST_F(FileTest, ListFiles) { - File file; - std::string path = test_vectors::kTestDir + kTestDirName; - EXPECT_TRUE(file.CreateDirectory(path)); - - path = test_vectors::kTestDir + kTestFileName; - std::string write_data = GenerateRandomData(600); - EXPECT_TRUE(file.Open(path, File::kCreate | File::kBinary)); - EXPECT_TRUE(file.Write(write_data.data(), write_data.size())); - file.Close(); - EXPECT_TRUE(file.Exists(path)); - - path = test_vectors::kTestDir + kTestFileName2; - write_data = GenerateRandomData(600); - EXPECT_TRUE(file.Open(path, File::kCreate | File::kBinary)); - EXPECT_TRUE(file.Write(write_data.data(), write_data.size())); - file.Close(); - EXPECT_TRUE(file.Exists(path)); - - std::vector files; - EXPECT_TRUE(file.List(test_vectors::kTestDir, &files)); - EXPECT_EQ(3u, files.size()); - - for (size_t i = 0; i < files.size(); ++i) { - EXPECT_TRUE(files[i] == kTestDirName || files[i] == kTestFileName || - files[i] == kTestFileName2); - } -} - } // namespace wvcdm diff --git a/core/test/generic_crypto_unittest.cpp b/core/test/generic_crypto_unittest.cpp new file mode 100644 index 00000000..571a2af4 --- /dev/null +++ b/core/test/generic_crypto_unittest.cpp @@ -0,0 +1,416 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// These tests are for the generic crypto operations. They call on the +// CdmEngine class and exercise the classes below it as well. In +// particular, we assume that the OEMCrypo layer works, and has a valid keybox. +// This is because we need a valid RSA certificate, and will attempt to connect +// to the provisioning server to request one if we don't. + +#include +#include +#include + +#include "cdm_engine.h" + +#include "license_request.h" +#include "log.h" +#include "oec_session_util.h" +#include "../../oemcrypto/mock/src/oemcrypto_key_mock.h" +#include "string_conversions.h" +#include "url_request.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_types.h" + +namespace { + +const std::string kKeySystem = "com.widevine.alpha"; + +} // namespace + +namespace wvcdm { + +class WvGenericOperationsTest : public testing::Test { + public: + virtual void SetUp() { + ::testing::Test::SetUp(); + + // Load test keybox. This keybox will be used by any CryptoSession + // created by the CDM under test. + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestKeybox()); + + // Perform CdmEngine setup + cdm_engine_ = new CdmEngine(&file_system_); + + CdmResponseType status = + cdm_engine_->OpenSession(kKeySystem, NULL, NULL, &session_id_); + if (status == NEED_PROVISIONING) { + Provision(); + status = cdm_engine_->OpenSession(kKeySystem, NULL, NULL, &session_id_); + } + ASSERT_EQ(NO_ERROR, status); + ASSERT_NE("", session_id_) << "Could not open CDM session."; + ASSERT_TRUE(cdm_engine_->IsOpenSession(session_id_)); + + // Get OEMCrypto session ID from the CDM + CdmQueryMap query; + cdm_engine_->QueryOemCryptoSessionId(session_id_, &query); + std::istringstream parse_int; + parse_int.str(query[QUERY_KEY_OEMCRYPTO_SESSION_ID]); + parse_int >> oec_session_id_; + + // Construct and install keys into the CDM's OEMCrypto session. + OecSessionSetup(oec_session_id_); + EncryptAndLoadKeys(); + } + + virtual void TearDown() { + oec_util_session_.close(); + cdm_engine_->CloseSession(session_id_); + // OEMCrypto_Terminate() will be performed during the test class's + // destruction (specifically by the CryptoSession destructor) + } + + void OecSessionSetup(uint32_t oec_session_id) { + buffer_size_ = 160; + oec_util_session_.SetSessionId(oec_session_id); + oec_util_session_.GenerateTestSessionKeys(); + MakeFourKeys(); + } + + enum GenericKeyType { + kGenericEncrypt = 0, + kGenericDecrypt = 1, + kGenericSign = 2, + kGenericVerify = 3 + }; + + virtual void MakeFourKeys( + uint32_t duration = wvoec::kDuration, uint32_t control = 0, + uint32_t nonce = 0, const std::string& pst = "") { + ASSERT_NO_FATAL_FAILURE( + oec_util_session_.FillSimpleMessage(duration, control, nonce, pst)); + oec_util_session_.license().keys[kGenericEncrypt].control.control_bits |= + htonl(wvoec_mock::kControlAllowEncrypt); + oec_util_session_.license().keys[kGenericDecrypt].control.control_bits |= + htonl(wvoec_mock::kControlAllowDecrypt); + oec_util_session_.license().keys[kGenericSign].control.control_bits |= + htonl(wvoec_mock::kControlAllowSign); + oec_util_session_.license().keys[kGenericVerify].control.control_bits |= + htonl(wvoec_mock::kControlAllowVerify); + + oec_util_session_.license().keys[kGenericSign].key_data_length = + wvcdm::MAC_KEY_SIZE; + oec_util_session_.license().keys[kGenericVerify].key_data_length = + wvcdm::MAC_KEY_SIZE; + + clear_buffer_.assign(buffer_size_, 0); + for (size_t i = 0; i < clear_buffer_.size(); i++) { + clear_buffer_[i] = 1 + i % 250; + } + for (size_t i = 0; i < wvcdm::KEY_IV_SIZE; i++) { + iv_[i] = i; + } + } + + std::string GetKeyId(GenericKeyType type) { + std::string key_id; + size_t key_id_length = oec_util_session_.license().keys[0].key_id_length; + key_id.assign( + &(oec_util_session_.license().keys[type].key_id[0]), + &(oec_util_session_.license().keys[type].key_id[key_id_length])); + return key_id; + } + + std::string GetClearBuffer() { + std::string buffer; + size_t buffer_length = clear_buffer_.size(); + buffer.assign(&clear_buffer_[0], &clear_buffer_[buffer_length]); + return buffer; + } + + std::string GetEncryptedBuffer() { + std::string buffer; + size_t buffer_length = encrypted_buffer_.size(); + buffer.assign(&encrypted_buffer_[0], &encrypted_buffer_[buffer_length]); + return buffer; + } + + std::string GetIvBlock() { + std::string buffer; + size_t buffer_length = wvcdm::KEY_IV_SIZE; + buffer.assign(&iv_[0], &iv_[buffer_length]); + return buffer; + } + + std::string GetSignatureBuffer() { + std::string buffer; + buffer.resize(SHA256_DIGEST_LENGTH); + return buffer; + } + + void EncryptAndLoadKeys() { + ASSERT_NO_FATAL_FAILURE(oec_util_session_.EncryptAndSign()); + oec_util_session_.LoadTestKeys(); + } + + protected: + void Provision() { + CdmProvisioningRequest prov_request; + std::string provisioning_server_url; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority; + std::string cert, wrapped_key; + ASSERT_EQ(NO_ERROR, + cdm_engine_->GetProvisioningRequest( + cert_type, cert_authority, &prov_request, + &provisioning_server_url)); + UrlRequest url_request(provisioning_server_url); + EXPECT_TRUE(url_request.is_connected()); + url_request.PostCertRequestInQueryString(prov_request); + std::string message; + bool ok = url_request.GetResponse(&message); + EXPECT_TRUE(ok); + ASSERT_EQ(NO_ERROR, + cdm_engine_->HandleProvisioningResponse(message, &cert, + &wrapped_key)); + } + + // This CryptoSession object handles Initialization and Termination + // calls on OEMCrypto for the duration of the test. CryptoSessions + // created by the CDM will share the OEMCrypto state of this CryptoSession, + // including, for example, a test keybox. + CryptoSession crypto_session_; + + FileSystem file_system_; + CdmEngine* cdm_engine_; + std::string key_msg_; + std::string session_id_; + std::string server_url_; + uint32_t oec_session_id_; + wvoec::Session oec_util_session_; + size_t buffer_size_; + vector clear_buffer_; + vector encrypted_buffer_; + uint8_t iv_[wvcdm::KEY_IV_SIZE]; +}; + +TEST_F(WvGenericOperationsTest, NormalSessionOpenClose) { + wvoec::Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.close()); +} + +TEST_F(WvGenericOperationsTest, GenerateSessionKeys) { + wvoec::Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.close()); +} + +TEST_F(WvGenericOperationsTest, GenericEncryptNoKey) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string out_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // No key + KeyId key_id("xyz"); + cdm_sts = cdm_engine_->GenericEncrypt(session_id_, in_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, + &out_buffer); + EXPECT_EQ(KEY_ERROR_1, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericEncryptKeyNotAllowed) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string out_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // Wrong key + std::string key_id = GetKeyId(kGenericDecrypt); + + cdm_sts = cdm_engine_->GenericEncrypt(session_id_, in_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, + &out_buffer); + EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericEncryptGood) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string out_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // Good key + std::string key_id = GetKeyId(kGenericEncrypt); + + cdm_sts = cdm_engine_->GenericEncrypt(session_id_, in_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, + &out_buffer); + EXPECT_EQ(NO_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericDecryptKeyNotAllowed) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string out_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // Wrong key + std::string key_id = GetKeyId(kGenericEncrypt); + + cdm_sts = cdm_engine_->GenericDecrypt(session_id_, in_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, + &out_buffer); + EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericDecryptGood) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string out_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // Good key + std::string key_id = GetKeyId(kGenericDecrypt); + + cdm_sts = cdm_engine_->GenericDecrypt(session_id_, in_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, + &out_buffer); + EXPECT_EQ(NO_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericSignKeyNotAllowed) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string signature_buffer; + + // Wrong key + std::string key_id = GetKeyId(kGenericVerify); + + cdm_sts = cdm_engine_->GenericSign(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + &signature_buffer); + EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericSignGood) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string signature_buffer; + + // Good key + std::string key_id = GetKeyId(kGenericSign); + + cdm_sts = cdm_engine_->GenericSign(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + &signature_buffer); + EXPECT_EQ(NO_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericVerifyKeyNotAllowed) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string signature_buffer = GetSignatureBuffer(); + + // Wrong key + std::string key_id = GetKeyId(kGenericSign); + + cdm_sts = cdm_engine_->GenericVerify(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + signature_buffer); + EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); +} + +TEST_F(WvGenericOperationsTest, GenericVerifyGood) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string signature_buffer = GetSignatureBuffer(); + + // Good key - signature not set. + std::string key_id = GetKeyId(kGenericVerify); + + cdm_sts = cdm_engine_->GenericVerify(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + signature_buffer); + // OEMCrypto error is OEMCrypto_ERROR_SIGNATURE_FAILURE + EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); +} + +class WvGenericOperationsDataTest : public WvGenericOperationsTest { + public: + // Construct keys for encrypt/decrypt and for sign/verify + virtual void MakeFourKeys( + uint32_t duration = wvoec::kDuration, uint32_t control = 0, + uint32_t nonce = 0, const std::string& pst = "") { + ASSERT_NO_FATAL_FAILURE( + oec_util_session_.FillSimpleMessage(duration, control, nonce, pst)); + oec_util_session_.license().keys[kGenericEncrypt].control.control_bits |= + htonl(wvoec_mock::kControlAllowEncrypt | + wvoec_mock::kControlAllowDecrypt); + oec_util_session_.license().keys[kGenericSign].control.control_bits |= + htonl(wvoec_mock::kControlAllowSign | wvoec_mock::kControlAllowVerify); + + oec_util_session_.license().keys[kGenericSign].key_data_length = + wvcdm::MAC_KEY_SIZE; + + clear_buffer_.assign(buffer_size_, 0); + for (size_t i = 0; i < clear_buffer_.size(); i++) { + clear_buffer_[i] = 1 + i % 250; + } + for (size_t i = 0; i < wvcdm::KEY_IV_SIZE; i++) { + iv_[i] = i; + } + } +}; + +TEST_F(WvGenericOperationsDataTest, GenericEncryptDecrypt) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string encrypted_buffer = GetEncryptedBuffer(); + std::string iv = GetIvBlock(); + + // Encrypt + std::string key_id = GetKeyId(kGenericEncrypt); + + cdm_sts = cdm_engine_->GenericEncrypt( + session_id_, in_buffer, key_id, iv, wvcdm::kEncryptionAlgorithmAesCbc128, + &encrypted_buffer); + + EXPECT_EQ(NO_ERROR, cdm_sts); + + // Decrypt, use same key as encrypt. + key_id = GetKeyId(kGenericEncrypt); + + std::string final_buffer; + final_buffer.resize(in_buffer.size()); + + cdm_sts = cdm_engine_->GenericDecrypt( + session_id_, encrypted_buffer, key_id, iv, + wvcdm::kEncryptionAlgorithmAesCbc128, &final_buffer); + + EXPECT_EQ(NO_ERROR, cdm_sts); + EXPECT_EQ(0, in_buffer.compare(final_buffer)); +} + +TEST_F(WvGenericOperationsDataTest, GenericSignVerify) { + CdmResponseType cdm_sts; + std::string in_buffer = GetClearBuffer(); + std::string signature_buffer = GetSignatureBuffer(); + + // Signing key + std::string key_id = GetKeyId(kGenericSign); + cdm_sts = cdm_engine_->GenericSign(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + &signature_buffer); + EXPECT_EQ(NO_ERROR, cdm_sts); + + // Verify signature, use same key as sign. + key_id = GetKeyId(kGenericSign); + cdm_sts = cdm_engine_->GenericVerify(session_id_, in_buffer, key_id, + wvcdm::kSigningAlgorithmHmacSha256, + signature_buffer); + EXPECT_EQ(NO_ERROR, cdm_sts); +} + +} // namespace wvcdm diff --git a/core/test/http_socket.cpp b/core/test/http_socket.cpp index 2a8eb7e6..f1d20daf 100644 --- a/core/test/http_socket.cpp +++ b/core/test/http_socket.cpp @@ -41,9 +41,12 @@ SSL_CTX* InitSslContext() { OpenSSL_add_all_algorithms(); SSL_load_error_strings(); - method = SSLv3_client_method(); + method = TLSv1_2_client_method(); ctx = SSL_CTX_new(method); if (!ctx) LOGE("failed to create SSL context"); + int ret = SSL_CTX_set_cipher_list( + ctx, "ALL:!RC4-MD5:!RC4-SHA:!ECDHE-ECDSA-RC4-SHA:!ECDHE-RSA-RC4-SHA"); + if (0 != ret) LOGE("error disabling vulnerable ciphers"); return ctx; } @@ -294,6 +297,10 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) { int total_read = 0; int to_read = len; + if (socket_fd_ == -1) { + LOGE("Socket to %s not open. Cannot read.", domain_name_.c_str()); + return -1; + } while (to_read > 0) { if (!SocketWait(socket_fd_, /* for_read */ true, timeout_in_ms)) { LOGE("unable to read from %s", domain_name_.c_str()); @@ -329,6 +336,10 @@ int HttpSocket::Write(const char* data, int len, int timeout_in_ms) { int total_sent = 0; int to_send = len; + if (socket_fd_ == -1) { + LOGE("Socket to %s not open. Cannot write.", domain_name_.c_str()); + return -1; + } while (to_send > 0) { int sent; if (secure_connect_) diff --git a/core/test/initialization_data_unittest.cpp b/core/test/initialization_data_unittest.cpp index a739803f..e8de2ea2 100644 --- a/core/test/initialization_data_unittest.cpp +++ b/core/test/initialization_data_unittest.cpp @@ -1,19 +1,44 @@ // Copyright 2015 Google Inc. All Rights Reserved. +#include #include #include + #include "initialization_data.h" +#include "license_protocol.pb.h" #include "string_conversions.h" #include "wv_cdm_constants.h" // References: // [1] http://dashif.org/identifiers/content-protection/ // [2] http://www.w3.org/TR/encrypted-media/cenc-format.html#common-system +// [3] https://tools.ietf.org/html/draft-pantos-http-live-streaming-18 namespace wvcdm { +// Protobuf generated classes. +using video_widevine_server::sdk::WidevineCencHeader; + namespace { +// Constants for JSON formatting +const std::string kLeftBrace = "{"; +const std::string kRightBrace = "}"; +const std::string kLeftBracket = "["; +const std::string kRightBracket = "]"; +const std::string kComma = ","; +const std::string kColon = ":"; +const std::string kDoubleQuote = "\""; +const std::string kNewline = "\n"; +const std::string kFourSpaceIndent = " "; + +const std::string kJsonProvider = "provider"; +const std::string kJsonContentId = "content_id"; +const std::string kJsonKeyIds = "key_ids"; + +const uint32_t kFourCcCbc1 = 0x63626331; +const uint32_t kFourCcCbcs = 0x63626373; + const std::string kWidevinePssh = a2bs_hex( // Widevine PSSH box "00000042" // atom size @@ -139,8 +164,245 @@ const std::string kZeroSizedPsshBox = a2bs_hex( // data: "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); +// HLS test attribute key and values +const std::string kHlsIvHexValue = "6DF49213A781E338628D0E9C812D328E"; +const std::string kHlsIvValue = "0x" + kHlsIvHexValue; +const std::string kHlsKeyFormatValue = "com.widevine.alpha"; +const std::string kHlsKeyFormatValueOther = "com.example"; +const std::string kHlsTestKey1 = "TESTKEY1"; +const std::string kHlsTestValue1 = "testvalue1"; +const std::string kHlsTestKey2 = "TESTKEY2"; +const std::string kHlsTestValue2 = "testvalue2"; +const std::string kHlsTestInvalidLowercaseKey = "testkey3"; +const std::string kHlsTestKeyWithDash = "TEST-KEY4"; +const std::string kHlsTestInvalidNonAlphanumKey = "TEST;KEY4"; +const std::string kHlsTestValueWithEmbeddedQuote = "test\"value1"; +const std::string kHlsTestEmptyHexValue = ""; +const std::string kHlsTestNoHexValue = "0x"; +const std::string kHlsTestHexValueWithOddBytes = kHlsIvHexValue + "7"; +const std::string kHlsTestInvalidHexValue = kHlsIvHexValue + "g7"; +char kHlsTestKeyFormatVersionsSeparator = '/'; +const std::string kHlsTestUriDataFormat = "data:text/plain;base64,"; +const std::string kHlsTestProvider = "youtube"; +const std::string kHlsTestContentId = "MjAxNV9UZWFycw=="; +const std::string kHlsTestKeyId1 = "371E135E1A985D75D198A7F41020DC23"; +const std::string kHlsTestKeyId2 = "E670D9B60AE61583E01BC9253FA19261"; +const std::string kHlsTestKeyId3 = "78094E72165DF39721B8A354D6A71390"; +const std::string kHlsTestInvalidKeyId = "B8A354D6A71390"; +const std::string kHlsTestKeyFormatVersion1 = "1"; +const std::string kHlsTestKeyFormatVersion3 = "3"; +const std::string kHlsTestKeyFormatVersion5 = "5"; +const std::string kHlsTestKeyFormatVersion13 = "13"; +const std::string kHlsTestKeyFormatVersion21 = "21"; +const std::string kHlsTestKeyFormatVersion37 = "37"; + +// HLS attribute helper functions +std::string QuoteString(const std::string& value) { + return "\"" + value + "\""; +} + +std::string GenerateJsonInitData(const std::string& provider, + const std::string& content_id, + const std::vector& key_ids) { + std::string json = kLeftBrace + kNewline; + if (provider.size() > 0) { + json += kFourSpaceIndent + kDoubleQuote + kJsonProvider + kDoubleQuote + + kColon + kDoubleQuote + provider + kDoubleQuote + kComma + kNewline; + } + if (content_id.size() > 0) { + json += kFourSpaceIndent + kDoubleQuote + kJsonContentId + kDoubleQuote + + kColon + kDoubleQuote + content_id + kDoubleQuote + kComma + + kNewline; + } + if (key_ids.size() > 0) { + json += kFourSpaceIndent + kDoubleQuote + kJsonKeyIds + kDoubleQuote + + kColon + kNewline; + json += kFourSpaceIndent + kLeftBracket + kNewline; + for (size_t i = 0; i < key_ids.size(); ++i) { + json += kFourSpaceIndent + kFourSpaceIndent + kDoubleQuote + key_ids[i] + + kDoubleQuote; + if (i != key_ids.size() - 1) { + json += kComma; + } + json += kNewline; + } + json += kFourSpaceIndent + kRightBracket + kNewline; + } + json += kRightBrace + kNewline; + return json; +} + +class VectorOfStrings { + public: + VectorOfStrings(const std::string& str) { vec_.push_back(str); } + VectorOfStrings& Add(const std::string& str) { + vec_.push_back(str); + return *this; + } + const std::vector Generate() { return vec_; } + + private: + std::vector vec_; +}; + +std::string GenerateHlsUriData(const std::string& provider, + const std::string& content_id, + const std::vector& key_ids) { + std::string json = GenerateJsonInitData(provider, content_id, key_ids); + std::vector json_init_data( + reinterpret_cast(json.data()), + reinterpret_cast(json.data() + json.size())); + return kHlsTestUriDataFormat + Base64Encode(json_init_data); +} + +std::string CreateHlsAttributeList(const std::string& method, + const std::string& provider, + const std::string& content_id, + const std::vector& key_ids, + const std::string& iv, + const std::string& key_format, + const std::string& key_format_version) { + return "EXT-X-KEY: " + HLS_METHOD_ATTRIBUTE + "=" + method + "," + + HLS_URI_ATTRIBUTE + "=" + + QuoteString(GenerateHlsUriData(provider, content_id, key_ids)) + "," + + HLS_IV_ATTRIBUTE + "=" + iv + "," + HLS_KEYFORMAT_ATTRIBUTE + "=" + + QuoteString(key_format) + "," + HLS_KEYFORMAT_VERSIONS_ATTRIBUTE + + "=" + QuoteString(key_format_version); +} + +// HLS attribute list for testing +const std::string kHlsAttributeList = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListKeyFormatUnknown = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsIvValue, + kHlsKeyFormatValueOther, HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListKeyFormatVersionUnsupported = + CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, kHlsTestProvider, + kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), + kHlsIvValue, kHlsKeyFormatValue, "2"); + +const std::string kHlsAttributeListKeyFormatVersionMultiple = + CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, kHlsTestProvider, + kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), + kHlsIvValue, kHlsKeyFormatValue, "1/2/5"); + +const std::string kHlsAttributeListMethodAes128 = CreateHlsAttributeList( + HLS_METHOD_AES_128, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListMethodNone = CreateHlsAttributeList( + HLS_METHOD_NONE, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListMethodInvalid = CreateHlsAttributeList( + kHlsTestValue1, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListInvalidUriNoProvider = + CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, "", kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), + kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListInvalidUriNoContentId = + CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, kHlsTestProvider, "", + VectorOfStrings(kHlsTestKeyId1).Generate(), + kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListInvalidUriNoKeyId = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings("").Generate(), kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListValidUriThreeKeyIds = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1) + .Add(kHlsTestKeyId2) + .Add(kHlsTestKeyId3) + .Generate(), + kHlsIvValue, kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListNoIv = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsTestNoHexValue, + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); + +const std::string kHlsAttributeListInvalidIv = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate(), kHlsTestHexValueWithOddBytes, + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); + +std::string InsertHlsAttributeInList(const std::string key, + const std::string& value) { + return kHlsAttributeList + "," + key + "=" + value + "," + kHlsTestKey2 + + "=" + kHlsTestValue2; +} + +struct HlsInitDataVariant { + HlsInitDataVariant(CdmHlsMethod method, const std::string& provider, + const std::string& content_id, const std::string& key_id, + bool success) + : method_(method), provider_(provider), content_id_(content_id), + success_(success) { + if (key_id.size() > 0) key_ids_.push_back(key_id); + } + HlsInitDataVariant& AddKeyId(const std::string& key_id) { + key_ids_.push_back(key_id); + return *this; + } + const CdmHlsMethod method_; + const std::string provider_; + const std::string content_id_; + std::vector key_ids_; + const bool success_; +}; + +struct HlsAttributeVariant { + HlsAttributeVariant(const std::string& attribute_list, const std::string& key, + const std::string& value, bool success) + : attribute_list_(attribute_list), + key_(key), + value_(value), + success_(success) {} + const std::string attribute_list_; + const std::string key_; + const std::string value_; + const bool success_; +}; + class InitializationDataTest : public ::testing::TestWithParam {}; +class HlsAttributeExtractionTest + : public ::testing::TestWithParam {}; + +class HlsHexAttributeExtractionTest + : public ::testing::TestWithParam {}; + +class HlsQuotedAttributeExtractionTest + : public ::testing::TestWithParam {}; + +class HlsKeyFormatVersionsExtractionTest + : public ::testing::TestWithParam > {}; + +class HlsConstructionTest + : public ::testing::TestWithParam {}; + +class HlsInitDataConstructionTest : public ::testing::Test {}; + +class HlsParseTest : public ::testing::TestWithParam {}; + +class HlsTest : public ::testing::Test {}; } // namespace TEST_P(InitializationDataTest, Parse) { @@ -148,17 +410,343 @@ TEST_P(InitializationDataTest, Parse) { EXPECT_FALSE(init_data.IsEmpty()); } +INSTANTIATE_TEST_CASE_P(ParsePssh, InitializationDataTest, + ::testing::Values(kWidevinePssh, kWidevinePsshFirst, + kWidevinePsshAfterV0Pssh, + kWidevinePsshAfterNonZeroFlags, + kWidevinePsshAfterV1Pssh, + kWidevineV1Pssh, kOtherBoxFirst, + kZeroSizedPsshBox)); + +TEST_P(HlsKeyFormatVersionsExtractionTest, ExtractKeyFormatVersions) { + std::vector versions = GetParam(); + std::string key_format_versions; + for (size_t i = 0; i < versions.size(); ++i) { + key_format_versions += versions[i] + kHlsTestKeyFormatVersionsSeparator; + } + key_format_versions.resize(key_format_versions.size() - + sizeof(kHlsTestKeyFormatVersionsSeparator)); + std::vector extracted_versions = + InitializationData::ExtractKeyFormatVersions(key_format_versions); + EXPECT_EQ(versions.size(), extracted_versions.size()); + for (size_t i = 0; i < versions.size(); ++i) { + bool found = false; + for (size_t j = 0; j < extracted_versions.size(); ++j) { + if (versions[i] == extracted_versions[j]) { + found = true; + break; + } + } + EXPECT_TRUE(found); + } +} + INSTANTIATE_TEST_CASE_P( - ParsePssh, InitializationDataTest, + HlsTest, HlsKeyFormatVersionsExtractionTest, + ::testing::Values(VectorOfStrings(kHlsTestKeyFormatVersion1).Generate(), + VectorOfStrings(kHlsTestKeyFormatVersion21).Generate(), + VectorOfStrings(kHlsTestKeyFormatVersion1) + .Add(kHlsTestKeyFormatVersion3) + .Generate(), + VectorOfStrings(kHlsTestKeyFormatVersion1) + .Add(kHlsTestKeyFormatVersion3) + .Add(kHlsTestKeyFormatVersion13) + .Generate(), + VectorOfStrings(kHlsTestKeyFormatVersion13) + .Add(kHlsTestKeyFormatVersion5) + .Add(kHlsTestKeyFormatVersion21) + .Add(kHlsTestKeyFormatVersion37) + .Generate())); + +TEST_P(HlsAttributeExtractionTest, ExtractAttribute) { + HlsAttributeVariant param = GetParam(); + std::string value; + if (param.success_) { + EXPECT_TRUE(InitializationData::ExtractAttribute(param.attribute_list_, + param.key_, &value)); + EXPECT_EQ(param.value_, value); + } else { + EXPECT_FALSE(InitializationData::ExtractAttribute(param.attribute_list_, + param.key_, &value)); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsAttributeExtractionTest, ::testing::Values( - kWidevinePssh, - kWidevinePsshFirst, - kWidevinePsshAfterV0Pssh, - kWidevinePsshAfterNonZeroFlags, - kWidevinePsshAfterV1Pssh, - kWidevineV1Pssh, - kOtherBoxFirst, - kZeroSizedPsshBox - )); + HlsAttributeVariant(kHlsAttributeList, HLS_METHOD_ATTRIBUTE, + HLS_METHOD_SAMPLE_AES, true), + HlsAttributeVariant(kHlsAttributeList, HLS_URI_ATTRIBUTE, + QuoteString(GenerateHlsUriData( + kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate())), + true), + HlsAttributeVariant(kHlsAttributeList, HLS_IV_ATTRIBUTE, kHlsIvValue, + true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_ATTRIBUTE, + QuoteString(kHlsKeyFormatValue), true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, + QuoteString(HLS_KEYFORMAT_VERSION_VALUE_1), true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestValue1), + kHlsTestKey2, kHlsTestValue2, true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1 + "\t", + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + " " + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestValue1 + " "), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1 + "3", + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, ""), + kHlsTestKey1, "", true), + HlsAttributeVariant(InsertHlsAttributeInList( + kHlsTestInvalidLowercaseKey, kHlsTestValue1), + kHlsTestInvalidLowercaseKey, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKeyWithDash, + kHlsTestValue1), + kHlsTestKeyWithDash, kHlsTestValue1, true), + HlsAttributeVariant(InsertHlsAttributeInList( + kHlsTestInvalidNonAlphanumKey, kHlsTestValue1), + kHlsTestInvalidNonAlphanumKey, kHlsTestValue1, + false), + HlsAttributeVariant( + InsertHlsAttributeInList(kHlsTestKey1, QuoteString(kHlsTestValue1)), + kHlsTestKey1, QuoteString(kHlsTestValue1), true), + HlsAttributeVariant( + InsertHlsAttributeInList( + kHlsTestKey1, QuoteString(kHlsTestValueWithEmbeddedQuote)), + kHlsTestKey1, QuoteString(kHlsTestValueWithEmbeddedQuote), true))); + +TEST_P(HlsHexAttributeExtractionTest, ExtractHexAttribute) { + HlsAttributeVariant param = GetParam(); + std::vector value; + if (param.success_) { + EXPECT_TRUE(InitializationData::ExtractHexAttribute(param.attribute_list_, + param.key_, &value)); + EXPECT_EQ(param.value_, b2a_hex(value)); + } else { + EXPECT_FALSE(InitializationData::ExtractHexAttribute(param.attribute_list_, + param.key_, &value)); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsHexAttributeExtractionTest, + ::testing::Values( + HlsAttributeVariant(kHlsAttributeList, HLS_IV_ATTRIBUTE, kHlsIvHexValue, + true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestEmptyHexValue), + kHlsTestKey1, kHlsTestEmptyHexValue, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestNoHexValue), + kHlsTestKey1, kHlsTestNoHexValue, false), + HlsAttributeVariant(InsertHlsAttributeInList( + kHlsTestKey1, kHlsTestHexValueWithOddBytes), + kHlsTestKey1, kHlsTestHexValueWithOddBytes, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestInvalidHexValue), + kHlsTestKey1, kHlsTestInvalidHexValue, false))); + +TEST_P(HlsQuotedAttributeExtractionTest, ExtractQuotedAttribute) { + HlsAttributeVariant param = GetParam(); + std::string value; + if (param.success_) { + EXPECT_TRUE(InitializationData::ExtractQuotedAttribute( + param.attribute_list_, param.key_, &value)); + EXPECT_EQ(param.value_, value); + } else { + EXPECT_FALSE(InitializationData::ExtractQuotedAttribute( + param.attribute_list_, param.key_, &value)); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsQuotedAttributeExtractionTest, + ::testing::Values( + HlsAttributeVariant( + kHlsAttributeList, HLS_URI_ATTRIBUTE, + GenerateHlsUriData(kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate()), + true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_ATTRIBUTE, + kHlsKeyFormatValue, true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, + HLS_KEYFORMAT_VERSION_VALUE_1, true), + HlsAttributeVariant( + InsertHlsAttributeInList(kHlsTestKey1, QuoteString(kHlsTestValue1)), + kHlsTestKey1, kHlsTestValue1, true), + HlsAttributeVariant( + InsertHlsAttributeInList( + kHlsTestKey1, QuoteString(kHlsTestValueWithEmbeddedQuote)), + kHlsTestKey1, kHlsTestValueWithEmbeddedQuote, false))); + +TEST_P(HlsConstructionTest, InitData) { + HlsInitDataVariant param = GetParam(); + + std::string uri = + GenerateHlsUriData(param.provider_, param.content_id_, param.key_ids_); + std::string value; + EXPECT_EQ(param.success_, InitializationData::ConstructWidevineInitData( + param.method_, uri, &value)); + if (param.success_) { + WidevineCencHeader cenc_header; + EXPECT_TRUE(cenc_header.ParseFromString(value)); + EXPECT_EQ(video_widevine_server::sdk::WidevineCencHeader_Algorithm_AESCTR, + cenc_header.algorithm()); + for (size_t i = 0; i < param.key_ids_.size(); ++i) { + bool key_id_found = false; + if (param.key_ids_[i].size() != 32) continue; + for (int j = 0; j < cenc_header.key_id_size(); ++j) { + if (param.key_ids_[i] == b2a_hex(cenc_header.key_id(j))) { + key_id_found = true; + break; + } + } + EXPECT_TRUE(key_id_found); + } + EXPECT_EQ(param.provider_, cenc_header.provider()); + std::vector param_content_id_vec(Base64Decode(param.content_id_)); + EXPECT_EQ( + std::string(param_content_id_vec.begin(), param_content_id_vec.end()), + cenc_header.content_id()); + uint32_t protection_scheme = 0; + switch (param.method_) { + case kHlsMethodAes128: protection_scheme = kFourCcCbc1; break; + case kHlsMethodSampleAes: protection_scheme = kFourCcCbcs; break; + default: break; + } + EXPECT_EQ(protection_scheme, ntohl(cenc_header.protection_scheme())); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsConstructionTest, + ::testing::Values( + HlsInitDataVariant(kHlsMethodAes128, "", kHlsTestContentId, + kHlsTestKeyId1, false), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + "", kHlsTestKeyId1, false), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + kHlsTestContentId, "", false), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + kHlsTestContentId, kHlsTestInvalidKeyId, false), + HlsInitDataVariant(kHlsMethodNone, kHlsTestProvider, kHlsTestContentId, + kHlsTestKeyId1, false), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + kHlsTestContentId, kHlsTestKeyId1, true), + HlsInitDataVariant(kHlsMethodSampleAes, kHlsTestProvider, + kHlsTestContentId, kHlsTestKeyId1, true), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + kHlsTestContentId, kHlsTestKeyId1, true) + .AddKeyId(kHlsTestKeyId2) + .AddKeyId(kHlsTestKeyId3), + HlsInitDataVariant(kHlsMethodSampleAes, kHlsTestProvider, + kHlsTestContentId, kHlsTestKeyId1, true) + .AddKeyId(kHlsTestKeyId2) + .AddKeyId(kHlsTestKeyId3), + HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, + kHlsTestContentId, kHlsTestInvalidKeyId, true) + .AddKeyId(kHlsTestKeyId1), + HlsInitDataVariant(kHlsMethodSampleAes, kHlsTestProvider, + kHlsTestContentId, kHlsTestInvalidKeyId, true) + .AddKeyId(kHlsTestKeyId1))); + +TEST_F(HlsInitDataConstructionTest, InvalidUriDataFormat) { + std::string json = + GenerateJsonInitData(kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate()); + std::vector json_init_data( + reinterpret_cast(json.data()), + reinterpret_cast(json.data() + json.size())); + std::string value; + EXPECT_FALSE(InitializationData::ConstructWidevineInitData( + kHlsMethodAes128, Base64Encode(json_init_data), &value)); +} + +TEST_F(HlsInitDataConstructionTest, InvalidUriBase64Encode) { + std::string json = + GenerateJsonInitData(kHlsTestProvider, kHlsTestContentId, + VectorOfStrings(kHlsTestKeyId1).Generate()); + std::string value; + EXPECT_FALSE(InitializationData::ConstructWidevineInitData( + kHlsMethodSampleAes, kHlsTestUriDataFormat + json, &value)); +} + +TEST_P(HlsParseTest, Parse) { + HlsAttributeVariant param = GetParam(); + InitializationData init_data(HLS_INIT_DATA_FORMAT, param.attribute_list_); + if (param.success_) { + EXPECT_TRUE(init_data.is_hls()); + EXPECT_FALSE(init_data.IsEmpty()); + if (param.key_.compare(HLS_METHOD_ATTRIBUTE) == 0) { + if (param.value_.compare(HLS_METHOD_SAMPLE_AES) == 0) { + EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_method()); + } else if (param.value_.compare(HLS_METHOD_AES_128) == 0) { + EXPECT_EQ(kHlsMethodAes128, init_data.hls_method()); + } else if (param.value_.compare(HLS_METHOD_NONE) == 0) { + EXPECT_EQ(kHlsMethodNone, init_data.hls_method()); + } + } else { + EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_method()); + } + + WidevineCencHeader cenc_header; + EXPECT_TRUE(cenc_header.ParseFromString(init_data.data())); + EXPECT_EQ(video_widevine_server::sdk::WidevineCencHeader_Algorithm_AESCTR, + cenc_header.algorithm()); + if (param.key_.compare(kJsonProvider) == 0) { + EXPECT_EQ(param.value_, cenc_header.provider()); + } else if (param.key_.compare(kJsonContentId) == 0) { + EXPECT_EQ(param.value_, cenc_header.content_id()); + } else if (param.key_.compare(kJsonKeyIds) == 0) { + EXPECT_EQ(param.value_, b2a_hex(cenc_header.key_id(0))); + } + + EXPECT_EQ(kHlsIvHexValue, b2a_hex(init_data.hls_iv())); + } else { + EXPECT_TRUE(init_data.is_hls()); + EXPECT_TRUE(init_data.IsEmpty()); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsParseTest, + ::testing::Values( + HlsAttributeVariant(kHlsAttributeList, "", "", true), + HlsAttributeVariant(kHlsAttributeListKeyFormatUnknown, + HLS_KEYFORMAT_ATTRIBUTE, kHlsKeyFormatValueOther, + false), + HlsAttributeVariant(kHlsAttributeListKeyFormatVersionUnsupported, + HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, "2", false), + HlsAttributeVariant(kHlsAttributeListMethodAes128, HLS_METHOD_ATTRIBUTE, + HLS_METHOD_AES_128, true), + HlsAttributeVariant(kHlsAttributeListMethodNone, HLS_METHOD_ATTRIBUTE, + HLS_METHOD_NONE, false), + HlsAttributeVariant(kHlsAttributeListKeyFormatVersionMultiple, + HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, + HLS_KEYFORMAT_VERSION_VALUE_1, true), + HlsAttributeVariant(kHlsAttributeListMethodInvalid, + HLS_METHOD_ATTRIBUTE, kHlsTestValue1, false), + HlsAttributeVariant(kHlsAttributeListInvalidUriNoProvider, + kJsonProvider, kHlsTestProvider, false), + HlsAttributeVariant(kHlsAttributeListInvalidUriNoContentId, + kJsonContentId, kHlsTestContentId, false), + HlsAttributeVariant(kHlsAttributeListInvalidUriNoKeyId, kJsonKeyIds, + kHlsTestKeyId1, false), + HlsAttributeVariant(kHlsAttributeListValidUriThreeKeyIds, kJsonKeyIds, + kHlsTestKeyId1, true), + HlsAttributeVariant(kHlsAttributeListNoIv, HLS_IV_ATTRIBUTE, + kHlsTestNoHexValue, false), + HlsAttributeVariant(kHlsAttributeListInvalidIv, HLS_IV_ATTRIBUTE, + kHlsTestHexValueWithOddBytes, false))); } // namespace wvcdm diff --git a/core/test/license_keys_unittest.cpp b/core/test/license_keys_unittest.cpp new file mode 100644 index 00000000..34ecf62b --- /dev/null +++ b/core/test/license_keys_unittest.cpp @@ -0,0 +1,888 @@ +// Copyright 2016 Google Inc. All Rights Reserved. + +#include +#include +#include +#include "license.h" +#include "license_key_status.h" + +namespace wvcdm { + +namespace { + +static const uint32_t dev_lo_res = 200; +static const uint32_t dev_hi_res = 400; +static const uint32_t dev_top_res = 800; + +static const uint32_t key_lo_res_min = 151; +static const uint32_t key_lo_res_max = 300; +static const uint32_t key_hi_res_min = 301; +static const uint32_t key_hi_res_max = 450; +static const uint32_t key_top_res_min = 451; +static const uint32_t key_top_res_max = 650; + +// Content Keys +static const KeyId ck_sw_crypto = "c_key_SW_SECURE_CRYPTO"; +static const KeyId ck_sw_decode = "c_key_SW_SECURE_DECODE"; +static const KeyId ck_hw_crypto = "c_key_HW_SECURE_CRYPTO"; +static const KeyId ck_hw_decode = "c_key_HW_SECURE_DECODE"; +static const KeyId ck_hw_secure = "c_key_HW_SECURE_ALL"; + +// Operator Session Keys +static const KeyId osk_decrypt = "os_key_generic_decrypt"; +static const KeyId osk_encrypt = "os_key_generic_encrypt"; +static const KeyId osk_sign = "os_key_generic_sign"; +static const KeyId osk_verify = "os_key_generic_verify"; +static const KeyId osk_encrypt_decrypt = "os_key_generic_encrypt_decrypt"; +static const KeyId osk_sign_verify = "os_key_generic_sign_verify"; +static const KeyId osk_all = "os_key_generic_all"; + +// HDCP test keys +static const KeyId ck_sw_crypto_NO_HDCP = "ck_sw_crypto_NO_HDCP"; +static const KeyId ck_hw_secure_NO_HDCP = "ck_hw_secure_NO_HDCP"; +static const KeyId ck_sw_crypto_HDCP_V2_1 = "ck_sw_crypto_HDCP_V2_1"; +static const KeyId ck_hw_secure_HDCP_V2_1 = "ck_hw_secure_HDCP_V2_1"; +static const KeyId ck_sw_crypto_HDCP_NO_OUTPUT = "ck_sw_crypto_HDCP_NO_OUT"; +static const KeyId ck_hw_secure_HDCP_NO_OUTPUT = "ck_hw_secure_HDCP_NO_OUT"; + +// Constraint test keys +static const KeyId ck_NO_HDCP_lo_res = "ck_NO_HDCP_lo_res"; +static const KeyId ck_HDCP_NO_OUTPUT_hi_res = "ck_HDCP_NO_OUTPUT_hi_res"; +static const KeyId ck_HDCP_V2_1_max_res = "ck_HDCP_V2_1_max_res"; +static const KeyId ck_NO_HDCP_dual_res = "ck_NO_HDCP_dual_res"; + +} // namespace + +// protobuf generated classes. +using video_widevine_server::sdk::License; +using video_widevine_server::sdk::LicenseIdentification; +using video_widevine_server::sdk::STREAMING; +using video_widevine_server::sdk::OFFLINE; + +typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; +typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint; + +class LicenseKeysTest : public ::testing::Test { + protected: + + enum KeyFlag { + kKeyFlagNull, + kKeyFlagFalse, + kKeyFlagTrue + }; + + static const KeyFlag kEncryptNull = kKeyFlagNull; + static const KeyFlag kEncryptFalse = kKeyFlagFalse; + static const KeyFlag kEncryptTrue = kKeyFlagTrue; + static const KeyFlag kDecryptNull = kKeyFlagNull; + static const KeyFlag kDecryptFalse = kKeyFlagFalse; + static const KeyFlag kDecryptTrue = kKeyFlagTrue; + static const KeyFlag kSignNull = kKeyFlagNull; + static const KeyFlag kSignFalse = kKeyFlagFalse; + static const KeyFlag kSignTrue = kKeyFlagTrue; + static const KeyFlag kVerifyNull = kKeyFlagNull; + static const KeyFlag kVerifyFalse = kKeyFlagFalse; + static const KeyFlag kVerifyTrue = kKeyFlagTrue; + + static const KeyFlag kContentSecureFalse = kKeyFlagFalse; + static const KeyFlag kContentSecureTrue = kKeyFlagTrue; + static const KeyFlag kContentClearFalse = kKeyFlagFalse; + static const KeyFlag kContentClearTrue = kKeyFlagTrue; + + virtual void SetUp() { + LicenseIdentification* id = license_.mutable_id(); + id->set_version(1); + id->set_type(STREAMING); + } + + virtual void AddContentKey( + const KeyId& key_id, bool set_level = false, + KeyContainer::SecurityLevel level = KeyContainer::SW_SECURE_CRYPTO, + bool set_hdcp = false, KeyContainer::OutputProtection::HDCP hdcp_value = + KeyContainer::OutputProtection::HDCP_NONE, + bool set_constraints = false, + std::vector* constraints = NULL) { + KeyContainer* key = license_.add_key(); + key->set_type(KeyContainer::CONTENT); + if (set_level) { + key->set_level(level); + } + if (set_hdcp) { + KeyContainer::OutputProtection* pro = key->mutable_required_protection(); + pro->set_hdcp(hdcp_value); + } + if (set_constraints) { + for (std::vector::iterator + it = constraints->begin(); it != constraints->end(); ++it) { + VideoResolutionConstraint* constraint = + key->add_video_resolution_constraints(); + constraint->set_min_resolution_pixels(it->min_resolution_pixels()); + constraint->set_max_resolution_pixels(it->max_resolution_pixels()); + constraint->mutable_required_protection()-> + set_hdcp(it->required_protection().hdcp()); + } + } + key->set_id(key_id); + } + + virtual void AddOperatorSessionKey( + const KeyId& key_id, bool set_perms = false, + KeyFlag encrypt = kKeyFlagNull, KeyFlag decrypt = kKeyFlagNull, + KeyFlag sign = kKeyFlagNull, KeyFlag verify = kKeyFlagNull) { + KeyContainer* non_content_key = license_.add_key(); + non_content_key->set_type(KeyContainer::OPERATOR_SESSION); + non_content_key->set_id(key_id); + if (set_perms) { + KeyContainer::OperatorSessionKeyPermissions* permissions = + non_content_key->mutable_operator_session_key_permissions(); + if (encrypt != kKeyFlagNull) { + permissions->set_allow_encrypt(encrypt == kKeyFlagTrue); + } + if (decrypt != kKeyFlagNull) { + permissions->set_allow_decrypt(decrypt == kKeyFlagTrue); + } + if (sign != kKeyFlagNull) { + permissions->set_allow_sign(sign == kKeyFlagTrue); + } + if (verify != kKeyFlagNull) { + permissions->set_allow_signature_verify(verify == kKeyFlagTrue); + } + } + } + + virtual void AddSigningKey(const KeyId& key_id) { + KeyContainer* key = license_.add_key(); + key->set_type(KeyContainer::SIGNING); + key->set_id(key_id); + } + + virtual void ExpectAllowedUsageContent( + const CdmKeyAllowedUsage& key_usage, KeyFlag secure, KeyFlag clear) { + EXPECT_EQ(key_usage.decrypt_to_secure_buffer, secure == kKeyFlagTrue); + EXPECT_EQ(key_usage.decrypt_to_clear_buffer, clear == kKeyFlagTrue); + EXPECT_FALSE(key_usage.generic_encrypt); + EXPECT_FALSE(key_usage.generic_decrypt); + EXPECT_FALSE(key_usage.generic_sign); + EXPECT_FALSE(key_usage.generic_verify); + } + + virtual void ExpectAllowedUsageOperator( + const CdmKeyAllowedUsage& key_usage, KeyFlag encrypt, KeyFlag decrypt, + KeyFlag sign, KeyFlag verify) { + EXPECT_FALSE(key_usage.decrypt_to_secure_buffer); + EXPECT_FALSE(key_usage.decrypt_to_clear_buffer); + EXPECT_EQ(key_usage.generic_encrypt, encrypt == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_decrypt, decrypt == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_sign, sign == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_verify, verify == kKeyFlagTrue); + } + + virtual int NumContentKeys() { + return content_key_count_; + } + + virtual void StageContentKeys() { + content_key_count_ = 0; + AddContentKey(ck_sw_crypto, true, KeyContainer::SW_SECURE_CRYPTO); + content_key_count_++; + AddContentKey(ck_sw_decode, true, KeyContainer::SW_SECURE_DECODE); + content_key_count_++; + AddContentKey(ck_hw_crypto, true, KeyContainer::HW_SECURE_CRYPTO); + content_key_count_++; + AddContentKey(ck_hw_decode, true, KeyContainer::HW_SECURE_DECODE); + content_key_count_++; + AddContentKey(ck_hw_secure, true, KeyContainer::HW_SECURE_ALL); + content_key_count_++; + license_keys_.SetFromLicense(license_); + } + + virtual void StageOperatorSessionKeys() { + AddOperatorSessionKey(osk_decrypt, true, + kEncryptNull, kDecryptTrue, kSignNull, kVerifyNull); + AddOperatorSessionKey(osk_encrypt, true, + kEncryptTrue, kDecryptNull, kSignNull, kVerifyNull); + AddOperatorSessionKey(osk_sign, true, + kEncryptNull, kDecryptNull, kSignTrue, kVerifyNull); + AddOperatorSessionKey(osk_verify, true, + kEncryptNull, kDecryptNull, kSignNull, kVerifyTrue); + AddOperatorSessionKey(osk_encrypt_decrypt, true, + kEncryptTrue, kDecryptTrue, kSignNull, kVerifyNull); + AddOperatorSessionKey(osk_sign_verify, true, + kEncryptNull, kDecryptNull, kSignTrue, kVerifyTrue); + AddOperatorSessionKey(osk_all, true, + kEncryptTrue, kDecryptTrue, kSignTrue, kVerifyTrue); + license_keys_.SetFromLicense(license_); + } + + virtual void StageHdcpKeys() { + content_key_count_ = 0; + AddContentKey(ck_sw_crypto_NO_HDCP, true, KeyContainer::SW_SECURE_CRYPTO, + true, KeyContainer::OutputProtection::HDCP_NONE); + content_key_count_++; + AddContentKey(ck_hw_secure_NO_HDCP, true, KeyContainer::HW_SECURE_ALL, true, + KeyContainer::OutputProtection::HDCP_NONE); + content_key_count_++; + + AddContentKey(ck_sw_crypto_HDCP_V2_1, true, KeyContainer::SW_SECURE_CRYPTO, + true, KeyContainer::OutputProtection::HDCP_V2_1); + content_key_count_++; + AddContentKey(ck_hw_secure_HDCP_V2_1, true, KeyContainer::HW_SECURE_ALL, + true, KeyContainer::OutputProtection::HDCP_V2_1); + content_key_count_++; + + AddContentKey(ck_sw_crypto_HDCP_NO_OUTPUT, true, + KeyContainer::SW_SECURE_CRYPTO, true, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + content_key_count_++; + AddContentKey(ck_hw_secure_HDCP_NO_OUTPUT, true, + KeyContainer::HW_SECURE_ALL, true, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + content_key_count_++; + license_keys_.SetFromLicense(license_); + } + + virtual void AddConstraint( + std::vector& constraints, uint32_t min_res, + uint32_t max_res, bool set_hdcp = false, + KeyContainer::OutputProtection::HDCP hdcp = + KeyContainer::OutputProtection::HDCP_NONE) { + VideoResolutionConstraint constraint; + constraint.set_min_resolution_pixels(min_res); + constraint.set_max_resolution_pixels(max_res); + if (set_hdcp) { + constraint.mutable_required_protection()->set_hdcp(hdcp); + } + constraints.push_back(constraint); + } + + virtual void StageConstraintKeys() { + content_key_count_ = 0; + + std::vector constraints; + + AddConstraint(constraints, key_lo_res_min, key_lo_res_max); + + AddContentKey(ck_NO_HDCP_lo_res, true, KeyContainer::SW_SECURE_CRYPTO, true, + KeyContainer::OutputProtection::HDCP_NONE, true, + &constraints); + content_key_count_++; + + constraints.clear(); + AddConstraint(constraints, key_hi_res_min, key_hi_res_max); + + AddContentKey( + ck_HDCP_NO_OUTPUT_hi_res, true, KeyContainer::SW_SECURE_CRYPTO, true, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT, true, + &constraints); + content_key_count_++; + + constraints.clear(); + AddConstraint(constraints, key_top_res_min, key_top_res_max); + + AddContentKey(ck_HDCP_V2_1_max_res, true, KeyContainer::SW_SECURE_CRYPTO, + true, KeyContainer::OutputProtection::HDCP_V2_1, true, + &constraints); + content_key_count_++; + + constraints.clear(); + AddConstraint(constraints, key_lo_res_min, key_lo_res_max); + AddConstraint(constraints, key_hi_res_min, key_hi_res_max, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + + AddContentKey(ck_NO_HDCP_dual_res, true, KeyContainer::HW_SECURE_ALL, true, + KeyContainer::OutputProtection::HDCP_NONE, true, + &constraints); + content_key_count_++; + + license_keys_.SetFromLicense(license_); + } + + virtual void ExpectKeyStatusesEqual(CdmKeyStatusMap& key_status_map, + CdmKeyStatus expected_status) { + for (CdmKeyStatusMap::iterator it = key_status_map.begin(); + it != key_status_map.end(); ++it) { + EXPECT_TRUE(it->second == expected_status); + } + } + + virtual void ExpectKeyStatusEqual(CdmKeyStatusMap& key_status_map, + const KeyId& key_id, + CdmKeyStatus expected_status) { + for (CdmKeyStatusMap::iterator it = key_status_map.begin(); + it != key_status_map.end(); ++it) { + if (key_id == it->first) { + EXPECT_TRUE(it->second == expected_status); + } + } + } + + + int content_key_count_; + LicenseKeys license_keys_; + License license_; +}; + +TEST_F(LicenseKeysTest, Empty) { + EXPECT_TRUE(license_keys_.Empty()); +} + +TEST_F(LicenseKeysTest, NotEmpty) { + const KeyId c_key = "content_key"; + AddContentKey(c_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.Empty()); +} + +TEST_F(LicenseKeysTest, BadKeyId) { + const KeyId c_key = "content_key"; + const KeyId os_key = "op_sess_key"; + const KeyId unk_key = "unknown_key"; + CdmKeyAllowedUsage allowed_usage; + AddContentKey(c_key); + AddOperatorSessionKey(os_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.IsContentKey(unk_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(unk_key)); + EXPECT_TRUE(license_keys_.MeetsConstraints(unk_key)); + EXPECT_FALSE(license_keys_.GetAllowedUsage(unk_key, &allowed_usage)); +} + +TEST_F(LicenseKeysTest, SigningKey) { + const KeyId c_key = "content_key"; + const KeyId os_key = "op_sess_key"; + const KeyId sign_key = "signing_key"; + CdmKeyAllowedUsage allowed_usage; + AddSigningKey(sign_key); + AddContentKey(c_key); + AddOperatorSessionKey(os_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.IsContentKey(sign_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(sign_key)); + EXPECT_TRUE(license_keys_.MeetsConstraints(sign_key)); + EXPECT_FALSE(license_keys_.GetAllowedUsage(sign_key, &allowed_usage)); +} + +TEST_F(LicenseKeysTest, ContentKey) { + const KeyId c_key = "content_key"; + AddContentKey(c_key); + EXPECT_FALSE(license_keys_.IsContentKey(c_key)); + + license_keys_.SetFromLicense(license_); + EXPECT_TRUE(license_keys_.IsContentKey(c_key)); +} + +TEST_F(LicenseKeysTest, OperatorSessionKey) { + const KeyId os_key = "op_sess_key"; + EXPECT_FALSE(license_keys_.IsContentKey(os_key)); + AddOperatorSessionKey(os_key); + + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.IsContentKey(os_key)); +} + +TEST_F(LicenseKeysTest, CanDecrypt) { + const KeyId os_key = "op_sess_key"; + const KeyId c_key = "content_key"; + EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + AddOperatorSessionKey(os_key); + AddContentKey(c_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + bool new_usable_keys = false; + bool any_change = false; + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + + any_change = license_keys_.ApplyStatusChange(kKeyStatusExpired, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); +} + +TEST_F(LicenseKeysTest, AllowedUsageNull) { + const KeyId os_key = "op_sess_key"; + const KeyId c_key = "content_key"; + const KeyId sign_key = "signing_key"; + AddOperatorSessionKey(os_key); + AddContentKey(c_key); + AddSigningKey(sign_key); + license_keys_.SetFromLicense(license_); + CdmKeyAllowedUsage usage_1; + EXPECT_FALSE(license_keys_.GetAllowedUsage(sign_key, &usage_1)); + + CdmKeyAllowedUsage usage_2; + EXPECT_TRUE(license_keys_.GetAllowedUsage(c_key, &usage_2)); + ExpectAllowedUsageContent(usage_2, kContentClearTrue, kContentSecureTrue); + + CdmKeyAllowedUsage usage_3; + EXPECT_TRUE(license_keys_.GetAllowedUsage(os_key, &usage_3)); + ExpectAllowedUsageContent(usage_3, kContentClearFalse, kContentSecureFalse); +} + +TEST_F(LicenseKeysTest, AllowedUsageContent) { + StageContentKeys(); + CdmKeyAllowedUsage u_sw_crypto; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_sw_crypto, &u_sw_crypto)); + ExpectAllowedUsageContent(u_sw_crypto, kContentSecureTrue, kContentClearTrue); + + CdmKeyAllowedUsage u_sw_decode; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_sw_decode, &u_sw_decode)); + ExpectAllowedUsageContent(u_sw_decode, kContentSecureTrue, kContentClearTrue); + + CdmKeyAllowedUsage u_hw_crypto; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_crypto, &u_hw_crypto)); + ExpectAllowedUsageContent(u_hw_crypto, kContentSecureTrue, kContentClearTrue); + + CdmKeyAllowedUsage u_hw_decode; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_decode, &u_hw_decode)); + ExpectAllowedUsageContent(u_hw_decode, kContentSecureTrue, + kContentClearFalse); + + CdmKeyAllowedUsage u_hw_secure; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_secure, &u_hw_secure)); + ExpectAllowedUsageContent(u_hw_secure, kContentSecureTrue, + kContentClearFalse); +} + +TEST_F(LicenseKeysTest, AllowedUsageOperatorSession) { + StageOperatorSessionKeys(); + CdmKeyAllowedUsage u_encrypt; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_encrypt, &u_encrypt)); + ExpectAllowedUsageOperator(u_encrypt, kEncryptTrue, kDecryptFalse, kSignFalse, + kVerifyFalse); + + CdmKeyAllowedUsage u_decrypt; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_decrypt, &u_decrypt)); + ExpectAllowedUsageOperator(u_decrypt, kEncryptFalse, kDecryptTrue, kSignFalse, + kVerifyFalse); + + CdmKeyAllowedUsage u_sign; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_sign, &u_sign)); + ExpectAllowedUsageOperator(u_sign, kEncryptFalse, kDecryptFalse, kSignTrue, + kVerifyFalse); + + CdmKeyAllowedUsage u_verify; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_verify, &u_verify)); + ExpectAllowedUsageOperator(u_verify, kEncryptFalse, kDecryptFalse, kSignFalse, + kVerifyTrue); + + CdmKeyAllowedUsage u_encrypt_decrypt; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_encrypt_decrypt, + &u_encrypt_decrypt)); + ExpectAllowedUsageOperator(u_encrypt_decrypt, kEncryptTrue, kDecryptTrue, + kSignFalse, kVerifyFalse); + + CdmKeyAllowedUsage u_sign_verify; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_sign_verify, &u_sign_verify)); + ExpectAllowedUsageOperator(u_sign_verify, kEncryptFalse, kDecryptFalse, + kSignTrue, kVerifyTrue); + + CdmKeyAllowedUsage u_all; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_all, &u_all)); + ExpectAllowedUsageOperator(u_all, kEncryptTrue, kDecryptTrue, kSignTrue, + kVerifyTrue); +} + +TEST_F(LicenseKeysTest, ExtractKeyStatuses) { + CdmKeyStatusMap key_status_map; + StageOperatorSessionKeys(); + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(0, key_status_map.size()); + StageContentKeys(); + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusInternalError); +} + +TEST_F(LicenseKeysTest, KeyStatusChanges) { + bool new_usable_keys = false; + bool any_change = false; + CdmKeyStatusMap key_status_map; + StageOperatorSessionKeys(); + StageContentKeys(); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusInternalError); + + // change to pending + any_change = license_keys_.ApplyStatusChange(kKeyStatusPending, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending); + + // change to pending (again) + any_change = license_keys_.ApplyStatusChange(kKeyStatusPending, + &new_usable_keys); + EXPECT_FALSE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending); + + // change to usable + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable); + + // change to usable (again) + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + EXPECT_FALSE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable); + + // change to expired + any_change = license_keys_.ApplyStatusChange(kKeyStatusExpired, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusExpired); +} + +TEST_F(LicenseKeysTest, HdcpChanges) { + bool new_usable_keys = false; + bool any_change = false; + CdmKeyStatusMap key_status_map; + StageHdcpKeys(); + + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ApplyConstraints(100, HDCP_NONE); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + license_keys_.ApplyConstraints(100, HDCP_V1); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_FALSE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + license_keys_.ApplyConstraints(100, HDCP_V2_2); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); + + license_keys_.ApplyConstraints(100, HDCP_NO_DIGITAL_OUTPUT); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusUsable); + + license_keys_.ApplyConstraints(100, HDCP_NONE); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); +} + +TEST_F(LicenseKeysTest, ConstraintChanges) { + bool new_usable_keys = false; + bool any_change = false; + CdmKeyStatusMap key_status_map; + StageConstraintKeys(); + + // No constraints set by device + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + // Low-res device, no HDCP support + license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + // Hi-res device, HDCP_V1 support + license_keys_.ApplyConstraints(dev_hi_res, HDCP_V1); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + // Lo-res device, HDCP V2.2 support + license_keys_.ApplyConstraints(dev_lo_res, HDCP_V2_2); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); + + // Hi-res device, Maximal HDCP support + license_keys_.ApplyConstraints(dev_hi_res, HDCP_NO_DIGITAL_OUTPUT); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusUsable); + + // Lo-res device, no HDCP support + license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); + + // Too-high-res -- all keys rejected + license_keys_.ApplyConstraints(dev_top_res, HDCP_NONE); + any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, + &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); +} + +} // namespace wvcdm diff --git a/core/test/license_request.cpp b/core/test/license_request.cpp index 3c8020ce..0b104998 100644 --- a/core/test/license_request.cpp +++ b/core/test/license_request.cpp @@ -55,30 +55,4 @@ void LicenseRequest::GetDrmMessage(const std::string& response, } } -// Returns heartbeat url in heartbeat_url. -// The heartbeat url is stored as meta data in the response message. -void LicenseRequest::GetHeartbeatUrl(const std::string& response, - std::string& heartbeat_url) { - if (response.empty()) { - heartbeat_url.clear(); - return; - } - - size_t header_end_pos = FindHeaderEndPosition(response); - if (header_end_pos != std::string::npos) { - header_end_pos += kTwoBlankLines.size(); // points to response body - - heartbeat_url.clear(); - size_t heartbeat_url_pos = response.find("Heartbeat-Url: ", header_end_pos); - if (heartbeat_url_pos != std::string::npos) { - heartbeat_url_pos += sizeof("Heartbeat-Url: "); - heartbeat_url.assign(response.substr(heartbeat_url_pos)); - } else { - LOGE("heartbeat url not found"); - } - } else { - LOGE("response body not found"); - } -} - } // namespace wvcdm diff --git a/core/test/license_request.h b/core/test/license_request.h index 8e228dd7..a023d1b1 100644 --- a/core/test/license_request.h +++ b/core/test/license_request.h @@ -17,7 +17,6 @@ class LicenseRequest { ~LicenseRequest() {}; void GetDrmMessage(const std::string& response, std::string& drm_msg); - void GetHeartbeatUrl(const std::string& response, std::string& heartbeat_url); private: size_t FindHeaderEndPosition(const std::string& response) const; diff --git a/core/test/max_res_engine_unittest.cpp b/core/test/max_res_engine_unittest.cpp deleted file mode 100644 index 5d03a659..00000000 --- a/core/test/max_res_engine_unittest.cpp +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. - -#include -#include -#include "crypto_session.h" -#include "license.h" -#include "max_res_engine.h" -#include "mock_clock.h" -#include "scoped_ptr.h" -#include "wv_cdm_types.h" - -namespace wvcdm { - -typedef ::video_widevine_server::sdk::License License; -typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; -typedef ::video_widevine_server::sdk::License::KeyContainer::OutputProtection - OutputProtection; -typedef ::video_widevine_server::sdk::License::KeyContainer:: - VideoResolutionConstraint VideoResolutionConstraint; -typedef ::google::protobuf::RepeatedPtrField KeyList; -typedef ::google::protobuf::RepeatedPtrField - ConstraintList; - -using namespace testing; - -namespace { - -const KeyId kKeyId1 = "357adc89f1673433c36c621f1b5c41ee"; -const KeyId kKeyId2 = "3d25f819250789ecfc9ed48cc99af164"; -const KeyId kKeyId3 = "fe3cf6b69e76c9a1c877922e1a661707"; -const KeyId kKeyId4 = "29a321b9886658078f916fdd41d6f570"; -const KeyId kKeyId5 = "cc5b031bcde371031c06822d935b9a63"; -const KeyId kKeyId6 = "90ac1332e4efc8acbaf929c8d321f50c"; - -const uint32_t kMinRes1 = 0; -const uint32_t kMaxRes1 = 2000; -const uint32_t kTargetRes1 = (kMinRes1 + kMaxRes1) / 2; -const uint32_t kMinRes2 = kMaxRes1; -const uint32_t kMaxRes2 = 4000; -const uint32_t kTargetRes2 = (kMinRes2 + kMaxRes2) / 2; -const uint32_t kTargetRes3 = kMaxRes2 + 1000; - -const OutputProtection::HDCP kHdcpDefault = OutputProtection::HDCP_V2; -const OutputProtection::HDCP kHdcpConstraint = OutputProtection::HDCP_V2_1; - -const int64_t kHdcpInterval = 10; - -class HdcpOnlyMockCryptoSession : public CryptoSession { - public: - MOCK_METHOD2(GetHdcpCapabilities, bool(HdcpCapability*, HdcpCapability*)); -}; - -} // namespace - -ACTION_P2(IncrementAndReturnPointee, p, a) { - *p += a; - return *p; -} - -class MaxResEngineTest : public Test { - protected: - virtual void SetUp() { - mock_clock_ = new NiceMock(); - current_time_ = 0; - - ON_CALL(*mock_clock_, GetCurrentTime()) - .WillByDefault( - IncrementAndReturnPointee(¤t_time_, kHdcpInterval)); - - max_res_engine_.reset(new MaxResEngine(&crypto_session_, mock_clock_)); - - KeyList* keys = license_.mutable_key(); - - // Key 1 - Content key w/ ID, no HDCP, no constraints - { - KeyContainer* key1 = keys->Add(); - key1->set_type(KeyContainer::CONTENT); - key1->set_id(kKeyId1); - } - - // Key 2 - Content key w/ ID, HDCP, no constraints - { - KeyContainer* key2 = keys->Add(); - key2->set_type(KeyContainer::CONTENT); - key2->set_id(kKeyId2); - key2->mutable_required_protection()->set_hdcp(kHdcpDefault); - } - - // Key 3 - Content key w/ ID, no HDCP, constraints - { - KeyContainer* key3 = keys->Add(); - key3->set_type(KeyContainer::CONTENT); - key3->set_id(kKeyId3); - AddConstraints(key3->mutable_video_resolution_constraints()); - } - - // Key 4 - Content key w/ ID, HDCP, constraints - { - KeyContainer* key4 = keys->Add(); - key4->set_type(KeyContainer::CONTENT); - key4->set_id(kKeyId4); - key4->mutable_required_protection()->set_hdcp(kHdcpDefault); - AddConstraints(key4->mutable_video_resolution_constraints()); - } - - // Key 5 - Content key w/o ID, HDCP, constraints - { - KeyContainer* key5 = keys->Add(); - key5->set_type(KeyContainer::CONTENT); - key5->mutable_required_protection()->set_hdcp(kHdcpDefault); - AddConstraints(key5->mutable_video_resolution_constraints()); - } - - // Key 6 - Non-content key - { - KeyContainer* key6 = keys->Add(); - key6->set_type(KeyContainer::OPERATOR_SESSION); - } - } - - void AddConstraints(ConstraintList* constraints) { - // Constraint 1 - Low-res and no HDCP - { - VideoResolutionConstraint* constraint1 = constraints->Add(); - constraint1->set_min_resolution_pixels(kMinRes1); - constraint1->set_max_resolution_pixels(kMaxRes1); - } - - // Constraint 2 - High-res and stricter HDCP - { - VideoResolutionConstraint* constraint2 = constraints->Add(); - constraint2->set_min_resolution_pixels(kMinRes2); - constraint2->set_max_resolution_pixels(kMaxRes2); - constraint2->mutable_required_protection()->set_hdcp(kHdcpConstraint); - } - } - - MockClock* mock_clock_; - int64_t current_time_; - StrictMock crypto_session_; - scoped_ptr max_res_engine_; - License license_; -}; - -TEST_F(MaxResEngineTest, IsPermissiveByDefault) { - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); - - max_res_engine_->OnTimerEvent(); - - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, IsPermissiveWithoutALicense) { - max_res_engine_->SetResolution(1, kTargetRes1); - max_res_engine_->OnTimerEvent(); - - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, IsPermissiveWithoutAResolution) { - max_res_engine_->SetLicense(license_); - max_res_engine_->OnTimerEvent(); - - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, HandlesResolutionsBasedOnConstraints) { - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) - .WillRepeatedly( - DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), - Return(true))); - - max_res_engine_->SetLicense(license_); - - max_res_engine_->SetResolution(1, kTargetRes1); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); - - max_res_engine_->SetResolution(1, kTargetRes2); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); - - max_res_engine_->SetResolution(1, kTargetRes3); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, RequestsHdcpImmediatelyAndOnlyAfterInterval) { - int64_t start_time = current_time_; - - { - InSequence calls; - EXPECT_CALL(*mock_clock_, GetCurrentTime()).WillOnce(Return(start_time)); - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) - .WillOnce( - DoAll(SetArgPointee<0>(HDCP_V2_2), - Return(true))); - EXPECT_CALL(*mock_clock_, GetCurrentTime()) - .WillOnce(Return(start_time + kHdcpInterval / 2)) - .WillOnce(Return(start_time + kHdcpInterval)); - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) - .WillOnce( - DoAll(SetArgPointee<0>(HDCP_V2_2), - Return(true))); - } - - max_res_engine_->SetLicense(license_); - max_res_engine_->SetResolution(1, kTargetRes1); - max_res_engine_->OnTimerEvent(); - max_res_engine_->OnTimerEvent(); - max_res_engine_->OnTimerEvent(); -} - -TEST_F(MaxResEngineTest, DoesNotRequestHdcpWithoutALicense) { - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)).Times(0); - - max_res_engine_->OnTimerEvent(); -} - -TEST_F(MaxResEngineTest, HandlesConstraintOverridingHdcp) { - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) - .WillRepeatedly( - DoAll(SetArgPointee<0>(HDCP_V2), - Return(true))); - - max_res_engine_->SetLicense(license_); - - max_res_engine_->SetResolution(1, kTargetRes1); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); - - max_res_engine_->SetResolution(1, kTargetRes2); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, HandlesNoHdcp) { - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) - .WillRepeatedly( - DoAll(SetArgPointee<0>(HDCP_NONE), - Return(true))); - - max_res_engine_->SetLicense(license_); - - max_res_engine_->SetResolution(1, kTargetRes1); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); - - max_res_engine_->SetResolution(1, kTargetRes2); - max_res_engine_->OnTimerEvent(); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_FALSE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -TEST_F(MaxResEngineTest, IgnoresHdcpWithoutAResolution) { - EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)).Times(0); - - max_res_engine_->SetLicense(license_); - max_res_engine_->OnTimerEvent(); - - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId1)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId2)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId3)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId4)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId5)); - EXPECT_TRUE(max_res_engine_->CanDecrypt(kKeyId6)); -} - -} // wvcdm diff --git a/core/test/policy_engine_constraints_unittest.cpp b/core/test/policy_engine_constraints_unittest.cpp new file mode 100644 index 00000000..44b1956b --- /dev/null +++ b/core/test/policy_engine_constraints_unittest.cpp @@ -0,0 +1,430 @@ +// Copyright 2016 Google Inc. All Rights Reserved. + +#include +#include +#include "crypto_session.h" +#include "license.h" +#include "policy_engine.h" +#include "mock_clock.h" +#include "scoped_ptr.h" +#include "wv_cdm_event_listener.h" +#include "wv_cdm_types.h" + +// protobuf generated classes. +using video_widevine_server::sdk::License; +using video_widevine_server::sdk::License_Policy; +using video_widevine_server::sdk::STREAMING; + +namespace wvcdm { + +typedef ::video_widevine_server::sdk::License License; +typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer; +typedef ::video_widevine_server::sdk::License::KeyContainer::OutputProtection + OutputProtection; +typedef ::video_widevine_server::sdk::License::KeyContainer:: + VideoResolutionConstraint VideoResolutionConstraint; +typedef ::google::protobuf::RepeatedPtrField KeyList; +typedef ::google::protobuf::RepeatedPtrField + ConstraintList; + +using namespace testing; + +namespace { + +const CdmSessionId kSessionId = "mock_session_id"; + +const KeyId kKeyId1 = "1111kKeyId1"; +const KeyId kKeyId2 = "2222kKeyId2"; +const KeyId kKeyId3 = "3333kKeyId3"; +const KeyId kKeyId4 = "4444kKeyId4"; +const KeyId kKeyId5 = "5555kKeyId5"; +const KeyId kKeyId6 = "6666kKeyId6"; + +const uint32_t kMinRes1 = 0; +const uint32_t kMaxRes1 = 2000; +const uint32_t kMinRes2 = kMaxRes1; +const uint32_t kMaxRes2 = 4000; +const uint32_t kTargetRes1 = (kMinRes1 + kMaxRes1) / 2; +const uint32_t kTargetRes2 = (kMinRes2 + kMaxRes2) / 2; +const uint32_t kTargetRes3 = kMaxRes2 + 1000; + +const int64_t kRentalDuration = 604800; // 7 days +const int64_t kPlaybackDuration = 172800; // 48 hours +const int64_t kStreamingLicenseDuration = 300; + +const OutputProtection::HDCP kHdcpV2 = OutputProtection::HDCP_V2; +const OutputProtection::HDCP kHdcpV2_1 = OutputProtection::HDCP_V2_1; + +// should match kHdcpCheckInterval in policy_engine.cpp +const int64_t kHdcpInterval = 10; + +class HdcpOnlyMockCryptoSession : public CryptoSession { + public: + MOCK_METHOD2(GetHdcpCapabilities, bool(HdcpCapability*, HdcpCapability*)); +}; + +class MockCdmEventListener : public WvCdmEventListener { + public: + MOCK_METHOD1(OnSessionRenewalNeeded, void(const CdmSessionId& session_id)); + MOCK_METHOD3(OnSessionKeysChange, void(const CdmSessionId& session_id, + const CdmKeyStatusMap& keys_status, + bool has_new_usable_key)); + MOCK_METHOD2(OnExpirationUpdate, void(const CdmSessionId& session_id, + int64_t new_expiry_time_seconds)); +}; + +} // namespace + +class PolicyEngineConstraintsTest : public Test { + protected: + virtual void SetUp() { + mock_clock_ = new NiceMock(); + current_time_ = 0; + + policy_engine_.reset(new PolicyEngine(kSessionId, &mock_event_listener_, + &crypto_session_)); + InjectMockClock(); + + SetupLicense(); + } + + void AddConstraints(ConstraintList* constraints) { + // Constraint 1 - Low-res and no HDCP + { + VideoResolutionConstraint* c_lo_res_no_hdcp = constraints->Add(); + c_lo_res_no_hdcp->set_min_resolution_pixels(kMinRes1); + c_lo_res_no_hdcp->set_max_resolution_pixels(kMaxRes1); + } + + // Constraint 2 - High-res and stricter HDCP + { + VideoResolutionConstraint* c_hi_res_hdcp_v2 = constraints->Add(); + c_hi_res_hdcp_v2->set_min_resolution_pixels(kMinRes2); + c_hi_res_hdcp_v2->set_max_resolution_pixels(kMaxRes2); + c_hi_res_hdcp_v2->mutable_required_protection()->set_hdcp(kHdcpV2_1); + } + } + + void SetupLicense() { + license_.set_license_start_time(current_time_); + + LicenseIdentification* id = license_.mutable_id(); + id->set_version(1); + id->set_type(STREAMING); + + License_Policy* policy = license_.mutable_policy(); + policy = license_.mutable_policy(); + policy->set_can_play(true); + policy->set_can_persist(false); + policy->set_rental_duration_seconds(kRentalDuration); + policy->set_playback_duration_seconds(kPlaybackDuration); + policy->set_license_duration_seconds(kStreamingLicenseDuration); + + KeyList* keys = license_.mutable_key(); + + // Key 1 - Content key w/ ID, no HDCP, no constraints + { + KeyContainer* key1 = keys->Add(); + key1->set_type(KeyContainer::CONTENT); + key1->set_id(kKeyId1); + } + + // Key 2 - Content key w/ ID, HDCP, no constraints + { + KeyContainer* key2 = keys->Add(); + key2->set_type(KeyContainer::CONTENT); + key2->set_id(kKeyId2); + key2->mutable_required_protection()->set_hdcp(kHdcpV2); + } + + // Key 3 - Content key w/ ID, no HDCP, constraints + { + KeyContainer* key3 = keys->Add(); + key3->set_type(KeyContainer::CONTENT); + key3->set_id(kKeyId3); + AddConstraints(key3->mutable_video_resolution_constraints()); + } + + // Key 4 - Content key w/ ID, HDCP, constraints + { + KeyContainer* key4 = keys->Add(); + key4->set_type(KeyContainer::CONTENT); + key4->set_id(kKeyId4); + key4->mutable_required_protection()->set_hdcp(kHdcpV2); + AddConstraints(key4->mutable_video_resolution_constraints()); + } + + // Key 5 - Content key w/o ID, HDCP, constraints + { + KeyContainer* key5 = keys->Add(); + key5->set_type(KeyContainer::CONTENT); + key5->mutable_required_protection()->set_hdcp(kHdcpV2); + AddConstraints(key5->mutable_video_resolution_constraints()); + } + + // Key 6 - Non-content key + { + KeyContainer* key6 = keys->Add(); + key6->set_type(KeyContainer::OPERATOR_SESSION); + } + } + + void InjectMockClock() { + mock_clock_ = new MockClock(); + policy_engine_->set_clock(mock_clock_); + } + + void ExpectSessionKeysChange(CdmKeyStatus expected_key_status, + bool expected_has_new_usable_key) { + EXPECT_CALL(mock_event_listener_, + OnSessionKeysChange( + kSessionId, UnorderedElementsAre( + Pair(kKeyId1, expected_key_status), + Pair(kKeyId2, expected_key_status), + Pair(kKeyId3, expected_key_status), + Pair(kKeyId4, expected_key_status)), + expected_has_new_usable_key)); + } + + void ExpectSessionKeysChanges(const KeyId& k1, CdmKeyStatus expected_1, + const KeyId& k2, CdmKeyStatus expected_2, + const KeyId& k3, CdmKeyStatus expected_3, + const KeyId& k4, CdmKeyStatus expected_4, + bool expected_has_new_usable_key) { + EXPECT_CALL(mock_event_listener_, + OnSessionKeysChange( + kSessionId, UnorderedElementsAre( + Pair(k1, expected_1), + Pair(k2, expected_2), + Pair(k3, expected_3), + Pair(k4, expected_4)), + expected_has_new_usable_key)); + } + + scoped_ptr policy_engine_; + MockClock* mock_clock_; + int64_t current_time_; + StrictMock crypto_session_; + StrictMock mock_event_listener_; + License license_; +}; + +TEST_F(PolicyEngineConstraintsTest, IsPermissiveWithoutAResolution) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()).Times(2); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + ExpectSessionKeysChange(kKeyStatusUsable, true); + + policy_engine_->SetLicense(license_); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); +} + +TEST_F(PolicyEngineConstraintsTest, HandlesResolutionsBasedOnConstraints) { + { + Sequence time; + for (int i=0; i<4; ++i) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()).InSequence(time) + .WillOnce(Return(i * 10)); + } + } + { + Sequence key_change; + ExpectSessionKeysChange(kKeyStatusUsable, true); + ExpectSessionKeysChanges(kKeyId1, kKeyStatusUsable, + kKeyId2, kKeyStatusUsable, + kKeyId3, kKeyStatusOutputNotAllowed, + kKeyId4, kKeyStatusOutputNotAllowed, false); + } + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), + Return(true))); + + + policy_engine_->SetLicense(license_); + policy_engine_->NotifyResolution(1, kTargetRes1); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); + + policy_engine_->NotifyResolution(1, kTargetRes2); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); + + policy_engine_->NotifyResolution(1, kTargetRes3); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); +} + +TEST_F(PolicyEngineConstraintsTest, + RequestsHdcpImmediatelyAndOnlyAfterInterval) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(0)) + .WillOnce(Return(5)); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + ExpectSessionKeysChange(kKeyStatusUsable, true); + + int64_t start_time = current_time_ + 5; + { + InSequence calls; + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) + .WillOnce( + DoAll(SetArgPointee<0>(HDCP_V2_2), + Return(true))); + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(start_time + kHdcpInterval / 2)) + .WillOnce(Return(start_time + kHdcpInterval)); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) + .WillOnce( + DoAll(SetArgPointee<0>(HDCP_V2_2), + Return(true))); + } + + policy_engine_->NotifyResolution(1, kTargetRes1); + policy_engine_->SetLicense(license_); + policy_engine_->OnTimerEvent(); + policy_engine_->OnTimerEvent(); + policy_engine_->OnTimerEvent(); +} + +TEST_F(PolicyEngineConstraintsTest, DoesNotRequestHdcpWithoutALicense) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(0)); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)).Times(0); + + policy_engine_->OnTimerEvent(); +} + +TEST_F(PolicyEngineConstraintsTest, HandlesConstraintOverridingHdcp) { + { + Sequence time; + for (int i=0; i<3; ++i) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()).InSequence(time) + .WillOnce(Return(i * 10)); + } + } + { + Sequence key_change; + ExpectSessionKeysChange(kKeyStatusUsable, true); + ExpectSessionKeysChanges(kKeyId1, kKeyStatusUsable, + kKeyId2, kKeyStatusUsable, + kKeyId3, kKeyStatusOutputNotAllowed, + kKeyId4, kKeyStatusOutputNotAllowed, false); + } + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(HDCP_V2), + Return(true))); + + policy_engine_->SetLicense(license_); + policy_engine_->NotifyResolution(1, kTargetRes1); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); + + policy_engine_->NotifyResolution(1, kTargetRes2); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); +} + +TEST_F(PolicyEngineConstraintsTest, HandlesNoHdcp) { + { + Sequence time; + for (int i=0; i<3; ++i) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()).InSequence(time) + .WillOnce(Return(i * 10)); + } + } + { + Sequence key_change; + ExpectSessionKeysChanges(kKeyId1, kKeyStatusUsable, + kKeyId2, kKeyStatusOutputNotAllowed, + kKeyId3, kKeyStatusUsable, + kKeyId4, kKeyStatusOutputNotAllowed, false); + ExpectSessionKeysChanges(kKeyId1, kKeyStatusUsable, + kKeyId2, kKeyStatusOutputNotAllowed, + kKeyId3, kKeyStatusOutputNotAllowed, + kKeyId4, kKeyStatusOutputNotAllowed, false); + } + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + ExpectSessionKeysChange(kKeyStatusUsable, true); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(HDCP_NONE), + Return(true))); + + policy_engine_->SetLicense(license_); + + policy_engine_->NotifyResolution(1, kTargetRes1); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); + + policy_engine_->NotifyResolution(1, kTargetRes2); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); +} + +TEST_F(PolicyEngineConstraintsTest, IgnoresHdcpWithoutAResolution) { + { + Sequence time; + for (int i=0; i<2; ++i) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()).InSequence(time) + .WillOnce(Return(i * 10)); + } + } + ExpectSessionKeysChange(kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _)); + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)).Times(0); + + policy_engine_->SetLicense(license_); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId1)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId2)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId3)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId4)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId5)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId6)); +} + +} // namespace wvcdm diff --git a/core/test/policy_engine_unittest.cpp b/core/test/policy_engine_unittest.cpp index 68b30b15..a74dcc3e 100644 --- a/core/test/policy_engine_unittest.cpp +++ b/core/test/policy_engine_unittest.cpp @@ -42,6 +42,7 @@ const char* kRenewalServerUrl = const KeyId kKeyId = "357adc89f1673433c36c621f1b5c41ee"; const KeyId kAnotherKeyId = "another_key_id"; const KeyId kSomeRandomKeyId = "some_random_key_id"; +const KeyId kUnknownKeyId = "some_random_unknown_key_id"; const CdmSessionId kSessionId = "mock_session_id"; int64_t GetLicenseRenewalDelay(int64_t license_duration) { @@ -60,13 +61,6 @@ class MockCdmEventListener : public WvCdmEventListener { int64_t new_expiry_time_seconds)); }; -class MockMaxResEngine : public MaxResEngine { - public: - MockMaxResEngine() : MaxResEngine(NULL) {} - - MOCK_METHOD1(CanDecrypt, bool(const KeyId& key_id)); -}; - } // namespace // protobuf generated classes. @@ -138,11 +132,6 @@ class PolicyEngineTest : public ::testing::Test { policy_engine_->set_clock(mock_clock_); } - void InjectMockMaxResEngine() { - mock_max_res_engine_ = new MockMaxResEngine(); - policy_engine_->set_max_res_engine(mock_max_res_engine_); - } - void ExpectSessionKeysChange(CdmKeyStatus expected_key_status, bool expected_has_new_usable_key) { EXPECT_CALL(mock_event_listener_, @@ -165,7 +154,6 @@ class PolicyEngineTest : public ::testing::Test { StrictMock mock_event_listener_; MockClock* mock_clock_; - MockMaxResEngine* mock_max_res_engine_; scoped_ptr policy_engine_; License license_; MockFunction check_; @@ -1036,56 +1024,204 @@ TEST_F(PolicyEngineTest, MultipleKeysInLicense) { EXPECT_FALSE(policy_engine_->CanDecrypt(kSomeRandomKeyId)); } -TEST_F(PolicyEngineTest, KeysOutputNotAllowedByMaxResEngine) { +class PolicyEngineKeyAllowedUsageTest : public PolicyEngineTest { + protected: + enum KeyFlag { + kKeyFlagNull, + kKeyFlagFalse, + kKeyFlagTrue + }; + + static const KeyFlag kEncryptNull = kKeyFlagNull; + static const KeyFlag kEncryptFalse = kKeyFlagFalse; + static const KeyFlag kEncryptTrue = kKeyFlagTrue; + static const KeyFlag kDecryptNull = kKeyFlagNull; + static const KeyFlag kDecryptFalse = kKeyFlagFalse; + static const KeyFlag kDecryptTrue = kKeyFlagTrue; + static const KeyFlag kSignNull = kKeyFlagNull; + static const KeyFlag kSignFalse = kKeyFlagFalse; + static const KeyFlag kSignTrue = kKeyFlagTrue; + static const KeyFlag kVerifyNull = kKeyFlagNull; + static const KeyFlag kVerifyFalse = kKeyFlagFalse; + static const KeyFlag kVerifyTrue = kKeyFlagTrue; + + static const KeyFlag kContentSecureFalse = kKeyFlagFalse; + static const KeyFlag kContentSecureTrue = kKeyFlagTrue; + static const KeyFlag kContentClearFalse = kKeyFlagFalse; + static const KeyFlag kContentClearTrue = kKeyFlagTrue; + + + void ExpectAllowedContentKeySettings(const CdmKeyAllowedUsage& key_usage, + KeyFlag secure, KeyFlag clear) { + EXPECT_EQ(key_usage.decrypt_to_secure_buffer, secure == kKeyFlagTrue); + EXPECT_EQ(key_usage.decrypt_to_clear_buffer, clear == kKeyFlagTrue); + EXPECT_FALSE(key_usage.generic_encrypt); + EXPECT_FALSE(key_usage.generic_decrypt); + EXPECT_FALSE(key_usage.generic_sign); + EXPECT_FALSE(key_usage.generic_verify); + } + + void ExpectAllowedOperatorKeySettings(const CdmKeyAllowedUsage& key_usage, + KeyFlag encrypt, KeyFlag decrypt, + KeyFlag sign, KeyFlag verify) { + EXPECT_FALSE(key_usage.decrypt_to_secure_buffer); + EXPECT_FALSE(key_usage.decrypt_to_clear_buffer); + EXPECT_EQ(key_usage.generic_encrypt, encrypt == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_decrypt, decrypt == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_sign, sign == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_verify, verify == kKeyFlagTrue); + } + + void ExpectSecureContentKey(const KeyId& key_id) { + CdmKeyAllowedUsage key_usage; + EXPECT_EQ(NO_ERROR, + policy_engine_->QueryKeyAllowedUsage(key_id, &key_usage)); + + ExpectAllowedContentKeySettings(key_usage, kContentSecureTrue, + kContentSecureFalse); + } + + void ExpectLessSecureContentKey(const KeyId& key_id) { + CdmKeyAllowedUsage key_usage; + EXPECT_EQ(NO_ERROR, + policy_engine_->QueryKeyAllowedUsage(key_id, &key_usage)); + + ExpectAllowedContentKeySettings(key_usage, kContentSecureTrue, + kContentSecureTrue); + } + + void ExpectOperatorSessionKey(const KeyId& key_id, KeyFlag encrypt, + KeyFlag decrypt, KeyFlag sign, KeyFlag verify) { + CdmKeyAllowedUsage key_usage; + EXPECT_EQ(NO_ERROR, + policy_engine_->QueryKeyAllowedUsage(key_id, &key_usage)); + + ExpectAllowedOperatorKeySettings(key_usage, encrypt, decrypt, sign, verify); + } + + void AddOperatorSessionKey(const KeyId& key_id, KeyFlag encrypt, + KeyFlag decrypt, KeyFlag sign, KeyFlag verify) { + License::KeyContainer* non_content_key = license_.add_key(); + non_content_key->set_type(License::KeyContainer::OPERATOR_SESSION); + non_content_key->set_id(key_id); + License::KeyContainer::OperatorSessionKeyPermissions* permissions = + non_content_key->mutable_operator_session_key_permissions(); + if (encrypt != kKeyFlagNull) { + permissions->set_allow_encrypt(encrypt == kKeyFlagTrue); + } + if (decrypt != kKeyFlagNull) { + permissions->set_allow_decrypt(decrypt == kKeyFlagTrue); + } + if (sign != kKeyFlagNull) { + permissions->set_allow_sign(sign == kKeyFlagTrue); + } + if (verify != kKeyFlagNull) { + permissions->set_allow_signature_verify(verify == kKeyFlagTrue); + } + } +}; + +TEST_F(PolicyEngineKeyAllowedUsageTest, AllowedUsageBasic) { + + const KeyId kGenericKeyId = "oper_session_key"; + license_.clear_key(); + + // most secure License::KeyContainer* content_key = license_.add_key(); content_key->set_type(License::KeyContainer::CONTENT); content_key->set_id(kKeyId); + content_key->set_level(License::KeyContainer::HW_SECURE_ALL); + + // generic operator session key (sign) + AddOperatorSessionKey(kGenericKeyId, kEncryptNull, kDecryptNull, kSignTrue, + kVerifyNull); + + License::KeyContainer* content_key_without_id = license_.add_key(); + content_key_without_id->set_type(License::KeyContainer::CONTENT); + + // default level - less secure License::KeyContainer* another_content_key = license_.add_key(); another_content_key->set_type(License::KeyContainer::CONTENT); another_content_key->set_id(kAnotherKeyId); EXPECT_CALL(*mock_clock_, GetCurrentTime()) - .WillOnce(Return(kLicenseStartTime + 1)) - .WillOnce(Return(kLicenseStartTime + 5)) - .WillOnce(Return(kLicenseStartTime + 8)) - .WillOnce(Return(kLicenseStartTime + 10)); + .WillOnce(Return(kLicenseStartTime + 1)); - InjectMockMaxResEngine(); - EXPECT_CALL(*mock_max_res_engine_, CanDecrypt(kKeyId)) - .WillOnce(Return(false)) - .WillOnce(Return(true)) - .WillOnce(Return(false)) - .WillOnce(Return(true)); - EXPECT_CALL(*mock_max_res_engine_, CanDecrypt(kAnotherKeyId)) - .WillOnce(Return(false)) - .WillOnce(Return(false)) - .WillOnce(Return(true)) - .WillOnce(Return(true)); - - InSequence s; - ExpectSessionKeysChange(kKeyStatusOutputNotAllowed, - kKeyStatusOutputNotAllowed, false); - EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, _)); - ExpectSessionKeysChange(kKeyStatusUsable, kKeyStatusOutputNotAllowed, true); - ExpectSessionKeysChange(kKeyStatusOutputNotAllowed, kKeyStatusUsable, true); ExpectSessionKeysChange(kKeyStatusUsable, kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, _)); policy_engine_->SetLicense(license_); - EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId)); - EXPECT_FALSE(policy_engine_->CanDecrypt(kAnotherKeyId)); - policy_engine_->OnTimerEvent(); - EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId)); - EXPECT_FALSE(policy_engine_->CanDecrypt(kAnotherKeyId)); + ExpectSecureContentKey(kKeyId); + ExpectLessSecureContentKey(kAnotherKeyId); + ExpectOperatorSessionKey(kGenericKeyId, kEncryptNull, kDecryptNull, + kSignTrue, kVerifyNull); - policy_engine_->OnTimerEvent(); - EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId)); - EXPECT_TRUE(policy_engine_->CanDecrypt(kAnotherKeyId)); + CdmKeyAllowedUsage key_usage; + EXPECT_EQ(KEY_NOT_FOUND_1, + policy_engine_->QueryKeyAllowedUsage(kUnknownKeyId, &key_usage)); +} - policy_engine_->OnTimerEvent(); - EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId)); - EXPECT_TRUE(policy_engine_->CanDecrypt(kAnotherKeyId)); +TEST_F(PolicyEngineKeyAllowedUsageTest, AllowedUsageGeneric) { + + const KeyId kGenericEncryptKeyId = "oper_session_key_1"; + const KeyId kGenericDecryptKeyId = "oper_session_key_2"; + const KeyId kGenericSignKeyId = "oper_session_key_3"; + const KeyId kGenericVerifyKeyId = "oper_session_key_4"; + const KeyId kGenericFullKeyId = "oper_session_key_5"; + const KeyId kGenericExplicitKeyId = "oper_session_key_6"; + + license_.clear_key(); + + // more secure + License::KeyContainer* content_key = license_.add_key(); + content_key->set_type(License::KeyContainer::CONTENT); + content_key->set_id(kKeyId); + content_key->set_level(License::KeyContainer::HW_SECURE_DECODE); + + // less secure + License::KeyContainer* another_content_key = license_.add_key(); + another_content_key->set_type(License::KeyContainer::CONTENT); + another_content_key->set_id(kAnotherKeyId); + another_content_key->set_level(License::KeyContainer::HW_SECURE_CRYPTO); + + // generic operator session keys + AddOperatorSessionKey(kGenericSignKeyId, kEncryptNull, kDecryptNull, + kSignTrue, kVerifyNull); + AddOperatorSessionKey(kGenericEncryptKeyId, kEncryptTrue, kDecryptNull, + kSignNull, kVerifyNull); + AddOperatorSessionKey(kGenericDecryptKeyId, kEncryptNull, kDecryptTrue, + kSignNull, kVerifyNull); + AddOperatorSessionKey(kGenericVerifyKeyId, kEncryptNull, kDecryptNull, + kSignNull, kVerifyTrue); + AddOperatorSessionKey(kGenericFullKeyId, kEncryptTrue, kDecryptTrue, + kSignTrue, kVerifyTrue); + AddOperatorSessionKey(kGenericExplicitKeyId, kEncryptFalse, kDecryptTrue, + kSignFalse, kVerifyTrue); + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)); + + ExpectSessionKeysChange(kKeyStatusUsable, kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, _)); + + policy_engine_->SetLicense(license_); + + ExpectSecureContentKey(kKeyId); + ExpectLessSecureContentKey(kAnotherKeyId); + ExpectOperatorSessionKey(kGenericEncryptKeyId, kEncryptTrue, kDecryptFalse, + kSignFalse, kVerifyFalse); + ExpectOperatorSessionKey(kGenericDecryptKeyId, kEncryptFalse, kDecryptTrue, + kSignFalse, kVerifyFalse); + ExpectOperatorSessionKey(kGenericSignKeyId, kEncryptFalse, kDecryptFalse, + kSignTrue, kVerifyFalse); + ExpectOperatorSessionKey(kGenericVerifyKeyId, kEncryptFalse, kDecryptFalse, + kSignFalse, kVerifyTrue); + ExpectOperatorSessionKey(kGenericFullKeyId, kEncryptTrue, kDecryptTrue, + kSignTrue, kVerifyTrue); + ExpectOperatorSessionKey(kGenericExplicitKeyId, kEncryptFalse, kDecryptTrue, + kSignFalse, kVerifyTrue); } class PolicyEngineQueryTest : public PolicyEngineTest { diff --git a/core/test/test_printers.cpp b/core/test/test_printers.cpp index d77d984a..2d470d44 100644 --- a/core/test/test_printers.cpp +++ b/core/test/test_printers.cpp @@ -26,59 +26,74 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case DEVICE_REVOKED: *os << "DEVICE_REVOKED"; break; - case INSUFFICIENT_CRYPTO_RESOURCES: *os << "INSUFFICIENT_CRYPTO_RESOURCES"; + case INSUFFICIENT_CRYPTO_RESOURCES: *os << "INSUFFICIENT_CRYPTO_RESOURCES"; break; case ADD_KEY_ERROR: *os << "ADD_KEY_ERROR"; break; - case CERT_PROVISIONING_GET_KEYBOX_ERROR_1: *os << "CERT_PROVISIONING_GET_KEYBOX_ERROR_1"; + case CERT_PROVISIONING_GET_KEYBOX_ERROR_1: + *os << "CERT_PROVISIONING_GET_KEYBOX_ERROR_1"; break; - case CERT_PROVISIONING_GET_KEYBOX_ERROR_2: *os << "CERT_PROVISIONING_GET_KEYBOX_ERROR_2"; + case CERT_PROVISIONING_GET_KEYBOX_ERROR_2: + *os << "CERT_PROVISIONING_GET_KEYBOX_ERROR_2"; break; - case CERT_PROVISIONING_INVALID_CERT_TYPE: *os << "CERT_PROVISIONING_INVALID_CERT_TYPE"; + case CERT_PROVISIONING_INVALID_CERT_TYPE: + *os << "CERT_PROVISIONING_INVALID_CERT_TYPE"; break; - case CERT_PROVISIONING_REQUEST_ERROR_1: *os << "CERT_PROVISIONING_REQUEST_ERROR_1"; + case CERT_PROVISIONING_REQUEST_ERROR_1: + *os << "CERT_PROVISIONING_REQUEST_ERROR_1"; break; - case CERT_PROVISIONING_REQUEST_ERROR_2: *os << "CERT_PROVISIONING_REQUEST_ERROR_2"; + case CERT_PROVISIONING_REQUEST_ERROR_2: + *os << "CERT_PROVISIONING_REQUEST_ERROR_2"; break; - case CERT_PROVISIONING_REQUEST_ERROR_3: *os << "CERT_PROVISIONING_REQUEST_ERROR_3"; + case CERT_PROVISIONING_REQUEST_ERROR_3: + *os << "CERT_PROVISIONING_REQUEST_ERROR_3"; break; - case CERT_PROVISIONING_REQUEST_ERROR_4: *os << "CERT_PROVISIONING_REQUEST_ERROR_4"; + case CERT_PROVISIONING_REQUEST_ERROR_4: + *os << "CERT_PROVISIONING_REQUEST_ERROR_4"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_1: *os << "CERT_PROVISIONING_RESPONSE_ERROR_1"; + case CERT_PROVISIONING_RESPONSE_ERROR_1: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_1"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_2: *os << "CERT_PROVISIONING_RESPONSE_ERROR_2"; + case CERT_PROVISIONING_RESPONSE_ERROR_2: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_2"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_3: *os << "CERT_PROVISIONING_RESPONSE_ERROR_3"; + case CERT_PROVISIONING_RESPONSE_ERROR_3: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_3"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_4: *os << "CERT_PROVISIONING_RESPONSE_ERROR_4"; + case CERT_PROVISIONING_RESPONSE_ERROR_4: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_4"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_5: *os << "CERT_PROVISIONING_RESPONSE_ERROR_5"; + case CERT_PROVISIONING_RESPONSE_ERROR_5: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_5"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_6: *os << "CERT_PROVISIONING_RESPONSE_ERROR_6"; + case CERT_PROVISIONING_RESPONSE_ERROR_6: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_6"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_7: *os << "CERT_PROVISIONING_RESPONSE_ERROR_7"; + case CERT_PROVISIONING_RESPONSE_ERROR_7: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_7"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_8: *os << "CERT_PROVISIONING_RESPONSE_ERROR_8"; + case CERT_PROVISIONING_RESPONSE_ERROR_8: + *os << "CERT_PROVISIONING_RESPONSE_ERROR_8"; break; - case CRYPTO_SESSION_OPEN_ERROR_1: *os << "CRYPTO_SESSION_OPEN_ERROR_1"; + case CRYPTO_SESSION_OPEN_ERROR_1: *os << "CRYPTO_SESSION_OPEN_ERROR_1"; break; - case CRYPTO_SESSION_OPEN_ERROR_2: *os << "CRYPTO_SESSION_OPEN_ERROR_2"; + case CRYPTO_SESSION_OPEN_ERROR_2: *os << "CRYPTO_SESSION_OPEN_ERROR_2"; break; - case CRYPTO_SESSION_OPEN_ERROR_3: *os << "CRYPTO_SESSION_OPEN_ERROR_3"; + case CRYPTO_SESSION_OPEN_ERROR_3: *os << "CRYPTO_SESSION_OPEN_ERROR_3"; break; - case CRYPTO_SESSION_OPEN_ERROR_4: *os << "CRYPTO_SESSION_OPEN_ERROR_4"; + case CRYPTO_SESSION_OPEN_ERROR_4: *os << "CRYPTO_SESSION_OPEN_ERROR_4"; break; - case CRYPTO_SESSION_OPEN_ERROR_5: *os << "CRYPTO_SESSION_OPEN_ERROR_5"; + case CRYPTO_SESSION_OPEN_ERROR_5: *os << "CRYPTO_SESSION_OPEN_ERROR_5"; break; case DECRYPT_NOT_READY: *os << "DECRYPT_NOT_READY"; break; - case DEVICE_CERTIFICATE_ERROR_1: *os << "DEVICE_CERTIFICATE_ERROR_1"; + case DEVICE_CERTIFICATE_ERROR_1: *os << "DEVICE_CERTIFICATE_ERROR_1"; break; - case DEVICE_CERTIFICATE_ERROR_2: *os << "DEVICE_CERTIFICATE_ERROR_2"; + case DEVICE_CERTIFICATE_ERROR_2: *os << "DEVICE_CERTIFICATE_ERROR_2"; break; - case DEVICE_CERTIFICATE_ERROR_3: *os << "DEVICE_CERTIFICATE_ERROR_3"; + case DEVICE_CERTIFICATE_ERROR_3: *os << "DEVICE_CERTIFICATE_ERROR_3"; break; - case DEVICE_CERTIFICATE_ERROR_4: *os << "DEVICE_CERTIFICATE_ERROR_4"; + case DEVICE_CERTIFICATE_ERROR_4: *os << "DEVICE_CERTIFICATE_ERROR_4"; break; case EMPTY_KEY_DATA_1: *os << "EMPTY_KEY_DATA_1"; break; @@ -96,25 +111,27 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case EMPTY_LICENSE_RENEWAL: *os << "EMPTY_LICENSE_RENEWAL"; break; - case EMPTY_LICENSE_RESPONSE_1: *os << "EMPTY_LICENSE_RESPONSE_1"; + case EMPTY_LICENSE_RESPONSE_1: *os << "EMPTY_LICENSE_RESPONSE_1"; break; - case EMPTY_LICENSE_RESPONSE_2: *os << "EMPTY_LICENSE_RESPONSE_2"; + case EMPTY_LICENSE_RESPONSE_2: *os << "EMPTY_LICENSE_RESPONSE_2"; break; - case EMPTY_PROVISIONING_CERTIFICATE_1: *os << "EMPTY_PROVISIONING_CERTIFICATE_1"; + case EMPTY_PROVISIONING_CERTIFICATE_1: + *os << "EMPTY_PROVISIONING_CERTIFICATE_1"; break; - case EMPTY_PROVISIONING_RESPONSE: *os << "EMPTY_PROVISIONING_RESPONSE"; + case EMPTY_PROVISIONING_RESPONSE: *os << "EMPTY_PROVISIONING_RESPONSE"; break; case EMPTY_SESSION_ID: *os << "EMPTY_SESSION_ID"; break; - case GENERATE_DERIVED_KEYS_ERROR: *os << "GENERATE_DERIVED_KEYS_ERROR"; + case GENERATE_DERIVED_KEYS_ERROR: *os << "GENERATE_DERIVED_KEYS_ERROR"; break; - case LICENSE_RENEWAL_NONCE_GENERATION_ERROR: *os << "LICENSE_RENEWAL_NONCE_GENERATION_ERROR"; + case LICENSE_RENEWAL_NONCE_GENERATION_ERROR: + *os << "LICENSE_RENEWAL_NONCE_GENERATION_ERROR"; break; - case GENERATE_USAGE_REPORT_ERROR: *os << "GENERATE_USAGE_REPORT_ERROR"; + case GENERATE_USAGE_REPORT_ERROR: *os << "GENERATE_USAGE_REPORT_ERROR"; break; case GET_LICENSE_ERROR: *os << "GET_LICENSE_ERROR"; break; - case GET_RELEASED_LICENSE_ERROR: *os << "GET_RELEASED_LICENSE_ERROR"; + case GET_RELEASED_LICENSE_ERROR: *os << "GET_RELEASED_LICENSE_ERROR"; break; case GET_USAGE_INFO_ERROR_1: *os << "GET_USAGE_INFO_ERROR_1"; break; @@ -126,51 +143,60 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case INIT_DATA_NOT_FOUND: *os << "INIT_DATA_NOT_FOUND"; break; - case INVALID_CRYPTO_SESSION_1: *os << "INVALID_CRYPTO_SESSION_1"; + case INVALID_CRYPTO_SESSION_1: *os << "INVALID_CRYPTO_SESSION_1"; break; - case INVALID_CRYPTO_SESSION_2: *os << "INVALID_CRYPTO_SESSION_2"; + case INVALID_CRYPTO_SESSION_2: *os << "INVALID_CRYPTO_SESSION_2"; break; - case INVALID_CRYPTO_SESSION_3: *os << "INVALID_CRYPTO_SESSION_3"; + case INVALID_CRYPTO_SESSION_3: *os << "INVALID_CRYPTO_SESSION_3"; break; - case INVALID_CRYPTO_SESSION_4: *os << "INVALID_CRYPTO_SESSION_4"; + case INVALID_CRYPTO_SESSION_4: *os << "INVALID_CRYPTO_SESSION_4"; break; - case INVALID_CRYPTO_SESSION_5: *os << "INVALID_CRYPTO_SESSION_5"; + case INVALID_CRYPTO_SESSION_5: *os << "INVALID_CRYPTO_SESSION_5"; break; - case INVALID_DECRYPT_PARAMETERS_ENG_1: *os << "INVALID_DECRYPT_PARAMETERS_ENG_1"; + case INVALID_DECRYPT_PARAMETERS_ENG_1: + *os << "INVALID_DECRYPT_PARAMETERS_ENG_1"; break; - case INVALID_DECRYPT_PARAMETERS_ENG_2: *os << "INVALID_DECRYPT_PARAMETERS_ENG_2"; + case INVALID_DECRYPT_PARAMETERS_ENG_2: + *os << "INVALID_DECRYPT_PARAMETERS_ENG_2"; break; - case INVALID_DECRYPT_PARAMETERS_ENG_3: *os << "INVALID_DECRYPT_PARAMETERS_ENG_3"; + case INVALID_DECRYPT_PARAMETERS_ENG_3: + *os << "INVALID_DECRYPT_PARAMETERS_ENG_3"; break; - case INVALID_DECRYPT_PARAMETERS_ENG_4: *os << "INVALID_DECRYPT_PARAMETERS_ENG_4"; + case INVALID_DECRYPT_PARAMETERS_ENG_4: + *os << "INVALID_DECRYPT_PARAMETERS_ENG_4"; break; - case INVALID_DEVICE_CERTIFICATE_TYPE: *os << "INVALID_DEVICE_CERTIFICATE_TYPE"; + case INVALID_DEVICE_CERTIFICATE_TYPE: + *os << "INVALID_DEVICE_CERTIFICATE_TYPE"; break; case INVALID_KEY_SYSTEM: *os << "INVALID_KEY_SYSTEM"; break; - case INVALID_LICENSE_RESPONSE: *os << "INVALID_LICENSE_RESPONSE"; + case INVALID_LICENSE_RESPONSE: *os << "INVALID_LICENSE_RESPONSE"; break; case INVALID_LICENSE_TYPE: *os << "INVALID_LICENSE_TYPE"; break; - case INVALID_PARAMETERS_ENG_1: *os << "INVALID_PARAMETERS_ENG_1"; + case INVALID_PARAMETERS_ENG_1: *os << "INVALID_PARAMETERS_ENG_1"; break; - case INVALID_PARAMETERS_ENG_2: *os << "INVALID_PARAMETERS_ENG_2"; + case INVALID_PARAMETERS_ENG_2: *os << "INVALID_PARAMETERS_ENG_2"; break; - case INVALID_PARAMETERS_ENG_3: *os << "INVALID_PARAMETERS_ENG_3"; + case INVALID_PARAMETERS_ENG_3: *os << "INVALID_PARAMETERS_ENG_3"; break; - case INVALID_PARAMETERS_ENG_4: *os << "INVALID_PARAMETERS_ENG_4"; + case INVALID_PARAMETERS_ENG_4: *os << "INVALID_PARAMETERS_ENG_4"; break; - case INVALID_PARAMETERS_LIC_1: *os << "INVALID_PARAMETERS_LIC_1"; + case INVALID_PARAMETERS_LIC_1: *os << "INVALID_PARAMETERS_LIC_1"; break; - case INVALID_PARAMETERS_LIC_2: *os << "INVALID_PARAMETERS_LIC_2"; + case INVALID_PARAMETERS_LIC_2: *os << "INVALID_PARAMETERS_LIC_2"; break; - case INVALID_PROVISIONING_PARAMETERS_1: *os << "INVALID_PROVISIONING_PARAMETERS_1"; + case INVALID_PROVISIONING_PARAMETERS_1: + *os << "INVALID_PROVISIONING_PARAMETERS_1"; break; - case INVALID_PROVISIONING_PARAMETERS_2: *os << "INVALID_PROVISIONING_PARAMETERS_2"; + case INVALID_PROVISIONING_PARAMETERS_2: + *os << "INVALID_PROVISIONING_PARAMETERS_2"; break; - case INVALID_PROVISIONING_REQUEST_PARAM_1: *os << "INVALID_PROVISIONING_REQUEST_PARAM_1"; + case INVALID_PROVISIONING_REQUEST_PARAM_1: + *os << "INVALID_PROVISIONING_REQUEST_PARAM_1"; break; - case INVALID_PROVISIONING_REQUEST_PARAM_2: *os << "INVALID_PROVISIONING_REQUEST_PARAM_2"; + case INVALID_PROVISIONING_REQUEST_PARAM_2: + *os << "INVALID_PROVISIONING_REQUEST_PARAM_2"; break; case INVALID_QUERY_KEY: *os << "INVALID_QUERY_KEY"; break; @@ -188,22 +214,28 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case LICENSE_ID_NOT_FOUND: *os << "LICENSE_ID_NOT_FOUND"; break; - case LICENSE_PARSER_INIT_ERROR: *os << "LICENSE_PARSER_INIT_ERROR"; + case LICENSE_PARSER_INIT_ERROR: *os << "LICENSE_PARSER_INIT_ERROR"; break; - case LICENSE_PARSER_NOT_INITIALIZED_1: *os << "LICENSE_PARSER_NOT_INITIALIZED_1"; + case LICENSE_PARSER_NOT_INITIALIZED_1: + *os << "LICENSE_PARSER_NOT_INITIALIZED_1"; break; - case LICENSE_PARSER_NOT_INITIALIZED_2: *os << "LICENSE_PARSER_NOT_INITIALIZED_2"; + case LICENSE_PARSER_NOT_INITIALIZED_2: + *os << "LICENSE_PARSER_NOT_INITIALIZED_2"; break; - case LICENSE_PARSER_NOT_INITIALIZED_3: *os << "LICENSE_PARSER_NOT_INITIALIZED_3"; + case LICENSE_PARSER_NOT_INITIALIZED_3: + *os << "LICENSE_PARSER_NOT_INITIALIZED_3"; break; - case LICENSE_RESPONSE_NOT_SIGNED: *os << "LICENSE_RESPONSE_NOT_SIGNED"; + case LICENSE_RESPONSE_NOT_SIGNED: *os << "LICENSE_RESPONSE_NOT_SIGNED"; break; break; - case LICENSE_RESPONSE_PARSE_ERROR_1: *os << "LICENSE_RESPONSE_PARSE_ERROR_1"; + case LICENSE_RESPONSE_PARSE_ERROR_1: + *os << "LICENSE_RESPONSE_PARSE_ERROR_1"; break; - case LICENSE_RESPONSE_PARSE_ERROR_2: *os << "LICENSE_RESPONSE_PARSE_ERROR_2"; + case LICENSE_RESPONSE_PARSE_ERROR_2: + *os << "LICENSE_RESPONSE_PARSE_ERROR_2"; break; - case LICENSE_RESPONSE_PARSE_ERROR_3: *os << "LICENSE_RESPONSE_PARSE_ERROR_3"; + case LICENSE_RESPONSE_PARSE_ERROR_3: + *os << "LICENSE_RESPONSE_PARSE_ERROR_3"; break; case LOAD_KEY_ERROR: *os << "LOAD_KEY_ERROR"; break; @@ -211,35 +243,34 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case REFRESH_KEYS_ERROR: *os << "REFRESH_KEYS_ERROR"; break; - case RELEASE_ALL_USAGE_INFO_ERROR_1: *os << "RELEASE_ALL_USAGE_INFO_ERROR_1"; + case RELEASE_ALL_USAGE_INFO_ERROR_1: + *os << "RELEASE_ALL_USAGE_INFO_ERROR_1"; break; - case RELEASE_ALL_USAGE_INFO_ERROR_2: *os << "RELEASE_ALL_USAGE_INFO_ERROR_2"; + case RELEASE_ALL_USAGE_INFO_ERROR_2: + *os << "RELEASE_ALL_USAGE_INFO_ERROR_2"; break; case RELEASE_KEY_ERROR: *os << "RELEASE_KEY_ERROR"; break; - case RELEASE_KEY_REQUEST_ERROR: *os << "RELEASE_KEY_REQUEST_ERROR"; + case RELEASE_KEY_REQUEST_ERROR: *os << "RELEASE_KEY_REQUEST_ERROR"; break; case RELEASE_LICENSE_ERROR_1: *os << "RELEASE_LICENSE_ERROR_1"; break; case RELEASE_LICENSE_ERROR_2: *os << "RELEASE_LICENSE_ERROR_2"; break; - case RELEASE_USAGE_INFO_ERROR: *os << "RELEASE_USAGE_INFO_ERROR"; + case RELEASE_USAGE_INFO_ERROR: *os << "RELEASE_USAGE_INFO_ERROR"; break; case RENEW_KEY_ERROR_1: *os << "RENEW_KEY_ERROR_1"; break; case RENEW_KEY_ERROR_2: *os << "RENEW_KEY_ERROR_2"; break; - case LICENSE_RENEWAL_SIGNING_ERROR: *os << "LICENSE_RENEWAL_SIGNING_ERROR"; + case LICENSE_RENEWAL_SIGNING_ERROR: *os << "LICENSE_RENEWAL_SIGNING_ERROR"; break; - case RESTORE_OFFLINE_LICENSE_ERROR_1: *os << "RESTORE_OFFLINE_LICENSE_ERROR_1"; - break; - case RESTORE_OFFLINE_LICENSE_ERROR_2: *os << "RESTORE_OFFLINE_LICENSE_ERROR_2"; - break; - case SESSION_INIT_ERROR_1: *os << "SESSION_INIT_ERROR_1"; + case RESTORE_OFFLINE_LICENSE_ERROR_2: + *os << "RESTORE_OFFLINE_LICENSE_ERROR_2"; break; case SESSION_INIT_ERROR_2: *os << "SESSION_INIT_ERROR_2"; break; - case SESSION_INIT_GET_KEYBOX_ERROR: *os << "SESSION_INIT_GET_KEYBOX_ERROR"; + case SESSION_INIT_GET_KEYBOX_ERROR: *os << "SESSION_INIT_GET_KEYBOX_ERROR"; break; case SESSION_NOT_FOUND_1: *os << "SESSION_NOT_FOUND_1"; break; @@ -261,7 +292,7 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case SESSION_NOT_FOUND_10: *os << "SESSION_NOT_FOUND_10"; break; - case SESSION_NOT_FOUND_FOR_DECRYPT: *os << "SESSION_NOT_FOUND_FOR_DECRYPT"; + case SESSION_NOT_FOUND_FOR_DECRYPT: *os << "SESSION_NOT_FOUND_FOR_DECRYPT"; break; case SESSION_KEYS_NOT_FOUND: *os << "SESSION_KEYS_NOT_FOUND"; break; @@ -271,8 +302,6 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case STORE_LICENSE_ERROR_2: *os << "STORE_LICENSE_ERROR_2"; break; - case STORE_LICENSE_ERROR_3: *os << "STORE_LICENSE_ERROR_3"; - break; case STORE_USAGE_INFO_ERROR: *os << "STORE_USAGE_INFO_ERROR"; break; case UNPROVISION_ERROR_1: *os << "UNPROVISION_ERROR_1"; @@ -290,58 +319,134 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case LICENSE_RENEWAL_SERVICE_CERTIFICATE_GENERATION_ERROR: *os << "LICENSE_RENEWAL_SERVICE_CERTIFICATE_GENERATION_ERROR"; break; - case EMPTY_PROVISIONING_CERTIFICATE_2: *os << "EMPTY_PROVISIONING_CERTIFICATE_2"; + case EMPTY_PROVISIONING_CERTIFICATE_2: + *os << "EMPTY_PROVISIONING_CERTIFICATE_2"; break; - case PARSE_SERVICE_CERTIFICATE_ERROR: *os << "PARSE_SERVICE_CERTIFICATE_ERROR"; + case PARSE_SERVICE_CERTIFICATE_ERROR: + *os << "PARSE_SERVICE_CERTIFICATE_ERROR"; break; - case SERVICE_CERTIFICATE_TYPE_ERROR: *os << "SERVICE_CERTIFICATE_TYPE_ERROR"; + case SERVICE_CERTIFICATE_TYPE_ERROR: + *os << "SERVICE_CERTIFICATE_TYPE_ERROR"; break; - case CLIENT_ID_GENERATE_RANDOM_ERROR: *os << "CLIENT_ID_GENERATE_RANDOM_ERROR"; + case CLIENT_ID_GENERATE_RANDOM_ERROR: + *os << "CLIENT_ID_GENERATE_RANDOM_ERROR"; break; - case CLIENT_ID_AES_INIT_ERROR: *os << "CLIENT_ID_AES_INIT_ERROR"; + case CLIENT_ID_AES_INIT_ERROR: *os << "CLIENT_ID_AES_INIT_ERROR"; break; - case CLIENT_ID_AES_ENCRYPT_ERROR: *os << "CLIENT_ID_AES_ENCRYPT_ERROR"; + case CLIENT_ID_AES_ENCRYPT_ERROR: *os << "CLIENT_ID_AES_ENCRYPT_ERROR"; break; - case CLIENT_ID_RSA_INIT_ERROR: *os << "CLIENT_ID_RSA_INIT_ERROR"; + case CLIENT_ID_RSA_INIT_ERROR: *os << "CLIENT_ID_RSA_INIT_ERROR"; break; - case CLIENT_ID_RSA_ENCRYPT_ERROR: *os << "CLIENT_ID_RSA_ENCRYPT_ERROR"; + case CLIENT_ID_RSA_ENCRYPT_ERROR: *os << "CLIENT_ID_RSA_ENCRYPT_ERROR"; break; case INVALID_QUERY_STATUS: *os << "INVALID_QUERY_STATUS"; break; - case LICENSE_PARSER_NOT_INITIALIZED_4: *os << "LICENSE_PARSER_NOT_INITIALIZED_4"; + case LICENSE_PARSER_NOT_INITIALIZED_4: + *os << "LICENSE_PARSER_NOT_INITIALIZED_4"; break; - case INVALID_PARAMETERS_LIC_3: *os << "INVALID_PARAMETERS_LIC_3"; + case INVALID_PARAMETERS_LIC_3: *os << "INVALID_PARAMETERS_LIC_3"; break; - case INVALID_PARAMETERS_LIC_4: *os << "INVALID_PARAMETERS_LIC_4"; + case INVALID_PARAMETERS_LIC_4: *os << "INVALID_PARAMETERS_LIC_4"; break; - case INVALID_PARAMETERS_LIC_6: *os << "INVALID_PARAMETERS_LIC_6"; + case INVALID_PARAMETERS_LIC_6: *os << "INVALID_PARAMETERS_LIC_6"; break; - case INVALID_PARAMETERS_LIC_7: *os << "INVALID_PARAMETERS_LIC_7"; + case INVALID_PARAMETERS_LIC_7: *os << "INVALID_PARAMETERS_LIC_7"; break; case LICENSE_REQUEST_SERVICE_CERTIFICATE_GENERATION_ERROR: *os << "LICENSE_REQUEST_SERVICE_CERTIFICATE_GENERATION_ERROR"; break; - case CENC_INIT_DATA_UNAVAILABLE: *os << "CENC_INIT_DATA_UNAVAILABLE"; + case CENC_INIT_DATA_UNAVAILABLE: *os << "CENC_INIT_DATA_UNAVAILABLE"; break; - case PREPARE_CENC_CONTENT_ID_FAILED: *os << "PREPARE_CENC_CONTENT_ID_FAILED"; + case PREPARE_CENC_CONTENT_ID_FAILED: + *os << "PREPARE_CENC_CONTENT_ID_FAILED"; break; - case WEBM_INIT_DATA_UNAVAILABLE: *os << "WEBM_INIT_DATA_UNAVAILABLE"; + case WEBM_INIT_DATA_UNAVAILABLE: *os << "WEBM_INIT_DATA_UNAVAILABLE"; break; - case PREPARE_WEBM_CONTENT_ID_FAILED: *os << "PREPARE_WEBM_CONTENT_ID_FAILED"; + case PREPARE_WEBM_CONTENT_ID_FAILED: + *os << "PREPARE_WEBM_CONTENT_ID_FAILED"; break; - case UNSUPPORTED_INIT_DATA_FORMAT: *os << "UNSUPPORTED_INIT_DATA_FORMAT"; + case UNSUPPORTED_INIT_DATA_FORMAT: *os << "UNSUPPORTED_INIT_DATA_FORMAT"; break; - case LICENSE_REQUEST_NONCE_GENERATION_ERROR: *os << "LICENSE_REQUEST_NONCE_GENERATION_ERROR"; + case LICENSE_REQUEST_NONCE_GENERATION_ERROR: + *os << "LICENSE_REQUEST_NONCE_GENERATION_ERROR"; break; - case LICENSE_REQUEST_SIGNING_ERROR: *os << "LICENSE_REQUEST_SIGNING_ERROR"; + case LICENSE_REQUEST_SIGNING_ERROR: *os << "LICENSE_REQUEST_SIGNING_ERROR"; break; case EMPTY_LICENSE_REQUEST: *os << "EMPTY_LICENSE_REQUEST"; break; - case DUPLICATE_SESSION_ID_SPECIFIED: *os << "DUPLICATE_SESSION_ID_SPECIFIED"; - case LICENSE_RENEWAL_PROHIBITED: *os << "LICENSE_RENEWAL_PROHIBITED"; + case DUPLICATE_SESSION_ID_SPECIFIED: + *os << "DUPLICATE_SESSION_ID_SPECIFIED"; + break; + case LICENSE_RENEWAL_PROHIBITED: *os << "LICENSE_RENEWAL_PROHIBITED"; + break; + case SESSION_FILE_HANDLE_INIT_ERROR: + *os << "SESSION_FILE_HANDLE_INIT_ERROR"; + break; + case INCORRECT_CRYPTO_MODE: *os << "INCORRECT_CRYPTO_MODE"; + break; + case INVALID_PARAMETERS_ENG_5: *os << "INVALID_PARAMETERS_ENG_5"; + break; + case DECRYPT_ERROR: *os << "DECRYPT_ERROR"; + break; + case INSUFFICIENT_OUTPUT_PROTECTION: + *os << "INSUFFICIENT_OUTPUT_PROTECTION"; + break; + case SESSION_NOT_FOUND_12: *os << "SESSION_NOT_FOUND_12"; + break; + case KEY_NOT_FOUND_1: *os << "KEY_NOT_FOUND_1"; + break; + case KEY_NOT_FOUND_2: *os << "KEY_NOT_FOUND_2"; + break; + case KEY_CONFLICT_1: *os << "KEY_CONFLICT_1"; + break; + case INVALID_PARAMETERS_ENG_6: *os << "INVALID_PARAMETERS_ENG_6"; + break; + case INVALID_PARAMETERS_ENG_7: *os << "INVALID_PARAMETERS_ENG_7"; + break; + case INVALID_PARAMETERS_ENG_8: *os << "INVALID_PARAMETERS_ENG_8"; + break; + case INVALID_PARAMETERS_ENG_9: *os << "INVALID_PARAMETERS_ENG_9"; + break; + case INVALID_PARAMETERS_ENG_10: *os << "INVALID_PARAMETERS_ENG_10"; + break; + case INVALID_PARAMETERS_ENG_11: *os << "INVALID_PARAMETERS_ENG_11"; + break; + case INVALID_PARAMETERS_ENG_12: *os << "INVALID_PARAMETERS_ENG_12"; + break; + case SESSION_NOT_FOUND_13: *os << "SESSION_NOT_FOUND_13"; + break; + case SESSION_NOT_FOUND_14: *os << "SESSION_NOT_FOUND_14"; + break; + case SESSION_NOT_FOUND_15: *os << "SESSION_NOT_FOUND_15"; + break; + case SESSION_NOT_FOUND_16: *os << "SESSION_NOT_FOUND_16"; + break; + case KEY_NOT_FOUND_3: *os << "KEY_NOT_FOUND_3"; + break; + case KEY_NOT_FOUND_4: *os << "KEY_NOT_FOUND_4"; + break; + case KEY_NOT_FOUND_5: *os << "KEY_NOT_FOUND_5"; + break; + case KEY_NOT_FOUND_6: *os << "KEY_NOT_FOUND_6"; + break; + case KEY_ERROR_1: *os << "KEY_ERROR_1"; + break; + case KEY_ERROR_2: *os << "KEY_ERROR_2"; + break; + case KEY_ERROR_3: *os << "KEY_ERROR_3"; + break; + case KEY_ERROR_4: *os << "KEY_ERROR_4"; + break; + case INVALID_PARAMETERS_ENG_13: *os << "INVALID_PARAMETERS_ENG_13"; + break; + case INVALID_PARAMETERS_ENG_14: *os << "INVALID_PARAMETERS_ENG_14"; + break; + case INVALID_PARAMETERS_ENG_15: *os << "INVALID_PARAMETERS_ENG_15"; + break; + case INVALID_PARAMETERS_ENG_16: *os << "INVALID_PARAMETERS_ENG_16"; break; default: - *os << "Unknown CdmResponseType"; + *os << "Unknown CdmResponseType"; break; } } @@ -364,7 +469,7 @@ void PrintTo(const enum CdmLicenseType& value, ::std::ostream* os) { void PrintTo(const enum CdmSecurityLevel& value, ::std::ostream* os) { switch (value) { - case kSecurityLevelUninitialized: *os << "kSecurityLevelUninitialized"; + case kSecurityLevelUninitialized: *os << "kSecurityLevelUninitialized"; break; case kSecurityLevelL1: *os << "kSecurityLevelL1"; break; @@ -387,7 +492,7 @@ void PrintTo(const enum CdmCertificateType& value, ::std::ostream* os) { case kCertificateX509: *os << "kCertificateX509"; break; default: - *os << "Unknown CdmCertificateType"; + *os << "Unknown CdmCertificateType"; break; } }; diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index 1056d691..ae23c5c9 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -121,6 +121,14 @@ typedef struct { } buffer; } OEMCrypto_DestBufferDesc; +/** OEMCryptoCipherMode is used in LoadKeys to prepare a key for either CTR + * decryption or CBC decryption. + */ +typedef enum OEMCryptoCipherMode { + OEMCrypto_CipherMode_CTR, + OEMCrypto_CipherMode_CBC, +} OEMCryptoCipherMode; + /* * OEMCrypto_KeyObject * Points to the relevant fields for a content key. The fields are extracted @@ -137,6 +145,8 @@ typedef struct { * key_control field. * key_control - the key control block. It is encrypted (AES-128-CBC) with * the content key from the key_data field. + * cipher_mode - whether the key should be prepared for CTR mode or CBC mode + * when used in later calls to DecryptCENC. * * The memory for the OEMCrypto_KeyObject fields is allocated and freed * by the caller of OEMCrypto_LoadKeys(). @@ -149,6 +159,7 @@ typedef struct { size_t key_data_length; const uint8_t* key_control_iv; const uint8_t* key_control; + OEMCryptoCipherMode cipher_mode; } OEMCrypto_KeyObject; /* @@ -188,11 +199,21 @@ typedef enum OEMCrypto_Algorithm { } OEMCrypto_Algorithm; /* - * Flags indicating data endpoints in OEMCrypto_DecryptCTR. + * Flags indicating data endpoints in OEMCrypto_DecryptCENC. */ #define OEMCrypto_FirstSubsample 1 #define OEMCrypto_LastSubsample 2 +/* OEMCrypto_CENCEncryptPatternDesc + * This is used in OEMCrypto_DecryptCENC to indicate the encrypt/skip pattern + * used, as specified in the CENC standard. + */ +typedef struct { + size_t encrypt; // number of 16 byte blocks to decrypt. + size_t skip; // number of 16 byte blocks to leave in clear. + size_t offset; // offset into the pattern in blocks for this call. +} OEMCrypto_CENCEncryptPatternDesc; + /* * OEMCrypto_Usage_Entry_Status. * Valid values for status in the usage table. @@ -260,7 +281,7 @@ typedef enum OEMCrypto_HDCP_Capability { #define OEMCrypto_WrapKeybox _oecc08 #define OEMCrypto_OpenSession _oecc09 #define OEMCrypto_CloseSession _oecc10 -#define OEMCrypto_DecryptCTR _oecc11 +#define OEMCrypto_DecryptCTR_V10 _oecc11 #define OEMCrypto_GenerateDerivedKeys _oecc12 #define OEMCrypto_GenerateSignature _oecc13 #define OEMCrypto_GenerateNonce _oecc14 @@ -284,7 +305,7 @@ typedef enum OEMCrypto_HDCP_Capability { #define OEMCrypto_ReportUsage _oecc32 #define OEMCrypto_DeleteUsageEntry _oecc33 #define OEMCrypto_DeleteUsageTable _oecc34 -#define OEMCrypto_LoadKeys _oecc35 +#define OEMCrypto_LoadKeys_V9_or_V10 _oecc35 #define OEMCrypto_GenerateRSASignature _oecc36 #define OEMCrypto_GetMaxNumberOfSessions _oecc37 #define OEMCrypto_GetNumberOfOpenSessions _oecc38 @@ -295,6 +316,9 @@ typedef enum OEMCrypto_HDCP_Capability { #define OEMCrypto_ForceDeleteUsageEntry _oecc43 #define OEMCrypto_GetHDCPCapability _oecc44 #define OEMCrypto_LoadTestRSAKey _oecc45 +#define OEMCrypto_Security_Patch_Level _oecc46 +#define OEMCrypto_LoadKeys _oecc47 +#define OEMCrypto_DecryptCENC _oecc48 /* @@ -373,7 +397,8 @@ OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION *session); * OEMCrypto_CloseSession * * Description: - * Closes the crypto security engine session and frees any associated resources. + * Closes the crypto security engine session and frees any associated + * resources. * * Parameters: * session (in) - handle for the session to be closed. @@ -565,10 +590,12 @@ OEMCryptoResult OEMCrypto_GenerateSignature( * the previous call to OEMCrypto_GenerateNonce(). * * This session’s elapsed time clock is started at 0. The clock will be used - * in OEMCrypto_DecryptCTR(). + * in OEMCrypto_DecryptCENC(). * - * NOTE: OEMCrypto_GenerateDerivedKeys() must be called first to establish the - * mac_key and encrypt_key. + * NOTE: The calling software must have previously established the mac_keys + * and encrypt_key with a call to OEMCrypto_GenerateDerivedKeys(), + * OEMCrypto_DeriveKeysFromSessionKey(), or a previous call to + * OEMCrypto_LoadKeys(). * * Refer to document "Widevine Modular DRM Security Integration Guide for * CENC" for details. @@ -604,9 +631,15 @@ OEMCryptoResult OEMCrypto_GenerateSignature( * the cache. Note that all the key control blocks in a particular call shall * have the same nonce value. * - * 6. If the key control block has a nonzero Replay_Control, then the + * 6. If any key control block has the Require_AntiRollback_Hardware bit set, + * and the device does not protect the usage table from rollback, then do not + * load the keys and return OEMCrypto_ERROR_UNKNOWN_FAILURE. + * + * 7. If the key control block has a nonzero Replay_Control, then the * verification described below is also performed. * + * 8. If num_keys == 0, then return OEMCrypto_ERROR_INVALID_CONTEXT. + * * Usage Table and Provider Session Token (pst): * * If a key control block has a nonzero value for Replay_Control, then all keys @@ -652,6 +685,10 @@ OEMCryptoResult OEMCrypto_GenerateSignature( * Devices that do not support the Usage Table will return * OEMCrypto_ERROR_INVALID_CONTEXT if the Replay_Control is nonzero. * + * Note: If LoadKeys creates a new entry in the usage table, OEMCrypto will + * increment the Usage Table’s generation number, and then sign, encrypt, and + * save the Usage Table. + * * Parameters: * session (in) - crypto session identifier. * message (in) - pointer to memory containing message to be verified. @@ -681,7 +718,7 @@ OEMCryptoResult OEMCrypto_GenerateSignature( * OEMCrypto_ERROR_UNKNOWN_FAILURE * * Version: - * This method changed in API version 9. + * This method changed in API version 11. */ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session, const uint8_t* message, @@ -714,7 +751,7 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session, * first to establish the mac_key[server]. * * This session’s elapsed time clock is reset to 0 when this function is called. - * The elapsed time clock is used in OEMCrypto_DecryptCTR(). + * The elapsed time clock is used in OEMCrypto_DecryptCENC(). * * This function does not add keys to the key table. It is only used to update a * key control block license duration. Refer to the License Signing and @@ -849,7 +886,7 @@ OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session, * * Description: * Select a content key and install it in the hardware key ladder for - * subsequent decryption operations (OEMCrypto_DecryptCTR()) for this session. + * subsequent decryption operations (OEMCrypto_DecryptCENC()) for this session. * The specified key must have been previously "installed" via * OEMCrypto_LoadKeys() or OEMCrypto_RefreshKeys(). * @@ -864,7 +901,7 @@ OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session, * permission flags and timers based on the key's control block. * * Step 3: use the latched content key to decrypt (AES-128-CTR) buffers passed in - * via OEMCrypto_DecryptCTR(). If the key is 256 bits it will be used for + * via OEMCrypto_DecryptCENC(). If the key is 256 bits it will be used for * OEMCrypto_Generic_Sign or OEMCrypto_Generic_Verify as specified in the key * control block. Continue to use this key until OEMCrypto_SelectKey() is called * again, or until OEMCrypto_CloseSession() is called. @@ -898,15 +935,17 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, size_t key_id_length); /* - * OEMCrypto_DecryptCTR + * OEMCrypto_DecryptCENC * * Description: - * Decrypts (AES-128-CTR) or copies the payload in the buffer referenced by the - * data_addr parameter into the buffer referenced by the out_buffer parameter, - * using the session context indicated by the session parameter. If is_encrypted - * is true, the content key associated with the session is latched in the active - * hardware key ladder and is used for the decryption operation. If is_encrypted - * is false, the data is simply copied. + * Decrypts or copies the payload in the buffer referenced by the *data_addr + * parameter into the buffer referenced by the out_buffer parameter, using + * the session context indicated by the session parameter. Decryption mode + * is AES-128-CTR or AES-128-CBC depending on the value of cipher_mode set in + * the OEMCrypto_KeyObject passed in to OEMCrypto_LoadKeys. If is_encrypted + * is true, the content key associated with the session is latched in the + * active hardware key ladder and is used for the decryption operation. If + * is_encrypted is false, the data is simply copied. * * After decryption, the data_length bytes are copied to the location described * by out_buffer. This could be one of @@ -921,13 +960,13 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * the decoder and rendered. * * NOTES: - * IV points to the counter value to be used for the initial - * encrypted block of the input buffer. The IV length is the AES - * block size. For subsequent encrypted AES blocks the IV is - * calculated by incrementing the lower 64 bits (byte 8-15) of the - * IV value used for the previous block. The counter rolls over to - * zero when it reaches its maximum value (0xFFFFFFFFFFFFFFFF). - * The upper 64 bits (byte 0-7) of the IV do not change. + * For CTR mode, IV points to the counter value to be used for the initial + * encrypted block of the input buffer. The IV length is the AES block + * size. For subsequent encrypted AES blocks the IV is calculated by + * incrementing the lower 64 bits (byte 8-15) of the IV value used for the + * previous block. The counter rolls over to zero when it reaches its maximum + * value (0xFFFFFFFFFFFFFFFF). The upper 64 bits (byte 0-7) of the IV do not + * change. * * This method may be called several times before the decrypted data is used. * For this reason, the parameter subsample_flags may be used to optimize @@ -938,7 +977,7 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * OEMCrypto_LastSubsample has been set. If an implementation decrypts data * immediately, it may ignore subsample_flags. * - * If the destination buffer is secure, an offset may be specified. DecryptCTR + * If the destination buffer is secure, an offset may be specified. DecryptCENC * begins storing data out_buffer->secure.offset bytes after the beginning of the * secure buffer. * @@ -946,6 +985,13 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * time_of_last_decrypt. If the status of the entry is "unused", then change the * status to "active" and set the time_of_first_decrypt. * + * The decryption mode, either OEMCrypto_CipherMode_CTR or + * OEMCrypto_CipherMode_CBC, was specified in the call to OEMCrypto_LoadKeys. + * The encryption pattern is specified in by the parameter pattern. A + * description of partial encryption patterns can be found in the document + * Draft International Standard ISO/IEC DIS 23001-7. Search for the codes + * "cenc", "cbc1", "cens" or "cbcs". + * * * Verification: * The following checks should be performed if is_encrypted is true. If any @@ -992,9 +1038,12 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * decryption block start address and data_addr are discarded * after decryption. It does not adjust the beginning of the * source or destination data. This parameter satisfies - * 0 <= block_offset < 16. + * 0 <= block_offset < 16. This paramater is only used + * for CTR mode. * out_buffer (in) - A caller-owned descriptor that specifies the handling of the * decrypted byte stream. See OEMCrypto_DestbufferDesc for details. + * pattern (in) - A caller-owned structure indicating the encrypt/skip + * pattern as specified in the CENC standard. * subsample_flags (in) - bitwise flags indicating if this is the first, middle, * or last subsample in a chunk of data. * 0 = neither first nor last subsample, @@ -1018,16 +1067,18 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * OEMCrypto_ERROR_UNKNOWN_FAILURE * * Version: - * This method changed in API version 9. + * This method changed in API version 11. + * This method changed its name in API version 11. */ -OEMCryptoResult OEMCrypto_DecryptCTR(OEMCrypto_SESSION session, - const uint8_t *data_addr, - size_t data_length, - bool is_encrypted, - const uint8_t *iv, - size_t block_offset, - OEMCrypto_DestBufferDesc* out_buffer, - uint8_t subsample_flags); +OEMCryptoResult OEMCrypto_DecryptCENC(OEMCrypto_SESSION session, + const uint8_t *data_addr, + size_t data_length, + bool is_encrypted, + const uint8_t *iv, + size_t block_offset, + OEMCrypto_DestBufferDesc* out_buffer, + const OEMCrypto_CENCEncryptPatternDesc* pattern, + uint8_t subsample_flags); /* @@ -1037,9 +1088,9 @@ OEMCryptoResult OEMCrypto_DecryptCTR(OEMCrypto_SESSION session, * Copies the payload in the buffer referenced by the *data_addr parameter into * the buffer referenced by the out_buffer parameter. The data is simply * copied. The definition of OEMCrypto_DestBufferDesc and subsample_flags are - * the same as in OEMCrypto_DecryptCTR, above. + * the same as in OEMCrypto_DecryptCENC, above. * - * The main difference between this and DecryptCTR is that this function does + * The main difference between this and DecryptCENC is that this function does * not need an open session, and it may be called concurrently with other * session functions on a multithreaded system. In particular, an application * will use this to copy the clear leader of a video to a secure buffer while @@ -1648,9 +1699,6 @@ OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(OEMCrypto_SESSION session, * There is no plan to introduce forward-compatibility. Applications will reject * a library with a newer version of the API. * - * The version specified in this document is 9. Any OEM that returns this - * version number guarantees it passes all unit tests associated this version. - * * Parameters: * none * @@ -1665,6 +1713,28 @@ OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(OEMCrypto_SESSION session, */ uint32_t OEMCrypto_APIVersion(); +/** + * OEMCrypto_Security_Patch_Level() + * + * Description: + * This function returns the current patch level of the software running in + * the trusted environment. The patch level is defined by the OEM, and is + * only incremented when a security update has been added. + * + * Parameters: + * none + * + * Returns: + * The OEM defined version number. + * + * Threading: + * This function may be called simultaneously with any other functions. + * + * Version: + * This method was introduced in API version 11. + */ +uint8_t OEMCrypto_Security_Patch_Level(); + /* * OEMCrypto_SecurityLevel() * diff --git a/oemcrypto/test/oec_device_features.cpp b/oemcrypto/test/oec_device_features.cpp new file mode 100644 index 00000000..d1c639b4 --- /dev/null +++ b/oemcrypto/test/oec_device_features.cpp @@ -0,0 +1,144 @@ +#include "oec_device_features.h" + +#include + +#include + +#include "oec_test_data.h" + +namespace wvoec { + +DeviceFeatures global_features; + +void DeviceFeatures::Initialize(bool is_cast_receiver, + bool force_load_test_keybox) { + cast_receiver = is_cast_receiver; + uses_keybox = false; + uses_certificate = false; + loads_certificate = false; + generic_crypto = false; + usage_table = false; + api_version = 0; + derive_key_method = NO_METHOD; + if (OEMCrypto_SUCCESS != OEMCrypto_Initialize()) { + printf("OEMCrypto_Initialze failed. All tests will fail.\n"); + return; + } + uint32_t nonce = 0; + uint8_t buffer[1]; + size_t size = 0; + uses_keybox = + (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_GetKeyData(buffer, &size)); + printf("uses_keybox = %s.\n", uses_keybox ? "true" : "false"); + loads_certificate = uses_keybox && (OEMCrypto_ERROR_NOT_IMPLEMENTED != + OEMCrypto_RewrapDeviceRSAKey( + 0, buffer, 0, buffer, 0, &nonce, + buffer, 0, buffer, buffer, &size)); + printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false"); + uses_certificate = (OEMCrypto_ERROR_NOT_IMPLEMENTED != + OEMCrypto_GenerateRSASignature(0, buffer, 0, buffer, + &size, kSign_RSASSA_PSS)); + printf("uses_certificate = %s.\n", uses_certificate ? "true" : "false"); + generic_crypto = + (OEMCrypto_ERROR_NOT_IMPLEMENTED != + OEMCrypto_Generic_Encrypt(0, buffer, 0, buffer, + OEMCrypto_AES_CBC_128_NO_PADDING, buffer)); + printf("generic_crypto = %s.\n", generic_crypto ? "true" : "false"); + api_version = OEMCrypto_APIVersion(); + printf("api_version = %d.\n", api_version); + usage_table = OEMCrypto_SupportsUsageTable(); + printf("usage_table = %s.\n", usage_table ? "true" : "false"); + if (force_load_test_keybox) { + derive_key_method = FORCE_TEST_KEYBOX; + } else { + PickDerivedKey(); + } + printf("cast_receiver = %s.\n", cast_receiver ? "true" : "false"); + switch (derive_key_method) { + case NO_METHOD: + printf("NO_METHOD: Cannot derive known session keys.\n"); + // Note: cast_receiver left unchanged because set by user on command line. + uses_keybox = false; + uses_certificate = false; + loads_certificate = false; + generic_crypto = false; + usage_table = false; + break; + case LOAD_TEST_KEYBOX: + printf("LOAD_TEST_KEYBOX: Call LoadTestKeybox before deriving keys.\n"); + break; + case LOAD_TEST_RSA_KEY: + printf("LOAD_TEST_RSA_KEY: Call LoadTestRSAKey before deriving keys.\n"); + break; + case EXISTING_TEST_KEYBOX: + printf("EXISTING_TEST_KEYBOX: Keybox is already the test keybox.\n"); + break; + case FORCE_TEST_KEYBOX: + printf("FORCE_TEST_KEYBOX: User requested calling InstallKeybox.\n"); + break; + } + OEMCrypto_Terminate(); +} + +std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { + std::string filter = initial_filter; + if (!uses_keybox) FilterOut(&filter, "*KeyboxTest*"); + if (derive_key_method + != FORCE_TEST_KEYBOX) FilterOut(&filter, "*ForceKeybox*"); + if (!uses_certificate) FilterOut(&filter, "OEMCrypto*Cert*"); + if (!loads_certificate) FilterOut(&filter, "OEMCryptoLoadsCert*"); + if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); + if (!cast_receiver) FilterOut(&filter, "*CastReceiver*"); + if (!usage_table) FilterOut(&filter, "*UsageTable*"); + if (derive_key_method == NO_METHOD) FilterOut(&filter, "*SessionTest*"); + if (api_version < 10) FilterOut(&filter, "*API10*"); + if (api_version < 11) FilterOut(&filter, "*API11*"); + // Performance tests take a long time. Filter them out if they are not + // specifically requested. + if (filter.find("Performance") == std::string::npos) { + FilterOut(&filter, "*Performance*"); + } + return filter; +} + +void DeviceFeatures::PickDerivedKey() { + if (uses_keybox) { + // If device uses a keybox, try to load the test keybox. + if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestKeybox()) { + derive_key_method = LOAD_TEST_KEYBOX; + } else if (IsTestKeyboxInstalled()) { + derive_key_method = EXISTING_TEST_KEYBOX; + } + } else if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestRSAKey()) { + derive_key_method = LOAD_TEST_RSA_KEY; + } +} + +bool DeviceFeatures::IsTestKeyboxInstalled() { + uint8_t key_data[256]; + size_t key_data_len = sizeof(key_data); + if (OEMCrypto_GetKeyData(key_data, &key_data_len) != OEMCrypto_SUCCESS) + return false; + if (key_data_len != sizeof(kTestKeybox.data_)) return false; + if (memcmp(key_data, kTestKeybox.data_, key_data_len)) return false; + uint8_t dev_id[128] = {0}; + size_t dev_id_len = 128; + if (OEMCrypto_GetDeviceID(dev_id, &dev_id_len) != OEMCrypto_SUCCESS) + return false; + // We use strncmp instead of memcmp because we don't really care about the + // multiple '\0' characters at the end of the device id. + return 0 == strncmp(reinterpret_cast(dev_id), + reinterpret_cast(kTestKeybox.device_id_), + sizeof(kTestKeybox.device_id_)); +} + +void DeviceFeatures::FilterOut(std::string* current_filter, + const std::string& new_filter) { + if (current_filter->find('-') == std::string::npos) { + *current_filter += "-" + new_filter; + } else { + *current_filter += ":" + new_filter; + } +} + +} // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test.h b/oemcrypto/test/oec_device_features.h similarity index 91% rename from oemcrypto/test/oemcrypto_test.h rename to oemcrypto/test/oec_device_features.h index 4a3988c7..76a3fc03 100644 --- a/oemcrypto/test/oemcrypto_test.h +++ b/oemcrypto/test/oec_device_features.h @@ -1,5 +1,7 @@ -#ifndef CDM_OEMCRYPTO_TEST_H_ -#define CDM_OEMCRYPTO_TEST_H_ +#ifndef CDM_OEC_DEVICE_FEATURES_H_ +#define CDM_OEC_DEVICE_FEATURES_H_ + +#include #include "OEMCryptoCENC.h" #include "wv_keybox.h" @@ -38,4 +40,4 @@ extern DeviceFeatures global_features; } // namespace wvoec -#endif // CDM_OEMCRYPTO_TEST_H_ +#endif // CDM_OEC_DEVICE_FEATURES_H_ diff --git a/oemcrypto/test/oec_session_util.cpp b/oemcrypto/test/oec_session_util.cpp new file mode 100644 index 00000000..79495426 --- /dev/null +++ b/oemcrypto/test/oec_session_util.cpp @@ -0,0 +1,802 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// OEMCrypto unit tests +// + +#include "oec_session_util.h" + +#include // needed for ntoh() +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "log.h" +#include "oec_device_features.h" +#include "oec_test_data.h" +#include "oemcrypto_key_mock.h" +#include "OEMCryptoCENC.h" +#include "string_conversions.h" +#include "wv_cdm_constants.h" +#include "wv_keybox.h" + +using namespace std; + +// GTest requires PrintTo to be in the same namespace as the thing it prints, +// which is std::vector in this case. +namespace std { + +void PrintTo(const vector& value, ostream* os) { + *os << wvcdm::b2a_hex(value); +} + +void PrintTo(const PatternTestVariant& param, ostream* os) { + *os << ((param.mode == OEMCrypto_CipherMode_CTR) ? "CTR mode" : "CBC mode") + << ", encrypt=" << param.pattern.encrypt + << ", skip=" << param.pattern.skip; +} + +} // namespace std + +namespace wvoec { + +// Increment counter for AES-CTR. The CENC spec specifies we increment only +// the low 64 bits of the IV counter, and leave the high 64 bits alone. This +// is different from the OpenSSL implementation, so we implement the CTR loop +// ourselves. +void ctr128_inc64(int64_t increaseBy, uint8_t* iv) { + ASSERT_NE(static_cast(NULL), iv); + uint64_t* counterBuffer = reinterpret_cast(&iv[8]); + (*counterBuffer) = + wvcdm::htonll64(wvcdm::ntohll64(*counterBuffer) + increaseBy); +} + +// Some compilers don't like the macro htonl within an ASSERT_EQ. +uint32_t htonl_fnc(uint32_t x) { return htonl(x); } + +void dump_openssl_error() { + while (unsigned long err = ERR_get_error()) { + char buffer[120]; + ERR_error_string_n(err, buffer, sizeof(buffer)); + cout << "openssl error -- " << buffer << "\n"; + } +} + +Session::Session() + : open_(false), + forced_session_id_(false), + session_id_(0), + mac_key_server_(wvcdm::MAC_KEY_SIZE), + mac_key_client_(wvcdm::MAC_KEY_SIZE), + enc_key_(wvcdm::KEY_SIZE), + public_rsa_(0) {} + +Session::~Session() { + if (!forced_session_id_ && open_) close(); + if (public_rsa_) RSA_free(public_rsa_); +} + +void Session::open() { + EXPECT_FALSE(forced_session_id_); + EXPECT_FALSE(open_); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_OpenSession(&session_id_)); + open_ = true; +} + +void Session::SetSessionId(uint32_t session_id) { + EXPECT_FALSE(open_); + session_id_ = session_id; + forced_session_id_ = true; +} + +void Session::close() { + EXPECT_TRUE(open_ || forced_session_id_); + if (open_) { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CloseSession(session_id_)); + } + forced_session_id_ = false; + open_ = false; +} + +void Session::GenerateNonce(uint32_t* nonce, int* error_counter) { + if (OEMCrypto_SUCCESS == OEMCrypto_GenerateNonce(session_id(), nonce)) { + return; + } + if (error_counter) { + (*error_counter)++; + } else { + sleep(1); // wait a second, then try again. + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GenerateNonce(session_id(), nonce)); + } +} + +void Session::FillDefaultContext(vector* mac_context, + vector* enc_context) { + /* Context strings + * These context strings are normally created by the CDM layer + * from a license request message. + * They are used to test MAC and ENC key generation. + */ + *mac_context = wvcdm::a2b_hex( + "41555448454e5449434154494f4e000a4c08001248000000020000101907d9ff" + "de13aa95c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e5873" + "4930acebe899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a" + "230a14080112100915007caa9b5931b76a3a85f046523e10011a093938373635" + "34333231180120002a0c31383836373837343035000000000200"); + *enc_context = wvcdm::a2b_hex( + "454e4352595054494f4e000a4c08001248000000020000101907d9ffde13aa95" + "c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e58734930aceb" + "e899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a230a1408" + "0112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231" + "180120002a0c31383836373837343035000000000080"); +} + +void Session::GenerateDerivedKeysFromKeybox() { + GenerateNonce(&nonce_); + vector mac_context; + vector enc_context; + FillDefaultContext(&mac_context, &enc_context); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GenerateDerivedKeys(session_id(), &mac_context[0], + mac_context.size(), &enc_context[0], + enc_context.size())); + + // Expected MAC and ENC keys generated from context strings + // with test keybox "installed". + mac_key_server_ = wvcdm::a2b_hex( + "3CFD60254786AF350B353B4FBB700AB382558400356866BA16C256BCD8C502BF"); + mac_key_client_ = wvcdm::a2b_hex( + "A9DE7B3E4E199ED8D1FBC29CD6B4C772CC4538C8B0D3E208B3E76F2EC0FD6F47"); + enc_key_ = wvcdm::a2b_hex("D0BFC35DA9E33436E81C4229E78CB9F4"); +} + +void Session::GenerateDerivedKeysFromSessionKey() { + // Uses test certificate. + GenerateNonce(&nonce_); + vector enc_session_key; + PreparePublicKey(); + ASSERT_TRUE(GenerateRSASessionKey(&enc_session_key)); + vector mac_context; + vector enc_context; + FillDefaultContext(&mac_context, &enc_context); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_DeriveKeysFromSessionKey( + session_id(), &enc_session_key[0], enc_session_key.size(), + &mac_context[0], mac_context.size(), &enc_context[0], + enc_context.size())); + + // Expected MAC and ENC keys generated from context strings + // with RSA certificate "installed". + mac_key_server_ = wvcdm::a2b_hex( + "1E451E59CB663DA1646194DD28880788ED8ED2EFF913CBD6A0D535D1D5A90381"); + mac_key_client_ = wvcdm::a2b_hex( + "F9AAE74690909F2207B53B13307FCA096CA8C49CC6DFE3659873CB952889A74B"); + enc_key_ = wvcdm::a2b_hex("CB477D09014D72C9B8DCE76C33EA43B3"); +} + +void Session::GenerateTestSessionKeys() { + if (global_features.derive_key_method == DeviceFeatures::LOAD_TEST_RSA_KEY) { + GenerateDerivedKeysFromSessionKey(); + } else { + GenerateDerivedKeysFromKeybox(); + } +} + +void Session::LoadTestKeys(const std::string& pst, bool new_mac_keys) { + uint8_t* pst_ptr = NULL; + if (pst.length() > 0) { + pst_ptr = encrypted_license_.pst; + } + if (new_mac_keys) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys( + session_id(), message_ptr(), sizeof(MessageData), + &signature_[0], signature_.size(), + encrypted_license_.mac_key_iv, encrypted_license_.mac_keys, + kNumKeys, key_array_, pst_ptr, pst.length())); + // Update new generated keys. + memcpy(&mac_key_server_[0], license_.mac_keys, wvcdm::MAC_KEY_SIZE); + memcpy(&mac_key_client_[0], license_.mac_keys + wvcdm::MAC_KEY_SIZE, + wvcdm::MAC_KEY_SIZE); + } else { + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(session_id(), message_ptr(), sizeof(MessageData), + &signature_[0], signature_.size(), NULL, NULL, + kNumKeys, key_array_, pst_ptr, pst.length())); + } + VerifyTestKeys(); +} + +void Session::VerifyTestKeys() { + for (unsigned int i = 0; i < kNumKeys; i++) { + KeyControlBlock block; + size_t size = sizeof(block); + OEMCryptoResult sts = OEMCrypto_QueryKeyControl( + session_id(), license_.keys[i].key_id, license_.keys[i].key_id_length, + reinterpret_cast(&block), &size); + if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(sizeof(block), size); + // control duration and bits stored in network byte order. For printing + // we change to host byte order. + ASSERT_EQ((htonl_fnc(license_.keys[i].control.duration)), + (htonl_fnc(block.duration))) << "For key " << i; + ASSERT_EQ(htonl_fnc(license_.keys[i].control.control_bits), + htonl_fnc(block.control_bits)) << "For key " << i; + } + } +} + +void Session::RefreshTestKeys(const size_t key_count, + uint32_t control_bits, uint32_t nonce, + OEMCryptoResult expected_result) { + // Note: we store the message in encrypted_license_, but the refresh key + // message is not actually encrypted. It is, however, signed. + FillRefreshMessage(key_count, control_bits, nonce); + ServerSignMessage(encrypted_license_, &signature_); + OEMCrypto_KeyRefreshObject key_array[key_count]; + FillRefreshArray(key_array, key_count); + OEMCryptoResult sts = OEMCrypto_RefreshKeys( + session_id(), message_ptr(), sizeof(MessageData), &signature_[0], + signature_.size(), key_count, key_array); + ASSERT_EQ(expected_result, sts); + + ASSERT_NO_FATAL_FAILURE(TestDecryptCTR()); + sleep(kShortSleep); // Should still be valid key. + ASSERT_NO_FATAL_FAILURE(TestDecryptCTR(false)); + sleep(kShortSleep + kLongSleep); // Should be after first expiration. + if (expected_result == OEMCrypto_SUCCESS) { + ASSERT_NO_FATAL_FAILURE(TestDecryptCTR(false, OEMCrypto_SUCCESS)); + } else { + ASSERT_NO_FATAL_FAILURE( + TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + } +} + +void Session::SetKeyId(int index, const string& key_id) { + MessageKeyData& key = license_.keys[index]; + key.key_id_length = key_id.length(); + ASSERT_LE(key.key_id_length, kTestKeyIdMaxLength); + memcpy(key.key_id, key_id.data(), key.key_id_length); +} + +void Session::FillSimpleMessage( + uint32_t duration, uint32_t control, uint32_t nonce, + const std::string& pst) { + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(license_.mac_key_iv, + sizeof(license_.mac_key_iv))); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(license_.mac_keys, sizeof(license_.mac_keys))); + for (unsigned int i = 0; i < kNumKeys; i++) { + memset(license_.keys[i].key_id, 0, kTestKeyIdMaxLength); + license_.keys[i].key_id_length = kDefaultKeyIdLength; + memset(license_.keys[i].key_id, i, license_.keys[i].key_id_length); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(license_.keys[i].key_data, + sizeof(license_.keys[i].key_data))); + license_.keys[i].key_data_length = wvcdm::KEY_SIZE; + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(license_.keys[i].key_iv, + sizeof(license_.keys[i].key_iv))); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(license_.keys[i].control_iv, + sizeof(license_.keys[i].control_iv))); + if (control & wvoec_mock::kControlSecurityPatchLevelMask) { + memcpy(license_.keys[i].control.verification, "kc11", 4); + } else if (control & wvoec_mock::kControlRequireAntiRollbackHardware) { + memcpy(license_.keys[i].control.verification, "kc10", 4); + } else if (control & (wvoec_mock::kControlHDCPVersionMask | + wvoec_mock::kControlReplayMask)) { + memcpy(license_.keys[i].control.verification, "kc09", 4); + } else { + memcpy(license_.keys[i].control.verification, "kctl", 4); + } + license_.keys[i].control.duration = htonl(duration); + license_.keys[i].control.nonce = htonl(nonce); + license_.keys[i].control.control_bits = htonl(control); + license_.keys[i].cipher_mode = OEMCrypto_CipherMode_CTR; + } + memcpy(license_.pst, pst.c_str(), min(sizeof(license_.pst), pst.length())); +} + +void Session::FillRefreshMessage(size_t key_count, uint32_t control_bits, + uint32_t nonce) { + for (unsigned int i = 0; i < key_count; i++) { + encrypted_license_.keys[i].key_id_length = license_.keys[i].key_id_length; + memcpy(encrypted_license_.keys[i].key_id, license_.keys[i].key_id, + encrypted_license_.keys[i].key_id_length); + memcpy(encrypted_license_.keys[i].control.verification, "kctl", 4); + encrypted_license_.keys[i].control.duration = htonl(kLongDuration); + encrypted_license_.keys[i].control.nonce = htonl(nonce); + encrypted_license_.keys[i].control.control_bits = htonl(control_bits); + } +} + +void Session::EncryptAndSign() { + encrypted_license_ = license_; + + uint8_t iv_buffer[16]; + memcpy(iv_buffer, &license_.mac_key_iv[0], wvcdm::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); + AES_cbc_encrypt(&license_.mac_keys[0], &encrypted_license_.mac_keys[0], + 2 * wvcdm::MAC_KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); + + for (unsigned int i = 0; i < kNumKeys; i++) { + memcpy(iv_buffer, &license_.keys[i].control_iv[0], wvcdm::KEY_IV_SIZE); + AES_set_encrypt_key(&license_.keys[i].key_data[0], 128, &aes_key); + AES_cbc_encrypt( + reinterpret_cast(&license_.keys[i].control), + reinterpret_cast(&encrypted_license_.keys[i].control), + wvcdm::KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); + + memcpy(iv_buffer, &license_.keys[i].key_iv[0], wvcdm::KEY_IV_SIZE); + AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); + AES_cbc_encrypt(&license_.keys[i].key_data[0], + &encrypted_license_.keys[i].key_data[0], + license_.keys[i].key_data_length, &aes_key, iv_buffer, + AES_ENCRYPT); + } + memcpy(encrypted_license_.pst, license_.pst, sizeof(license_.pst)); + ServerSignMessage(encrypted_license_, &signature_); + FillKeyArray(encrypted_license_, key_array_); +} + +void Session::EncryptMessage(RSAPrivateKeyMessage* data, + RSAPrivateKeyMessage* encrypted) { + *encrypted = *data; + size_t padding = wvcdm::KEY_SIZE - (data->rsa_key_length % wvcdm::KEY_SIZE); + memset(data->rsa_key + data->rsa_key_length, static_cast(padding), + padding); + encrypted->rsa_key_length = data->rsa_key_length + padding; + uint8_t iv_buffer[16]; + memcpy(iv_buffer, &data->rsa_key_iv[0], wvcdm::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); + AES_cbc_encrypt(&data->rsa_key[0], &encrypted->rsa_key[0], + encrypted->rsa_key_length, &aes_key, iv_buffer, + AES_ENCRYPT); +} + +template +void Session::ServerSignMessage(const T& data, + std::vector* signature) { + signature->assign(SHA256_DIGEST_LENGTH, 0); + unsigned int md_len = SHA256_DIGEST_LENGTH; + HMAC(EVP_sha256(), &mac_key_server_[0], mac_key_server_.size(), + reinterpret_cast(&data), sizeof(data), + &(signature->front()), &md_len); +} + +void Session::ClientSignMessage(const vector& data, + std::vector* signature) { + signature->assign(SHA256_DIGEST_LENGTH, 0); + unsigned int md_len = SHA256_DIGEST_LENGTH; + HMAC(EVP_sha256(), &mac_key_client_[0], mac_key_client_.size(), + &(data.front()), data.size(), &(signature->front()), &md_len); +} + +void Session::FillKeyArray(const MessageData& data, + OEMCrypto_KeyObject* key_array) { + for (unsigned int i = 0; i < kNumKeys; i++) { + key_array[i].key_id = data.keys[i].key_id; + key_array[i].key_id_length = data.keys[i].key_id_length; + key_array[i].key_data_iv = data.keys[i].key_iv; + key_array[i].key_data = data.keys[i].key_data; + key_array[i].key_data_length = data.keys[i].key_data_length; + key_array[i].key_control_iv = data.keys[i].control_iv; + key_array[i].key_control = + reinterpret_cast(&data.keys[i].control); + key_array[i].cipher_mode = data.keys[i].cipher_mode; + } +} + +void Session::FillRefreshArray(OEMCrypto_KeyRefreshObject* key_array, + size_t key_count) { + for (size_t i = 0; i < key_count; i++) { + if (key_count > 1) { + key_array[i].key_id = encrypted_license_.keys[i].key_id; + key_array[i].key_id_length = encrypted_license_.keys[i].key_id_length; + } else { + key_array[i].key_id = NULL; + key_array[i].key_id_length = 0; + } + key_array[i].key_control_iv = NULL; + key_array[i].key_control = + reinterpret_cast(&encrypted_license_.keys[i].control); + } +} + +void Session::EncryptCTR( + const vector& in_buffer, const uint8_t *key, + const uint8_t* starting_iv, vector* out_buffer) { + ASSERT_NE(static_cast(NULL), key); + ASSERT_NE(static_cast(NULL), starting_iv); + ASSERT_NE(static_cast(NULL), out_buffer); + AES_KEY aes_key; + AES_set_encrypt_key(key, AES_BLOCK_SIZE * 8, &aes_key); + out_buffer->resize(in_buffer.size()); + + uint8_t iv[AES_BLOCK_SIZE]; // Current iv. + + memcpy(iv, &starting_iv[0], AES_BLOCK_SIZE); + size_t l = 0; // byte index into encrypted subsample. + while (l < in_buffer.size()) { + uint8_t aes_output[AES_BLOCK_SIZE]; + AES_encrypt(iv, aes_output, &aes_key); + for (size_t n = 0; n < AES_BLOCK_SIZE && l < in_buffer.size(); n++, l++) { + (*out_buffer)[l] = aes_output[n] ^ in_buffer[l]; + } + ctr128_inc64(1, iv); + } +} + +void Session::TestDecryptCTR(bool select_key_first, + OEMCryptoResult expected_result) { + OEMCryptoResult sts; + if (select_key_first) { + // Select the key (from FillSimpleMessage) + sts = OEMCrypto_SelectKey(session_id(), license_.keys[0].key_id, + license_.keys[0].key_id_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + } + + vector unencryptedData(256); + for(size_t i=0; i < unencryptedData.size(); i++) unencryptedData[i] = i % 256; + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&unencryptedData[0], unencryptedData.size())); + vector encryptionIv(wvcdm::KEY_IV_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], wvcdm::KEY_IV_SIZE)); + vector encryptedData(unencryptedData.size()); + EncryptCTR(unencryptedData, license_.keys[0].key_data, &encryptionIv[0], + &encryptedData); + + // Describe the output + vector outputBuffer(256); + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer.data(); + destBuffer.buffer.clear.max_length = outputBuffer.size(); + OEMCrypto_CENCEncryptPatternDesc pattern; + pattern.encrypt = 0; + pattern.skip = 0; + pattern.offset = 0; + // Decrypt the data + sts = OEMCrypto_DecryptCENC( + session_id(), &encryptedData[0], encryptedData.size(), true, + &encryptionIv[0], 0, &destBuffer, &pattern, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + // We only have a few errors that we test are reported. + if (expected_result == OEMCrypto_SUCCESS) { // No error. + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(unencryptedData, outputBuffer); + } else if (expected_result == OEMCrypto_ERROR_KEY_EXPIRED) { + // Report stale keys. + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, sts); + ASSERT_NE(unencryptedData, outputBuffer); + } else if (expected_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP) { + // Report HDCP errors. + ASSERT_EQ(OEMCrypto_ERROR_INSUFFICIENT_HDCP, sts); + ASSERT_NE(unencryptedData, outputBuffer); + } else { + // OEM's can fine tune other error codes for debugging. + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(unencryptedData, outputBuffer); + } +} + +void Session::MakeRSACertificate( + struct RSAPrivateKeyMessage* encrypted, std::vector* signature, + uint32_t allowed_schemes, const vector& rsa_key) { + // Dummy context for testing signature generation. + vector context = wvcdm::a2b_hex( + "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" + "8f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202" + "fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931" + "b76a3a85f046523e10011a09393837363534333231180120002a0c3138383637" + "38373430350000"); + + OEMCryptoResult sts; + + // Generate signature + size_t gen_signature_length = 0; + sts = OEMCrypto_GenerateSignature(session_id(), &context[0], context.size(), + NULL, &gen_signature_length); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + ASSERT_EQ(static_cast(32), gen_signature_length); + vector gen_signature(gen_signature_length); + sts = OEMCrypto_GenerateSignature(session_id(), &context[0], context.size(), + &gen_signature[0], &gen_signature_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + std::vector expected_signature; + ClientSignMessage(context, &expected_signature); + ASSERT_EQ(expected_signature, gen_signature); + + // Rewrap Canned Response + + // In the real world, the signature above would just have been used to + // contact the certificate provisioning server to get this response. + + struct RSAPrivateKeyMessage message; + if (allowed_schemes != kSign_RSASSA_PSS) { + uint32_t algorithm_n = htonl(allowed_schemes); + memcpy(message.rsa_key, "SIGN", 4); + memcpy(message.rsa_key + 4, &algorithm_n, 4); + memcpy(message.rsa_key + 8, rsa_key.data(), rsa_key.size()); + message.rsa_key_length = 8 + rsa_key.size(); + } else { + memcpy(message.rsa_key, rsa_key.data(), rsa_key.size()); + message.rsa_key_length = rsa_key.size(); + } + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(message.rsa_key_iv, wvcdm::KEY_IV_SIZE)); + message.nonce = nonce_; + + EncryptMessage(&message, encrypted); + ServerSignMessage(*encrypted, signature); +} + +void Session::RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted, + const std::vector& signature, + vector* wrapped_key, bool force) { + size_t wrapped_key_length = 0; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_RewrapDeviceRSAKey( + session_id(), message_ptr, sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, encrypted.rsa_key, + encrypted.rsa_key_length, encrypted.rsa_key_iv, NULL, + &wrapped_key_length)); + wrapped_key->clear(); + wrapped_key->assign(wrapped_key_length, 0); + OEMCryptoResult sts = OEMCrypto_RewrapDeviceRSAKey( + session_id(), message_ptr, sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, encrypted.rsa_key, + encrypted.rsa_key_length, encrypted.rsa_key_iv, &(wrapped_key->front()), + &wrapped_key_length); + if (force) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + } + if (OEMCrypto_SUCCESS != sts) { + wrapped_key->clear(); + } +} + +void Session::PreparePublicKey(const uint8_t* rsa_key, + size_t rsa_key_length) { + if (rsa_key == NULL) { + rsa_key = kTestRSAPKCS8PrivateKeyInfo2_2048; + rsa_key_length = sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048); + } + uint8_t* p = const_cast(rsa_key); + BIO* bio = BIO_new_mem_buf(p, rsa_key_length); + ASSERT_TRUE(NULL != bio); + PKCS8_PRIV_KEY_INFO* pkcs8_pki = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL); + ASSERT_TRUE(NULL != pkcs8_pki); + EVP_PKEY* evp = NULL; + evp = EVP_PKCS82PKEY(pkcs8_pki); + ASSERT_TRUE(NULL != evp); + if (public_rsa_) RSA_free(public_rsa_); + public_rsa_ = EVP_PKEY_get1_RSA(evp); + EVP_PKEY_free(evp); + PKCS8_PRIV_KEY_INFO_free(pkcs8_pki); + BIO_free(bio); + if (!public_rsa_) { + cout << "d2i_RSAPrivateKey failed. "; + dump_openssl_error(); + ASSERT_TRUE(false); + } + switch (RSA_check_key(public_rsa_)) { + case 1: // valid. + ASSERT_TRUE(true); + return; + case 0: // not valid. + cout << "[rsa key not valid] "; + dump_openssl_error(); + ASSERT_TRUE(false); + default: // -1 == check failed. + cout << "[error checking rsa key] "; + dump_openssl_error(); + ASSERT_TRUE(false); + } +} + +bool Session::VerifyPSSSignature( + EVP_PKEY* pkey, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length) { + EVP_MD_CTX ctx; + EVP_MD_CTX_init(&ctx); + EVP_PKEY_CTX* pctx = NULL; + + if (EVP_DigestVerifyInit(&ctx, &pctx, EVP_sha1(), NULL /* no ENGINE */, + pkey) != 1) { + LOGE("EVP_DigestVerifyInit failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_PKEY_CTX_set_signature_md(pctx, EVP_sha1()) != 1) { + LOGE("EVP_PKEY_CTX_set_signature_md failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) != 1) { + LOGE("EVP_PKEY_CTX_set_rsa_padding failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, SHA_DIGEST_LENGTH) != 1) { + LOGE("EVP_PKEY_CTX_set_rsa_pss_saltlen failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_DigestVerifyUpdate(&ctx, message, message_length) != 1) { + LOGE("EVP_DigestVerifyUpdate failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_DigestVerifyFinal(&ctx, const_cast(signature), + signature_length) != 1) { + LOGE( + "EVP_DigestVerifyFinal failed in VerifyPSSSignature. (Probably a bad " + "signature.)"); + goto err; + } + + EVP_MD_CTX_cleanup(&ctx); + return true; + + err: + dump_openssl_error(); + EVP_MD_CTX_cleanup(&ctx); + return false; +} + +void Session::VerifyRSASignature( + const vector& message, const uint8_t* signature, + size_t signature_length, RSA_Padding_Scheme padding_scheme) { + EXPECT_TRUE(NULL != public_rsa_) + << "No public RSA key loaded in test code.\n"; + EXPECT_EQ(static_cast(RSA_size(public_rsa_)), signature_length) + << "Signature size is wrong. " << signature_length << ", should be " + << RSA_size(public_rsa_) << "\n"; + + if (padding_scheme == kSign_RSASSA_PSS) { + EVP_PKEY* pkey = EVP_PKEY_new(); + ASSERT_TRUE(EVP_PKEY_set1_RSA(pkey, public_rsa_) == 1); + + const bool ok = VerifyPSSSignature(pkey, &message[0], message.size(), + signature, signature_length); + EVP_PKEY_free(pkey); + EXPECT_TRUE(ok) << "PSS signature check failed."; + } else if (padding_scheme == kSign_PKCS1_Block1) { + vector padded_digest(signature_length); + int size; + // RSA_public_decrypt decrypts the signature, and then verifies that + // it was padded with RSA PKCS1 padding. + size = RSA_public_decrypt(signature_length, signature, &padded_digest[0], + public_rsa_, RSA_PKCS1_PADDING); + EXPECT_GT(size, 0); + padded_digest.resize(size); + EXPECT_EQ(message, padded_digest); + } else { + EXPECT_TRUE(false) << "Padding scheme not supported."; + } +} + +bool Session::GenerateRSASessionKey(vector* enc_session_key) { + if (!public_rsa_) { + cout << "No public RSA key loaded in test code.\n"; + return false; + } + vector session_key = + wvcdm::a2b_hex("6fa479c731d2770b6a61a5d1420bb9d1"); + enc_session_key->assign(RSA_size(public_rsa_), 0); + int status = RSA_public_encrypt(session_key.size(), &session_key[0], + &(enc_session_key->front()), public_rsa_, + RSA_PKCS1_OAEP_PADDING); + int size = static_cast(RSA_size(public_rsa_)); + if (status != size) { + cout << "GenerateRSASessionKey error encrypting session key. "; + dump_openssl_error(); + return false; + } + return true; +} + +void Session::InstallRSASessionTestKey( + const vector& wrapped_rsa_key) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadDeviceRSAKey(session_id(), &wrapped_rsa_key[0], + wrapped_rsa_key.size())); + GenerateDerivedKeysFromSessionKey(); +} + +void Session::DisallowDeriveKeys() { + GenerateNonce(&nonce_); + vector enc_session_key; + PreparePublicKey(); + ASSERT_TRUE(GenerateRSASessionKey(&enc_session_key)); + vector mac_context; + vector enc_context; + FillDefaultContext(&mac_context, &enc_context); + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_DeriveKeysFromSessionKey( + session_id(), &enc_session_key[0], enc_session_key.size(), + &mac_context[0], mac_context.size(), &enc_context[0], + enc_context.size())); +} + +void Session::GenerateReport( + const std::string& pst, bool expect_success, Session* other) { + if (other) { // If other is specified, copy mac keys. + mac_key_server_ = other->mac_key_server_; + mac_key_client_ = other->mac_key_client_; + } + size_t length = 0; + OEMCryptoResult sts = OEMCrypto_ReportUsage( + session_id(), reinterpret_cast(pst.c_str()), pst.length(), + pst_report(), &length); + if (expect_success) { + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + } + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { + ASSERT_LE(sizeof(OEMCrypto_PST_Report), length); + pst_report_buffer_.resize(length); + } + sts = OEMCrypto_ReportUsage(session_id(), + reinterpret_cast(pst.c_str()), + pst.length(), pst_report(), &length); + if (!expect_success) { + ASSERT_NE(OEMCrypto_SUCCESS, sts); + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector computed_signature(SHA_DIGEST_LENGTH); + unsigned int sig_len = SHA_DIGEST_LENGTH; + HMAC(EVP_sha1(), &mac_key_client_[0], mac_key_client_.size(), + reinterpret_cast(pst_report()) + SHA_DIGEST_LENGTH, + length - SHA_DIGEST_LENGTH, &computed_signature[0], &sig_len); + EXPECT_EQ(0, memcmp(&computed_signature[0], pst_report()->signature, + SHA_DIGEST_LENGTH)); + EXPECT_GE(kInactive, pst_report()->status); + EXPECT_GE(kHardwareSecureClock, pst_report()->clock_security_level); + EXPECT_EQ(pst.length(), pst_report()->pst_length); + EXPECT_EQ(0, memcmp(pst.c_str(), pst_report()->pst, pst.length())); +} + +OEMCrypto_PST_Report* Session::pst_report() { + return reinterpret_cast(&pst_report_buffer_[0]); +} + +void Session::DeleteEntry(const std::string& pst) { + uint8_t* pst_ptr = encrypted_license_.pst; + memcpy(pst_ptr, pst.c_str(), min(sizeof(license_.pst), pst.length())); + ServerSignMessage(encrypted_license_, &signature_); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_DeleteUsageEntry(session_id(), pst_ptr, pst.length(), + message_ptr(), sizeof(MessageData), + &signature_[0], signature_.size())); +} + +void Session::ForceDeleteEntry(const std::string& pst) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_ForceDeleteUsageEntry( + reinterpret_cast(pst.c_str()), pst.length())); +} + +const uint8_t* Session::message_ptr() { + return reinterpret_cast(&encrypted_license_); +} + +} // namespace wvoec diff --git a/oemcrypto/test/oec_session_util.h b/oemcrypto/test/oec_session_util.h new file mode 100644 index 00000000..4a2bcc10 --- /dev/null +++ b/oemcrypto/test/oec_session_util.h @@ -0,0 +1,207 @@ +#ifndef CDM_OEC_SESSION_UTIL_H_ +#define CDM_OEC_SESSION_UTIL_H_ + +// Copyright 2016 Google Inc. All Rights Reserved. +// +// OEMCrypto unit tests +// +#include +#include +#include + +#include "oec_device_features.h" +#include "wv_cdm_constants.h" + +using namespace std; + +// GTest requires PrintTo to be in the same namespace as the thing it prints, +// which is std::vector in this case. +namespace std { + +struct PatternTestVariant { + PatternTestVariant(size_t encrypt, size_t skip, OEMCryptoCipherMode mode) { + this->pattern.encrypt = encrypt; + this->pattern.skip = skip; + this->pattern.offset = 0; + this->mode = mode; + } + OEMCrypto_CENCEncryptPatternDesc pattern; + OEMCryptoCipherMode mode; +}; + +void PrintTo(const vector& value, ostream* os); +void PrintTo(const PatternTestVariant& param, ostream* os); + +} // namespace std + +namespace wvoec { + +const size_t kNumKeys = 4; + +namespace { +#if defined(TEST_SPEED_MULTIPLIER) // Can slow test time limits when + // debugging is slowing everything. +const int kSpeedMultiplier = TEST_SPEED_MULTIPLIER; +#else +const int kSpeedMultiplier = 1; +#endif +const int kShortSleep = 1 * kSpeedMultiplier; +const int kLongSleep = 2 * kSpeedMultiplier; +const uint32_t kDuration = 2 * kSpeedMultiplier; +const uint32_t kLongDuration = 5 * kSpeedMultiplier; +const int32_t kTimeTolerance = 3 * kSpeedMultiplier; +} // namespace + +typedef struct { + uint8_t verification[4]; + uint32_t duration; + uint32_t nonce; + uint32_t control_bits; +} KeyControlBlock; + +// Note: The API does not specify a maximum key id length. We specify a +// maximum just for these tests, so that we have a fixed message size. +const size_t kTestKeyIdMaxLength = 16; + +// Most content will use a key id that is 16 bytes long. +const int kDefaultKeyIdLength = 16; + +const size_t kMaxTestRSAKeyLength = 2000; // Rough estimate. + +typedef struct { + uint8_t key_id[kTestKeyIdMaxLength]; + size_t key_id_length; + uint8_t key_data[wvcdm::MAC_KEY_SIZE]; + size_t key_data_length; + uint8_t key_iv[wvcdm::KEY_IV_SIZE]; + uint8_t control_iv[wvcdm::KEY_IV_SIZE]; + KeyControlBlock control; + // Note: cipher_mode may not be part of a real signed message. For these + // tests, it is convenient to keep it in this structure anyway. + OEMCryptoCipherMode cipher_mode; +} MessageKeyData; + +// This structure will be signed to simulate a message from the server. +struct MessageData { + MessageKeyData keys[kNumKeys]; + uint8_t mac_key_iv[wvcdm::KEY_IV_SIZE]; + uint8_t mac_keys[2 * wvcdm::MAC_KEY_SIZE]; + uint8_t pst[kTestKeyIdMaxLength]; +}; + +struct RSAPrivateKeyMessage { + uint8_t rsa_key[kMaxTestRSAKeyLength]; + uint8_t rsa_key_iv[wvcdm::KEY_IV_SIZE]; + size_t rsa_key_length; + uint32_t nonce; +}; + +// Increment counter for AES-CTR. The CENC spec specifies we increment only +// the low 64 bits of the IV counter, and leave the high 64 bits alone. This +// is different from the OpenSSL implementation, so we implement the CTR loop +// ourselves. +void ctr128_inc64(int64_t increaseBy, uint8_t* iv); + +// Some compilers don't like the macro htonl within an ASSERT_EQ. +uint32_t htonl_fnc(uint32_t x); + +// Prints error string from openSSL +void dump_openssl_error(); + +class Session { + public: + Session(); + ~Session(); + + uint32_t get_nonce() { return nonce_; } + + uint32_t session_id() { return (uint32_t)session_id_; } + + void open(); + void close(); + void SetSessionId(uint32_t session_id); + + uint32_t GetOecSessionId() { return session_id_; } + void GenerateNonce(uint32_t* nonce, int* error_counter = NULL); + void FillDefaultContext(vector* mac_context, + vector* enc_context); + void GenerateDerivedKeysFromKeybox(); + void GenerateDerivedKeysFromSessionKey(); + void GenerateTestSessionKeys(); + void LoadTestKeys(const std::string& pst = "", bool new_mac_keys = true); + void VerifyTestKeys(); + void RefreshTestKeys(const size_t key_count, uint32_t control_bits, + uint32_t nonce, OEMCryptoResult expected_result); + void SetKeyId(int index, const string& key_id); + void FillSimpleMessage(uint32_t duration, uint32_t control, uint32_t nonce, + const std::string& pst = ""); + + void FillRefreshMessage(size_t key_count, uint32_t control_bits, + uint32_t nonce); + void EncryptAndSign(); + void EncryptMessage(RSAPrivateKeyMessage* data, + RSAPrivateKeyMessage* encrypted); + + template + void ServerSignMessage(const T& data, std::vector* signature); + + void ClientSignMessage(const vector& data, + std::vector* signature); + void FillKeyArray(const MessageData& data, OEMCrypto_KeyObject* key_array); + void FillRefreshArray(OEMCrypto_KeyRefreshObject* key_array, + size_t key_count); + void EncryptCTR( + const vector& in_buffer, const uint8_t *key, + const uint8_t* starting_iv, vector* out_buffer); + void TestDecryptCTR(bool select_key_first = true, + OEMCryptoResult expected_result = OEMCrypto_SUCCESS); + void MakeRSACertificate( + struct RSAPrivateKeyMessage* encrypted, std::vector* signature, + uint32_t allowed_schemes, const vector& rsa_key); + void RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted, + const std::vector& signature, + vector* wrapped_key, bool force); + void PreparePublicKey(const uint8_t* rsa_key = NULL, + size_t rsa_key_length = 0); + static bool VerifyPSSSignature( + EVP_PKEY* pkey, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length); + void VerifyRSASignature( + const vector& message, const uint8_t* signature, + size_t signature_length, RSA_Padding_Scheme padding_scheme); + bool GenerateRSASessionKey(vector* enc_session_key); + void InstallRSASessionTestKey(const vector& wrapped_rsa_key); + void DisallowDeriveKeys(); + void GenerateReport(const std::string& pst, bool expect_success = true, + Session* other = 0); + OEMCrypto_PST_Report* pst_report(); + void DeleteEntry(const std::string& pst); + void ForceDeleteEntry(const std::string& pst); + + MessageData& license() { return license_; } + MessageData& encrypted_license() { return encrypted_license_; } + + const uint8_t* message_ptr(); + + OEMCrypto_KeyObject* key_array() { return key_array_; } + std::vector& signature() { return signature_; } + + private: + bool open_; + bool forced_session_id_; + OEMCrypto_SESSION session_id_; + vector mac_key_server_; + vector mac_key_client_; + vector enc_key_; + uint32_t nonce_; + RSA* public_rsa_; + vector pst_report_buffer_; + MessageData license_; + MessageData encrypted_license_; + OEMCrypto_KeyObject key_array_[kNumKeys]; + std::vector signature_; +}; + +} // namespace wvoec + +#endif // CDM_OEC_SESSION_UTIL_H_ diff --git a/oemcrypto/test/oec_test_data.h b/oemcrypto/test/oec_test_data.h new file mode 100644 index 00000000..120e536b --- /dev/null +++ b/oemcrypto/test/oec_test_data.h @@ -0,0 +1,273 @@ +#ifndef CDM_OEC_TEST_DATA_H_ +#define CDM_OEC_TEST_DATA_H_ + +#include + +#if 0 +#include "OEMCryptoCENC.h" +#include "wv_keybox.h" +#endif + +namespace wvoec { + +// These are test keyboxes. They will not be accepted by production systems. +// By using known keyboxes for these tests, the results for a given set of +// inputs to a test are predictable and can be compared to the actual results. +// The first keybox, kTestKeybox, with deviceID "TestKey01" is used for most of +// the tests. It should be loaded by OEMCrypto when OEMCrypto_LoadTestKeybox +// is called. +const wvoec_mock::WidevineKeybox kTestKeybox = { + // Sample keybox used for test vectors + { + // deviceID + 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey01 + 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + }, { + // key + 0xfb, 0xda, 0x04, 0x89, 0xa1, 0x58, 0x16, 0x0e, + 0xa4, 0x02, 0xe9, 0x29, 0xe3, 0xb6, 0x8f, 0x04, + }, { + // data + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, + 0x07, 0xd9, 0xff, 0xde, 0x13, 0xaa, 0x95, 0xc1, + 0x22, 0x67, 0x80, 0x53, 0x36, 0x21, 0x36, 0xbd, + 0xf8, 0x40, 0x8f, 0x82, 0x76, 0xe4, 0xc2, 0xd8, + 0x7e, 0xc5, 0x2b, 0x61, 0xaa, 0x1b, 0x9f, 0x64, + 0x6e, 0x58, 0x73, 0x49, 0x30, 0xac, 0xeb, 0xe8, + 0x99, 0xb3, 0xe4, 0x64, 0x18, 0x9a, 0x14, 0xa8, + 0x72, 0x02, 0xfb, 0x02, 0x57, 0x4e, 0x70, 0x64, + 0x0b, 0xd2, 0x2e, 0xf4, 0x4b, 0x2d, 0x7e, 0x39, + }, { + // magic + 0x6b, 0x62, 0x6f, 0x78, + }, { + // Crc + 0x0a, 0x7a, 0x2c, 0x35, + } +}; + +static wvoec_mock::WidevineKeybox kValidKeybox02 = { + // Sample keybox used for test vectors + { + // deviceID + 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey02 + 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + }, { + // key + 0x76, 0x5d, 0xce, 0x01, 0x04, 0x89, 0xb3, 0xd0, + 0xdf, 0xce, 0x54, 0x8a, 0x49, 0xda, 0xdc, 0xb6, + }, { + // data + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, + 0x92, 0x27, 0x0b, 0x1f, 0x1a, 0xd5, 0xc6, 0x93, + 0x19, 0x3f, 0xaa, 0x74, 0x1f, 0xdd, 0x5f, 0xb4, + 0xe9, 0x40, 0x2f, 0x34, 0xa4, 0x92, 0xf4, 0xae, + 0x9a, 0x52, 0x39, 0xbc, 0xb7, 0x24, 0x38, 0x13, + 0xab, 0xf4, 0x92, 0x96, 0xc4, 0x81, 0x60, 0x33, + 0xd8, 0xb8, 0x09, 0xc7, 0x55, 0x0e, 0x12, 0xfa, + 0xa8, 0x98, 0x62, 0x8a, 0xec, 0xea, 0x74, 0x8a, + 0x4b, 0xfa, 0x5a, 0x9e, 0xb6, 0x49, 0x0d, 0x80, + }, { + // magic + 0x6b, 0x62, 0x6f, 0x78, + }, { + // Crc + 0x2a, 0x3b, 0x3e, 0xe4, + } +}; + +static wvoec_mock::WidevineKeybox kValidKeybox03 = { + // Sample keybox used for test vectors + { + // deviceID + 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey03 + 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + }, { + // key + 0x25, 0xe5, 0x2a, 0x02, 0x29, 0x68, 0x04, 0xa2, + 0x92, 0xfd, 0x7c, 0x67, 0x0b, 0x67, 0x1f, 0x31, + }, { + // data + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, + 0xf4, 0x0a, 0x0e, 0xa2, 0x0a, 0x71, 0xd5, 0x92, + 0xfa, 0xa3, 0x25, 0xc6, 0x4b, 0x76, 0xf1, 0x64, + 0xf4, 0x60, 0xa0, 0x30, 0x72, 0x23, 0xbe, 0x03, + 0xcd, 0xde, 0x7a, 0x06, 0xd4, 0x01, 0xeb, 0xdc, + 0xe0, 0x50, 0xc0, 0x53, 0x0a, 0x50, 0xb0, 0x37, + 0xe5, 0x05, 0x25, 0x0e, 0xa4, 0xc8, 0x5a, 0xff, + 0x46, 0x6e, 0xa5, 0x31, 0xf3, 0xdd, 0x94, 0xb7, + 0xe0, 0xd3, 0xf9, 0x04, 0xb2, 0x54, 0xb1, 0x64, + }, { + // magic + 0x6b, 0x62, 0x6f, 0x78, + }, { + // Crc + 0xa1, 0x99, 0x5f, 0x46, + } +}; + +// A 2048 bit RSA key in PKCS#8 PrivateKeyInfo format +// Used to verify the functions that manipulate RSA keys. +static const uint8_t kTestRSAPKCS8PrivateKeyInfo2_2048[] = { + 0x30, 0x82, 0x04, 0xbc, 0x02, 0x01, 0x00, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x04, 0xa6, 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, + 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, + 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, + 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, + 0x94, 0x58, 0xdd, 0xde, 0xa7, 0x1f, 0x3c, 0x2c, + 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, + 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, + 0x2a, 0xaa, 0x9d, 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, + 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, + 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, + 0x28, 0xda, 0x3f, 0xce, 0x31, 0x7b, 0xfd, 0x06, + 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, + 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, + 0x29, 0xf2, 0x73, 0x9e, 0x39, 0xd8, 0xf6, 0x6f, + 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, + 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, + 0xcd, 0x9a, 0x13, 0x8b, 0x54, 0x73, 0x54, 0x25, + 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, + 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, + 0x98, 0x56, 0x57, 0x54, 0x71, 0xcd, 0x12, 0x7f, + 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, + 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, + 0xc9, 0x83, 0x06, 0x51, 0x5a, 0x88, 0x65, 0x13, + 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, + 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, + 0x2d, 0x5f, 0xf8, 0x12, 0x7f, 0xa2, 0xfc, 0xbb, + 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, + 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, + 0x82, 0x46, 0x0b, 0x3a, 0x77, 0x8f, 0xc0, 0x72, + 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, + 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, + 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, + 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, + 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, + 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x5e, + 0x79, 0x65, 0x49, 0xa5, 0x76, 0x79, 0xf9, 0x05, + 0x45, 0x0f, 0xf4, 0x03, 0xbd, 0xa4, 0x7d, 0x29, + 0xd5, 0xde, 0x33, 0x63, 0xd8, 0xb8, 0xac, 0x97, + 0xeb, 0x3f, 0x5e, 0x55, 0xe8, 0x7d, 0xf3, 0xe7, + 0x3b, 0x5c, 0x2d, 0x54, 0x67, 0x36, 0xd6, 0x1d, + 0x46, 0xf5, 0xca, 0x2d, 0x8b, 0x3a, 0x7e, 0xdc, + 0x45, 0x38, 0x79, 0x7e, 0x65, 0x71, 0x5f, 0x1c, + 0x5e, 0x79, 0xb1, 0x40, 0xcd, 0xfe, 0xc5, 0xe1, + 0xc1, 0x6b, 0x78, 0x04, 0x4e, 0x8e, 0x79, 0xf9, + 0x0a, 0xfc, 0x79, 0xb1, 0x5e, 0xb3, 0x60, 0xe3, + 0x68, 0x7b, 0xc6, 0xef, 0xcb, 0x71, 0x4c, 0xba, + 0xa7, 0x79, 0x5c, 0x7a, 0x81, 0xd1, 0x71, 0xe7, + 0x00, 0x21, 0x13, 0xe2, 0x55, 0x69, 0x0e, 0x75, + 0xbe, 0x09, 0xc3, 0x4f, 0xa9, 0xc9, 0x68, 0x22, + 0x0e, 0x97, 0x8d, 0x89, 0x6e, 0xf1, 0xe8, 0x88, + 0x7a, 0xd1, 0xd9, 0x09, 0x5d, 0xd3, 0x28, 0x78, + 0x25, 0x0b, 0x1c, 0x47, 0x73, 0x25, 0xcc, 0x21, + 0xb6, 0xda, 0xc6, 0x24, 0x5a, 0xd0, 0x37, 0x14, + 0x46, 0xc7, 0x94, 0x69, 0xe4, 0x43, 0x6f, 0x47, + 0xde, 0x00, 0x33, 0x4d, 0x8f, 0x95, 0x72, 0xfa, + 0x68, 0x71, 0x17, 0x66, 0x12, 0x1a, 0x87, 0x27, + 0xf7, 0xef, 0x7e, 0xe0, 0x35, 0x58, 0xf2, 0x4d, + 0x6f, 0x35, 0x01, 0xaa, 0x96, 0xe2, 0x3d, 0x51, + 0x13, 0x86, 0x9c, 0x79, 0xd0, 0xb7, 0xb6, 0x64, + 0xe8, 0x86, 0x65, 0x50, 0xbf, 0xcc, 0x27, 0x53, + 0x1f, 0x51, 0xd4, 0xca, 0xbe, 0xf5, 0xdd, 0x77, + 0x70, 0x98, 0x0f, 0xee, 0xa8, 0x96, 0x07, 0x5f, + 0x45, 0x6a, 0x7a, 0x0d, 0x03, 0x9c, 0x4f, 0x29, + 0xf6, 0x06, 0xf3, 0x5d, 0x58, 0x6c, 0x47, 0xd0, + 0x96, 0xa9, 0x03, 0x17, 0xbb, 0x4e, 0xc9, 0x21, + 0xe0, 0xac, 0xcd, 0x78, 0x78, 0xb2, 0xfe, 0x81, + 0xb2, 0x51, 0x53, 0xa6, 0x1f, 0x98, 0x45, 0x02, + 0x81, 0x81, 0x00, 0xcf, 0x73, 0x8c, 0xbe, 0x6d, + 0x45, 0x2d, 0x0c, 0x0b, 0x5d, 0x5c, 0x6c, 0x75, + 0x78, 0xcc, 0x35, 0x48, 0xb6, 0x98, 0xf1, 0xb9, + 0x64, 0x60, 0x8c, 0x43, 0xeb, 0x85, 0xab, 0x04, + 0xb6, 0x7d, 0x1b, 0x71, 0x75, 0x06, 0xe2, 0xda, + 0x84, 0x68, 0x2e, 0x7f, 0x4c, 0xe3, 0x73, 0xb4, + 0xde, 0x51, 0x4b, 0xb6, 0x51, 0x86, 0x7b, 0xd0, + 0xe6, 0x4d, 0xf3, 0xd1, 0xcf, 0x1a, 0xfe, 0x7f, + 0x3a, 0x83, 0xba, 0xb3, 0xe1, 0xff, 0x54, 0x13, + 0x93, 0xd7, 0x9c, 0x27, 0x80, 0xb7, 0x1e, 0x64, + 0x9e, 0xf7, 0x32, 0x2b, 0x46, 0x29, 0xf7, 0xf8, + 0x18, 0x6c, 0xf7, 0x4a, 0xbe, 0x4b, 0xee, 0x96, + 0x90, 0x8f, 0xa2, 0x16, 0x22, 0x6a, 0xcc, 0x48, + 0x06, 0x74, 0x63, 0x43, 0x7f, 0x27, 0x22, 0x44, + 0x3c, 0x2d, 0x3b, 0x62, 0xf1, 0x1c, 0xb4, 0x27, + 0x33, 0x85, 0x26, 0x60, 0x48, 0x16, 0xcb, 0xef, + 0xf8, 0xcd, 0x37, 0x02, 0x81, 0x81, 0x00, 0xce, + 0x15, 0x43, 0x6e, 0x4b, 0x0f, 0xf9, 0x3f, 0x87, + 0xc3, 0x41, 0x45, 0x97, 0xb1, 0x49, 0xc2, 0x19, + 0x23, 0x87, 0xe4, 0x24, 0x1c, 0x64, 0xe5, 0x28, + 0xcb, 0x43, 0x10, 0x14, 0x14, 0x0e, 0x19, 0xcb, + 0xbb, 0xdb, 0xfd, 0x11, 0x9d, 0x17, 0x68, 0x78, + 0x6d, 0x61, 0x70, 0x63, 0x3a, 0xa1, 0xb3, 0xf3, + 0xa7, 0x5b, 0x0e, 0xff, 0xb7, 0x61, 0x11, 0x54, + 0x91, 0x99, 0xe5, 0x91, 0x32, 0x2d, 0xeb, 0x3f, + 0xd8, 0x3e, 0xf7, 0xd4, 0xcb, 0xd2, 0xa3, 0x41, + 0xc1, 0xee, 0xc6, 0x92, 0x13, 0xeb, 0x7f, 0x42, + 0x58, 0xf4, 0xd0, 0xb2, 0x74, 0x1d, 0x8e, 0x87, + 0x46, 0xcd, 0x14, 0xb8, 0x16, 0xad, 0xb5, 0xbd, + 0x0d, 0x6c, 0x95, 0x5a, 0x16, 0xbf, 0xe9, 0x53, + 0xda, 0xfb, 0xed, 0x83, 0x51, 0x67, 0xa9, 0x55, + 0xab, 0x54, 0x02, 0x95, 0x20, 0xa6, 0x68, 0x17, + 0x53, 0xa8, 0xea, 0x43, 0xe5, 0xb0, 0xa3, 0x02, + 0x81, 0x80, 0x67, 0x9c, 0x32, 0x83, 0x39, 0x57, + 0xff, 0x73, 0xb0, 0x89, 0x64, 0x8b, 0xd6, 0xf0, + 0x0a, 0x2d, 0xe2, 0xaf, 0x30, 0x1c, 0x2a, 0x97, + 0xf3, 0x90, 0x9a, 0xab, 0x9b, 0x0b, 0x1b, 0x43, + 0x79, 0xa0, 0xa7, 0x3d, 0xe7, 0xbe, 0x8d, 0x9c, + 0xeb, 0xdb, 0xad, 0x40, 0xdd, 0xa9, 0x00, 0x80, + 0xb8, 0xe1, 0xb3, 0xa1, 0x6c, 0x25, 0x92, 0xe4, + 0x33, 0xb2, 0xbe, 0xeb, 0x4d, 0x74, 0x26, 0x5f, + 0x37, 0x43, 0x9c, 0x6c, 0x17, 0x76, 0x0a, 0x81, + 0x20, 0x82, 0xa1, 0x48, 0x2c, 0x2d, 0x45, 0xdc, + 0x0f, 0x62, 0x43, 0x32, 0xbb, 0xeb, 0x59, 0x41, + 0xf9, 0xca, 0x58, 0xce, 0x4a, 0x66, 0x53, 0x54, + 0xc8, 0x28, 0x10, 0x1e, 0x08, 0x71, 0x16, 0xd8, + 0x02, 0x71, 0x41, 0x58, 0xd4, 0x56, 0xcc, 0xf5, + 0xb1, 0x31, 0xa3, 0xed, 0x00, 0x85, 0x09, 0xbf, + 0x35, 0x95, 0x41, 0x29, 0x40, 0x19, 0x83, 0x35, + 0x24, 0x69, 0x02, 0x81, 0x80, 0x55, 0x10, 0x0b, + 0xcc, 0x3b, 0xa9, 0x75, 0x3d, 0x16, 0xe1, 0xae, + 0x50, 0x76, 0x63, 0x94, 0x49, 0x4c, 0xad, 0x10, + 0xcb, 0x47, 0x68, 0x7c, 0xf0, 0xe5, 0xdc, 0xb8, + 0x6a, 0xab, 0x8e, 0xf7, 0x9f, 0x08, 0x2c, 0x1b, + 0x8a, 0xa2, 0xb9, 0x8f, 0xce, 0xec, 0x5e, 0x61, + 0xa8, 0xcd, 0x1c, 0x87, 0x60, 0x4a, 0xc3, 0x1a, + 0x5f, 0xdf, 0x87, 0x26, 0xc6, 0xcb, 0x7c, 0x69, + 0xe4, 0x8b, 0x01, 0x06, 0x59, 0x22, 0xfa, 0x34, + 0x4b, 0x81, 0x87, 0x3c, 0x03, 0x6d, 0x02, 0x0a, + 0x77, 0xe6, 0x15, 0xd8, 0xcf, 0xa7, 0x68, 0x26, + 0x6c, 0xfa, 0x2b, 0xd9, 0x83, 0x5a, 0x2d, 0x0c, + 0x3b, 0x70, 0x1c, 0xd4, 0x48, 0xbe, 0xa7, 0x0a, + 0xd9, 0xbe, 0xdc, 0xc3, 0x0c, 0x21, 0x33, 0xb3, + 0x66, 0xff, 0x1c, 0x1b, 0xc8, 0x96, 0x76, 0xe8, + 0x6f, 0x44, 0x74, 0xbc, 0x9b, 0x1c, 0x7d, 0xc8, + 0xac, 0x21, 0xa8, 0x6e, 0x37, 0x02, 0x81, 0x80, + 0x2c, 0x7c, 0xad, 0x1e, 0x75, 0xf6, 0x69, 0x1d, + 0xe7, 0xa6, 0xca, 0x74, 0x7d, 0x67, 0xc8, 0x65, + 0x28, 0x66, 0xc4, 0x43, 0xa6, 0xbd, 0x40, 0x57, + 0xae, 0xb7, 0x65, 0x2c, 0x52, 0xf9, 0xe4, 0xc7, + 0x81, 0x7b, 0x56, 0xa3, 0xd2, 0x0d, 0xe8, 0x33, + 0x70, 0xcf, 0x06, 0x84, 0xb3, 0x4e, 0x44, 0x50, + 0x75, 0x61, 0x96, 0x86, 0x4b, 0xb6, 0x2b, 0xad, + 0xf0, 0xad, 0x57, 0xd0, 0x37, 0x0d, 0x1d, 0x35, + 0x50, 0xcb, 0x69, 0x22, 0x39, 0x29, 0xb9, 0x3a, + 0xd3, 0x29, 0x23, 0x02, 0x60, 0xf7, 0xab, 0x30, + 0x40, 0xda, 0x8e, 0x4d, 0x45, 0x70, 0x26, 0xf4, + 0xa2, 0x0d, 0xd0, 0x64, 0x5d, 0x47, 0x3c, 0x18, + 0xf4, 0xd4, 0x52, 0x95, 0x00, 0xae, 0x84, 0x6b, + 0x47, 0xb2, 0x3c, 0x82, 0xd3, 0x72, 0x53, 0xde, + 0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, + 0x56, 0xfe, 0x39, 0x28, 0x33, 0xe0, 0xdb, 0x03 }; + +} // namespace wvoec + +#endif // CDM_OEC_TEST_DATA_H_ diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index 6f53fdc8..734c573e 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -5,18 +5,6 @@ #include // needed for ntoh() #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include #include #include #include @@ -24,10 +12,24 @@ #include #include #include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include #include "log.h" +#include "oec_session_util.h" +#include "oec_test_data.h" #include "oemcrypto_key_mock.h" -#include "oemcrypto_test.h" +#include "oec_device_features.h" #include "OEMCryptoCENC.h" #include "properties.h" #include "string_conversions.h" @@ -39,1201 +41,7 @@ using ::testing::WithParamInterface; using ::testing::Range; using ::testing::Values; -// GTest requires PrintTo to be in the same namespace as the thing it prints, -// which is std::vector in this case. -namespace std { -void PrintTo(const vector& value, std::ostream* os) { - *os << wvcdm::b2a_hex(value); -} -} // namespace std - namespace wvoec { -namespace { -const size_t kNumKeys = 4; -#if defined(TEST_SPEED_MULTIPLIER) // Can slow test time limits when - // debugging is slowing everything. -const int kSpeedMultiplier = TEST_SPEED_MULTIPLIER; -#else -const int kSpeedMultiplier = 1; -#endif -const int kShortSleep = 1 * kSpeedMultiplier; -const int kLongSleep = 2 * kSpeedMultiplier; -const uint32_t kDuration = 2 * kSpeedMultiplier; -const uint32_t kLongDuration = 5 * kSpeedMultiplier; -const int32_t kAlmostRange = 3 * kSpeedMultiplier; -} // namespace - -typedef struct { - uint8_t verification[4]; - uint32_t duration; - uint32_t nonce; - uint32_t control_bits; -} KeyControlBlock; - -// Note: The API does not specify a maximum key id length. We specify a -// maximum just for these tests, so that we have a fixed message size. -const size_t kTestKeyIdMaxLength = 16; -// Most content will use a key id that is 16 bytes long. -const int kDefaultKeyIdLength = 16; -typedef struct { - uint8_t key_id[kTestKeyIdMaxLength]; - size_t key_id_length; - uint8_t key_data[wvcdm::MAC_KEY_SIZE]; - size_t key_data_length; - uint8_t key_iv[wvcdm::KEY_IV_SIZE]; - uint8_t control_iv[wvcdm::KEY_IV_SIZE]; - KeyControlBlock control; -} MessageKeyData; - -// This structure will be signed to simulate a message from the server. -struct MessageData { - MessageKeyData keys[kNumKeys]; - uint8_t mac_key_iv[wvcdm::KEY_IV_SIZE]; - uint8_t mac_keys[2 * wvcdm::MAC_KEY_SIZE]; - uint8_t pst[kTestKeyIdMaxLength]; -}; - -const size_t kMaxTestRSAKeyLength = 2000; // Rough estimate. -struct RSAPrivateKeyMessage { - uint8_t rsa_key[kMaxTestRSAKeyLength]; - uint8_t rsa_key_iv[wvcdm::KEY_IV_SIZE]; - size_t rsa_key_length; - uint32_t nonce; -}; - -// These are test keyboxes. They will not be accepted by production systems. -// By using known keyboxes for these tests, the results for a given set of -// inputs to a test are predictable and can be compared to the actual results. -// The first keybox, kTestKeybox, with deviceID "TestKey01" is used for most of -// the tests. It should be loaded by OEMCrypto when OEMCrypto_LoadTestKeybox -// is called. -const wvoec_mock::WidevineKeybox kTestKeybox = { - // Sample keybox used for test vectors - { - // deviceID - 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey01 - 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - }, { - // key - 0xfb, 0xda, 0x04, 0x89, 0xa1, 0x58, 0x16, 0x0e, - 0xa4, 0x02, 0xe9, 0x29, 0xe3, 0xb6, 0x8f, 0x04, - }, { - // data - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, - 0x07, 0xd9, 0xff, 0xde, 0x13, 0xaa, 0x95, 0xc1, - 0x22, 0x67, 0x80, 0x53, 0x36, 0x21, 0x36, 0xbd, - 0xf8, 0x40, 0x8f, 0x82, 0x76, 0xe4, 0xc2, 0xd8, - 0x7e, 0xc5, 0x2b, 0x61, 0xaa, 0x1b, 0x9f, 0x64, - 0x6e, 0x58, 0x73, 0x49, 0x30, 0xac, 0xeb, 0xe8, - 0x99, 0xb3, 0xe4, 0x64, 0x18, 0x9a, 0x14, 0xa8, - 0x72, 0x02, 0xfb, 0x02, 0x57, 0x4e, 0x70, 0x64, - 0x0b, 0xd2, 0x2e, 0xf4, 0x4b, 0x2d, 0x7e, 0x39, - }, { - // magic - 0x6b, 0x62, 0x6f, 0x78, - }, { - // Crc - 0x0a, 0x7a, 0x2c, 0x35, - } -}; - -static wvoec_mock::WidevineKeybox kValidKeybox02 = { - // Sample keybox used for test vectors - { - // deviceID - 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey02 - 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - }, { - // key - 0x76, 0x5d, 0xce, 0x01, 0x04, 0x89, 0xb3, 0xd0, - 0xdf, 0xce, 0x54, 0x8a, 0x49, 0xda, 0xdc, 0xb6, - }, { - // data - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, - 0x92, 0x27, 0x0b, 0x1f, 0x1a, 0xd5, 0xc6, 0x93, - 0x19, 0x3f, 0xaa, 0x74, 0x1f, 0xdd, 0x5f, 0xb4, - 0xe9, 0x40, 0x2f, 0x34, 0xa4, 0x92, 0xf4, 0xae, - 0x9a, 0x52, 0x39, 0xbc, 0xb7, 0x24, 0x38, 0x13, - 0xab, 0xf4, 0x92, 0x96, 0xc4, 0x81, 0x60, 0x33, - 0xd8, 0xb8, 0x09, 0xc7, 0x55, 0x0e, 0x12, 0xfa, - 0xa8, 0x98, 0x62, 0x8a, 0xec, 0xea, 0x74, 0x8a, - 0x4b, 0xfa, 0x5a, 0x9e, 0xb6, 0x49, 0x0d, 0x80, - }, { - // magic - 0x6b, 0x62, 0x6f, 0x78, - }, { - // Crc - 0x2a, 0x3b, 0x3e, 0xe4, - } -}; - -static wvoec_mock::WidevineKeybox kValidKeybox03 = { - // Sample keybox used for test vectors - { - // deviceID - 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey03 - 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - }, { - // key - 0x25, 0xe5, 0x2a, 0x02, 0x29, 0x68, 0x04, 0xa2, - 0x92, 0xfd, 0x7c, 0x67, 0x0b, 0x67, 0x1f, 0x31, - }, { - // data - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, - 0xf4, 0x0a, 0x0e, 0xa2, 0x0a, 0x71, 0xd5, 0x92, - 0xfa, 0xa3, 0x25, 0xc6, 0x4b, 0x76, 0xf1, 0x64, - 0xf4, 0x60, 0xa0, 0x30, 0x72, 0x23, 0xbe, 0x03, - 0xcd, 0xde, 0x7a, 0x06, 0xd4, 0x01, 0xeb, 0xdc, - 0xe0, 0x50, 0xc0, 0x53, 0x0a, 0x50, 0xb0, 0x37, - 0xe5, 0x05, 0x25, 0x0e, 0xa4, 0xc8, 0x5a, 0xff, - 0x46, 0x6e, 0xa5, 0x31, 0xf3, 0xdd, 0x94, 0xb7, - 0xe0, 0xd3, 0xf9, 0x04, 0xb2, 0x54, 0xb1, 0x64, - }, { - // magic - 0x6b, 0x62, 0x6f, 0x78, - }, { - // Crc - 0xa1, 0x99, 0x5f, 0x46, - } -}; - -// A 2048 bit RSA key in PKCS#8 PrivateKeyInfo format -// Used to verify the functions that manipulate RSA keys. -static const uint8_t kTestRSAPKCS8PrivateKeyInfo2_2048[] = { - 0x30, 0x82, 0x04, 0xbc, 0x02, 0x01, 0x00, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, - 0x04, 0xa6, 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, - 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, - 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, - 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, - 0x94, 0x58, 0xdd, 0xde, 0xa7, 0x1f, 0x3c, 0x2c, - 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, - 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, - 0x2a, 0xaa, 0x9d, 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, - 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, - 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, - 0x28, 0xda, 0x3f, 0xce, 0x31, 0x7b, 0xfd, 0x06, - 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, - 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, - 0x29, 0xf2, 0x73, 0x9e, 0x39, 0xd8, 0xf6, 0x6f, - 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, - 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, - 0xcd, 0x9a, 0x13, 0x8b, 0x54, 0x73, 0x54, 0x25, - 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, - 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, - 0x98, 0x56, 0x57, 0x54, 0x71, 0xcd, 0x12, 0x7f, - 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, - 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, - 0xc9, 0x83, 0x06, 0x51, 0x5a, 0x88, 0x65, 0x13, - 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, - 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, - 0x2d, 0x5f, 0xf8, 0x12, 0x7f, 0xa2, 0xfc, 0xbb, - 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, - 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, - 0x82, 0x46, 0x0b, 0x3a, 0x77, 0x8f, 0xc0, 0x72, - 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, - 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, - 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, - 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, - 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, - 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x5e, - 0x79, 0x65, 0x49, 0xa5, 0x76, 0x79, 0xf9, 0x05, - 0x45, 0x0f, 0xf4, 0x03, 0xbd, 0xa4, 0x7d, 0x29, - 0xd5, 0xde, 0x33, 0x63, 0xd8, 0xb8, 0xac, 0x97, - 0xeb, 0x3f, 0x5e, 0x55, 0xe8, 0x7d, 0xf3, 0xe7, - 0x3b, 0x5c, 0x2d, 0x54, 0x67, 0x36, 0xd6, 0x1d, - 0x46, 0xf5, 0xca, 0x2d, 0x8b, 0x3a, 0x7e, 0xdc, - 0x45, 0x38, 0x79, 0x7e, 0x65, 0x71, 0x5f, 0x1c, - 0x5e, 0x79, 0xb1, 0x40, 0xcd, 0xfe, 0xc5, 0xe1, - 0xc1, 0x6b, 0x78, 0x04, 0x4e, 0x8e, 0x79, 0xf9, - 0x0a, 0xfc, 0x79, 0xb1, 0x5e, 0xb3, 0x60, 0xe3, - 0x68, 0x7b, 0xc6, 0xef, 0xcb, 0x71, 0x4c, 0xba, - 0xa7, 0x79, 0x5c, 0x7a, 0x81, 0xd1, 0x71, 0xe7, - 0x00, 0x21, 0x13, 0xe2, 0x55, 0x69, 0x0e, 0x75, - 0xbe, 0x09, 0xc3, 0x4f, 0xa9, 0xc9, 0x68, 0x22, - 0x0e, 0x97, 0x8d, 0x89, 0x6e, 0xf1, 0xe8, 0x88, - 0x7a, 0xd1, 0xd9, 0x09, 0x5d, 0xd3, 0x28, 0x78, - 0x25, 0x0b, 0x1c, 0x47, 0x73, 0x25, 0xcc, 0x21, - 0xb6, 0xda, 0xc6, 0x24, 0x5a, 0xd0, 0x37, 0x14, - 0x46, 0xc7, 0x94, 0x69, 0xe4, 0x43, 0x6f, 0x47, - 0xde, 0x00, 0x33, 0x4d, 0x8f, 0x95, 0x72, 0xfa, - 0x68, 0x71, 0x17, 0x66, 0x12, 0x1a, 0x87, 0x27, - 0xf7, 0xef, 0x7e, 0xe0, 0x35, 0x58, 0xf2, 0x4d, - 0x6f, 0x35, 0x01, 0xaa, 0x96, 0xe2, 0x3d, 0x51, - 0x13, 0x86, 0x9c, 0x79, 0xd0, 0xb7, 0xb6, 0x64, - 0xe8, 0x86, 0x65, 0x50, 0xbf, 0xcc, 0x27, 0x53, - 0x1f, 0x51, 0xd4, 0xca, 0xbe, 0xf5, 0xdd, 0x77, - 0x70, 0x98, 0x0f, 0xee, 0xa8, 0x96, 0x07, 0x5f, - 0x45, 0x6a, 0x7a, 0x0d, 0x03, 0x9c, 0x4f, 0x29, - 0xf6, 0x06, 0xf3, 0x5d, 0x58, 0x6c, 0x47, 0xd0, - 0x96, 0xa9, 0x03, 0x17, 0xbb, 0x4e, 0xc9, 0x21, - 0xe0, 0xac, 0xcd, 0x78, 0x78, 0xb2, 0xfe, 0x81, - 0xb2, 0x51, 0x53, 0xa6, 0x1f, 0x98, 0x45, 0x02, - 0x81, 0x81, 0x00, 0xcf, 0x73, 0x8c, 0xbe, 0x6d, - 0x45, 0x2d, 0x0c, 0x0b, 0x5d, 0x5c, 0x6c, 0x75, - 0x78, 0xcc, 0x35, 0x48, 0xb6, 0x98, 0xf1, 0xb9, - 0x64, 0x60, 0x8c, 0x43, 0xeb, 0x85, 0xab, 0x04, - 0xb6, 0x7d, 0x1b, 0x71, 0x75, 0x06, 0xe2, 0xda, - 0x84, 0x68, 0x2e, 0x7f, 0x4c, 0xe3, 0x73, 0xb4, - 0xde, 0x51, 0x4b, 0xb6, 0x51, 0x86, 0x7b, 0xd0, - 0xe6, 0x4d, 0xf3, 0xd1, 0xcf, 0x1a, 0xfe, 0x7f, - 0x3a, 0x83, 0xba, 0xb3, 0xe1, 0xff, 0x54, 0x13, - 0x93, 0xd7, 0x9c, 0x27, 0x80, 0xb7, 0x1e, 0x64, - 0x9e, 0xf7, 0x32, 0x2b, 0x46, 0x29, 0xf7, 0xf8, - 0x18, 0x6c, 0xf7, 0x4a, 0xbe, 0x4b, 0xee, 0x96, - 0x90, 0x8f, 0xa2, 0x16, 0x22, 0x6a, 0xcc, 0x48, - 0x06, 0x74, 0x63, 0x43, 0x7f, 0x27, 0x22, 0x44, - 0x3c, 0x2d, 0x3b, 0x62, 0xf1, 0x1c, 0xb4, 0x27, - 0x33, 0x85, 0x26, 0x60, 0x48, 0x16, 0xcb, 0xef, - 0xf8, 0xcd, 0x37, 0x02, 0x81, 0x81, 0x00, 0xce, - 0x15, 0x43, 0x6e, 0x4b, 0x0f, 0xf9, 0x3f, 0x87, - 0xc3, 0x41, 0x45, 0x97, 0xb1, 0x49, 0xc2, 0x19, - 0x23, 0x87, 0xe4, 0x24, 0x1c, 0x64, 0xe5, 0x28, - 0xcb, 0x43, 0x10, 0x14, 0x14, 0x0e, 0x19, 0xcb, - 0xbb, 0xdb, 0xfd, 0x11, 0x9d, 0x17, 0x68, 0x78, - 0x6d, 0x61, 0x70, 0x63, 0x3a, 0xa1, 0xb3, 0xf3, - 0xa7, 0x5b, 0x0e, 0xff, 0xb7, 0x61, 0x11, 0x54, - 0x91, 0x99, 0xe5, 0x91, 0x32, 0x2d, 0xeb, 0x3f, - 0xd8, 0x3e, 0xf7, 0xd4, 0xcb, 0xd2, 0xa3, 0x41, - 0xc1, 0xee, 0xc6, 0x92, 0x13, 0xeb, 0x7f, 0x42, - 0x58, 0xf4, 0xd0, 0xb2, 0x74, 0x1d, 0x8e, 0x87, - 0x46, 0xcd, 0x14, 0xb8, 0x16, 0xad, 0xb5, 0xbd, - 0x0d, 0x6c, 0x95, 0x5a, 0x16, 0xbf, 0xe9, 0x53, - 0xda, 0xfb, 0xed, 0x83, 0x51, 0x67, 0xa9, 0x55, - 0xab, 0x54, 0x02, 0x95, 0x20, 0xa6, 0x68, 0x17, - 0x53, 0xa8, 0xea, 0x43, 0xe5, 0xb0, 0xa3, 0x02, - 0x81, 0x80, 0x67, 0x9c, 0x32, 0x83, 0x39, 0x57, - 0xff, 0x73, 0xb0, 0x89, 0x64, 0x8b, 0xd6, 0xf0, - 0x0a, 0x2d, 0xe2, 0xaf, 0x30, 0x1c, 0x2a, 0x97, - 0xf3, 0x90, 0x9a, 0xab, 0x9b, 0x0b, 0x1b, 0x43, - 0x79, 0xa0, 0xa7, 0x3d, 0xe7, 0xbe, 0x8d, 0x9c, - 0xeb, 0xdb, 0xad, 0x40, 0xdd, 0xa9, 0x00, 0x80, - 0xb8, 0xe1, 0xb3, 0xa1, 0x6c, 0x25, 0x92, 0xe4, - 0x33, 0xb2, 0xbe, 0xeb, 0x4d, 0x74, 0x26, 0x5f, - 0x37, 0x43, 0x9c, 0x6c, 0x17, 0x76, 0x0a, 0x81, - 0x20, 0x82, 0xa1, 0x48, 0x2c, 0x2d, 0x45, 0xdc, - 0x0f, 0x62, 0x43, 0x32, 0xbb, 0xeb, 0x59, 0x41, - 0xf9, 0xca, 0x58, 0xce, 0x4a, 0x66, 0x53, 0x54, - 0xc8, 0x28, 0x10, 0x1e, 0x08, 0x71, 0x16, 0xd8, - 0x02, 0x71, 0x41, 0x58, 0xd4, 0x56, 0xcc, 0xf5, - 0xb1, 0x31, 0xa3, 0xed, 0x00, 0x85, 0x09, 0xbf, - 0x35, 0x95, 0x41, 0x29, 0x40, 0x19, 0x83, 0x35, - 0x24, 0x69, 0x02, 0x81, 0x80, 0x55, 0x10, 0x0b, - 0xcc, 0x3b, 0xa9, 0x75, 0x3d, 0x16, 0xe1, 0xae, - 0x50, 0x76, 0x63, 0x94, 0x49, 0x4c, 0xad, 0x10, - 0xcb, 0x47, 0x68, 0x7c, 0xf0, 0xe5, 0xdc, 0xb8, - 0x6a, 0xab, 0x8e, 0xf7, 0x9f, 0x08, 0x2c, 0x1b, - 0x8a, 0xa2, 0xb9, 0x8f, 0xce, 0xec, 0x5e, 0x61, - 0xa8, 0xcd, 0x1c, 0x87, 0x60, 0x4a, 0xc3, 0x1a, - 0x5f, 0xdf, 0x87, 0x26, 0xc6, 0xcb, 0x7c, 0x69, - 0xe4, 0x8b, 0x01, 0x06, 0x59, 0x22, 0xfa, 0x34, - 0x4b, 0x81, 0x87, 0x3c, 0x03, 0x6d, 0x02, 0x0a, - 0x77, 0xe6, 0x15, 0xd8, 0xcf, 0xa7, 0x68, 0x26, - 0x6c, 0xfa, 0x2b, 0xd9, 0x83, 0x5a, 0x2d, 0x0c, - 0x3b, 0x70, 0x1c, 0xd4, 0x48, 0xbe, 0xa7, 0x0a, - 0xd9, 0xbe, 0xdc, 0xc3, 0x0c, 0x21, 0x33, 0xb3, - 0x66, 0xff, 0x1c, 0x1b, 0xc8, 0x96, 0x76, 0xe8, - 0x6f, 0x44, 0x74, 0xbc, 0x9b, 0x1c, 0x7d, 0xc8, - 0xac, 0x21, 0xa8, 0x6e, 0x37, 0x02, 0x81, 0x80, - 0x2c, 0x7c, 0xad, 0x1e, 0x75, 0xf6, 0x69, 0x1d, - 0xe7, 0xa6, 0xca, 0x74, 0x7d, 0x67, 0xc8, 0x65, - 0x28, 0x66, 0xc4, 0x43, 0xa6, 0xbd, 0x40, 0x57, - 0xae, 0xb7, 0x65, 0x2c, 0x52, 0xf9, 0xe4, 0xc7, - 0x81, 0x7b, 0x56, 0xa3, 0xd2, 0x0d, 0xe8, 0x33, - 0x70, 0xcf, 0x06, 0x84, 0xb3, 0x4e, 0x44, 0x50, - 0x75, 0x61, 0x96, 0x86, 0x4b, 0xb6, 0x2b, 0xad, - 0xf0, 0xad, 0x57, 0xd0, 0x37, 0x0d, 0x1d, 0x35, - 0x50, 0xcb, 0x69, 0x22, 0x39, 0x29, 0xb9, 0x3a, - 0xd3, 0x29, 0x23, 0x02, 0x60, 0xf7, 0xab, 0x30, - 0x40, 0xda, 0x8e, 0x4d, 0x45, 0x70, 0x26, 0xf4, - 0xa2, 0x0d, 0xd0, 0x64, 0x5d, 0x47, 0x3c, 0x18, - 0xf4, 0xd4, 0x52, 0x95, 0x00, 0xae, 0x84, 0x6b, - 0x47, 0xb2, 0x3c, 0x82, 0xd3, 0x72, 0x53, 0xde, - 0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, - 0x56, 0xfe, 0x39, 0x28, 0x33, 0xe0, 0xdb, 0x03 }; - -DeviceFeatures global_features; - -void DeviceFeatures::Initialize(bool is_cast_receiver, bool force_load_test_keybox) { - cast_receiver = is_cast_receiver; - uses_keybox = false; - uses_certificate = false; - loads_certificate = false; - generic_crypto = false; - usage_table = false; - api_version = 0; - derive_key_method = NO_METHOD; - if (OEMCrypto_SUCCESS != OEMCrypto_Initialize()) { - printf("OEMCrypto_Initialze failed. All tests will fail.\n"); - return; - } - uint32_t nonce = 0; - uint8_t buffer[1]; - size_t size = 0; - uses_keybox = - (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_GetKeyData(buffer, &size)); - printf("uses_keybox = %s.\n", uses_keybox ? "true" : "false"); - loads_certificate = uses_keybox && - (OEMCrypto_ERROR_NOT_IMPLEMENTED != - OEMCrypto_RewrapDeviceRSAKey(0, buffer, 0, buffer, 0, &nonce, - buffer, 0, buffer, - buffer, &size)); - printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false"); - uses_certificate = - (OEMCrypto_ERROR_NOT_IMPLEMENTED - != OEMCrypto_GenerateRSASignature(0, buffer, 0, buffer, &size, - kSign_RSASSA_PSS)); - printf("uses_certificate = %s.\n", uses_certificate ? "true" : "false"); - generic_crypto = - (OEMCrypto_ERROR_NOT_IMPLEMENTED != - OEMCrypto_Generic_Encrypt(0, buffer, 0, buffer, - OEMCrypto_AES_CBC_128_NO_PADDING, buffer)); - printf("generic_crypto = %s.\n", generic_crypto ? "true" : "false"); - api_version = OEMCrypto_APIVersion(); - printf("api_version = %d.\n", api_version); - usage_table = OEMCrypto_SupportsUsageTable(); - printf("usage_table = %s.\n", usage_table ? "true" : "false"); - if (force_load_test_keybox) { - derive_key_method = FORCE_TEST_KEYBOX; - } else { - PickDerivedKey(); - } - printf("cast_receiver = %s.\n", cast_receiver ? "true" : "false"); - switch(derive_key_method) { - case NO_METHOD: - printf("NO_METHOD: Cannot derive known session keys.\n"); - // Note: cast_receiver left unchanged because set by user on command line. - uses_keybox = false; - uses_certificate = false; - loads_certificate = false; - generic_crypto = false; - usage_table = false; - break; - case LOAD_TEST_KEYBOX: - printf("LOAD_TEST_KEYBOX: Call LoadTestKeybox before deriving keys.\n"); - break; - case LOAD_TEST_RSA_KEY: - printf("LOAD_TEST_RSA_KEY: Call LoadTestRSAKey before deriving keys.\n"); - break; - case EXISTING_TEST_KEYBOX: - printf("EXISTING_TEST_KEYBOX: Keybox is already the test keybox.\n"); - break; - case FORCE_TEST_KEYBOX: - printf("FORCE_TEST_KEYBOX: User requested calling InstallKeybox.\n"); - break; - } - OEMCrypto_Terminate(); -} - -std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { - std::string filter = initial_filter; - if (!uses_keybox) FilterOut(&filter, "*KeyboxTest*"); - if (derive_key_method - != FORCE_TEST_KEYBOX) FilterOut(&filter, "*ForceKeybox*"); - if (!uses_certificate) FilterOut(&filter, "*Certificate*"); - if (!loads_certificate) FilterOut(&filter, "*LoadsCert*"); - if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); - if (!cast_receiver) FilterOut(&filter, "*CastReceiver*"); - if (!usage_table) FilterOut(&filter, "*UsageTable*"); - if (derive_key_method == NO_METHOD) FilterOut(&filter, "*SessionTest*"); - if (api_version < 10) FilterOut(&filter, "*API10*"); - // Performance tests take a long time. Filter them out if they are not - // specifically requested. - if (filter.find("Performance") == std::string::npos) { - FilterOut(&filter, "*Performance*"); - } - return filter; -} - -void DeviceFeatures::PickDerivedKey() { - if (uses_keybox) { - // If device uses a keybox, try to load the test keybox. - if(OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestKeybox()) { - derive_key_method = LOAD_TEST_KEYBOX; - } else if(IsTestKeyboxInstalled()) { - derive_key_method = EXISTING_TEST_KEYBOX; - } - } else if(OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestRSAKey()) { - derive_key_method = LOAD_TEST_RSA_KEY; - } -} - -bool DeviceFeatures::IsTestKeyboxInstalled() { - uint8_t key_data[256]; - size_t key_data_len = sizeof(key_data); - if (OEMCrypto_GetKeyData(key_data, &key_data_len) != OEMCrypto_SUCCESS) - return false; - if (key_data_len != sizeof(kTestKeybox.data_)) return false; - if (memcmp(key_data, kTestKeybox.data_, key_data_len)) return false; - uint8_t dev_id[128] = {0}; - size_t dev_id_len = 128; - if (OEMCrypto_GetDeviceID(dev_id, &dev_id_len) != OEMCrypto_SUCCESS) - return false; - // We use strncmp instead of memcmp because we don't really care about the - // multiple '\0' characters at the end of the device id. - return 0 == - strncmp(reinterpret_cast(dev_id), - reinterpret_cast(kTestKeybox.device_id_), - sizeof(kTestKeybox.device_id_)); -} - -void DeviceFeatures::FilterOut(std::string* current_filter, - const std::string& new_filter) { - if (current_filter->find('-') == std::string::npos) { - *current_filter += "-" + new_filter; - } else { - *current_filter += ":" + new_filter; - } -} - -static void dump_openssl_error() { - while (unsigned long err = ERR_get_error()) { - char buffer[120]; - ERR_error_string_n(err, buffer, sizeof(buffer)); - cout << "openssl error -- " << buffer << "\n"; - } -} - -// We don't expect exact timing. -#define EXPECT_ALMOST(A, B) \ - EXPECT_GE(A + kAlmostRange, B); \ - EXPECT_LE(A - kAlmostRange, B); - -class Session { - public: - Session() - : open_(false), - session_id_(0), - mac_key_server_(wvcdm::MAC_KEY_SIZE), - mac_key_client_(wvcdm::MAC_KEY_SIZE), - enc_key_(wvcdm::KEY_SIZE), - public_rsa_(0) {} - - ~Session() { - if (open_) close(); - if (public_rsa_) RSA_free(public_rsa_); - } - - bool isOpen() { return open_; } - OEMCryptoResult getStatus() { return session_status_; } - uint32_t get_nonce() { return nonce_; } - - uint32_t session_id() { return (uint32_t)session_id_; } - void set_session_id(uint32_t newsession) { - session_id_ = (OEMCrypto_SESSION)newsession; - } - - void open() { - EXPECT_FALSE(open_); - session_status_ = OEMCrypto_OpenSession(&session_id_); - if (OEMCrypto_SUCCESS == session_status_) { - open_ = true; - } - } - - void close() { - session_status_ = OEMCrypto_CloseSession(session_id_); - if (OEMCrypto_SUCCESS == session_status_) { - open_ = false; - } - } - - void GenerateNonce(uint32_t* nonce, int* error_counter = NULL) { - if (OEMCrypto_SUCCESS == OEMCrypto_GenerateNonce(session_id(), nonce)) { - return; - } - if (error_counter) { - (*error_counter)++; - } else { - sleep(1); // wait a second, then try again. - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_GenerateNonce(session_id(), nonce)); - } - } - - void FillDefaultContext(vector* mac_context, - vector* enc_context) { - /* Context strings - * These context strings are normally created by the CDM layer - * from a license request message. - * They are used to test MAC and ENC key generation. - */ - *mac_context = wvcdm::a2b_hex( - "41555448454e5449434154494f4e000a4c08001248000000020000101907d9ff" - "de13aa95c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e5873" - "4930acebe899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a" - "230a14080112100915007caa9b5931b76a3a85f046523e10011a093938373635" - "34333231180120002a0c31383836373837343035000000000200"); - *enc_context = wvcdm::a2b_hex( - "454e4352595054494f4e000a4c08001248000000020000101907d9ffde13aa95" - "c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e58734930aceb" - "e899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a230a1408" - "0112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231" - "180120002a0c31383836373837343035000000000080"); - } - - void GenerateDerivedKeysFromKeybox() { - GenerateNonce(&nonce_); - vector mac_context; - vector enc_context; - FillDefaultContext(&mac_context, &enc_context); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_GenerateDerivedKeys(session_id(), &mac_context[0], - mac_context.size(), &enc_context[0], - enc_context.size())); - - // Expected MAC and ENC keys generated from context strings - // with test keybox "installed". - mac_key_server_ = wvcdm::a2b_hex( - "3CFD60254786AF350B353B4FBB700AB382558400356866BA16C256BCD8C502BF"); - mac_key_client_ = wvcdm::a2b_hex( - "A9DE7B3E4E199ED8D1FBC29CD6B4C772CC4538C8B0D3E208B3E76F2EC0FD6F47"); - enc_key_ = wvcdm::a2b_hex("D0BFC35DA9E33436E81C4229E78CB9F4"); - } - - void GenerateDerivedKeysFromSessionKey() { // Uses test certificate. - GenerateNonce(&nonce_); - vector enc_session_key; - PreparePublicKey(); - ASSERT_TRUE(GenerateRSASessionKey(&enc_session_key)); - vector mac_context; - vector enc_context; - FillDefaultContext(&mac_context, &enc_context); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_DeriveKeysFromSessionKey( - session_id(), &enc_session_key[0], enc_session_key.size(), - &mac_context[0], mac_context.size(), &enc_context[0], - enc_context.size())); - - // Expected MAC and ENC keys generated from context strings - // with RSA certificate "installed". - mac_key_server_ = wvcdm::a2b_hex( - "1E451E59CB663DA1646194DD28880788ED8ED2EFF913CBD6A0D535D1D5A90381"); - mac_key_client_ = wvcdm::a2b_hex( - "F9AAE74690909F2207B53B13307FCA096CA8C49CC6DFE3659873CB952889A74B"); - enc_key_ = wvcdm::a2b_hex("CB477D09014D72C9B8DCE76C33EA43B3"); - - } - - void GenerateTestSessionKeys() { - if (global_features.derive_key_method - == DeviceFeatures::LOAD_TEST_RSA_KEY) { - GenerateDerivedKeysFromSessionKey(); - } else { - GenerateDerivedKeysFromKeybox(); - } - } - - void LoadTestKeys(const std::string& pst = "", bool new_mac_keys = true) { - uint8_t* pst_ptr = NULL; - if (pst.length() > 0) { - pst_ptr = encrypted_license_.pst; - } - if (new_mac_keys) { - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys( - session_id(), message_ptr(), sizeof(MessageData), - &signature_[0], signature_.size(), - encrypted_license_.mac_key_iv, encrypted_license_.mac_keys, - kNumKeys, key_array_, pst_ptr, pst.length())); - // Update new generated keys. - memcpy(&mac_key_server_[0], license_.mac_keys, wvcdm::MAC_KEY_SIZE); - memcpy(&mac_key_client_[0], license_.mac_keys + wvcdm::MAC_KEY_SIZE, - wvcdm::MAC_KEY_SIZE); - } else { - ASSERT_EQ( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(session_id(), message_ptr(), sizeof(MessageData), - &signature_[0], signature_.size(), NULL, NULL, - kNumKeys, key_array_, pst_ptr, pst.length())); - } - VerifyTestKeys(); - } - - void VerifyTestKeys() { - for (unsigned int i = 0; i < kNumKeys; i++) { - KeyControlBlock block; - size_t size = sizeof(block); - OEMCryptoResult sts = OEMCrypto_QueryKeyControl( - session_id(), license_.keys[i].key_id, license_.keys[i].key_id_length, - reinterpret_cast(&block), &size); - if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(sizeof(block), size); - // control duration and bits stored in network byte order. For printing - // we change to host byte order. - ASSERT_EQ(htonl(license_.keys[i].control.duration), - htonl(block.duration)) << "For key " << i; - ASSERT_EQ(htonl(license_.keys[i].control.control_bits), - htonl(block.control_bits)) << "For key " << i; - } - } - } - - void RefreshTestKeys(const size_t key_count, uint32_t control_bits, - uint32_t nonce, OEMCryptoResult expected_result) { - // Note: we store the message in encrypted_license_, but the refresh key - // message is not actually encrypted. It is, however, signed. - FillRefreshMessage(key_count, control_bits, nonce); - ServerSignMessage(encrypted_license_, &signature_); - OEMCrypto_KeyRefreshObject key_array[key_count]; - FillRefreshArray(key_array, key_count); - OEMCryptoResult sts = OEMCrypto_RefreshKeys( - session_id(), message_ptr(), sizeof(MessageData), &signature_[0], - signature_.size(), key_count, key_array); - ASSERT_EQ(expected_result, sts); - - TestDecryptCTR(); - sleep(kShortSleep); // Should still be valid key. - TestDecryptCTR(false); - sleep(kShortSleep + kLongSleep); // Should be after first expiration. - if (expected_result == OEMCrypto_SUCCESS) { - TestDecryptCTR(false, OEMCrypto_SUCCESS); - } else { - TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE); - } - } - - void SetKeyId(int index, const string& key_id) { - MessageKeyData &key = license_.keys[index]; - key.key_id_length = key_id.length(); - ASSERT_LE(key.key_id_length, kTestKeyIdMaxLength); - memcpy(key.key_id, key_id.data(), key.key_id_length); - } - - void FillSimpleMessage(uint32_t duration, uint32_t control, uint32_t nonce, - const std::string& pst = "") { - OEMCrypto_GetRandom(license_.mac_key_iv, sizeof(license_.mac_key_iv)); - OEMCrypto_GetRandom(license_.mac_keys, sizeof(license_.mac_keys)); - for (unsigned int i = 0; i < kNumKeys; i++) { - memset(license_.keys[i].key_id, 0, kTestKeyIdMaxLength); - license_.keys[i].key_id_length = kDefaultKeyIdLength; - memset(license_.keys[i].key_id, i, license_.keys[i].key_id_length); - OEMCrypto_GetRandom(license_.keys[i].key_data, - sizeof(license_.keys[i].key_data)); - license_.keys[i].key_data_length = wvcdm::KEY_SIZE; - OEMCrypto_GetRandom(license_.keys[i].key_iv, - sizeof(license_.keys[i].key_iv)); - OEMCrypto_GetRandom(license_.keys[i].control_iv, - sizeof(license_.keys[i].control_iv)); - if (control & wvoec_mock::kControlRequireAntiRollbackHardware) { - memcpy(license_.keys[i].control.verification, "kc10", 4); - } else if (control & (wvoec_mock::kControlHDCPVersionMask | - wvoec_mock::kControlReplayMask)) { - memcpy(license_.keys[i].control.verification, "kc09", 4); - } else { - memcpy(license_.keys[i].control.verification, "kctl", 4); - } - license_.keys[i].control.duration = htonl(duration); - license_.keys[i].control.nonce = htonl(nonce); - license_.keys[i].control.control_bits = htonl(control); - } - memcpy(license_.pst, pst.c_str(), min(sizeof(license_.pst), pst.length())); - - // The first key for the canned decryption content. - vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); - memcpy(license_.keys[0].key_data, &key[0], key.size()); - } - - void FillRefreshMessage(size_t key_count, uint32_t control_bits, - uint32_t nonce) { - for (unsigned int i = 0; i < key_count; i++) { - encrypted_license_.keys[i].key_id_length = license_.keys[i].key_id_length; - memcpy(encrypted_license_.keys[i].key_id, license_.keys[i].key_id, - encrypted_license_.keys[i].key_id_length); - memcpy(encrypted_license_.keys[i].control.verification, "kctl", 4); - encrypted_license_.keys[i].control.duration = htonl(kLongDuration); - encrypted_license_.keys[i].control.nonce = htonl(nonce); - encrypted_license_.keys[i].control.control_bits = htonl(control_bits); - } - } - - void EncryptAndSign() { - encrypted_license_ = license_; - - uint8_t iv_buffer[16]; - memcpy(iv_buffer, &license_.mac_key_iv[0], wvcdm::KEY_IV_SIZE); - AES_KEY aes_key; - AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); - AES_cbc_encrypt(&license_.mac_keys[0], &encrypted_license_.mac_keys[0], - 2 * wvcdm::MAC_KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); - - for (unsigned int i = 0; i < kNumKeys; i++) { - memcpy(iv_buffer, &license_.keys[i].control_iv[0], wvcdm::KEY_IV_SIZE); - AES_set_encrypt_key(&license_.keys[i].key_data[0], 128, &aes_key); - AES_cbc_encrypt( - reinterpret_cast(&license_.keys[i].control), - reinterpret_cast(&encrypted_license_.keys[i].control), - wvcdm::KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); - - memcpy(iv_buffer, &license_.keys[i].key_iv[0], wvcdm::KEY_IV_SIZE); - AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); - AES_cbc_encrypt(&license_.keys[i].key_data[0], - &encrypted_license_.keys[i].key_data[0], - license_.keys[i].key_data_length, &aes_key, iv_buffer, - AES_ENCRYPT); - } - memcpy(encrypted_license_.pst, license_.pst, sizeof(license_.pst)); - ServerSignMessage(encrypted_license_, &signature_); - FillKeyArray(encrypted_license_, key_array_); - } - - void EncryptMessage(RSAPrivateKeyMessage* data, - RSAPrivateKeyMessage* encrypted) { - *encrypted = *data; - size_t padding = wvcdm::KEY_SIZE - (data->rsa_key_length % wvcdm::KEY_SIZE); - memset(data->rsa_key + data->rsa_key_length, static_cast(padding), - padding); - encrypted->rsa_key_length = data->rsa_key_length + padding; - uint8_t iv_buffer[16]; - memcpy(iv_buffer, &data->rsa_key_iv[0], wvcdm::KEY_IV_SIZE); - AES_KEY aes_key; - AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); - AES_cbc_encrypt(&data->rsa_key[0], &encrypted->rsa_key[0], - encrypted->rsa_key_length, &aes_key, iv_buffer, - AES_ENCRYPT); - } - - template - void ServerSignMessage(const T& data, std::vector* signature) { - signature->assign(SHA256_DIGEST_LENGTH, 0); - unsigned int md_len = SHA256_DIGEST_LENGTH; - HMAC(EVP_sha256(), &mac_key_server_[0], mac_key_server_.size(), - reinterpret_cast(&data), sizeof(data), - &(signature->front()), &md_len); - } - - void ClientSignMessage(const vector& data, - std::vector* signature) { - signature->assign(SHA256_DIGEST_LENGTH, 0); - unsigned int md_len = SHA256_DIGEST_LENGTH; - HMAC(EVP_sha256(), &mac_key_client_[0], mac_key_client_.size(), - &(data.front()), data.size(), &(signature->front()), &md_len); - } - - void FillKeyArray(const MessageData& data, OEMCrypto_KeyObject* key_array) { - for (unsigned int i = 0; i < kNumKeys; i++) { - key_array[i].key_id = data.keys[i].key_id; - key_array[i].key_id_length = data.keys[i].key_id_length; - key_array[i].key_data_iv = data.keys[i].key_iv; - key_array[i].key_data = data.keys[i].key_data; - key_array[i].key_data_length = data.keys[i].key_data_length; - key_array[i].key_control_iv = data.keys[i].control_iv; - key_array[i].key_control = - reinterpret_cast(&data.keys[i].control); - } - } - - void FillRefreshArray(OEMCrypto_KeyRefreshObject* key_array, - size_t key_count) { - for (size_t i = 0; i < key_count; i++) { - if (key_count > 1) { - key_array[i].key_id = encrypted_license_.keys[i].key_id; - key_array[i].key_id_length = encrypted_license_.keys[i].key_id_length; - } else { - key_array[i].key_id = NULL; - key_array[i].key_id_length = 0; - } - key_array[i].key_control_iv = NULL; - key_array[i].key_control = - reinterpret_cast(&encrypted_license_.keys[i].control); - } - } - - void TestDecryptCTR(bool select_key_first = true, - OEMCryptoResult expected_result = OEMCrypto_SUCCESS) { - OEMCryptoResult sts; - if (select_key_first) { - // Select the key (from FillSimpleMessage) - sts = OEMCrypto_SelectKey(session_id(), license_.keys[0].key_id, - license_.keys[0].key_id_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - } - - // Set up our expected input and output - // This is dummy encrypted data. - vector encryptedData = wvcdm::a2b_hex( - "ec261c115f9d5cda1d5cc7d33c4e37362d1397c89efdd1da5f0065c4848b0462" - "337ba14693735203c9b4184e362439c0cea5e5d1a628425eddf8a6bf9ba901ca" - "46f5a9fd973cffbbe3c276af9919e2e8f6f3f420538b7a0d6dc41487874d96b8" - "efaedb45a689b91beb8c20d36140ad467d9d620b19a5fc6f223b57e0e6a7f913" - "00fd899e5e1b89963e83067ca0912aa5b79df683e2530b55a9645be341bc5f07" - "cffc724790af635c959e2644e51ba7f23bae710eb55a1f2f4e060c3c1dd1387c" - "74415dc880492dd1d5b9ecf3f01de48a44baeb4d3ea5cc4f8d561d0865afcabb" - "fc14a9ab9647e6e31adabb72d792f0c9ba99dc3e9205657d28fc7771d64e6d4b"); - vector encryptionIv = - wvcdm::a2b_hex("719dbcb253b2ec702bb8c1b1bc2f3bc6"); - // This is the expected decrypted data. - vector unencryptedData = wvcdm::a2b_hex( - "19ef4361e16e6825b336e2012ad8ffc9ce176ab2256e1b98aa15b7877bd8c626" - "fa40b2e88373457cbcf4f1b4b9793434a8ac03a708f85974cff01bddcbdd7a8e" - "e33fd160c1d5573bfd8104efd23237edcf28205c3673920553f8dd5e916604b0" - "1082345181dceeae5ea39d829c7f49e1850c460645de33c288723b7ae3d91a17" - "a3f04195cd1945ba7b0f37fef7e82368be30f04365d877766f6d56f67d22a244" - "ef2596d3053f657c1b5d90b64e11797edf1c198a23a7bfc20e4d44c74ae41280" - "a8317f443255f4020eda850ff0954e308f53a634cbce799ae58911bc59ccd6a5" - "de2ac53ee0fa7ea15fc692cc892acc0090865dc57becacddf362a092dfd3040b"); - - // Describe the output - vector outputBuffer(256); - OEMCrypto_DestBufferDesc destBuffer; - destBuffer.type = OEMCrypto_BufferType_Clear; - destBuffer.buffer.clear.address = outputBuffer.data(); - destBuffer.buffer.clear.max_length = outputBuffer.size(); - // Decrypt the data - sts = OEMCrypto_DecryptCTR( - session_id(), &encryptedData[0], encryptedData.size(), true, - &encryptionIv[0], 0, &destBuffer, - OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); - // We only have a few errors that we test are reported. - if (expected_result == OEMCrypto_SUCCESS) { // No error. - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(unencryptedData, outputBuffer); - } else if (expected_result == OEMCrypto_ERROR_KEY_EXPIRED) { - // Report stale keys. - ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, sts); - ASSERT_NE(unencryptedData, outputBuffer); - } else if (expected_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP) { - // Report HDCP errors. - ASSERT_EQ(OEMCrypto_ERROR_INSUFFICIENT_HDCP, sts); - ASSERT_NE(unencryptedData, outputBuffer); - } else { - // OEM's can fine tune other error codes for debugging. - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NE(unencryptedData, outputBuffer); - } - } - - void MakeRSACertificate(struct RSAPrivateKeyMessage* encrypted, - std::vector* signature, - uint32_t allowed_schemes, - const vector& rsa_key) { - // Dummy context for testing signature generation. - vector context = wvcdm::a2b_hex( - "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" - "8f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202" - "fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931" - "b76a3a85f046523e10011a09393837363534333231180120002a0c3138383637" - "38373430350000"); - - OEMCryptoResult sts; - - // Generate signature - size_t gen_signature_length = 0; - sts = OEMCrypto_GenerateSignature(session_id(), &context[0], context.size(), - NULL, &gen_signature_length); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - ASSERT_EQ(static_cast(32), gen_signature_length); - vector gen_signature(gen_signature_length); - sts = OEMCrypto_GenerateSignature(session_id(), &context[0], context.size(), - &gen_signature[0], &gen_signature_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - std::vector expected_signature; - ClientSignMessage(context, &expected_signature); - ASSERT_EQ(expected_signature, gen_signature); - - // Rewrap Canned Response - - // In the real world, the signature above would just have been used to - // contact the certificate provisioning server to get this response. - - struct RSAPrivateKeyMessage message; - if (allowed_schemes != kSign_RSASSA_PSS) { - uint32_t algorithm_n = htonl(allowed_schemes); - memcpy(message.rsa_key, "SIGN", 4); - memcpy(message.rsa_key + 4, &algorithm_n, 4); - memcpy(message.rsa_key + 8, rsa_key.data(), rsa_key.size()); - message.rsa_key_length = 8 + rsa_key.size(); - } else { - memcpy(message.rsa_key, rsa_key.data(), rsa_key.size()); - message.rsa_key_length = rsa_key.size(); - } - OEMCrypto_GetRandom(message.rsa_key_iv, wvcdm::KEY_IV_SIZE); - message.nonce = nonce_; - - EncryptMessage(&message, encrypted); - ServerSignMessage(*encrypted, signature); - } - - void RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted, - const std::vector& signature, - vector* wrapped_key, bool force) { - size_t wrapped_key_length = 0; - const uint8_t* message_ptr = reinterpret_cast(&encrypted); - - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey( - session_id(), message_ptr, sizeof(encrypted), &signature[0], - signature.size(), &encrypted.nonce, encrypted.rsa_key, - encrypted.rsa_key_length, encrypted.rsa_key_iv, NULL, - &wrapped_key_length)); - wrapped_key->clear(); - wrapped_key->assign(wrapped_key_length, 0); - OEMCryptoResult sts = OEMCrypto_RewrapDeviceRSAKey( - session_id(), message_ptr, sizeof(encrypted), &signature[0], - signature.size(), &encrypted.nonce, encrypted.rsa_key, - encrypted.rsa_key_length, encrypted.rsa_key_iv, &(wrapped_key->front()), - &wrapped_key_length); - if (force) { - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - } - if (OEMCrypto_SUCCESS != sts) { - wrapped_key->clear(); - } - } - - void PreparePublicKey(const uint8_t* rsa_key = NULL, - size_t rsa_key_length = 0) { - if (rsa_key == NULL) { - rsa_key = kTestRSAPKCS8PrivateKeyInfo2_2048; - rsa_key_length = sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048); - } - uint8_t* p = const_cast(rsa_key); - BIO* bio = BIO_new_mem_buf(p, rsa_key_length); - ASSERT_TRUE(NULL != bio); - PKCS8_PRIV_KEY_INFO* pkcs8_pki = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL); - ASSERT_TRUE(NULL != pkcs8_pki); - EVP_PKEY* evp = NULL; - evp = EVP_PKCS82PKEY(pkcs8_pki); - ASSERT_TRUE(NULL != evp); - if (public_rsa_) RSA_free(public_rsa_); - public_rsa_ = EVP_PKEY_get1_RSA(evp); - EVP_PKEY_free(evp); - PKCS8_PRIV_KEY_INFO_free(pkcs8_pki); - BIO_free(bio); - if (!public_rsa_) { - cout << "d2i_RSAPrivateKey failed. "; - dump_openssl_error(); - ASSERT_TRUE(false); - } - switch (RSA_check_key(public_rsa_)) { - case 1: // valid. - ASSERT_TRUE(true); - return; - case 0: // not valid. - cout << "[rsa key not valid] "; - dump_openssl_error(); - ASSERT_TRUE(false); - default: // -1 == check failed. - cout << "[error checking rsa key] "; - dump_openssl_error(); - ASSERT_TRUE(false); - } - } - - static bool VerifyPSSSignature(EVP_PKEY* pkey, const uint8_t* message, - size_t message_length, - const uint8_t* signature, - size_t signature_length) { - EVP_MD_CTX ctx; - EVP_MD_CTX_init(&ctx); - EVP_PKEY_CTX* pctx = NULL; - - if (EVP_DigestVerifyInit(&ctx, &pctx, EVP_sha1(), NULL /* no ENGINE */, - pkey) != 1) { - LOGE("EVP_DigestVerifyInit failed in VerifyPSSSignature"); - goto err; - } - - if (EVP_PKEY_CTX_set_signature_md(pctx, EVP_sha1()) != 1) { - LOGE("EVP_PKEY_CTX_set_signature_md failed in VerifyPSSSignature"); - goto err; - } - - if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) != 1) { - LOGE("EVP_PKEY_CTX_set_rsa_padding failed in VerifyPSSSignature"); - goto err; - } - - if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, SHA_DIGEST_LENGTH) != 1) { - LOGE("EVP_PKEY_CTX_set_rsa_pss_saltlen failed in VerifyPSSSignature"); - goto err; - } - - if (EVP_DigestVerifyUpdate(&ctx, message, message_length) != 1) { - LOGE("EVP_DigestVerifyUpdate failed in VerifyPSSSignature"); - goto err; - } - - if (EVP_DigestVerifyFinal(&ctx, const_cast(signature), - signature_length) != 1) { - LOGE( - "EVP_DigestVerifyFinal failed in VerifyPSSSignature. (Probably a bad " - "signature.)"); - goto err; - } - - EVP_MD_CTX_cleanup(&ctx); - return true; - - err: - dump_openssl_error(); - EVP_MD_CTX_cleanup(&ctx); - return false; - } - - void VerifyRSASignature(const vector& message, - const uint8_t* signature, size_t signature_length, - RSA_Padding_Scheme padding_scheme) { - EXPECT_TRUE(NULL != public_rsa_) - << "No public RSA key loaded in test code.\n"; - EXPECT_EQ(static_cast(RSA_size(public_rsa_)), signature_length) - << "Signature size is wrong. " << signature_length << ", should be " - << RSA_size(public_rsa_) << "\n"; - - if (padding_scheme == kSign_RSASSA_PSS) { - EVP_PKEY *pkey = EVP_PKEY_new(); - ASSERT_TRUE(EVP_PKEY_set1_RSA(pkey, public_rsa_) == 1); - - const bool ok = VerifyPSSSignature(pkey, &message[0], message.size(), - signature, signature_length); - EVP_PKEY_free(pkey); - EXPECT_TRUE(ok) << "PSS signature check failed."; - } else if (padding_scheme == kSign_PKCS1_Block1) { - vector padded_digest(signature_length); - int size; - // RSA_public_decrypt decrypts the signature, and then verifies that - // it was padded with RSA PKCS1 padding. - size = RSA_public_decrypt(signature_length, signature, &padded_digest[0], - public_rsa_, RSA_PKCS1_PADDING); - EXPECT_GT(size, 0); - padded_digest.resize(size); - EXPECT_EQ(message, padded_digest); - } else { - EXPECT_TRUE(false) << "Padding scheme not supported."; - } - } - - bool GenerateRSASessionKey(vector* enc_session_key) { - if (!public_rsa_) { - cout << "No public RSA key loaded in test code.\n"; - return false; - } - vector session_key = - wvcdm::a2b_hex("6fa479c731d2770b6a61a5d1420bb9d1"); - enc_session_key->assign(RSA_size(public_rsa_), 0); - int status = RSA_public_encrypt(session_key.size(), &session_key[0], - &(enc_session_key->front()), public_rsa_, - RSA_PKCS1_OAEP_PADDING); - int size = static_cast(RSA_size(public_rsa_)); - if (status != size) { - cout << "GenerateRSASessionKey error encrypting session key. "; - dump_openssl_error(); - return false; - } - return true; - } - - void InstallRSASessionTestKey(const vector& wrapped_rsa_key) { - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadDeviceRSAKey(session_id(), &wrapped_rsa_key[0], - wrapped_rsa_key.size())); - GenerateDerivedKeysFromSessionKey(); - } - - void DisallowDeriveKeys() { - GenerateNonce(&nonce_); - vector enc_session_key; - PreparePublicKey(); - ASSERT_TRUE(GenerateRSASessionKey(&enc_session_key)); - vector mac_context; - vector enc_context; - FillDefaultContext(&mac_context, &enc_context); - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_DeriveKeysFromSessionKey( - session_id(), &enc_session_key[0], enc_session_key.size(), - &mac_context[0], mac_context.size(), &enc_context[0], - enc_context.size())); - } - - void GenerateReport(const std::string& pst, bool expect_success = true, - Session* other = 0) { - if (other) { // If other is specified, copy mac keys. - mac_key_server_ = other->mac_key_server_; - mac_key_client_ = other->mac_key_client_; - } - size_t length = 0; - OEMCryptoResult sts = OEMCrypto_ReportUsage( - session_id(), reinterpret_cast(pst.c_str()), - pst.length(), pst_report(), &length); - if (expect_success) { - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - } - if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { - ASSERT_LE(sizeof(OEMCrypto_PST_Report), length); - pst_report_buffer_.resize(length); - } - sts = OEMCrypto_ReportUsage(session_id(), - reinterpret_cast(pst.c_str()), - pst.length(), pst_report(), &length); - if (!expect_success) { - ASSERT_NE(OEMCrypto_SUCCESS, sts); - return; - } - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - vector computed_signature(SHA_DIGEST_LENGTH); - unsigned int sig_len = SHA_DIGEST_LENGTH; - HMAC(EVP_sha1(), &mac_key_client_[0], mac_key_client_.size(), - reinterpret_cast(pst_report()) + SHA_DIGEST_LENGTH, - length - SHA_DIGEST_LENGTH, &computed_signature[0], &sig_len); - EXPECT_EQ(0, memcmp(&computed_signature[0], pst_report()->signature, - SHA_DIGEST_LENGTH)); - EXPECT_GE(kInactive, pst_report()->status); - EXPECT_GE(kHardwareSecureClock, pst_report()->clock_security_level); - EXPECT_EQ(pst.length(), pst_report()->pst_length); - EXPECT_EQ(0, memcmp(pst.c_str(), pst_report()->pst, pst.length())); - } - - OEMCrypto_PST_Report* pst_report() { - return reinterpret_cast(&pst_report_buffer_[0]); - } - - void DeleteEntry(const std::string& pst) { - uint8_t* pst_ptr = encrypted_license_.pst; - memcpy(pst_ptr, pst.c_str(), min(sizeof(license_.pst), pst.length())); - ServerSignMessage(encrypted_license_, &signature_); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_DeleteUsageEntry(session_id(), pst_ptr, pst.length(), - message_ptr(), sizeof(MessageData), - &signature_[0], signature_.size())); - } - - void ForceDeleteEntry(const std::string& pst) { - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_ForceDeleteUsageEntry( - reinterpret_cast(pst.c_str()), pst.length())); - } - - MessageData& license() { return license_; } - MessageData& encrypted_license() { return encrypted_license_; } - const uint8_t* message_ptr() { - return reinterpret_cast(&encrypted_license_); - } - OEMCrypto_KeyObject* key_array() { return key_array_; } - std::vector& signature() { return signature_; } - - private: - bool open_; - OEMCrypto_SESSION session_id_; - OEMCryptoResult session_status_; - vector mac_key_server_; - vector mac_key_client_; - vector enc_key_; - uint32_t nonce_; - RSA* public_rsa_; - vector pst_report_buffer_; - MessageData license_; - MessageData encrypted_license_; - OEMCrypto_KeyObject key_array_[kNumKeys]; - std::vector signature_; -}; class OEMCryptoClientTest : public ::testing::Test { protected: @@ -1246,8 +54,7 @@ class OEMCryptoClientTest : public ::testing::Test { wvcdm::g_cutoff = wvcdm::LOG_INFO; const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); - LOGD("Running test %s.%s", test_info->name(), - test_info->test_case_name()); + LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name()); } virtual void TearDown() { @@ -1283,7 +90,7 @@ TEST_F(OEMCryptoClientTest, VersionNumber) { cout << " OEMCrypto does not support usage tables." << endl; } ASSERT_GE(version, 8u); - ASSERT_LE(version, 10u); + ASSERT_LE(version, 11u); } const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value) { @@ -1372,33 +179,17 @@ TEST_F(OEMCryptoClientTest, NormalInitTermination) { // TEST_F(OEMCryptoClientTest, NormalSessionOpenClose) { Session s; - s.open(); - ASSERT_EQ(OEMCrypto_SUCCESS, s.getStatus()); - ASSERT_TRUE(s.isOpen()); - s.close(); - ASSERT_EQ(OEMCrypto_SUCCESS, s.getStatus()); - ASSERT_FALSE(s.isOpen()); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.close()); } TEST_F(OEMCryptoClientTest, TwoSessionsOpenClose) { Session s1; Session s2; - - s1.open(); - ASSERT_EQ(OEMCrypto_SUCCESS, s1.getStatus()); - ASSERT_TRUE(s1.isOpen()); - - s2.open(); - ASSERT_EQ(OEMCrypto_SUCCESS, s2.getStatus()); - ASSERT_TRUE(s2.isOpen()); - - s1.close(); - ASSERT_EQ(OEMCrypto_SUCCESS, s1.getStatus()); - ASSERT_FALSE(s1.isOpen()); - - s2.close(); - ASSERT_EQ(OEMCrypto_SUCCESS, s2.getStatus()); - ASSERT_FALSE(s2.isOpen()); + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE(s2.close()); } // This test should still pass for API v9. A better test is below, but it only @@ -1406,14 +197,10 @@ TEST_F(OEMCryptoClientTest, TwoSessionsOpenClose) { TEST_F(OEMCryptoClientTest, EightSessionsOpenClose) { vector s(8); for (int i = 0; i < 8; i++) { - s[i].open(); - ASSERT_EQ(OEMCrypto_SUCCESS, s[i].getStatus()); - ASSERT_TRUE(s[i].isOpen()); + ASSERT_NO_FATAL_FAILURE(s[i].open()); } for (int i = 0; i < 8; i++) { - s[i].close(); - ASSERT_EQ(OEMCrypto_SUCCESS, s[i].getStatus()); - ASSERT_FALSE(s[i].isOpen()); + ASSERT_NO_FATAL_FAILURE(s[i].close()); } } @@ -1429,8 +216,8 @@ TEST_F(OEMCryptoClientTest, MaxSessionsOpenCloseAPI10) { ASSERT_GE(max_sessions, kMinimumSupportedMaxNumberOfSessions); // We allow GetMaxNumberOfSessions to return an estimate. This tests with a // pad of 5%. Even if it's just an estimate, we still require 8 sessions. - size_t max_sessions_with_pad = max(max_sessions * 19/20, - kMinimumSupportedMaxNumberOfSessions); + size_t max_sessions_with_pad = + max(max_sessions * 19 / 20, kMinimumSupportedMaxNumberOfSessions); vector sessions; // Limit the number of sessions for testing. const size_t kMaxNumberOfSessionsForTesting = 0x100u; @@ -1469,17 +256,16 @@ TEST_F(OEMCryptoClientTest, MaxSessionsOpenCloseAPI10) { TEST_F(OEMCryptoClientTest, GenerateNonce) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); uint32_t nonce; s.GenerateNonce(&nonce); } TEST_F(OEMCryptoClientTest, GenerateTwoNonces) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); uint32_t nonce1; uint32_t nonce2; - s.GenerateNonce(&nonce1); s.GenerateNonce(&nonce2); ASSERT_TRUE(nonce1 != nonce2); @@ -1487,7 +273,7 @@ TEST_F(OEMCryptoClientTest, GenerateTwoNonces) { TEST_F(OEMCryptoClientTest, PreventNonceFlood) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); int error_counter = 0; uint32_t nonce; time_t test_start = time(NULL); @@ -1520,8 +306,7 @@ TEST_F(OEMCryptoClientTest, PreventNonceFlood2) { const int kFloodCount = 80; for (int i = 0; i < kFloodCount; i++) { Session s; - s.open(); - EXPECT_TRUE(s.isOpen()); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateNonce(&nonce, &error_counter); } time_t test_end = time(NULL); @@ -1534,7 +319,7 @@ TEST_F(OEMCryptoClientTest, PreventNonceFlood2) { error_counter = 0; sleep(2); // After a pause, we should be able to regenerate nonces. Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateNonce(&nonce, &error_counter); EXPECT_EQ(0, error_counter); } @@ -1552,8 +337,7 @@ TEST_F(OEMCryptoClientTest, PreventNonceFlood3) { // To allow for some slop, we actually test for more. Session s[8]; for (int i = 0; i < 8; i++) { - s[i].open(); - EXPECT_TRUE(s[i].isOpen()); + ASSERT_NO_FATAL_FAILURE(s[i].open()); for (int j = 0; j < 10; j++) { request_counter++; s[i].GenerateNonce(&nonce, &error_counter); @@ -1568,7 +352,6 @@ TEST_F(OEMCryptoClientTest, PreventNonceFlood3) { EXPECT_LE(valid_counter, 20 * (test_end - test_start + 2)); error_counter = 0; sleep(2); // After a pause, we should be able to regenerate nonces. - EXPECT_TRUE(s[0].isOpen()); s[0].GenerateNonce(&nonce, &error_counter); EXPECT_EQ(0, error_counter); } @@ -1582,30 +365,30 @@ TEST_F(OEMCryptoClientTest, ClearCopyTestAPI10) { dest_buffer.type = OEMCrypto_BufferType_Clear; dest_buffer.buffer.clear.address = &output_buffer[0]; dest_buffer.buffer.clear.max_length = output_buffer.size(); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), - &dest_buffer, OEMCrypto_FirstSubsample - | OEMCrypto_LastSubsample)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), &dest_buffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); ASSERT_EQ(input_buffer, output_buffer); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, - OEMCrypto_CopyBuffer(NULL, input_buffer.size(), - &dest_buffer, OEMCrypto_FirstSubsample - | OEMCrypto_LastSubsample)); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, - OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), - NULL, OEMCrypto_FirstSubsample - | OEMCrypto_LastSubsample)); + ASSERT_EQ( + OEMCrypto_ERROR_INVALID_CONTEXT, + OEMCrypto_CopyBuffer(NULL, input_buffer.size(), &dest_buffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); + ASSERT_EQ( + OEMCrypto_ERROR_INVALID_CONTEXT, + OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), NULL, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); dest_buffer.buffer.clear.address = NULL; - ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, - OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), - &dest_buffer, OEMCrypto_FirstSubsample - | OEMCrypto_LastSubsample)); + ASSERT_EQ( + OEMCrypto_ERROR_INVALID_CONTEXT, + OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), &dest_buffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); dest_buffer.buffer.clear.address = &output_buffer[0]; dest_buffer.buffer.clear.max_length = output_buffer.size() - 1; - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), - &dest_buffer, OEMCrypto_FirstSubsample - | OEMCrypto_LastSubsample)); + ASSERT_EQ( + OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_CopyBuffer(&input_buffer[0], input_buffer.size(), &dest_buffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); } TEST_F(OEMCryptoClientTest, CanLoadTestKeys) { @@ -1627,13 +410,20 @@ TEST_F(OEMCryptoKeyboxTest, NormalGetKeyData) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); } +TEST_F(OEMCryptoKeyboxTest, GetKeyDataNullPointer) { + OEMCryptoResult sts; + uint8_t key_data[256]; + sts = OEMCrypto_GetKeyData(key_data, NULL); + ASSERT_NE(OEMCrypto_SUCCESS, sts); +} + TEST_F(OEMCryptoKeyboxTest, ProductionKeyboxValid) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); } TEST_F(OEMCryptoKeyboxTest, GenerateDerivedKeysFromKeybox) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); } @@ -1653,7 +443,7 @@ class OEMCryptoSessionTests : public OEMCryptoClientTest { } void EnsureTestKeys() { - switch(global_features.derive_key_method) { + switch (global_features.derive_key_method) { case DeviceFeatures::LOAD_TEST_KEYBOX: ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestKeybox()); break; @@ -1693,8 +483,7 @@ class OEMCryptoSessionTests : public OEMCryptoClientTest { } }; -class OEMCryptoSessionTestKeyboxTest : public OEMCryptoSessionTests {}; - +class OEMCryptoSessionTestKeyboxTest : public OEMCryptoSessionTests {}; TEST_F(OEMCryptoSessionTestKeyboxTest, TestKeyboxIsValid) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); @@ -1754,9 +543,9 @@ TEST_F(OEMCryptoSessionTestKeyboxTest, BadDataForceKeybox) { TEST_F(OEMCryptoSessionTestKeyboxTest, GenerateSignature) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); - s.GenerateDerivedKeysFromKeybox(); + ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox()); // Dummy context for testing signature generation. vector context = wvcdm::a2b_hex( @@ -1776,7 +565,7 @@ TEST_F(OEMCryptoSessionTestKeyboxTest, GenerateSignature) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); static const uint32_t SignatureExpectedLength = 32; - ASSERT_EQ(signature_length, SignatureExpectedLength); + ASSERT_EQ(SignatureExpectedLength, signature_length); signature.resize(signature_length); std::vector expected_signature; @@ -1786,29 +575,30 @@ TEST_F(OEMCryptoSessionTestKeyboxTest, GenerateSignature) { TEST_F(OEMCryptoSessionTests, LoadKeyNoNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, 0, 42); - s.EncryptAndSign(); - s.LoadTestKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 42)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); } TEST_F(OEMCryptoSessionTests, LoadKeyWithNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); } TEST_F(OEMCryptoSessionTests, LoadKeyWithNoMAC) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys("", false); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", false)); vector context = wvcdm::a2b_hex( "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" @@ -1828,7 +618,7 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithNoMAC) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); static const uint32_t SignatureExpectedLength = 32; - ASSERT_EQ(signature_length, SignatureExpectedLength); + ASSERT_EQ(SignatureExpectedLength, signature_length); signature.resize(signature_length); std::vector expected_signature; @@ -1841,10 +631,10 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithNoMAC) { not point into the message buffer */ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange1) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector mac_keys( s.encrypted_license().mac_keys, s.encrypted_license().mac_keys + sizeof(s.encrypted_license().mac_keys)); @@ -1859,10 +649,10 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange1) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange2) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector mac_key_iv(s.encrypted_license().mac_key_iv, s.encrypted_license().mac_key_iv + sizeof(s.encrypted_license().mac_key_iv)); @@ -1877,13 +667,13 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange2) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange3) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector bad_buffer(s.encrypted_license().keys[0].key_id, s.encrypted_license().keys[0].key_id + - s.encrypted_license().keys[0].key_id_length); + s.encrypted_license().keys[0].key_id_length); s.key_array()[0].key_id = &bad_buffer[0]; OEMCryptoResult sts = OEMCrypto_LoadKeys( @@ -1895,10 +685,10 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange3) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange4) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector bad_buffer( s.encrypted_license().keys[1].key_data, @@ -1914,10 +704,10 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange4) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange5) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector bad_buffer(s.encrypted_license().keys[1].key_iv, s.encrypted_license().keys[1].key_iv + sizeof(s.encrypted_license().keys[1].key_iv)); @@ -1931,11 +721,11 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange5) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange6) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector bad_buffer(s.key_array()[2].key_control, s.key_array()[2].key_control + @@ -1951,11 +741,11 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange6) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange7) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); vector bad_buffer( s.key_array()[2].key_control_iv, s.key_array()[2].key_control_iv + @@ -1971,10 +761,12 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange7) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, 42); // bad nonce. - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, + wvoec_mock::kControlNonceEnabled, + 42)); // bad nonce. + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, @@ -1985,19 +777,21 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadNonce) { TEST_F(OEMCryptoSessionTests, LoadKeyWithRepeatNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); uint32_t nonce = s.get_nonce(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, nonce); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.close(); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, nonce)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.close()); - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, - nonce); // same old nonce. - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, + wvoec_mock::kControlNonceEnabled, + nonce)); // same old nonce. + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, @@ -2008,12 +802,12 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithRepeatNonce) { TEST_F(OEMCryptoSessionTests, LoadKeyWithBadVerification) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); s.license().keys[1].control.verification[2] = 'Z'; - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, @@ -2022,12 +816,28 @@ TEST_F(OEMCryptoSessionTests, LoadKeyWithBadVerification) { ASSERT_NE(OEMCrypto_SUCCESS, sts); } +TEST_F(OEMCryptoSessionTests, LoadKeyWithFutureVerification) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + // OEMCrypto should reject API12 until the spec has been defined. + memcpy(s.license().keys[1].control.verification, "kc12", 4); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + OEMCryptoResult sts = OEMCrypto_LoadKeys( + s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], + s.signature().size(), s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, s.key_array(), NULL, 0); + ASSERT_NE(OEMCrypto_SUCCESS, sts); +} + TEST_F(OEMCryptoSessionTests, LoadKeysBadSignature) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); s.signature()[0] ^= 42; // Bad signature. OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], @@ -2038,10 +848,10 @@ TEST_F(OEMCryptoSessionTests, LoadKeysBadSignature) { TEST_F(OEMCryptoSessionTests, LoadKeysWithNoDerivedKeys) { Session s; - s.open(); - // s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + // ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys())); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, @@ -2049,25 +859,57 @@ TEST_F(OEMCryptoSessionTests, LoadKeysWithNoDerivedKeys) { ASSERT_NE(OEMCrypto_SUCCESS, sts); } +// To prevent initial loading shared licenses without usage table or nonce, +// LoadKeys should reject an empty list of keys. +TEST_F(OEMCryptoSessionTests, LoadKeyNoKeys) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 42)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + int kNoKeys = 0; + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), NULL, NULL, + kNoKeys, s.key_array(), NULL, 0)); +} + +TEST_F(OEMCryptoSessionTests, LoadKeyNoKeyWithNonce) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + int kNoKeys = 0; + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), NULL, NULL, + kNoKeys, s.key_array(), NULL, 0)); +} + TEST_F(OEMCryptoSessionTests, QueryKeyControl) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); // Note: successful cases are tested in VerifyTestKeys. KeyControlBlock block; size_t size = sizeof(block) - 1; - OEMCryptoResult sts = OEMCrypto_QueryKeyControl( - s.session_id(), s.license().keys[0].key_id, - s.license().keys[0].key_id_length, reinterpret_cast(&block), - &size); + OEMCryptoResult sts = + OEMCrypto_QueryKeyControl(s.session_id(), s.license().keys[0].key_id, + s.license().keys[0].key_id_length, + reinterpret_cast(&block), &size); if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { return; } ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - const char *key_id = "no_key"; + const char* key_id = "no_key"; size = sizeof(block); ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_QueryKeyControl( @@ -2077,10 +919,11 @@ TEST_F(OEMCryptoSessionTests, QueryKeyControl) { TEST_F(OEMCryptoSessionTests, AntiRollbackHardwareRequired) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlRequireAntiRollbackHardware, 0); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, wvoec_mock::kControlRequireAntiRollbackHardware, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, @@ -2092,8 +935,58 @@ TEST_F(OEMCryptoSessionTests, AntiRollbackHardwareRequired) { } } +TEST_F(OEMCryptoSessionTests, CheckMinimumPatchLevel) { + uint8_t patch_level = OEMCrypto_Security_Patch_Level(); + printf(" Current Patch Level: %u.\n", patch_level); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, patch_level << wvoec_mock::kControlSecurityPatchLevelShift, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), + s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, + s.key_array(), NULL, 0)); + if (patch_level < 0x3F) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, (patch_level + 1) << wvoec_mock::kControlSecurityPatchLevelShift, + 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_EQ( + OEMCrypto_ERROR_UNKNOWN_FAILURE, + OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), + s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, + s.key_array(), NULL, 0)); + } + if (patch_level > 0) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, (patch_level - 1) << wvoec_mock::kControlSecurityPatchLevelShift, + 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), + s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, + s.key_array(), NULL, 0)); + } +} + class SessionTestDecryptWithHDCP : public OEMCryptoSessionTests, - public WithParamInterface { + public WithParamInterface { public: void DecryptWithHDCP(OEMCrypto_HDCP_Capability version) { OEMCryptoResult sts; @@ -2101,19 +994,21 @@ class SessionTestDecryptWithHDCP : public OEMCryptoSessionTests, sts = OEMCrypto_GetHDCPCapability(¤t, &maximum); ASSERT_EQ(OEMCrypto_SUCCESS, sts); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, (version << wvoec_mock::kControlHDCPVersionShift) | - wvoec_mock::kControlObserveHDCP | - wvoec_mock::kControlHDCPRequired, - 0); - s.EncryptAndSign(); - s.LoadTestKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, + (version << wvoec_mock::kControlHDCPVersionShift) | + wvoec_mock::kControlObserveHDCP | wvoec_mock::kControlHDCPRequired, + 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); if (version > current) { - s.TestDecryptCTR(true, OEMCrypto_ERROR_INSUFFICIENT_HDCP); + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(true, OEMCrypto_ERROR_INSUFFICIENT_HDCP)); } else { - s.TestDecryptCTR(true, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(true, OEMCrypto_SUCCESS)); } } }; @@ -2122,7 +1017,7 @@ TEST_P(SessionTestDecryptWithHDCP, Decrypt) { // Test parameterized by HDCP version. DecryptWithHDCP(static_cast(GetParam())); } -INSTANTIATE_TEST_CASE_P(TestHDCP, SessionTestDecryptWithHDCP, Range(1, 5)); +INSTANTIATE_TEST_CASE_P(TestHDCP, SessionTestDecryptWithHDCP, Range(1, 5)); // // Load, Refresh Keys Test @@ -2145,56 +1040,59 @@ class SessionTestRefreshKeyTest TEST_P(SessionTestRefreshKeyTest, RefreshWithNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, wvoec_mock::kControlNonceEnabled, - s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys("", new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); uint32_t nonce; s.GenerateNonce(&nonce); - s.RefreshTestKeys(num_keys_, wvoec_mock::kControlNonceEnabled, nonce, - OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.RefreshTestKeys( + num_keys_, wvoec_mock::kControlNonceEnabled, nonce, OEMCrypto_SUCCESS)); } TEST_P(SessionTestRefreshKeyTest, RefreshNoNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys("", new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); uint32_t nonce; s.GenerateNonce(&nonce); - s.RefreshTestKeys(num_keys_, 0, 0, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE( + s.RefreshTestKeys(num_keys_, 0, 0, OEMCrypto_SUCCESS)); } TEST_P(SessionTestRefreshKeyTest, RefreshOldNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, wvoec_mock::kControlNonceEnabled, - s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys("", new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); uint32_t nonce = s.get_nonce(); - s.RefreshTestKeys(num_keys_, wvoec_mock::kControlNonceEnabled, nonce, - OEMCrypto_ERROR_INVALID_NONCE); + ASSERT_NO_FATAL_FAILURE( + s.RefreshTestKeys(num_keys_, wvoec_mock::kControlNonceEnabled, nonce, + OEMCrypto_ERROR_INVALID_NONCE)); } TEST_P(SessionTestRefreshKeyTest, RefreshBadNonce) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, wvoec_mock::kControlNonceEnabled, - s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys("", new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); uint32_t nonce; s.GenerateNonce(&nonce); nonce ^= 42; - s.RefreshTestKeys(num_keys_, wvoec_mock::kControlNonceEnabled, nonce, - OEMCrypto_ERROR_INVALID_NONCE); + ASSERT_NO_FATAL_FAILURE( + s.RefreshTestKeys(num_keys_, wvoec_mock::kControlNonceEnabled, nonce, + OEMCrypto_ERROR_INVALID_NONCE)); } // Of only one key control block in the refesh, we update all the keys. @@ -2212,37 +1110,517 @@ INSTANTIATE_TEST_CASE_P(TestRefreshEachKeys, SessionTestRefreshKeyTest, // TEST_F(OEMCryptoSessionTests, Decrypt) { Session s; - s.open(); - - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); } -TEST_F(OEMCryptoSessionTests, DecryptPerformance) { +TEST_F(OEMCryptoSessionTests, DecryptZeroDuration) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); +} + +TEST_F(OEMCryptoSessionTests, SimultaneousDecrypt) { + vector s(8); + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].open()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s[i].FillSimpleMessage(kLongDuration, 0, + s[i].get_nonce())); + ASSERT_NO_FATAL_FAILURE(s[i].EncryptAndSign()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].LoadTestKeys()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); + } + // Second call to decrypt for each session. + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); + } +} + +TEST_F(OEMCryptoSessionTests, SimultaneousDecryptWithLostMessage) { + vector s(8); + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].open()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s[i].FillSimpleMessage(kLongDuration, 0, + s[i].get_nonce())); + ASSERT_NO_FATAL_FAILURE(s[i].EncryptAndSign()); + } + // First set of messages are lost. Generate second set. + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s[i].FillSimpleMessage(kLongDuration, 0, + s[i].get_nonce())); + ASSERT_NO_FATAL_FAILURE(s[i].EncryptAndSign()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].LoadTestKeys()); + } + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); + } + // Second call to decrypt for each session. + for (int i = 0; i < 8; i++) { + ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); + } +} + +struct SampleSize { + size_t clear_size; + size_t encrypted_size; + SampleSize(size_t clear, size_t encrypted) + : clear_size(clear), encrypted_size(encrypted) {} +}; + +struct SampleInitData { + uint8_t iv[AES_BLOCK_SIZE]; + size_t block_offset; +}; + +class OEMCryptoSessionTestsDecryptTests + : public OEMCryptoSessionTests, + public WithParamInterface { + protected: + virtual void SetUp() { + OEMCryptoSessionTests::SetUp(); + pattern_ = GetParam().pattern; + cipher_mode_ = GetParam().mode; + } + + void FindTotalSize() { + total_size_ = 0; + for (size_t i = 0; i < subsample_size_.size(); i++) { + total_size_ += + subsample_size_[i].clear_size + subsample_size_[i].encrypted_size; + } + } + + void EncryptData(const vector& key, + const vector& starting_iv, + const vector& in_buffer, + vector* out_buffer) { + AES_KEY aes_key; + AES_set_encrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key); + out_buffer->resize(in_buffer.size()); + + uint8_t iv[AES_BLOCK_SIZE]; // Current iv. + + memcpy(iv, &starting_iv[0], AES_BLOCK_SIZE); + + size_t buffer_index = 0; // byte index into in and out. + size_t block_offset = 0; // byte index into current block. + for (size_t i = 0; i < subsample_size_.size(); i++) { + // Copy clear content. + if (subsample_size_[i].clear_size > 0) { + memcpy(&(*out_buffer)[buffer_index], &in_buffer[buffer_index], + subsample_size_[i].clear_size); + buffer_index += subsample_size_[i].clear_size; + } + // Save the current iv and offsets for call to DecryptCENC. + sample_init_data_.push_back(SampleInitData()); + memcpy(sample_init_data_[i].iv, iv, AES_BLOCK_SIZE); + // Note: final CENC spec specifies the pattern_offset = 0 at the + // start of each subsample. + size_t pattern_offset = 0; + sample_init_data_[i].block_offset = block_offset; + + size_t subsample_end = buffer_index + subsample_size_[i].encrypted_size; + while (buffer_index < subsample_end) { + size_t size = min(subsample_end - buffer_index, + AES_BLOCK_SIZE - block_offset); + size_t pattern_length = pattern_.encrypt + pattern_.skip; + bool skip_block = (pattern_offset >= pattern_.encrypt) + && (pattern_length>0); + if (pattern_length > 0) { + pattern_offset = (pattern_offset + 1) % pattern_length; + } + // CBC mode should just copy a partial block at the end. If there + // is a partial block at the beginning, an error is returned, so we + // can put whatever we want in the output buffer. + if (skip_block || ((cipher_mode_ == OEMCrypto_CipherMode_CBC) && + (size < AES_BLOCK_SIZE))) { + memcpy(&(*out_buffer)[buffer_index], &in_buffer[buffer_index], + size); + } else { + if (cipher_mode_ == OEMCrypto_CipherMode_CTR) { + uint8_t aes_output[AES_BLOCK_SIZE]; + AES_encrypt(iv, aes_output, &aes_key); + for (size_t n = 0; n < size; n++) { + (*out_buffer)[buffer_index + n] = + aes_output[n + block_offset] ^ + in_buffer[buffer_index + n]; + } + ctr128_inc64(1, iv); + } else { + uint8_t aes_input[AES_BLOCK_SIZE]; + for (size_t n = 0; n < size; n++) { + aes_input[n] = in_buffer[buffer_index + n] ^ iv[n]; + } + AES_encrypt(aes_input, &(*out_buffer)[buffer_index], &aes_key); + memcpy(iv, &(*out_buffer)[buffer_index], AES_BLOCK_SIZE); + } + } + buffer_index += size; + block_offset = 0; + } + block_offset = + (block_offset + subsample_size_[i].encrypted_size) % AES_BLOCK_SIZE; + } + } + + void TestDecryptCENC(const vector& key, + const vector& /* encryptionIv */, + const vector& encryptedData, + const vector& unencryptedData) { + OEMCryptoResult sts; + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); + memcpy(s.license().keys[0].key_data, &key[0], key.size()); + s.license().keys[0].cipher_mode = cipher_mode_; + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[0].key_id, + s.license().keys[0].key_id_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // We decrypt each subsample. + vector outputBuffer(total_size_ + 16, 0xaa); + size_t buffer_offset = 0; + for (size_t i = 0; i < subsample_size_.size(); i++) { + OEMCrypto_CENCEncryptPatternDesc pattern = pattern_; + pattern.offset = 0; // Final CENC spec says pattern offset always 0. + bool is_encrypted = false; + OEMCrypto_DestBufferDesc destBuffer; + size_t block_offset = 0; + uint8_t subsample_flags = 0; + if (subsample_size_[i].clear_size > 0) { + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = &outputBuffer[buffer_offset]; + destBuffer.buffer.clear.max_length = total_size_ - buffer_offset; + if (i == 0) subsample_flags |= OEMCrypto_FirstSubsample; + if ((i == subsample_size_.size() - 1) && + (subsample_size_[i].encrypted_size == 0)) { + subsample_flags |= OEMCrypto_LastSubsample; + } + sts = + OEMCrypto_DecryptCENC(s.session_id(), &encryptedData[buffer_offset], + subsample_size_[i].clear_size, is_encrypted, + sample_init_data_[i].iv, block_offset, + &destBuffer, &pattern, subsample_flags); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + buffer_offset += subsample_size_[i].clear_size; + } + if (subsample_size_[i].encrypted_size > 0) { + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = &outputBuffer[buffer_offset]; + destBuffer.buffer.clear.max_length = total_size_ - buffer_offset; + is_encrypted = true; + block_offset = sample_init_data_[i].block_offset; + subsample_flags = 0; + if ((i == 0) && (subsample_size_[i].clear_size == 0)) { + subsample_flags |= OEMCrypto_FirstSubsample; + } + if (i == subsample_size_.size() - 1) { + subsample_flags |= OEMCrypto_LastSubsample; + } + sts = OEMCrypto_DecryptCENC( + s.session_id(), &encryptedData[buffer_offset], + subsample_size_[i].encrypted_size, is_encrypted, + sample_init_data_[i].iv, block_offset, &destBuffer, &pattern, + subsample_flags); + // CBC mode should not accept a block offset. + if ((block_offset > 0) && (cipher_mode_ == OEMCrypto_CipherMode_CBC)) { + ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts) + << "CBC Mode should reject a non-zero block offset."; + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + buffer_offset += subsample_size_[i].encrypted_size; + } + } + EXPECT_EQ(0xaa, outputBuffer[total_size_]) << "Buffer overrun."; + outputBuffer.resize(total_size_); + EXPECT_EQ(unencryptedData, outputBuffer); + } + + OEMCrypto_CENCEncryptPatternDesc pattern_; + OEMCryptoCipherMode cipher_mode_; + vector subsample_size_; + size_t total_size_; + vector sample_init_data_; +}; + +// Tests that generate partial ending blocks. These tests should not be used +// with CTR mode and pattern encrypt. +class OEMCryptoSessionTestsPartialBlockTests : + public OEMCryptoSessionTestsDecryptTests {}; + +TEST_P(OEMCryptoSessionTestsDecryptTests, SingleLargeSubsample) { + // This subsample size should be larger a few encrypt/skip patterns. Most + // test cases use a pattern length of 160, so we'll run through at least two + // full patterns. + subsample_size_.push_back(SampleSize(0, 400)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +TEST_P(OEMCryptoSessionTestsDecryptTests, PatternPlusOneBlock) { + // When the pattern length is 10 blocks, there is a discrepancy between the + // HLS and the CENC standards for samples of size 160*N+16, for N = 1, 2, 3 ... + // We require the CENC standard for OEMCrypto, and let a layer above us break + // samples into pieces if they wish to use the HLS standard. + subsample_size_.push_back(SampleSize(0, 160+16)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +TEST_P(OEMCryptoSessionTestsDecryptTests, OneBlock) { + subsample_size_.push_back(SampleSize(0, 16)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +// This tests the ability to decrypt multiple subsamples with no offset. +// There is no offset within the block, used by CTR mode. However, there might +// be an offset in the encrypt/skip pattern. +TEST_P(OEMCryptoSessionTestsDecryptTests, NoOffset) { + subsample_size_.push_back(SampleSize(25, 160)); + subsample_size_.push_back(SampleSize(50, 256)); + subsample_size_.push_back(SampleSize(25, 160)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +// This tests an offset into the block for the second encrypted subsample. +// This should only work for CTR mode, for CBC mode an error is expected in +// the decrypt step. +// If this test fails for CTR mode, then it is probably handleing the +// block_offset incorrectly. +TEST_P(OEMCryptoSessionTestsPartialBlockTests, EvenOffset) { + subsample_size_.push_back(SampleSize(25, 8)); + subsample_size_.push_back(SampleSize(25, 32)); + subsample_size_.push_back(SampleSize(25, 50)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + // CTR Mode is self-inverse -- i.e. We can pick the encrypted data and + // compute the unencrypted data. By picking the encrypted data to be all 0, + // it is easier to re-encrypt the data and debug problems. Similarly, we + // pick an iv = 0. + EncryptData(key, encryptionIv, encryptedData, &unencryptedData); + // Run EncryptData again to correctly compute intermediate IV vectors. + // For CBC mode, this also computes the real encrypted data. + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +// If the EvenOffset test passes, but this one doesn't, then DecryptCTR might +// be using the wrong definition of block offset. Adding the block offset to +// the block boundary should give you the beginning of the encrypted data. +// This should only work for CTR mode, for CBC mode, the block offset must be +// 0, so an error is expected in the decrypt step. +// Another way to view the block offset is with the formula: +// block_boundary + block_offset = beginning of subsample. +TEST_P(OEMCryptoSessionTestsPartialBlockTests, OddOffset) { + subsample_size_.push_back(SampleSize(10, 50)); + subsample_size_.push_back(SampleSize(10, 75)); + subsample_size_.push_back(SampleSize(10, 25)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +// This tests that the algorithm used to increment the counter for +// AES-CTR mode is correct. There are two possible implementations: +// 1) increment the counter as if it were a 128 bit number, +// 2) increment the low 64 bits as a 64 bit number and leave the high bits +// alone. +// For CENC, the algorithm we should use is the second one. OpenSSL defaults to +// the first. If this test is not passing, you should look at the way you +// increment the counter. Look at the example code in ctr128_inc64 above. +// If you start with an IV of 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, after you +// increment twice, you should get 0xFFFFFFFFFFFFFFFF0000000000000000. +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptWithNearWrap) { + subsample_size_.push_back(SampleSize(0, 256)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + encryptionIv = wvcdm::a2b_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +// This tests the case where an encrypted sample is not an even number of +// blocks. For CTR mode, the partial block is encrypted. For CBC mode the +// partial block should be a copy of the clear data. +TEST_P(OEMCryptoSessionTestsPartialBlockTests, PartialBlock) { + // Note: for more complete test coverage, we want a sample size that is in + // the encrypted range for some tests, e.g. (3,7), and in the skip range for + // other tests, e.g. (7, 3). 3*16 < 50 and 7*16 > 50. + subsample_size_.push_back(SampleSize(0, 50)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptUnencrypted) { + subsample_size_.push_back(SampleSize(256, 0)); + FindTotalSize(); + vector unencryptedData(total_size_); + vector encryptedData(total_size_); + vector encryptionIv(AES_BLOCK_SIZE); + vector key(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&key[0], AES_BLOCK_SIZE)); + for (size_t i = 0; i < total_size_; i++) unencryptedData[i] = i % 256; + EncryptData(key, encryptionIv, unencryptedData, &encryptedData); + TestDecryptCENC(key, encryptionIv, encryptedData, unencryptedData); +} + +TEST_F(OEMCryptoSessionTests, DecryptUnencryptedNoKey) { OEMCryptoResult sts; Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + // Clear data should be copied even if there is no key selected. + // Set up our expected input and output + // This is dummy decrypted data. + vector in_buffer(256); + for (size_t i = 0; i < in_buffer.size(); i++) in_buffer[i] = i % 256; + vector encryptionIv(AES_BLOCK_SIZE); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetRandom(&encryptionIv[0], AES_BLOCK_SIZE)); + // Describe the output + vector out_buffer(in_buffer.size()); + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = &out_buffer[0]; + destBuffer.buffer.clear.max_length = out_buffer.size(); + OEMCrypto_CENCEncryptPatternDesc pattern; + pattern.encrypt = 0; + pattern.skip = 0; + pattern.offset = 0; + + // Decrypt the data + sts = OEMCrypto_DecryptCENC( + s.session_id(), &in_buffer[0], in_buffer.size(), false, + &encryptionIv[0], 0, &destBuffer, &pattern, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(in_buffer, out_buffer); +} + +// This test is not run by default, because it takes a long time and +// is used to measure decrypt performance, not test functionality. +// TODO(fredgc): Verify this test runs correctly. +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptCENCPerformance) { + OEMCryptoResult sts; + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); const time_t TestDuration = 5; - s.FillSimpleMessage(600, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - vector keyId = wvcdm::a2b_hex("000000000000000000000000"); - sts = OEMCrypto_SelectKey(s.session_id(), &keyId[0], keyId.size()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(600, 0, 0)); + s.license().keys[0].cipher_mode = GetParam().mode; + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[0].key_id, + s.license().keys[0].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); vector encryptionIv = wvcdm::a2b_hex("719dbcb253b2ec702bb8c1b1bc2f3bc6"); - const size_t max_length = 250*1000; + const size_t max_length = 250 * 1024; vector input(max_length); printf("Size of input is %zd\n", input.size()); - for(unsigned int i=0; i < max_length; i++) input[i] = i % 256; + for (unsigned int i = 0; i < max_length; i++) input[i] = i % 256; vector output(max_length); OEMCrypto_DestBufferDesc destBuffer; destBuffer.type = OEMCrypto_BufferType_Clear; destBuffer.buffer.clear.address = &output[0]; + OEMCrypto_CENCEncryptPatternDesc pattern = GetParam().pattern; const char* level = OEMCrypto_SecurityLevel(); const int n = 10; @@ -2251,10 +1629,11 @@ TEST_F(OEMCryptoSessionTests, DecryptPerformance) { double ysum = 0.0; double xysum = 0.0; double xsqsum = 0.0; - printf("PERF:head, security, bytes, bytes/frame, time(ms)/frame, bandwidth\n"); + printf( + "PERF:head, security, bytes, bytes/frame, time(ms)/frame, bandwidth\n"); - for(int i=0; i < n; i++) { - size_t length = 1000 + i*1000; + for (int i = 0; i < n; i++) { + size_t length = 1024 + i * 1024; destBuffer.buffer.clear.max_length = length; time_t test_start = time(NULL); time_t test_end = time(NULL); @@ -2262,328 +1641,93 @@ TEST_F(OEMCryptoSessionTests, DecryptPerformance) { size_t total = 0; do { ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_DecryptCTR( - s.session_id(), &input[0], length, true, - &encryptionIv[0], 0, &destBuffer, + OEMCrypto_DecryptCENC( + s.session_id(), &input[0], length, true, &encryptionIv[0], + 0, &destBuffer, &pattern, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); count++; total += length; test_end = time(NULL); - } while(test_end - test_start < TestDuration); + } while (test_end - test_start < TestDuration); x[i] = length; - y[i] = 1000*(test_end-test_start)/((double)count); + y[i] = 1024 * (test_end - test_start) / ((double)count); xsum += x[i]; ysum += y[i]; - xysum += x[i]*y[i]; - xsqsum += x[i]*x[i]; - printf("PERF:stat, %s, %12zd, %12g, %12g, %12g\n", level, total, - x[i], y[i], - ((double)total)/((double)(test_end-test_start)) - ); + xysum += x[i] * y[i]; + xsqsum += x[i] * x[i]; + printf("PERF:stat, %s, %12zd, %12g, %12g, %12g\n", level, total, x[i], + y[i], ((double)total) / ((double)(test_end - test_start))); } - double b = (n*xysum - xsum*ysum) / (n*xsqsum - xsum*xsum); - double a = (ysum - b*xsum)/n; + double b = (n * xysum - xsum * ysum) / (n * xsqsum - xsum * xsum); + double a = (ysum - b * xsum) / n; printf("PERF-FIT, security=%s fit time(ms)/frame = %g + %g * buffer_size\n", - level, a, b); - for(int i=0; i < n; i++) { - printf("PERF-FIT, %12g, %12g, %12g\n", x[i], y[i], a + b*x[i]); + level, a, b); + for (int i = 0; i < n; i++) { + printf("PERF-FIT, %12g, %12g, %12g\n", x[i], y[i], a + b * x[i]); } } -TEST_F(OEMCryptoSessionTests, DecryptZeroDuration) { - Session s; - s.open(); - - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.TestDecryptCTR(); -} - -class OEMCryptoSessionTestsDecryptEdgeCases : public OEMCryptoSessionTests { - public: - // Increment counter for AES-CTR. The CENC spec specifies we increment only - // the low 64 bits of the IV counter, and leave the high 64 bits alone. This - // is different from the OpenSSL implementation, so we implement the CTR loop - // ourselves. - void ctr128_inc64(int64_t increaseBy, uint8_t* iv) { - uint64_t* counterBuffer = reinterpret_cast(&iv[8]); - (*counterBuffer) = wvcdm::htonll64(wvcdm::ntohll64(*counterBuffer) + - increaseBy); - } - - size_t FindTotalSize(const vector& subsample_size) { - size_t total_size = 0; - for(size_t i=0; i < subsample_size.size(); i++) - total_size += subsample_size[i]; - return total_size; - } - - void EncryptCTR(const vector& key, const vector& iv, - const vector& in, vector* out) { - AES_KEY aes_key; - AES_set_encrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key); - - uint8_t aes_iv[AES_BLOCK_SIZE]; - memcpy(aes_iv, &iv[0], AES_BLOCK_SIZE); - - // Encrypt the IV. - uint8_t ecount_buf[AES_BLOCK_SIZE]; - - out->resize(in.size()); - - size_t cipher_data_length = in.size(); - size_t l = 0; - while (l < cipher_data_length) { - AES_encrypt(aes_iv, ecount_buf, &aes_key); - for (int n = 0; n < AES_BLOCK_SIZE && l < cipher_data_length; - ++n, ++l) { - (*out)[l] = in[l] ^ ecount_buf[n]; - } - ctr128_inc64(1, aes_iv); - } - } - - void TestDecrypt(const vector& unencryptedData, - const vector& encryptedData, - const vector& encryptionIv, - size_t total_size, const vector subsample_size) { - OEMCryptoResult sts; - Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - sts = OEMCrypto_SelectKey(s.session_id(), - s.license().keys[0].key_id, - s.license().keys[0].key_id_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - - // We decrypt three subsamples. each with a block offset. - vector outputBuffer(total_size, 0xaa); - size_t buffer_offset = 0; - for(size_t i=0; i < subsample_size.size(); i++) { - const size_t block_offset = buffer_offset % AES_BLOCK_SIZE; - uint8_t subsample_flags = 0; - if (i == 0) subsample_flags |= OEMCrypto_FirstSubsample; - if (i == subsample_size.size()-1) { - subsample_flags |= OEMCrypto_LastSubsample; - } - OEMCrypto_DestBufferDesc destBuffer; - destBuffer.type = OEMCrypto_BufferType_Clear; - destBuffer.buffer.clear.address = &outputBuffer[buffer_offset]; - destBuffer.buffer.clear.max_length = total_size-buffer_offset; - uint8_t aes_iv[AES_BLOCK_SIZE]; - memcpy(aes_iv, &encryptionIv[0], AES_BLOCK_SIZE); - size_t iv_increment = buffer_offset / AES_BLOCK_SIZE; - ctr128_inc64(iv_increment, aes_iv); - sts = OEMCrypto_DecryptCTR( - s.session_id(), &encryptedData[buffer_offset], subsample_size[i], - true, aes_iv, block_offset, &destBuffer, subsample_flags); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - buffer_offset += subsample_size[i]; - } - EXPECT_EQ(unencryptedData, outputBuffer); - // If there was a problem, compare the outputBuffer at the offset with the - // correct data at 0. A common error is to ignore the offset when - // decrypting. - if (unencryptedData != outputBuffer && 2*subsample_size[0] < total_size - && 0 == memcmp(&unencryptedData[0], &outputBuffer[subsample_size[0]], - subsample_size[0])){ - printf("The first %zd bytes are repeating. This is an indication \n", - subsample_size[0]); - printf("that DecryptCTR is ignoring the offset.\n"); - } - } -}; - -TEST_F(OEMCryptoSessionTestsDecryptEdgeCases, EvenOffset) { - vector subsample_size; - subsample_size.push_back(8); - subsample_size.push_back(32); - subsample_size.push_back(50); - const size_t total_size = FindTotalSize(subsample_size); - vector unencryptedData(total_size); - vector encryptedData(total_size); - vector encryptionIv(AES_BLOCK_SIZE); - vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); - // Note: DecryptCTR is self-inverse -- ie it's the same as EncryptCTR. - // So we can pick the encrypted data and compute the unencrypted data if we - // want. By picking the encrypted data to be all 0, it is easier to - // re-encrypt the data and debug problems. - EncryptCTR(key, encryptionIv, encryptedData, &unencryptedData); - TestDecrypt(unencryptedData, encryptedData, encryptionIv, total_size, - subsample_size); -} - -// This tests the ability to decrypt multiple subsamples with no offset. -TEST_F(OEMCryptoSessionTestsDecryptEdgeCases, NoOffset) { - vector subsample_size; - subsample_size.push_back(64); - subsample_size.push_back(64); - subsample_size.push_back(64); - const size_t total_size = FindTotalSize(subsample_size); - vector unencryptedData(total_size); - vector encryptedData(total_size); - vector encryptionIv(AES_BLOCK_SIZE); - encryptionIv = wvcdm::a2b_hex("c09454479a280829c946df3c22f25539"); - for(size_t i=0; i < total_size; i++) unencryptedData[i] = i % 256; - vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); - EncryptCTR(key, encryptionIv, unencryptedData, &encryptedData); - TestDecrypt(unencryptedData, encryptedData, encryptionIv, total_size, - subsample_size); -} - -// If the EvenOffset test passes, but this one doesn't, then DecryptCTR might -// be using the wrong definition of offset. Adding the offset to the block -// boundary should give you the beginning of the encrypted data. -TEST_F(OEMCryptoSessionTestsDecryptEdgeCases, OddOffset) { - vector subsample_size; - subsample_size.push_back(50); - subsample_size.push_back(75); - subsample_size.push_back(25); - const size_t total_size = FindTotalSize(subsample_size); - vector unencryptedData(total_size); - vector encryptedData(total_size); - vector encryptionIv(AES_BLOCK_SIZE); - encryptionIv = wvcdm::a2b_hex("c09454479a280829c946df3c22f25539"); - for(size_t i=0; i < total_size; i++) unencryptedData[i] = i % 256; - vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); - EncryptCTR(key, encryptionIv, unencryptedData, &encryptedData); - TestDecrypt(unencryptedData, encryptedData, encryptionIv, total_size, - subsample_size); -} - -// This tests that the algorithm used to increment the counter for -// AES-CTR mode is correct. There are two possible implementations: -// 1) increment the counter as if it were a 128 bit number, -// 2) increment the low 64 bits as a 64 bit number and leave the high bits alone. -// For CENC, the algorithm we should use is the second one. OpenSSL defaults to -// the first. If this test is not passing, you should look at the way you -// increment the counter. Look at the example code in ctr128_inc64 above. -// If you start with an IV of 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, after you -// increment twice, you should get 0xFFFFFFFFFFFFFFFF0000000000000000. -TEST_F(OEMCryptoSessionTestsDecryptEdgeCases, DecryptWithNearWrap) { - vector subsample_size; - subsample_size.push_back(150); - const size_t total_size = FindTotalSize(subsample_size); - vector unencryptedData(total_size); - vector encryptedData(total_size); - vector encryptionIv(AES_BLOCK_SIZE); - encryptionIv = wvcdm::a2b_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"); - for(size_t i=0; i < total_size; i++) unencryptedData[i] = i % 256; - vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); - EncryptCTR(key, encryptionIv, unencryptedData, &encryptedData); - TestDecrypt(unencryptedData, encryptedData, encryptionIv, total_size, - subsample_size); -} - -TEST_F(OEMCryptoSessionTests, DecryptUnencrypted) { - OEMCryptoResult sts; - Session s; - s.open(); - - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - - // Select the key (from FillSimpleMessage) - sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[0].key_id, - s.license().keys[0].key_id_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - - // Set up our expected input and output - // This is dummy decrypted data. - vector unencryptedData = wvcdm::a2b_hex( - "1558497b6d994be343ed1c6d6313e0537b843e9a9c0836d1e83fe33154191ce9" - "a14d8d95bebaddc03bd471827170f527c0a166b9068b273d1bc57fbb13975ee4" - "f6b9a31743da6c447acbb712e81b13eddfd4e96c76010ac9b8aa1b6b3152b0fc" - "39ad33e5719656069f9ede9ebba7a77dd2e2074eec5c1b7ffc427a6f1be168f0" - "b5857713a44623862c903284bc53417e23c65602b52c1cb699e8352453eb9698" - "0b31459b90c26c907b549c1ab293725e414d4e45f5b30af7a55f95499a7dc89f" - "7d13ba90b34aef6b49484b0701bf96ea8b660c24bb4e92a2d1c43beb434fa386" - "1071380388799ac31d79285f5817687ed3e2eeb73a30744e77b757686c9ba5ad"); - vector encryptionIv = wvcdm::a2b_hex( - "49fc3efaaf614ed81d595847b928edd0"); - - // Describe the output - vector outputBuffer(256); - OEMCrypto_DestBufferDesc destBuffer; - destBuffer.type = OEMCrypto_BufferType_Clear; - destBuffer.buffer.clear.address = &outputBuffer[0]; - destBuffer.buffer.clear.max_length = outputBuffer.size(); - - // Decrypt the data - sts = OEMCrypto_DecryptCTR( - s.session_id(), &unencryptedData[0], unencryptedData.size(), false, - &encryptionIv[0], 0, &destBuffer, - OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(unencryptedData, outputBuffer); -} - -TEST_F(OEMCryptoSessionTests, DecryptUnencryptedNoKey) { - OEMCryptoResult sts; - Session s; - s.open(); - // Clear data should be copied even if there is no key selected. - // Set up our expected input and output - // This is dummy decrypted data. - vector unencryptedData = wvcdm::a2b_hex( - "1558497b6d994be343ed1c6d6313e0537b843e9a9c0836d1e83fe33154191ce9" - "a14d8d95bebaddc03bd471827170f527c0a166b9068b273d1bc57fbb13975ee4" - "f6b9a31743da6c447acbb712e81b13eddfd4e96c76010ac9b8aa1b6b3152b0fc" - "39ad33e5719656069f9ede9ebba7a77dd2e2074eec5c1b7ffc427a6f1be168f0" - "b5857713a44623862c903284bc53417e23c65602b52c1cb699e8352453eb9698" - "0b31459b90c26c907b549c1ab293725e414d4e45f5b30af7a55f95499a7dc89f" - "7d13ba90b34aef6b49484b0701bf96ea8b660c24bb4e92a2d1c43beb434fa386" - "1071380388799ac31d79285f5817687ed3e2eeb73a30744e77b757686c9ba5ad"); - vector encryptionIv = wvcdm::a2b_hex( - "49fc3efaaf614ed81d595847b928edd0"); - - // Describe the output - vector outputBuffer(256); - OEMCrypto_DestBufferDesc destBuffer; - destBuffer.type = OEMCrypto_BufferType_Clear; - destBuffer.buffer.clear.address = &outputBuffer[0]; - destBuffer.buffer.clear.max_length = outputBuffer.size(); - - // Decrypt the data - sts = OEMCrypto_DecryptCTR( - s.session_id(), &unencryptedData[0], unencryptedData.size(), false, - &encryptionIv[0], 0, &destBuffer, - OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(unencryptedData, outputBuffer); -} +INSTANTIATE_TEST_CASE_P( + CTRTests, OEMCryptoSessionTestsPartialBlockTests, + Values(PatternTestVariant(0, 0, OEMCrypto_CipherMode_CTR) + )); +INSTANTIATE_TEST_CASE_P( + CBCTests, OEMCryptoSessionTestsPartialBlockTests, + Values(PatternTestVariant(0, 0, OEMCrypto_CipherMode_CBC), + PatternTestVariant(3, 7, OEMCrypto_CipherMode_CBC), + // HLS Edge case. We should follow the CENC spec, not HLS spec. + PatternTestVariant(9, 1, OEMCrypto_CipherMode_CBC), + PatternTestVariant(1, 9, OEMCrypto_CipherMode_CBC), + PatternTestVariant(1, 3, OEMCrypto_CipherMode_CBC), + PatternTestVariant(2, 1, OEMCrypto_CipherMode_CBC) + )); +INSTANTIATE_TEST_CASE_P( + CTRTests, OEMCryptoSessionTestsDecryptTests, + Values(PatternTestVariant(0, 0, OEMCrypto_CipherMode_CTR), + PatternTestVariant(3, 7, OEMCrypto_CipherMode_CTR), + // Pattern length should be 10, but that is not guaranteed. + PatternTestVariant(1, 3, OEMCrypto_CipherMode_CTR), + PatternTestVariant(2, 1, OEMCrypto_CipherMode_CTR) + )); +INSTANTIATE_TEST_CASE_P( + CBCTests, OEMCryptoSessionTestsDecryptTests, + Values(PatternTestVariant(0, 0, OEMCrypto_CipherMode_CBC), + PatternTestVariant(3, 7, OEMCrypto_CipherMode_CBC), + // HLS Edge case. We should follow the CENC spec, not HLS spec. + PatternTestVariant(9, 1, OEMCrypto_CipherMode_CBC), + PatternTestVariant(1, 9, OEMCrypto_CipherMode_CBC), + // Pattern length should be 10, but that is not guaranteed. + PatternTestVariant(1, 3, OEMCrypto_CipherMode_CBC), + PatternTestVariant(2, 1, OEMCrypto_CipherMode_CBC) + )); TEST_F(OEMCryptoSessionTests, DecryptSecureToClear) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, wvoec_mock::kControlObserveDataPath | - wvoec_mock::kControlDataPathSecure, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + kDuration, + wvoec_mock::kControlObserveDataPath | wvoec_mock::kControlDataPathSecure, + 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); } TEST_F(OEMCryptoSessionTests, KeyDuration) { Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(kDuration, wvoec_mock::kControlNonceEnabled, - s.get_nonce()); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.TestDecryptCTR(true, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(true, OEMCrypto_SUCCESS)); sleep(kShortSleep); // Should still be valid key. - s.TestDecryptCTR(false, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(false, OEMCrypto_SUCCESS)); sleep(kLongSleep); // Should be expired key. - s.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); } // @@ -2591,21 +1735,22 @@ TEST_F(OEMCryptoSessionTests, KeyDuration) { // class OEMCryptoLoadsCertificate : public OEMCryptoSessionTestKeyboxTest { protected: - OEMCryptoLoadsCertificate() : - encoded_rsa_key_(kTestRSAPKCS8PrivateKeyInfo2_2048, - kTestRSAPKCS8PrivateKeyInfo2_2048 + - sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)) {} + OEMCryptoLoadsCertificate() + : encoded_rsa_key_(kTestRSAPKCS8PrivateKeyInfo2_2048, + kTestRSAPKCS8PrivateKeyInfo2_2048 + + sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)) {} void CreateWrappedRSAKey(vector* wrapped_key, uint32_t allowed_schemes, bool force) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; - s.MakeRSACertificate(&encrypted, &signature, allowed_schemes, - encoded_rsa_key_); - s.RewrapRSAKey(encrypted, signature, wrapped_key, force); + ASSERT_NO_FATAL_FAILURE(s.MakeRSACertificate( + &encrypted, &signature, allowed_schemes, encoded_rsa_key_)); + ASSERT_NO_FATAL_FAILURE( + s.RewrapRSAKey(encrypted, signature, wrapped_key, force)); // Verify that the clear key is not contained in the wrapped key. // It should be encrypted. ASSERT_EQ(NULL, find(*wrapped_key, encoded_rsa_key_)); @@ -2618,8 +1763,8 @@ TEST_F(OEMCryptoLoadsCertificate, LoadRSASessionKey) { std::vector wrapped_rsa_key; CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); Session s; - s.open(); - s.InstallRSASessionTestKey(wrapped_rsa_key); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key)); } TEST_F(OEMCryptoLoadsCertificate, CertificateProvision) { @@ -2632,12 +1777,12 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvision) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; - s.MakeRSACertificate(&encrypted, &signature, kSign_RSASSA_PSS, - encoded_rsa_key_); + ASSERT_NO_FATAL_FAILURE(s.MakeRSACertificate( + &encrypted, &signature, kSign_RSASSA_PSS, encoded_rsa_key_)); vector wrapped_key; const uint8_t* message_ptr = reinterpret_cast(&encrypted); size_t wrapped_key_length = 0; @@ -2660,7 +1805,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; @@ -2690,7 +1835,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; @@ -2721,7 +1866,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadSignature) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; @@ -2750,7 +1895,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadSignature) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; @@ -2779,7 +1924,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce) { TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRSAKey) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateDerivedKeysFromKeybox(); struct RSAPrivateKeyMessage encrypted; std::vector signature; @@ -2812,7 +1957,7 @@ TEST_F(OEMCryptoLoadsCertificate, LoadWrappedRSAKey) { CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key[0], wrapped_rsa_key.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -2824,27 +1969,27 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateDecrypt) { std::vector wrapped_rsa_key; CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); Session s; - s.open(); - s.InstallRSASessionTestKey(wrapped_rsa_key); - s.FillSimpleMessage(kDuration, 0, 0); - s.EncryptAndSign(); - s.LoadTestKeys(); - s.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key)); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); } class OEMCryptoUsesCertificate : public OEMCryptoLoadsCertificate { protected: virtual void SetUp() { OEMCryptoLoadsCertificate::SetUp(); - session_.open(); - if (global_features.derive_key_method - != DeviceFeatures::LOAD_TEST_RSA_KEY) { + ASSERT_NO_FATAL_FAILURE(session_.open()); + if (global_features.derive_key_method != + DeviceFeatures::LOAD_TEST_RSA_KEY) { std::vector wrapped_rsa_key; CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadDeviceRSAKey(session_.session_id(), - &wrapped_rsa_key[0], - wrapped_rsa_key.size())); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadDeviceRSAKey(session_.session_id(), &wrapped_rsa_key[0], + wrapped_rsa_key.size())); } } @@ -2856,22 +2001,24 @@ class OEMCryptoUsesCertificate : public OEMCryptoLoadsCertificate { Session session_; }; -// Test performance -TEST_F( OEMCryptoLoadsCertificate, RSAPerformance) { +// This test is not run by default, because it takes a long time and +// is used to measure RSA performance, not test functionality. +TEST_F(OEMCryptoLoadsCertificate, RSAPerformance) { OEMCryptoResult sts; - sleep(2); // Make sure are not nonce limited. - const uint32_t TestDuration = 5000; // milliseconds. + sleep(2); // Make sure are not nonce limited. + + const uint32_t TestDuration = 5000; // milliseconds. struct timeval start_time, end_time; gettimeofday(&start_time, NULL); gettimeofday(&end_time, NULL); double mtime = 0; long count = 0; - for(int i=0; i< 15; i++) { // Only 20 nonce available. + for (int i = 0; i < 15; i++) { // Only 20 nonce available. vector wrapped_key; CreateWrappedRSAKey(&wrapped_key, kSign_RSASSA_PSS, true); count++; gettimeofday(&end_time, NULL); - long seconds = end_time.tv_sec - start_time.tv_sec; + long seconds = end_time.tv_sec - start_time.tv_sec; long useconds = end_time.tv_usec - start_time.tv_usec; mtime = seconds * 1e3 + useconds * 1e-3; } @@ -2886,7 +2033,7 @@ TEST_F( OEMCryptoLoadsCertificate, RSAPerformance) { count = 0; do { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key[0], wrapped_rsa_key.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -2906,14 +2053,14 @@ TEST_F( OEMCryptoLoadsCertificate, RSAPerformance) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); count++; gettimeofday(&end_time, NULL); - long seconds = end_time.tv_sec - start_time.tv_sec; + long seconds = end_time.tv_sec - start_time.tv_sec; long useconds = end_time.tv_usec - start_time.tv_usec; mtime = seconds * 1e3 + useconds * 1e-3; - } while(mtime < TestDuration); + } while (mtime < TestDuration); double license_request_time = mtime / count; Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key[0], wrapped_rsa_key.size())); @@ -2954,16 +2101,16 @@ TEST_F( OEMCryptoLoadsCertificate, RSAPerformance) { enc_context.size())); count++; gettimeofday(&end_time, NULL); - long seconds = end_time.tv_sec - start_time.tv_sec; + long seconds = end_time.tv_sec - start_time.tv_sec; long useconds = end_time.tv_usec - start_time.tv_usec; mtime = seconds * 1e3 + useconds * 1e-3; - } while(mtime < TestDuration); + } while (mtime < TestDuration); double derive_keys_time = mtime / count; const char* level = OEMCrypto_SecurityLevel(); printf("PERF:head, security, provision (ms), lic req(ms), derive keys(ms)\n"); printf("PERF:stat, %s, %8.3f, %8.3f, %8.3f\n", level, provision_time, - license_request_time, derive_keys_time); + license_request_time, derive_keys_time); } TEST_F(OEMCryptoUsesCertificate, RSASignature) { @@ -2974,25 +2121,24 @@ TEST_F(OEMCryptoUsesCertificate, RSASignature) { size_t signature_length = 0; uint8_t signature[500]; - sts = OEMCrypto_GenerateRSASignature(session_.session_id(), - &licenseRequest[0], - licenseRequest.size(), signature, - &signature_length, kSign_RSASSA_PSS); + sts = OEMCrypto_GenerateRSASignature( + session_.session_id(), &licenseRequest[0], licenseRequest.size(), + signature, &signature_length, kSign_RSASSA_PSS); ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); ASSERT_NE(static_cast(0), signature_length); ASSERT_GE(sizeof(signature), signature_length); - sts = OEMCrypto_GenerateRSASignature(session_.session_id(), &licenseRequest[0], - licenseRequest.size(), signature, - &signature_length, kSign_RSASSA_PSS); + sts = OEMCrypto_GenerateRSASignature( + session_.session_id(), &licenseRequest[0], licenseRequest.size(), + signature, &signature_length, kSign_RSASSA_PSS); ASSERT_EQ(OEMCrypto_SUCCESS, sts); // In the real world, the signature above would just have been used to contact // the license server to get this response. session_.PreparePublicKey(); - session_.VerifyRSASignature(licenseRequest, signature, signature_length, - kSign_RSASSA_PSS); + ASSERT_NO_FATAL_FAILURE(session_.VerifyRSASignature( + licenseRequest, signature, signature_length, kSign_RSASSA_PSS)); } // This test attempts to use alternate algorithms for loaded device certs. @@ -3001,7 +2147,7 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { void DisallowForbiddenPadding(RSA_Padding_Scheme scheme, size_t size) { OEMCryptoResult sts; Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key_[0], wrapped_rsa_key_.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -3023,7 +2169,7 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { &signature_length, scheme); } - ASSERT_NE(OEMCrypto_SUCCESS, sts) + EXPECT_NE(OEMCrypto_SUCCESS, sts) << "Signed with forbidden padding scheme=" << (int)scheme << ", size=" << (int)size; vector zero(signature_length, 0); @@ -3033,7 +2179,7 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { void TestSignature(RSA_Padding_Scheme scheme, size_t size) { OEMCryptoResult sts; Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key_[0], wrapped_rsa_key_.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -3056,14 +2202,15 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { << "Failed to sign with padding scheme=" << (int)scheme << ", size=" << (int)size; s.PreparePublicKey(); - s.VerifyRSASignature(licenseRequest, signature, signature_length, scheme); + ASSERT_NO_FATAL_FAILURE(s.VerifyRSASignature(licenseRequest, signature, + signature_length, scheme)); delete[] signature; } void DisallowDeriveKeys() { OEMCryptoResult sts; Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key_[0], wrapped_rsa_key_.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -3165,96 +2312,89 @@ class OEMCryptoCastReceiverTest : public OEMCryptoLoadsCertificateAlternates { // This encodes the RSA key used in the PKCS#1 signing tests below. void BuildRSAKey() { vector field_n = - encode(0x02, wvcdm::a2b_hex( - "00" - "df271fd25f8644496b0c81be4bd50297" - "ef099b002a6fd67727eb449cea566ed6" - "a3981a71312a141cabc9815c1209e320" - "a25b32464e9999f18ca13a9fd3892558" - "f9e0adefdd3650dd23a3f036d60fe398" - "843706a40b0b8462c8bee3bce12f1f28" - "60c2444cdc6a44476a75ff4aa24273cc" - "be3bf80248465f8ff8c3a7f3367dfc0d" - "f5b6509a4f82811cedd81cdaaa73c491" - "da412170d544d4ba96b97f0afc806549" - "8d3a49fd910992a1f0725be24f465cfe" - "7e0eabf678996c50bc5e7524abf73f15" - "e5bef7d518394e3138ce4944506aaaaf" - "3f9b236dcab8fc00f87af596fdc3d9d6" - "c75cd508362fae2cbeddcc4c7450b17b" - "776c079ecca1f256351a43b97dbe2153")); + encode(0x02, wvcdm::a2b_hex("00" + "df271fd25f8644496b0c81be4bd50297" + "ef099b002a6fd67727eb449cea566ed6" + "a3981a71312a141cabc9815c1209e320" + "a25b32464e9999f18ca13a9fd3892558" + "f9e0adefdd3650dd23a3f036d60fe398" + "843706a40b0b8462c8bee3bce12f1f28" + "60c2444cdc6a44476a75ff4aa24273cc" + "be3bf80248465f8ff8c3a7f3367dfc0d" + "f5b6509a4f82811cedd81cdaaa73c491" + "da412170d544d4ba96b97f0afc806549" + "8d3a49fd910992a1f0725be24f465cfe" + "7e0eabf678996c50bc5e7524abf73f15" + "e5bef7d518394e3138ce4944506aaaaf" + "3f9b236dcab8fc00f87af596fdc3d9d6" + "c75cd508362fae2cbeddcc4c7450b17b" + "776c079ecca1f256351a43b97dbe2153")); vector field_e = encode(0x02, wvcdm::a2b_hex("010001")); vector field_d = - encode(0x02, wvcdm::a2b_hex( - "5bd910257830dce17520b03441a51a8c" - "ab94020ac6ecc252c808f3743c95b7c8" - "3b8c8af1a5014346ebc4242cdfb5d718" - "e30a733e71f291e4d473b61bfba6daca" - "ed0a77bd1f0950ae3c91a8f901118825" - "89e1d62765ee671e7baeea309f64d447" - "bbcfa9ea12dce05e9ea8939bc5fe6108" - "581279c982b308794b3448e7f7b95229" - "2df88c80cb40142c4b5cf5f8ddaa0891" - "678d610e582fcb880f0d707caf47d09a" - "84e14ca65841e5a3abc5e9dba94075a9" - "084341f0edad9b68e3b8e082b80b6e6e" - "8a0547b44fb5061b6a9131603a5537dd" - "abd01d8e863d8922e9aa3e4bfaea0b39" - "d79283ad2cbc8a59cce7a6ecf4e4c81e" - "d4c6591c807defd71ab06866bb5e7745")); + encode(0x02, wvcdm::a2b_hex("5bd910257830dce17520b03441a51a8c" + "ab94020ac6ecc252c808f3743c95b7c8" + "3b8c8af1a5014346ebc4242cdfb5d718" + "e30a733e71f291e4d473b61bfba6daca" + "ed0a77bd1f0950ae3c91a8f901118825" + "89e1d62765ee671e7baeea309f64d447" + "bbcfa9ea12dce05e9ea8939bc5fe6108" + "581279c982b308794b3448e7f7b95229" + "2df88c80cb40142c4b5cf5f8ddaa0891" + "678d610e582fcb880f0d707caf47d09a" + "84e14ca65841e5a3abc5e9dba94075a9" + "084341f0edad9b68e3b8e082b80b6e6e" + "8a0547b44fb5061b6a9131603a5537dd" + "abd01d8e863d8922e9aa3e4bfaea0b39" + "d79283ad2cbc8a59cce7a6ecf4e4c81e" + "d4c6591c807defd71ab06866bb5e7745")); vector field_p = - encode(0x02, wvcdm::a2b_hex( - "00" - "f44f5e4246391f482b2f5296e3602eb3" - "4aa136427710f7c0416d403fd69d4b29" - "130cfebef34e885abdb1a8a0a5f0e9b5" - "c33e1fc3bfc285b1ae17e40cc67a1913" - "dd563719815ebaf8514c2a7aa0018e63" - "b6c631dc315a46235716423d11ff5803" - "4e610645703606919f5c7ce2660cd148" - "bd9efc123d9c54b6705590d006cfcf3f")); + encode(0x02, wvcdm::a2b_hex("00" + "f44f5e4246391f482b2f5296e3602eb3" + "4aa136427710f7c0416d403fd69d4b29" + "130cfebef34e885abdb1a8a0a5f0e9b5" + "c33e1fc3bfc285b1ae17e40cc67a1913" + "dd563719815ebaf8514c2a7aa0018e63" + "b6c631dc315a46235716423d11ff5803" + "4e610645703606919f5c7ce2660cd148" + "bd9efc123d9c54b6705590d006cfcf3f")); vector field_q = - encode(0x02, wvcdm::a2b_hex( - "00" - "e9d49841e0e0a6ad0d517857133e36dc" - "72c1bdd90f9174b52e26570f373640f1" - "c185e7ea8e2ed7f1e4ebb951f70a5802" - "3633b0097aec67c6dcb800fc1a67f9bb" - "0563610f08ebc8746ad129772136eb1d" - "daf46436450d318332a84982fe5d28db" - "e5b3e912407c3e0e03100d87d436ee40" - "9eec1cf85e80aba079b2e6106b97bced")); + encode(0x02, wvcdm::a2b_hex("00" + "e9d49841e0e0a6ad0d517857133e36dc" + "72c1bdd90f9174b52e26570f373640f1" + "c185e7ea8e2ed7f1e4ebb951f70a5802" + "3633b0097aec67c6dcb800fc1a67f9bb" + "0563610f08ebc8746ad129772136eb1d" + "daf46436450d318332a84982fe5d28db" + "e5b3e912407c3e0e03100d87d436ee40" + "9eec1cf85e80aba079b2e6106b97bced")); vector field_exp1 = - encode(0x02, wvcdm::a2b_hex( - "00" - "ed102acdb26871534d1c414ecad9a4d7" - "32fe95b10eea370da62f05de2c393b1a" - "633303ea741b6b3269c97f704b352702" - "c9ae79922f7be8d10db67f026a8145de" - "41b30c0a42bf923bac5f7504c248604b" - "9faa57ed6b3246c6ba158e36c644f8b9" - "548fcf4f07e054a56f768674054440bc" - "0dcbbc9b528f64a01706e05b0b91106f")); + encode(0x02, wvcdm::a2b_hex("00" + "ed102acdb26871534d1c414ecad9a4d7" + "32fe95b10eea370da62f05de2c393b1a" + "633303ea741b6b3269c97f704b352702" + "c9ae79922f7be8d10db67f026a8145de" + "41b30c0a42bf923bac5f7504c248604b" + "9faa57ed6b3246c6ba158e36c644f8b9" + "548fcf4f07e054a56f768674054440bc" + "0dcbbc9b528f64a01706e05b0b91106f")); vector field_exp2 = - encode(0x02, wvcdm::a2b_hex( - "6827924a85e88b55ba00f8219128bd37" - "24c6b7d1dfe5629ef197925fecaff5ed" - "b9cdf3a7befd8ea2e8dd3707138b3ff8" - "7c3c39c57f439e562e2aa805a39d7cd7" - "9966d2ece7845f1dbc16bee99999e4d0" - "bf9eeca45fcda8a8500035fe6b5f03bc" - "2f6d1bfc4d4d0a3723961af0cdce4a01" - "eec82d7f5458ec19e71b90eeef7dff61")); + encode(0x02, wvcdm::a2b_hex("6827924a85e88b55ba00f8219128bd37" + "24c6b7d1dfe5629ef197925fecaff5ed" + "b9cdf3a7befd8ea2e8dd3707138b3ff8" + "7c3c39c57f439e562e2aa805a39d7cd7" + "9966d2ece7845f1dbc16bee99999e4d0" + "bf9eeca45fcda8a8500035fe6b5f03bc" + "2f6d1bfc4d4d0a3723961af0cdce4a01" + "eec82d7f5458ec19e71b90eeef7dff61")); vector field_invq = - encode(0x02, wvcdm::a2b_hex( - "57b73888d183a99a6307422277551a3d" - "9e18adf06a91e8b55ceffef9077c8496" - "948ecb3b16b78155cb2a3a57c119d379" - "951c010aa635edcf62d84c5a122a8d67" - "ab5fa9e5a4a8772a1e943bafc70ae3a4" - "c1f0f3a4ddffaefd1892c8cb33bb0d0b" - "9590e963a69110fb34db7b906fc4ba28" - "36995aac7e527490ac952a02268a4f18")); + encode(0x02, wvcdm::a2b_hex("57b73888d183a99a6307422277551a3d" + "9e18adf06a91e8b55ceffef9077c8496" + "948ecb3b16b78155cb2a3a57c119d379" + "951c010aa635edcf62d84c5a122a8d67" + "ab5fa9e5a4a8772a1e943bafc70ae3a4" + "c1f0f3a4ddffaefd1892c8cb33bb0d0b" + "9590e963a69110fb34db7b906fc4ba28" + "36995aac7e527490ac952a02268a4f18")); // Header of rsa key is constant. encoded_rsa_key_ = wvcdm::a2b_hex( @@ -3298,7 +2438,7 @@ class OEMCryptoCastReceiverTest : public OEMCryptoLoadsCertificateAlternates { const vector& correct_signature) { OEMCryptoResult sts; Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key_[0], wrapped_rsa_key_.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -3341,9 +2481,10 @@ class OEMCryptoCastReceiverTest : public OEMCryptoLoadsCertificateAlternates { // Also verify that our verification algorithm agrees. This is not needed // to test OEMCrypto, but it does verify that this test is valid. - s.VerifyRSASignature(digest, &signature[0], signature_length, scheme); - s.VerifyRSASignature(digest, &correct_signature[0], - correct_signature.size(), scheme); + ASSERT_NO_FATAL_FAILURE( + s.VerifyRSASignature(digest, &signature[0], signature_length, scheme)); + ASSERT_NO_FATAL_FAILURE(s.VerifyRSASignature( + digest, &correct_signature[0], correct_signature.size(), scheme)); } }; @@ -4042,11 +3183,14 @@ TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_20) { class GenericCryptoTest : public OEMCryptoSessionTests { protected: + GenericCryptoTest() : buffer_size_(160) {} + virtual void SetUp() { OEMCryptoSessionTests::SetUp(); - session_.open(); - session_.GenerateTestSessionKeys(); - MakeFourKeys(); + buffer_size_ = 160; + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(session_.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(MakeFourKeys()); } virtual void TearDown() { @@ -4056,7 +3200,8 @@ class GenericCryptoTest : public OEMCryptoSessionTests { void MakeFourKeys(uint32_t duration = kDuration, uint32_t control = 0, uint32_t nonce = 0, const std::string& pst = "") { - session_.FillSimpleMessage(duration, control, nonce, pst); + ASSERT_NO_FATAL_FAILURE( + session_.FillSimpleMessage(duration, control, nonce, pst)); session_.license().keys[0].control.control_bits |= htonl(wvoec_mock::kControlAllowEncrypt); session_.license().keys[1].control.control_bits |= @@ -4069,7 +3214,7 @@ class GenericCryptoTest : public OEMCryptoSessionTests { session_.license().keys[2].key_data_length = wvcdm::MAC_KEY_SIZE; session_.license().keys[3].key_data_length = wvcdm::MAC_KEY_SIZE; - clear_buffer_.assign(kBufferSize, 0); + clear_buffer_.assign(buffer_size_, 0); for (size_t i = 0; i < clear_buffer_.size(); i++) { clear_buffer_[i] = 1 + i % 250; } @@ -4079,15 +3224,16 @@ class GenericCryptoTest : public OEMCryptoSessionTests { } void EncryptAndLoadKeys() { - session_.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); session_.LoadTestKeys(); } void EncryptBuffer(unsigned int key_index, const vector& in_buffer, vector* out_buffer) { AES_KEY aes_key; - ASSERT_EQ(0, AES_set_encrypt_key(session_.license().keys[key_index].key_data, - AES_BLOCK_SIZE * 8, &aes_key)); + ASSERT_EQ(0, + AES_set_encrypt_key(session_.license().keys[key_index].key_data, + AES_BLOCK_SIZE * 8, &aes_key)); uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; memcpy(iv_buffer, iv_, wvcdm::KEY_IV_SIZE); out_buffer->resize(in_buffer.size()); @@ -4098,8 +3244,7 @@ class GenericCryptoTest : public OEMCryptoSessionTests { } // Sign the buffer with the specified key. - void SignBuffer(unsigned int key_index, - const vector& in_buffer, + void SignBuffer(unsigned int key_index, const vector& in_buffer, vector* signature) { unsigned int md_len = SHA256_DIGEST_LENGTH; signature->resize(SHA256_DIGEST_LENGTH); @@ -4113,16 +3258,17 @@ class GenericCryptoTest : public OEMCryptoSessionTests { OEMCryptoResult sts; vector expected_encrypted; EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); - sts = OEMCrypto_SelectKey( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length); + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); vector encrypted(buffer_length); - sts = OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - buffer_length, iv_, algorithm, &encrypted[0]); - ASSERT_NE(OEMCrypto_SUCCESS, sts); + sts = + OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], + buffer_length, iv_, algorithm, &encrypted[0]); + EXPECT_NE(OEMCrypto_SUCCESS, sts); expected_encrypted.resize(buffer_length); - ASSERT_NE(encrypted, expected_encrypted); + EXPECT_NE(encrypted, expected_encrypted); } void BadDecrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, @@ -4130,16 +3276,16 @@ class GenericCryptoTest : public OEMCryptoSessionTests { OEMCryptoResult sts; vector encrypted; EncryptBuffer(key_index, clear_buffer_, &encrypted); - sts = OEMCrypto_SelectKey( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length); + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); vector resultant(encrypted.size()); - sts = OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - buffer_length, iv_, algorithm, - &resultant[0]); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NE(clear_buffer_, resultant); + sts = + OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], + buffer_length, iv_, algorithm, &resultant[0]); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(clear_buffer_, resultant); } void BadSign(unsigned int key_index, OEMCrypto_Algorithm algorithm) { @@ -4147,17 +3293,17 @@ class GenericCryptoTest : public OEMCryptoSessionTests { vector expected_signature; SignBuffer(key_index, clear_buffer_, &expected_signature); - sts = OEMCrypto_SelectKey( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length); + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); size_t signature_length = (size_t)SHA256_DIGEST_LENGTH; vector signature(SHA256_DIGEST_LENGTH); sts = OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), algorithm, - &signature[0], &signature_length); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NE(signature, expected_signature); + clear_buffer_.size(), algorithm, &signature[0], + &signature_length); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(signature, expected_signature); } void BadVerify(unsigned int key_index, OEMCrypto_Algorithm algorithm, @@ -4169,52 +3315,52 @@ class GenericCryptoTest : public OEMCryptoSessionTests { signature[0] ^= 42; } - sts = OEMCrypto_SelectKey( - session_.session_id(), session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length); + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); sts = OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), algorithm, &signature[0], signature_size); - ASSERT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(OEMCrypto_SUCCESS, sts); } - static const size_t kBufferSize = 160; // multiple of encryption block size. + // This must be a multiple of encryption block size. + size_t buffer_size_; vector clear_buffer_; vector encrypted_buffer_; uint8_t iv_[wvcdm::KEY_IV_SIZE]; Session session_; }; -TEST_F(GenericCryptoTest, GenericKeyLoad) { - EncryptAndLoadKeys(); -} +TEST_F(GenericCryptoTest, GenericKeyLoad) { EncryptAndLoadKeys(); } TEST_F(GenericCryptoTest, GenericKeyEncrypt) { EncryptAndLoadKeys(); unsigned int key_index = 0; vector expected_encrypted; EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); vector encrypted(clear_buffer_.size()); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, - &encrypted[0])); - ASSERT_EQ(encrypted, expected_encrypted); + OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); + ASSERT_EQ(expected_encrypted, encrypted); } TEST_F(GenericCryptoTest, GenericKeyBadEncrypt) { EncryptAndLoadKeys(); - BadEncrypt(0, OEMCrypto_HMAC_SHA256, kBufferSize); - BadEncrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize - 10); - BadEncrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); - BadEncrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); - BadEncrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); + BadEncrypt(0, OEMCrypto_HMAC_SHA256, buffer_size_); + // The buffer size must be a multiple of 16, so subtracting 10 is bad. + BadEncrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_ - 10); + BadEncrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadEncrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadEncrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); } TEST_F(GenericCryptoTest, GenericKeyDecrypt) { @@ -4222,16 +3368,16 @@ TEST_F(GenericCryptoTest, GenericKeyDecrypt) { unsigned int key_index = 1; vector encrypted; EncryptBuffer(key_index, clear_buffer_, &encrypted); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); vector resultant(encrypted.size()); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), - iv_, OEMCrypto_AES_CBC_128_NO_PADDING, - &resultant[0])); + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); ASSERT_EQ(clear_buffer_, resultant); } @@ -4242,26 +3388,27 @@ TEST_F(GenericCryptoTest, GenericSecureToClear) { unsigned int key_index = 1; vector encrypted; EncryptBuffer(key_index, clear_buffer_, &encrypted); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); vector resultant(encrypted.size()); ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, - &resultant[0])); + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); ASSERT_NE(clear_buffer_, resultant); } TEST_F(GenericCryptoTest, GenericKeyBadDecrypt) { EncryptAndLoadKeys(); - BadDecrypt(1, OEMCrypto_HMAC_SHA256, kBufferSize); - BadDecrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize - 10); - BadDecrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); - BadDecrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); - BadDecrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); + BadDecrypt(1, OEMCrypto_HMAC_SHA256, buffer_size_); + // The buffer size must be a multiple of 16, so subtracting 10 is bad. + BadDecrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_ - 10); + BadDecrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadDecrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadDecrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); } TEST_F(GenericCryptoTest, GenericKeySign) { @@ -4270,10 +3417,11 @@ TEST_F(GenericCryptoTest, GenericKeySign) { vector expected_signature; SignBuffer(key_index, clear_buffer_, &expected_signature); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); size_t gen_signature_length = 0; ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], @@ -4285,7 +3433,7 @@ TEST_F(GenericCryptoTest, GenericKeySign) { OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), OEMCrypto_HMAC_SHA256, &signature[0], &gen_signature_length)); - ASSERT_EQ(signature, expected_signature); + ASSERT_EQ(expected_signature, signature); } TEST_F(GenericCryptoTest, GenericKeyBadSign) { @@ -4302,14 +3450,15 @@ TEST_F(GenericCryptoTest, GenericKeyVerify) { vector signature; SignBuffer(key_index, clear_buffer_, &signature); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - &signature[0], signature.size())); + OEMCrypto_Generic_Verify( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, &signature[0], signature.size())); } TEST_F(GenericCryptoTest, GenericKeyBadVerify) { @@ -4323,6 +3472,91 @@ TEST_F(GenericCryptoTest, GenericKeyBadVerify) { BadVerify(3, OEMCrypto_AES_CBC_128_NO_PADDING, SHA256_DIGEST_LENGTH, false); } +TEST_F(GenericCryptoTest, GenericKeyEncryptLargeBufferAPI11) { + // Some applications are known to pass in a block that is almost 400k, but + // the layer above oemcrypto can break it into 100k chunks. + buffer_size_ = 100 * 1024; + EncryptAndLoadKeys(); + unsigned int key_index = 0; + vector expected_encrypted; + EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); + vector encrypted(clear_buffer_.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); + ASSERT_EQ(expected_encrypted, encrypted); +} + +TEST_F(GenericCryptoTest, GenericKeyDecryptLargeBufferAPI11) { + // Some applications are known to pass in a block that is almost 400k. + buffer_size_ = 400 * 1024; + EncryptAndLoadKeys(); + unsigned int key_index = 1; + vector encrypted; + EncryptBuffer(key_index, clear_buffer_, &encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); + vector resultant(encrypted.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); + ASSERT_EQ(clear_buffer_, resultant); +} + +TEST_F(GenericCryptoTest, GenericKeySignLargeBufferAPI11) { + buffer_size_ = 100 * 1024; + EncryptAndLoadKeys(); + unsigned int key_index = 2; + vector expected_signature; + SignBuffer(key_index, clear_buffer_, &expected_signature); + + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); + size_t gen_signature_length = 0; + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + NULL, &gen_signature_length)); + ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); + vector signature(SHA256_DIGEST_LENGTH); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + &signature[0], &gen_signature_length)); + ASSERT_EQ(expected_signature, signature); +} + +TEST_F(GenericCryptoTest, GenericKeyVerifyLargeBufferAPI11) { + buffer_size_ = 100 * 1024; + EncryptAndLoadKeys(); + unsigned int key_index = 3; + vector signature; + SignBuffer(key_index, clear_buffer_, &signature); + + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Verify( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, &signature[0], signature.size())); +} + TEST_F(GenericCryptoTest, KeyDurationEncrypt) { EncryptAndLoadKeys(); vector expected_encrypted; @@ -4332,23 +3566,24 @@ TEST_F(GenericCryptoTest, KeyDurationEncrypt) { sleep(kShortSleep); // Should still be valid key. + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); - ASSERT_EQ(encrypted, expected_encrypted); + OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); + ASSERT_EQ(expected_encrypted, encrypted); sleep(kLongSleep); // Should be expired key. encrypted.assign(clear_buffer_.size(), 0); ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, - OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); + OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0])); ASSERT_NE(encrypted, expected_encrypted); } @@ -4358,29 +3593,28 @@ TEST_F(GenericCryptoTest, KeyDurationDecrypt) { unsigned int key_index = 1; vector encrypted; EncryptBuffer(key_index, clear_buffer_, &encrypted); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); sleep(kShortSleep); // Should still be valid key. vector resultant(encrypted.size()); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, - &resultant[0])); + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); ASSERT_EQ(clear_buffer_, resultant); sleep(kLongSleep); // Should be expired key. resultant.assign(encrypted.size(), 0); ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, - OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, - &resultant[0])); + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); ASSERT_NE(clear_buffer_, resultant); } @@ -4393,10 +3627,11 @@ TEST_F(GenericCryptoTest, KeyDurationSign) { size_t signature_length = signature.size(); SignBuffer(key_index, clear_buffer_, &expected_signature); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); sleep(kShortSleep); // Should still be valid key. @@ -4404,7 +3639,7 @@ TEST_F(GenericCryptoTest, KeyDurationSign) { OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), OEMCrypto_HMAC_SHA256, &signature[0], &signature_length)); - ASSERT_EQ(signature, expected_signature); + ASSERT_EQ(expected_signature, signature); sleep(kLongSleep); // Should be expired key. @@ -4413,7 +3648,7 @@ TEST_F(GenericCryptoTest, KeyDurationSign) { OEMCrypto_Generic_Sign(session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), OEMCrypto_HMAC_SHA256, &signature[0], &signature_length)); - ASSERT_NE(signature, expected_signature); + ASSERT_NE(expected_signature, signature); } TEST_F(GenericCryptoTest, KeyDurationVerify) { @@ -4423,24 +3658,25 @@ TEST_F(GenericCryptoTest, KeyDurationVerify) { vector signature; SignBuffer(key_index, clear_buffer_, &signature); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length)); sleep(kShortSleep); // Should still be valid key. ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - &signature[0], signature.size())); + OEMCrypto_Generic_Verify( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, &signature[0], signature.size())); sleep(kLongSleep); // Should be expired key. ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, - OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - &signature[0], signature.size())); + OEMCrypto_Generic_Verify( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, &signature[0], signature.size())); } const unsigned int kLongKeyId = 2; @@ -4450,11 +3686,11 @@ class GenericCryptoKeyIdLengthTest : public GenericCryptoTest { virtual void SetUp() { GenericCryptoTest::SetUp(); const uint32_t kNoNonce = 0; - session_.FillSimpleMessage(kDuration, wvoec_mock::kControlAllowDecrypt, - kNoNonce); + ASSERT_NO_FATAL_FAILURE(session_.FillSimpleMessage( + kDuration, wvoec_mock::kControlAllowDecrypt, kNoNonce)); // We are testing that the key ids do not have to have the same length. - session_.SetKeyId(0, "123456789012"); // 12 bytes (common key id length). - session_.SetKeyId(1, "12345"); // short key id. + session_.SetKeyId(0, "123456789012"); // 12 bytes (common key id length). + session_.SetKeyId(1, "12345"); // short key id. session_.SetKeyId(2, "1234567890123456"); // 16 byte key id. (default) session_.SetKeyId(3, "12345678901234"); // 14 byte. (uncommon) ASSERT_EQ(2u, kLongKeyId); @@ -4462,9 +3698,9 @@ class GenericCryptoKeyIdLengthTest : public GenericCryptoTest { // Make all four keys have the same length. void SetUniformKeyIdLength(size_t key_id_length) { - for(unsigned int i = 0; i < 4; i++) { + for (unsigned int i = 0; i < 4; i++) { string key_id; - key_id.resize(key_id_length, i + 'a'); + key_id.resize(key_id_length, i + 'a'); session_.SetKeyId(i, key_id); } } @@ -4484,30 +3720,24 @@ class GenericCryptoKeyIdLengthTest : public GenericCryptoTest { memcpy(key_id_buffer.data(), session_.license().keys[kLongKeyId].key_id, session_.license().keys[kLongKeyId].key_id_length); EncryptBuffer(key_index, clear_buffer_, &encrypted); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey(session_.session_id(), key_id_buffer.data(), - session_.license().keys[key_index].key_id_length)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), key_id_buffer.data(), + session_.license().keys[key_index].key_id_length)); vector resultant(encrypted.size()); ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, - &resultant[0])); + OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0])); ASSERT_EQ(clear_buffer_, resultant); } }; -TEST_F(GenericCryptoKeyIdLengthTest, MediumKeyId) { - TestWithKey(0); -} +TEST_F(GenericCryptoKeyIdLengthTest, MediumKeyId) { TestWithKey(0); } -TEST_F(GenericCryptoKeyIdLengthTest, ShortKeyId) { - TestWithKey(1); -} +TEST_F(GenericCryptoKeyIdLengthTest, ShortKeyId) { TestWithKey(1); } -TEST_F(GenericCryptoKeyIdLengthTest, LongKeyId) { - TestWithKey(2); -} +TEST_F(GenericCryptoKeyIdLengthTest, LongKeyId) { TestWithKey(2); } TEST_F(GenericCryptoKeyIdLengthTest, UniformShortKeyId) { SetUniformKeyIdLength(5); @@ -4537,18 +3767,20 @@ class UsageTableTest : public GenericCryptoTest { } void LoadOfflineLicense(Session& s, const std::string& pst) { - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, wvoec_mock::kControlNonceOrEntry, s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); s.GenerateReport(pst); s.GenerateReport(pst); EXPECT_EQ(kUnused, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - s.close(); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE(s.close()); } void PrintDotsWhileSleep(time_t total_seconds, time_t interval_seconds) { @@ -4587,20 +3819,20 @@ TEST_F(UsageTableTest, PSTReportSizes) { OEMCrypto_PST_Report report; uint8_t* location = reinterpret_cast(&report); EXPECT_EQ(48u, sizeof(report)); - uint8_t *field; - field = reinterpret_cast(&report.status); + uint8_t* field; + field = reinterpret_cast(&report.status); EXPECT_EQ(20, field - location); - field = reinterpret_cast(&report.clock_security_level); + field = reinterpret_cast(&report.clock_security_level); EXPECT_EQ(21, field - location); - field = reinterpret_cast(&report.pst_length); + field = reinterpret_cast(&report.pst_length); EXPECT_EQ(22, field - location); - field = reinterpret_cast(&report.seconds_since_license_received); + field = reinterpret_cast(&report.seconds_since_license_received); EXPECT_EQ(24, field - location); - field = reinterpret_cast(&report.seconds_since_first_decrypt); + field = reinterpret_cast(&report.seconds_since_first_decrypt); EXPECT_EQ(32, field - location); - field = reinterpret_cast(&report.seconds_since_last_decrypt); + field = reinterpret_cast(&report.seconds_since_last_decrypt); EXPECT_EQ(40, field - location); - field = reinterpret_cast(&report.pst); + field = reinterpret_cast(&report.pst); EXPECT_EQ(48, field - location); } @@ -4608,13 +3840,13 @@ TEST_P(UsageTableTestWithMAC, OnlineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); s.GenerateReport(pst); @@ -4622,297 +3854,301 @@ TEST_P(UsageTableTestWithMAC, OnlineLicense) { s.GenerateReport(pst); s.GenerateReport(pst); EXPECT_EQ(kUnused, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - s.TestDecryptCTR(); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); s.GenerateReport(pst); EXPECT_EQ(kActive, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR(0, wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR(0, wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); DeactivatePST(pst); s.GenerateReport(pst); EXPECT_EQ(kInactive, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); } TEST_F(UsageTableTest, RepeatOnlineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); Session s2; - s2.open(); - s2.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.GenerateTestSessionKeys()); uint8_t* pst_ptr = s.encrypted_license().pst; // Trying to reuse a PST is bad. We use session ID for s2, everything else // reused from s. - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), - sizeof(MessageData), &s.signature()[0], - s.signature().size(), - s.encrypted_license().mac_key_iv, - s.encrypted_license().mac_keys, kNumKeys, - s.key_array(), pst_ptr, pst.length())); - s2.close(); + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), + s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, + s.key_array(), pst_ptr, pst.length())); + ASSERT_NO_FATAL_FAILURE(s2.close()); } // A license with non-zero replay control bits needs a valid pst.. TEST_F(UsageTableTest, OnlineEmptyPST) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce()); - s.EncryptAndSign(); + s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, - s.encrypted_license().mac_keys, kNumKeys, s.key_array(), - NULL, 0); + s.encrypted_license().mac_keys, kNumKeys, s.key_array(), NULL, 0); ASSERT_NE(OEMCrypto_SUCCESS, sts); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); } TEST_F(UsageTableTest, EmptyTable) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "no_pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); OEMCrypto_DeleteUsageTable(); Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, false); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); } TEST_F(UsageTableTest, FiftyEntries) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s1; - s1.open(); - s1.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(s1.GenerateTestSessionKeys()); std::string pst1 = "pst saved"; - s1.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s1.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s1.get_nonce(), pst1); - s1.EncryptAndSign(); - s1.LoadTestKeys(pst1, new_mac_keys_); + s1.get_nonce(), pst1)); + ASSERT_NO_FATAL_FAILURE(s1.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst1, new_mac_keys_)); sleep(kShortSleep); - const size_t ENTRY_COUNT = 49;// API says should hold at least 50 entries. + const size_t ENTRY_COUNT = 49; // API says should hold at least 50 entries. vector sessions(ENTRY_COUNT); - for (size_t i=0; i < ENTRY_COUNT; i++) { - sessions[i].open(); - sessions[i].GenerateTestSessionKeys(); + for (size_t i = 0; i < ENTRY_COUNT; i++) { + ASSERT_NO_FATAL_FAILURE(sessions[i].open()); + ASSERT_NO_FATAL_FAILURE(sessions[i].GenerateTestSessionKeys()); std::string pst = "pst "; char c = 'A' + i; pst = pst + c; - sessions[i].FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(sessions[i].FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - sessions[i].get_nonce(), pst); - sessions[i].EncryptAndSign(); - sessions[i].LoadTestKeys(pst, new_mac_keys_); + sessions[i].get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(sessions[i].EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)); sessions[i].GenerateReport(pst); - sessions[i].close(); + ASSERT_NO_FATAL_FAILURE(sessions[i].close()); } - for (size_t i=0; istatus); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); } sleep(kShortSleep); // If I add too many entries, it can delete the older ones first, except // it shouldn't delete the one attached to an open session. (s1) - for (size_t i=0; i < ENTRY_COUNT; i++) { - sessions[i].open(); - sessions[i].GenerateTestSessionKeys(); + for (size_t i = 0; i < ENTRY_COUNT; i++) { + ASSERT_NO_FATAL_FAILURE(sessions[i].open()); + ASSERT_NO_FATAL_FAILURE(sessions[i].GenerateTestSessionKeys()); std::string pst = "newer pst "; char c = 'A' + i; pst = pst + c; - sessions[i].FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(sessions[i].FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - sessions[i].get_nonce(), pst); - sessions[i].EncryptAndSign(); - sessions[i].LoadTestKeys(pst, new_mac_keys_); + sessions[i].get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(sessions[i].EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)); sessions[i].GenerateReport(pst); - sessions[i].close(); + ASSERT_NO_FATAL_FAILURE(sessions[i].close()); } - for (int i=0; i<49; i++) { + for (int i = 0; i < 49; i++) { Session s; - s.open(); + ASSERT_NO_FATAL_FAILURE(s.open()); std::string pst = "newer pst "; char c = 'A' + i; pst = pst + c; s.GenerateReport(pst, true, &sessions[i]); EXPECT_EQ(kUnused, s.pst_report()->status); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); } - s1.close(); - s1.open(); // Make sure s1's entry is still in the table. + ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE( + s1.open()); // Make sure s1's entry is still in the table. s1.GenerateReport(pst1); EXPECT_EQ(kUnused, s1.pst_report()->status); - s1.close(); + ASSERT_NO_FATAL_FAILURE(s1.close()); } TEST_P(UsageTableTestWithMAC, DeleteUnusedEntry) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should be able to generate report and copy mac keys. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, true, &s); EXPECT_EQ(kUnused, s2.pst_report()->status); s2.DeleteEntry(pst); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // Now that session is deleted, we can't generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, false); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeleteActiveEntry) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); - s.TestDecryptCTR(); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should be able to generate report and copy mac keys. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, true, &s); EXPECT_EQ(kActive, s2.pst_report()->status); s2.DeleteEntry(pst); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // Now that session is deleted, we can't generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, false); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, ForceDeleteActiveEntry) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); - s.TestDecryptCTR(); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); s.ForceDeleteEntry(pst); // Now that session is deleted, we can't generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, false); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeleteInactiveEntry) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); DeactivatePST(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should be able to generate report and copy mac keys. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, true, &s); EXPECT_EQ(kInactive, s2.pst_report()->status); s2.DeleteEntry(pst); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // Now that session is deleted, we can't generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, false); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeleteEntryBadSignature) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should be able to generate report and copy mac keys. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, true, &s); uint8_t* pst_ptr = s2.encrypted_license().pst; memcpy(pst_ptr, pst.c_str(), min(sizeof(s2.license().pst), pst.length())); @@ -4923,34 +4159,34 @@ TEST_P(UsageTableTestWithMAC, DeleteEntryBadSignature) { OEMCrypto_DeleteUsageEntry(s2.session_id(), pst_ptr, pst.length(), s2.message_ptr(), sizeof(MessageData), &signature[0], signature.size())); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // The session is not deleted, we can still generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, true, &s); EXPECT_EQ(kUnused, s3.pst_report()->status); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeleteEntryWrongSession) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should not be able to delete without using GenerateReport // to load mac keys. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); // s2.GenerateReport(pst, true, &s); uint8_t* pst_ptr = s2.encrypted_license().pst; memcpy(pst_ptr, pst.c_str(), min(sizeof(s2.license().pst), pst.length())); @@ -4960,34 +4196,34 @@ TEST_P(UsageTableTestWithMAC, DeleteEntryWrongSession) { OEMCrypto_DeleteUsageEntry(s2.session_id(), pst_ptr, pst.length(), s2.message_ptr(), sizeof(MessageData), &signature[0], signature.size())); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // The session is not deleted, we can still generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, true, &s); EXPECT_EQ(kUnused, s3.pst_report()->status); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeleteEntryBadRange) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); std::string pst = "my pst"; - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); // New session should not be able to delete if pst doesn't point into // message. Session s2; - s2.open(); + ASSERT_NO_FATAL_FAILURE(s2.open()); s2.GenerateReport(pst, true, &s); uint8_t* pst_ptr = s2.license().pst; memcpy(pst_ptr, pst.c_str(), min(sizeof(s2.license().pst), pst.length())); @@ -4997,14 +4233,14 @@ TEST_P(UsageTableTestWithMAC, DeleteEntryBadRange) { OEMCrypto_DeleteUsageEntry(s2.session_id(), pst_ptr, pst.length(), s2.message_ptr(), sizeof(MessageData), &signature[0], signature.size())); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.close()); // The session is not deleted, we can still generate a report for it. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, true, &s); EXPECT_EQ(kUnused, s3.pst_report()->status); - s3.close(); + ASSERT_NO_FATAL_FAILURE(s3.close()); } TEST_P(UsageTableTestWithMAC, DeactivateBadPST) { @@ -5023,41 +4259,46 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoEncrypt) { std::string pst = "A PST"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); - MakeFourKeys(0, wvoec_mock::kControlNonceEnabled | - wvoec_mock::kControlNonceRequired, - nonce, pst); - session_.EncryptAndSign(); - session_.LoadTestKeys(pst, new_mac_keys_); + MakeFourKeys( + 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, + nonce, pst); + ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; unsigned int key_index = 0; vector expected_encrypted; EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); - sts = OEMCrypto_SelectKey(session_.session_id(), session_.license().keys[key_index].key_id, + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); vector encrypted(clear_buffer_.size()); - sts = OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0]); + sts = OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0]); ASSERT_EQ(OEMCrypto_SUCCESS, sts); - EXPECT_EQ(encrypted, expected_encrypted); + EXPECT_EQ(expected_encrypted, encrypted); session_.GenerateReport(pst); EXPECT_EQ(kActive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); DeactivatePST(pst); session_.GenerateReport(pst); EXPECT_EQ(kInactive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); encrypted.assign(clear_buffer_.size(), 0); - sts = OEMCrypto_Generic_Encrypt(session_.session_id(), &clear_buffer_[0], - clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0]); + sts = OEMCrypto_Generic_Encrypt( + session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0]); ASSERT_NE(OEMCrypto_SUCCESS, sts); EXPECT_NE(encrypted, expected_encrypted); } @@ -5066,41 +4307,46 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoDecrypt) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); - MakeFourKeys(0, wvoec_mock::kControlNonceEnabled | - wvoec_mock::kControlNonceRequired, - nonce, pst); - session_.EncryptAndSign(); - session_.LoadTestKeys(pst, new_mac_keys_); + MakeFourKeys( + 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, + nonce, pst); + ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; unsigned int key_index = 1; vector encrypted; EncryptBuffer(key_index, clear_buffer_, &encrypted); - sts = OEMCrypto_SelectKey(session_.session_id(), session_.license().keys[key_index].key_id, + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); vector resultant(encrypted.size()); - sts = OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0]); + sts = OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0]); ASSERT_EQ(OEMCrypto_SUCCESS, sts); EXPECT_EQ(clear_buffer_, resultant); session_.GenerateReport(pst); EXPECT_EQ(kActive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); DeactivatePST(pst); session_.GenerateReport(pst); EXPECT_EQ(kInactive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); resultant.assign(encrypted.size(), 0); - sts = OEMCrypto_Generic_Decrypt(session_.session_id(), &encrypted[0], - encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0]); + sts = OEMCrypto_Generic_Decrypt( + session_.session_id(), &encrypted[0], encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0]); ASSERT_NE(OEMCrypto_SUCCESS, sts); EXPECT_NE(clear_buffer_, resultant); } @@ -5109,18 +4355,19 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoSign) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); - MakeFourKeys(0, wvoec_mock::kControlNonceEnabled | - wvoec_mock::kControlNonceRequired, - nonce, pst); - session_.EncryptAndSign(); - session_.LoadTestKeys(pst, new_mac_keys_); + MakeFourKeys( + 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, + nonce, pst); + ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; unsigned int key_index = 2; vector expected_signature; SignBuffer(key_index, clear_buffer_, &expected_signature); - sts = OEMCrypto_SelectKey(session_.session_id(), session_.license().keys[key_index].key_id, + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); size_t gen_signature_length = 0; @@ -5134,21 +4381,25 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoSign) { clear_buffer_.size(), OEMCrypto_HMAC_SHA256, &signature[0], &gen_signature_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(signature, expected_signature); + ASSERT_EQ(expected_signature, signature); session_.GenerateReport(pst); EXPECT_EQ(kActive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); DeactivatePST(pst); session_.GenerateReport(pst); EXPECT_EQ(kInactive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); signature.assign(SHA256_DIGEST_LENGTH, 0); gen_signature_length = SHA256_DIGEST_LENGTH; @@ -5163,18 +4414,19 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoVerify) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); - MakeFourKeys(0, wvoec_mock::kControlNonceEnabled | - wvoec_mock::kControlNonceRequired, - nonce, pst); - session_.EncryptAndSign(); - session_.LoadTestKeys(pst, new_mac_keys_); + MakeFourKeys( + 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, + nonce, pst); + ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; unsigned int key_index = 3; vector signature; SignBuffer(key_index, clear_buffer_, &signature); - sts = OEMCrypto_SelectKey(session_.session_id(), session_.license().keys[key_index].key_id, + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); sts = OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], @@ -5184,17 +4436,21 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoVerify) { session_.GenerateReport(pst); EXPECT_EQ(kActive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); DeactivatePST(pst); session_.GenerateReport(pst); EXPECT_EQ(kInactive, session_.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received)); + EXPECT_NEAR( + 0, wvcdm::htonll64(session_.pst_report()->seconds_since_license_received), + kTimeTolerance); sts = OEMCrypto_Generic_Verify(session_.session_id(), &clear_buffer_[0], clear_buffer_.size(), OEMCrypto_HMAC_SHA256, @@ -5206,74 +4462,66 @@ TEST_P(UsageTableTestWithMAC, OfflineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - LoadOfflineLicense(s, pst); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); } TEST_P(UsageTableTestWithMAC, ReloadOfflineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - LoadOfflineLicense(s, pst); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); - // If there are errors in LoadOfflineLicense, that function will exit but this - // test will continue. The session will be left open and in an unknown state. - // Best just to abort in that case. - ASSERT_FALSE(s.isOpen()) << "LoadOfflineLicense() failed. Aborting."; - - s.open(); // Offline license can be reused. - s.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s.open()); // Offline license can be reused. + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); // We will reuse the encrypted and signed message, so we don't call // FillSimpleMessage again. - s.LoadTestKeys(pst, new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); s.GenerateReport(pst); EXPECT_EQ(kUnused, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - s.TestDecryptCTR(); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); s.GenerateReport(pst); EXPECT_EQ(kActive, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST(0, - wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(0, - wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt)); - s.close(); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR(0, wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR(0, wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE(s.close()); } TEST_P(UsageTableTestWithMAC, BadReloadOfflineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - LoadOfflineLicense(s, pst); - - // If there are errors in LoadOfflineLicense, that function will exit but this - // test will continue. The session will be left open and in an unknown state. - // Best just to abort in that case. - ASSERT_FALSE(s.isOpen()) << "LoadOfflineLicense() failed. Aborting."; + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); // Offline license with new mac keys should fail. Session s2; - s2.open(); - s2.GenerateTestSessionKeys(); - s2.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, - s2.get_nonce(), pst); - s2.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s2.FillSimpleMessage( + 0, wvoec_mock::kControlNonceOrEntry, s2.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s2.EncryptAndSign()); uint8_t* pst_ptr = s2.encrypted_license().pst; - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s2.session_id(), s2.message_ptr(), - sizeof(MessageData), &s2.signature()[0], - s2.signature().size(), - s2.encrypted_license().mac_key_iv, - s2.encrypted_license().mac_keys, kNumKeys, - s2.key_array(), pst_ptr, pst.length())); - s2.close(); + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s2.session_id(), s2.message_ptr(), sizeof(MessageData), + &s2.signature()[0], s2.signature().size(), + s2.encrypted_license().mac_key_iv, + s2.encrypted_license().mac_keys, kNumKeys, + s2.key_array(), pst_ptr, pst.length())); + ASSERT_NO_FATAL_FAILURE(s2.close()); // Offline license with same mac keys should still be OK. - s.open(); - s.GenerateTestSessionKeys(); - s.LoadTestKeys(pst, new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); s.GenerateReport(pst); EXPECT_EQ(kUnused, s.pst_report()->status); } @@ -5283,76 +4531,75 @@ TEST_P(UsageTableTestWithMAC, OfflineBadNonce) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, 42, pst); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, 42, pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); uint8_t* pst_ptr = s.encrypted_license().pst; OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, - s.encrypted_license().mac_keys, kNumKeys, s.key_array(), - pst_ptr, pst.length()); + s.encrypted_license().mac_keys, kNumKeys, s.key_array(), pst_ptr, + pst.length()); ASSERT_NE(OEMCrypto_SUCCESS, sts); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); } // An offline license needs a valid pst. TEST_P(UsageTableTestWithMAC, OfflineEmptyPST) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, s.get_nonce()); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, s.get_nonce())); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, - s.encrypted_license().mac_keys, kNumKeys, s.key_array(), - NULL, 0); + s.encrypted_license().mac_keys, kNumKeys, s.key_array(), NULL, 0); ASSERT_NE(OEMCrypto_SUCCESS, sts); - s.close(); + ASSERT_NO_FATAL_FAILURE(s.close()); } TEST_P(UsageTableTestWithMAC, DeactivateOfflineLicense) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - LoadOfflineLicense(s, pst); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); - // If there are errors in LoadOfflineLicense, that function will exit but this - // test will continue. The session will be left open and in an unknown state. - // Best just to abort in that case. - ASSERT_FALSE(s.isOpen()) << "LoadOfflineLicense() failed. Aborting."; - - s.open(); - s.GenerateTestSessionKeys(); - s.LoadTestKeys(pst, new_mac_keys_); // Reload the license - s.TestDecryptCTR(); // Should be able to decrypt. - DeactivatePST(pst); // Then deactivate. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE( + s.LoadTestKeys(pst, new_mac_keys_)); // Reload the license + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); // Should be able to decrypt. + DeactivatePST(pst); // Then deactivate. // After deactivate, should not be able to decrypt. - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE); + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); s.GenerateReport(pst); EXPECT_EQ(kInactive, s.pst_report()->status); - EXPECT_ALMOST( - 0, wvcdm::htonll64(s.pst_report()->seconds_since_license_received)); - s.close(); + EXPECT_NEAR(0, + wvcdm::htonll64(s.pst_report()->seconds_since_license_received), + kTimeTolerance); + ASSERT_NO_FATAL_FAILURE(s.close()); Session s2; - s2.open(); - s2.GenerateTestSessionKeys(); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.GenerateTestSessionKeys()); // Offile license can not be reused if it has been deactivated. uint8_t* pst_ptr = s.encrypted_license().pst; - EXPECT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), - sizeof(MessageData), &s.signature()[0], - s.signature().size(), - s.encrypted_license().mac_key_iv, - s.encrypted_license().mac_keys, kNumKeys, - s.key_array(), pst_ptr, pst.length())); + EXPECT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), sizeof(MessageData), + &s.signature()[0], s.signature().size(), + s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, + s.key_array(), pst_ptr, pst.length())); // But we can still generate a report. Session s3; - s3.open(); + ASSERT_NO_FATAL_FAILURE(s3.open()); s3.GenerateReport(pst, true, &s); EXPECT_EQ(kInactive, s3.pst_report()->status); } @@ -5361,10 +4608,11 @@ TEST_P(UsageTableTestWithMAC, BadRange) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry,s.get_nonce(), pst); - s.EncryptAndSign(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, wvoec_mock::kControlNonceOrEntry, s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); uint8_t* pst_ptr = s.license().pst; // Bad: not in encrypted_license. ASSERT_NE( OEMCrypto_SUCCESS, @@ -5383,62 +4631,58 @@ TEST_F(UsageTableTest, TimingTest) { Session s1; Session s2; Session s3; - LoadOfflineLicense(s1, pst1); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s1, pst1)); time_t loaded1 = time(NULL); - LoadOfflineLicense(s2, pst2); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s2, pst2)); time_t loaded2 = time(NULL); - LoadOfflineLicense(s3, pst3); + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s3, pst3)); time_t loaded3 = time(NULL); - // If there are errors in LoadOfflineLicense, that function will exit but this - // test will continue. The sessions will be left open and in an unknown state. - // Best just to abort in that case. - ASSERT_FALSE(s1.isOpen()) << "LoadOfflineLicense() failed. Aborting."; - ASSERT_FALSE(s2.isOpen()) << "LoadOfflineLicense() failed. Aborting."; - ASSERT_FALSE(s3.isOpen()) << "LoadOfflineLicense() failed. Aborting."; - sleep(kLongSleep); - s1.open(); - s1.GenerateTestSessionKeys(); - s1.LoadTestKeys(pst1, new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(s1.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst1, new_mac_keys_)); time_t first_decrypt1 = time(NULL); - s1.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); - s2.open(); - s2.GenerateTestSessionKeys(); - s2.LoadTestKeys(pst2, new_mac_keys_); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s2.LoadTestKeys(pst2, new_mac_keys_)); time_t first_decrypt2 = time(NULL); - s2.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); sleep(kLongSleep); time_t second_decrypt = time(NULL); - s1.TestDecryptCTR(); - s2.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); sleep(kLongSleep); DeactivatePST(pst1); - s1.close(); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE(s2.close()); sleep(kLongSleep); // This is as close to reboot as we can simulate in code. + ASSERT_NO_FATAL_FAILURE(session_.close()); // Close sessions before terminate. OEMCrypto_Terminate(); sleep(kShortSleep); OEMCrypto_Initialize(); EnsureTestKeys(); + // Test teardown expects session_ to be open. + ASSERT_NO_FATAL_FAILURE(session_.open()); // After a reboot, we should be able to reload keys, and generate reports. sleep(kLongSleep); time_t third_decrypt = time(NULL); - s2.open(); - s2.GenerateTestSessionKeys(); - s2.LoadTestKeys(pst2, new_mac_keys_); - s2.TestDecryptCTR(); - s2.close(); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s2.LoadTestKeys(pst2, new_mac_keys_)); + ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s2.close()); - s1.open(); - s2.open(); - s3.open(); + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s3.open()); sleep(kLongSleep); time_t report_generated1 = time(NULL); s1.GenerateReport(pst1); @@ -5448,29 +4692,31 @@ TEST_F(UsageTableTest, TimingTest) { s3.GenerateReport(pst3); EXPECT_EQ(kInactive, s1.pst_report()->status); - EXPECT_ALMOST( - report_generated1 - loaded1, - wvcdm::htonll64(s1.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST( - report_generated1 - first_decrypt1, - wvcdm::htonll64(s1.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(report_generated1 - second_decrypt, - wvcdm::htonll64(s1.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR(report_generated1 - loaded1, + wvcdm::htonll64(s1.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR(report_generated1 - first_decrypt1, + wvcdm::htonll64(s1.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR(report_generated1 - second_decrypt, + wvcdm::htonll64(s1.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); EXPECT_EQ(kActive, s2.pst_report()->status); - EXPECT_ALMOST( - report_generated2 - loaded2, - wvcdm::htonll64(s2.pst_report()->seconds_since_license_received)); - EXPECT_ALMOST( - report_generated2 - first_decrypt2, - wvcdm::htonll64(s2.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(report_generated2 - third_decrypt, - wvcdm::htonll64(s2.pst_report()->seconds_since_last_decrypt)); + EXPECT_NEAR(report_generated2 - loaded2, + wvcdm::htonll64(s2.pst_report()->seconds_since_license_received), + kTimeTolerance); + EXPECT_NEAR(report_generated2 - first_decrypt2, + wvcdm::htonll64(s2.pst_report()->seconds_since_first_decrypt), + kTimeTolerance); + EXPECT_NEAR(report_generated2 - third_decrypt, + wvcdm::htonll64(s2.pst_report()->seconds_since_last_decrypt), + kTimeTolerance); EXPECT_EQ(kUnused, s3.pst_report()->status); - EXPECT_ALMOST( - report_generated3 - loaded3, - wvcdm::htonll64(s3.pst_report()->seconds_since_license_received)); + EXPECT_NEAR(report_generated3 - loaded3, + wvcdm::htonll64(s3.pst_report()->seconds_since_license_received), + kTimeTolerance); // We don't expect first or last decrypt for unused report. } @@ -5478,13 +4724,13 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { std::string pst = "my_pst"; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; - s.open(); - s.GenerateTestSessionKeys(); - s.FillSimpleMessage( + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst); - s.EncryptAndSign(); - s.LoadTestKeys(pst, new_mac_keys_); + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); const int kLicenseReceivedTimeTolerance = kSpeedMultiplier; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); @@ -5499,11 +4745,12 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { const time_t kUsageTableTimeTolerance = 10; cout << "This test verifies the elapsed time reported in the usage table " - "for a 2 minute simulated playback." << endl; - cout << "The total time for this test is about " << - kPlaybackLoopInSeconds + 2 * kIdleInSeconds << " seconds." << endl; - cout << "Wait " << kIdleInSeconds << - " seconds to verify usage table time before playback." << endl; + "for a 2 minute simulated playback." + << endl; + cout << "The total time for this test is about " + << kPlaybackLoopInSeconds + 2 * kIdleInSeconds << " seconds." << endl; + cout << "Wait " << kIdleInSeconds + << " seconds to verify usage table time before playback." << endl; PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); @@ -5518,7 +4765,7 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { time_t playback_time = 0; time_t start_time = time(NULL); do { - s.TestDecryptCTR(); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); s.GenerateReport(pst); EXPECT_EQ(kActive, s.pst_report()->status); playback_time = time(NULL) - start_time; @@ -5537,15 +4784,16 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { playback_time + kIdleInSeconds, kLicenseReceivedTimeTolerance); EXPECT_NEAR(wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt), playback_time, kUsageTableTimeTolerance); - EXPECT_NEAR(wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), - 0, kUsageTableTimeTolerance); - EXPECT_NEAR( - wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt) - - wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), - playback_time, kUsageTableTimeTolerance); + EXPECT_NEAR(wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), 0, + kUsageTableTimeTolerance); + EXPECT_NEAR(wvcdm::htonll64(s.pst_report()->seconds_since_first_decrypt) - + wvcdm::htonll64(s.pst_report()->seconds_since_last_decrypt), + playback_time, kUsageTableTimeTolerance); - cout << "Wait another " << kIdleInSeconds << " seconds " - "to verify usage table time since playback ended." << endl; + cout << "Wait another " << kIdleInSeconds + << " seconds " + "to verify usage table time since playback ended." + << endl; PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); // At this point, this is what we expect: @@ -5567,10 +4815,42 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { DeactivatePST(pst); s.GenerateReport(pst); EXPECT_EQ(kInactive, s.pst_report()->status); - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE); + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// This is a special case where a collection of licenses can be shared with +// multiple devices. In order for this to work, a single session must first +// load a device specific license, and then a shared content license. +TEST_F(UsageTableTest, LoadSharedLicense) { + // session_.generatersasignature. + // session_.GenerateNonce + // DeriveKeysFromSessionKey - (specify enc/mac keys. + // LoadKeys replay control = 2. loads new mac keys. + // LoadKeys replay control = 0. uses same mac key. + // check second loadkeys without first fails. + std::string pst = "my_pst"; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); + Session s; + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); + + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.GenerateTestSessionKeys()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, true)); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + // The second set of keys are not loaded. + for (unsigned int i = 0; i < kNumKeys; i++) { + memset(s.license().keys[i].key_id, 'A' + i, + s.license().keys[i].key_id_length); + } + // TODO(fredgc,jfore): Decide if first set of keys need to stay loaded, or if + // they are replaced. + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.close()); } INSTANTIATE_TEST_CASE_P(TestUsageTables, UsageTableTestWithMAC, Values(true, false)); // With and without new_mac_keys. - } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test_android.cpp b/oemcrypto/test/oemcrypto_test_android.cpp index 1002939b..d08f3eb6 100644 --- a/oemcrypto/test/oemcrypto_test_android.cpp +++ b/oemcrypto/test/oemcrypto_test_android.cpp @@ -88,7 +88,7 @@ TEST_F(OEMCryptoAndroidLMPTest, Level1Required) { } // These tests are required for M Android devices. -class OEMCryptoAndroidMNCTest : public OEMCryptoAndroidLMPTest {}; +class OEMCryptoAndroidMNCTest : public OEMCryptoAndroidLMPTest {}; TEST_F(OEMCryptoAndroidMNCTest, MinVersionNumber10) { uint32_t version = OEMCrypto_APIVersion(); @@ -111,4 +111,12 @@ TEST_F(OEMCryptoAndroidMNCTest, QueryKeyControlImplemented) { OEMCrypto_QueryKeyControl(0, NULL, 0, NULL, NULL)); } +// These tests are required for N Android devices. +class OEMCryptoAndroidNYCTest : public OEMCryptoAndroidMNCTest {}; + +TEST_F(OEMCryptoAndroidNYCTest, MinVersionNumber11) { + uint32_t version = OEMCrypto_APIVersion(); + ASSERT_GE(version, 11u); +} + } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test_main.cpp b/oemcrypto/test/oemcrypto_test_main.cpp index a6d14959..e989bd90 100644 --- a/oemcrypto/test/oemcrypto_test_main.cpp +++ b/oemcrypto/test/oemcrypto_test_main.cpp @@ -2,7 +2,7 @@ #include #include "log.h" -#include "oemcrypto_test.h" +#include "oec_device_features.h" #include "OEMCryptoCENC.h" #include "properties.h" diff --git a/third_party/gyp/MSVSNew.py b/third_party/gyp/MSVSNew.py index 845dcb06..593f0e5b 100644 --- a/third_party/gyp/MSVSNew.py +++ b/third_party/gyp/MSVSNew.py @@ -172,7 +172,7 @@ class MSVSProject(MSVSSolutionEntry): #------------------------------------------------------------------------------ -class MSVSSolution: +class MSVSSolution(object): """Visual Studio solution.""" def __init__(self, path, version, entries=None, variants=None, diff --git a/third_party/gyp/MSVSSettings.py b/third_party/gyp/MSVSSettings.py index 773b74e9..8ae19180 100644 --- a/third_party/gyp/MSVSSettings.py +++ b/third_party/gyp/MSVSSettings.py @@ -2,7 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -"""Code to validate and convert settings of the Microsoft build tools. +r"""Code to validate and convert settings of the Microsoft build tools. This file contains code to validate and convert settings of the Microsoft build tools. The function ConvertToMSBuildSettings(), ValidateMSVSSettings(), @@ -314,7 +314,14 @@ def _MSBuildOnly(tool, name, setting_type): name: the name of the setting. setting_type: the type of this setting. """ + + def _Translate(value, msbuild_settings): + # Let msbuild-only properties get translated as-is from msvs_settings. + tool_settings = msbuild_settings.setdefault(tool.msbuild_name, {}) + tool_settings[name] = value + _msbuild_validators[tool.msbuild_name][name] = setting_type.ValidateMSBuild + _msvs_to_msbuild_converters[tool.msvs_name][name] = _Translate def _ConvertedToAdditionalOption(tool, msvs_name, flag): @@ -367,6 +374,35 @@ fix_vc_macro_slashes_regex = re.compile( r'(\$\((?:%s)\))(?:[\\/]+)' % "|".join(fix_vc_macro_slashes_regex_list) ) +# Regular expression to detect keys that were generated by exclusion lists +_EXCLUDED_SUFFIX_RE = re.compile('^(.*)_excluded$') + + +def _ValidateExclusionSetting(setting, settings, error_msg, stderr=sys.stderr): + """Verify that 'setting' is valid if it is generated from an exclusion list. + + If the setting appears to be generated from an exclusion list, the root name + is checked. + + Args: + setting: A string that is the setting name to validate + settings: A dictionary where the keys are valid settings + error_msg: The message to emit in the event of error + stderr: The stream receiving the error messages. + """ + # This may be unrecognized because it's an exclusion list. If the + # setting name has the _excluded suffix, then check the root name. + unrecognized = True + m = re.match(_EXCLUDED_SUFFIX_RE, setting) + if m: + root_setting = m.group(1) + unrecognized = root_setting not in settings + + if unrecognized: + # We don't know this setting. Give a warning. + print >> stderr, error_msg + + def FixVCMacroSlashes(s): """Replace macros which have excessive following slashes. @@ -388,11 +424,11 @@ def ConvertVCMacrosToMSBuild(s): if '$' in s: replace_map = { '$(ConfigurationName)': '$(Configuration)', - '$(InputDir)': '%(RootDir)%(Directory)', + '$(InputDir)': '%(RelativeDir)', '$(InputExt)': '%(Extension)', '$(InputFileName)': '%(Filename)%(Extension)', '$(InputName)': '%(Filename)', - '$(InputPath)': '%(FullPath)', + '$(InputPath)': '%(Identity)', '$(ParentName)': '$(ProjectFileName)', '$(PlatformName)': '$(Platform)', '$(SafeInputName)': '%(Filename)', @@ -403,9 +439,6 @@ def ConvertVCMacrosToMSBuild(s): return s -_EXCLUDED_SUFFIX_RE = re.compile('^(.*)_excluded$') - - def ConvertToMSBuildSettings(msvs_settings, stderr=sys.stderr): """Converts MSVS settings (VS2008 and earlier) to MSBuild settings (VS2010+). @@ -432,19 +465,12 @@ def ConvertToMSBuildSettings(msvs_settings, stderr=sys.stderr): print >> stderr, ('Warning: while converting %s/%s to MSBuild, ' '%s' % (msvs_tool_name, msvs_setting, e)) else: - # This may be unrecognized because it's an exclusion list. If the - # setting name has the _excluded suffix, then check the root name. - unrecognized = True - m = re.match(_EXCLUDED_SUFFIX_RE, msvs_setting) - if m: - root_msvs_setting = m.group(1) - unrecognized = root_msvs_setting not in msvs_tool - - if unrecognized: - # We don't know this setting. Give a warning. - print >> stderr, ('Warning: unrecognized setting %s/%s ' - 'while converting to MSBuild.' % - (msvs_tool_name, msvs_setting)) + _ValidateExclusionSetting(msvs_setting, + msvs_tool, + ('Warning: unrecognized setting %s/%s ' + 'while converting to MSBuild.' % + (msvs_tool_name, msvs_setting)), + stderr) else: print >> stderr, ('Warning: unrecognized tool %s while converting to ' 'MSBuild.' % msvs_tool_name) @@ -495,8 +521,12 @@ def _ValidateSettings(validators, settings, stderr): print >> stderr, ('Warning: for %s/%s, %s' % (tool_name, setting, e)) else: - print >> stderr, ('Warning: unrecognized setting %s/%s' % - (tool_name, setting)) + _ValidateExclusionSetting(setting, + tool_validators, + ('Warning: unrecognized setting %s/%s' % + (tool_name, setting)), + stderr) + else: print >> stderr, ('Warning: unrecognized tool %s' % tool_name) @@ -508,6 +538,7 @@ _midl = _Tool('VCMIDLTool', 'Midl') _rc = _Tool('VCResourceCompilerTool', 'ResourceCompile') _lib = _Tool('VCLibrarianTool', 'Lib') _manifest = _Tool('VCManifestTool', 'Manifest') +_masm = _Tool('MASM', 'MASM') _AddTool(_compile) @@ -516,6 +547,7 @@ _AddTool(_midl) _AddTool(_rc) _AddTool(_lib) _AddTool(_manifest) +_AddTool(_masm) # Add sections only found in the MSBuild settings. _msbuild_validators[''] = {} _msbuild_validators['ProjectReference'] = {} @@ -560,6 +592,7 @@ _Same(_compile, 'UndefinePreprocessorDefinitions', _string_list) # /U _Same(_compile, 'UseFullPaths', _boolean) # /FC _Same(_compile, 'WholeProgramOptimization', _boolean) # /GL _Same(_compile, 'XMLDocumentationFileName', _file_name) +_Same(_compile, 'CompileAsWinRT', _boolean) # /ZW _Same(_compile, 'AssemblerOutput', _Enumeration(['NoListing', @@ -579,7 +612,8 @@ _Same(_compile, 'BrowseInformation', _Same(_compile, 'CallingConvention', _Enumeration(['Cdecl', # /Gd 'FastCall', # /Gr - 'StdCall'])) # /Gz + 'StdCall', # /Gz + 'VectorCall'])) # /Gv _Same(_compile, 'CompileAs', _Enumeration(['Default', 'CompileAsC', # /TC @@ -593,7 +627,12 @@ _Same(_compile, 'DebugInformationFormat', _Same(_compile, 'EnableEnhancedInstructionSet', _Enumeration(['NotSet', 'StreamingSIMDExtensions', # /arch:SSE - 'StreamingSIMDExtensions2'])) # /arch:SSE2 + 'StreamingSIMDExtensions2', # /arch:SSE2 + 'AdvancedVectorExtensions', # /arch:AVX (vs2012+) + 'NoExtensions', # /arch:IA32 (vs2012+) + # This one only exists in the new msbuild format. + 'AdvancedVectorExtensions2', # /arch:AVX2 (vs2013r2+) + ])) _Same(_compile, 'ErrorReporting', _Enumeration(['None', # /errorReport:none 'Prompt', # /errorReport:prompt @@ -670,10 +709,7 @@ _MSVSOnly(_compile, 'UseUnicodeResponseFiles', _boolean) _MSBuildOnly(_compile, 'BuildingInIDE', _boolean) _MSBuildOnly(_compile, 'CompileAsManaged', _Enumeration([], new=['false', - 'true', # /clr - 'Pure', # /clr:pure - 'Safe', # /clr:safe - 'OldSyntax'])) # /clr:oldSyntax + 'true'])) # /clr _MSBuildOnly(_compile, 'CreateHotpatchableImage', _boolean) # /hotpatch _MSBuildOnly(_compile, 'MultiProcessorCompilation', _boolean) # /MP _MSBuildOnly(_compile, 'PreprocessOutputPath', _string) # /Fi @@ -848,13 +884,6 @@ _Moved(_link, 'UseLibraryDependencyInputs', 'ProjectReference', _boolean) # MSVS options not found in MSBuild. _MSVSOnly(_link, 'OptimizeForWindows98', _newly_boolean) _MSVSOnly(_link, 'UseUnicodeResponseFiles', _boolean) -# These settings generate correctly in the MSVS output files when using -# e.g. DelayLoadDLLs! or AdditionalDependencies! to exclude files from -# configuration entries, but result in spurious artifacts which can be -# safely ignored here. See crbug.com/246570 -_MSVSOnly(_link, 'AdditionalLibraryDirectories_excluded', _folder_list) -_MSVSOnly(_link, 'DelayLoadDLLs_excluded', _file_list) -_MSVSOnly(_link, 'AdditionalDependencies_excluded', _file_list) # MSBuild options not found in MSVS. _MSBuildOnly(_link, 'BuildingInIDE', _boolean) @@ -1003,9 +1032,6 @@ _Same(_lib, 'TargetMachine', _target_machine_enumeration) # ProjectReference. We may want to validate that they are consistent. _Moved(_lib, 'LinkLibraryDependencies', 'ProjectReference', _boolean) -# TODO(jeanluc) I don't think these are genuine settings but byproducts of Gyp. -_MSVSOnly(_lib, 'AdditionalLibraryDirectories_excluded', _folder_list) - _MSBuildOnly(_lib, 'DisplayLibrary', _string) # /LIST Visible='false' _MSBuildOnly(_lib, 'ErrorReporting', _Enumeration([], new=['PromptImmediately', # /ERRORREPORT:PROMPT @@ -1061,3 +1087,11 @@ _MSBuildOnly(_manifest, 'ManifestFromManagedAssembly', _MSBuildOnly(_manifest, 'OutputResourceManifests', _string) # /outputresource _MSBuildOnly(_manifest, 'SuppressDependencyElement', _boolean) # /nodependency _MSBuildOnly(_manifest, 'TrackerLogDirectory', _folder_name) + + +# Directives for MASM. +# See "$(VCTargetsPath)\BuildCustomizations\masm.xml" for the schema of the +# MSBuild MASM settings. + +# Options that have the same name in MSVS and MSBuild. +_Same(_masm, 'UseSafeExceptionHandlers', _boolean) # /safeseh diff --git a/third_party/gyp/MSVSSettings_test.py b/third_party/gyp/MSVSSettings_test.py index 9bd37ec3..bf6ea6b8 100755 --- a/third_party/gyp/MSVSSettings_test.py +++ b/third_party/gyp/MSVSSettings_test.py @@ -267,7 +267,7 @@ class TestSequenceFunctions(unittest.TestCase): 'Warning: for VCCLCompilerTool/BrowseInformation, ' "invalid literal for int() with base 10: 'fdkslj'", 'Warning: for VCCLCompilerTool/CallingConvention, ' - 'index value (-1) not in expected range [0, 3)', + 'index value (-1) not in expected range [0, 4)', 'Warning: for VCCLCompilerTool/DebugInformationFormat, ' 'converted value for 2 not specified.', 'Warning: unrecognized setting VCCLCompilerTool/Enableprefast', @@ -296,7 +296,7 @@ class TestSequenceFunctions(unittest.TestCase): 'BuildingInIDE': 'true', 'CallingConvention': 'Cdecl', 'CompileAs': 'CompileAsC', - 'CompileAsManaged': 'Pure', + 'CompileAsManaged': 'true', 'CreateHotpatchableImage': 'true', 'DebugInformationFormat': 'ProgramDatabase', 'DisableLanguageExtensions': 'true', diff --git a/third_party/gyp/MSVSUtil.py b/third_party/gyp/MSVSUtil.py index 62e8d260..f5e0c1d8 100644 --- a/third_party/gyp/MSVSUtil.py +++ b/third_party/gyp/MSVSUtil.py @@ -8,10 +8,12 @@ import copy import os -_TARGET_TYPE_EXT = { - 'executable': '.exe', - 'loadable_module': '.dll', - 'shared_library': '.dll', +# A dictionary mapping supported target types to extensions. +TARGET_TYPE_EXT = { + 'executable': 'exe', + 'loadable_module': 'dll', + 'shared_library': 'dll', + 'static_library': 'lib', } @@ -108,16 +110,17 @@ def ShardTargets(target_list, target_dicts): else: new_target_dicts[t] = target_dicts[t] # Shard dependencies. - for t in new_target_dicts: - dependencies = copy.copy(new_target_dicts[t].get('dependencies', [])) - new_dependencies = [] - for d in dependencies: - if d in targets_to_shard: - for i in range(targets_to_shard[d]): - new_dependencies.append(_ShardName(d, i)) - else: - new_dependencies.append(d) - new_target_dicts[t]['dependencies'] = new_dependencies + for t in sorted(new_target_dicts): + for deptype in ('dependencies', 'dependencies_original'): + dependencies = copy.copy(new_target_dicts[t].get(deptype, [])) + new_dependencies = [] + for d in dependencies: + if d in targets_to_shard: + for i in range(targets_to_shard[d]): + new_dependencies.append(_ShardName(d, i)) + else: + new_dependencies.append(d) + new_target_dicts[t][deptype] = new_dependencies return (new_target_list, new_target_dicts) @@ -156,7 +159,7 @@ def _GetPdbPath(target_dict, config_name, vars): pdb_base = target_dict.get('product_name', target_dict['target_name']) - pdb_base = '%s%s.pdb' % (pdb_base, _TARGET_TYPE_EXT[target_dict['type']]) + pdb_base = '%s.%s.pdb' % (pdb_base, TARGET_TYPE_EXT[target_dict['type']]) pdb_path = vars['PRODUCT_DIR'] + '/' + pdb_base return pdb_path @@ -264,4 +267,4 @@ def InsertLargePdbShims(target_list, target_dicts, vars): # Update the original target to depend on the shim target. target_dict.setdefault('dependencies', []).append(full_shim_target_name) - return (target_list, target_dicts) \ No newline at end of file + return (target_list, target_dicts) diff --git a/third_party/gyp/MSVSVersion.py b/third_party/gyp/MSVSVersion.py index 03b6d8ad..edaf6eed 100644 --- a/third_party/gyp/MSVSVersion.py +++ b/third_party/gyp/MSVSVersion.py @@ -68,26 +68,29 @@ class VisualStudioVersion(object): of a user override.""" return self.default_toolset - def SetupScript(self, target_arch): + def _SetupScriptInternal(self, target_arch): """Returns a command (with arguments) to be used to set up the environment.""" - # Check if we are running in the SDK command line environment and use - # the setup script from the SDK if so. |target_arch| should be either - # 'x86' or 'x64'. + # If WindowsSDKDir is set and SetEnv.Cmd exists then we are using the + # depot_tools build tools and should run SetEnv.Cmd to set up the + # environment. The check for WindowsSDKDir alone is not sufficient because + # this is set by running vcvarsall.bat. assert target_arch in ('x86', 'x64') sdk_dir = os.environ.get('WindowsSDKDir') - if self.sdk_based and sdk_dir: - return [os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.Cmd')), - '/' + target_arch] + if sdk_dir: + setup_path = os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.Cmd')) + if self.sdk_based and sdk_dir and os.path.exists(setup_path): + return [setup_path, '/' + target_arch] else: # We don't use VC/vcvarsall.bat for x86 because vcvarsall calls # vcvars32, which it can only find if VS??COMNTOOLS is set, which it # isn't always. if target_arch == 'x86': - if self.short_name == '2013' and ( + if self.short_name >= '2013' and self.short_name[-1] != 'e' and ( os.environ.get('PROCESSOR_ARCHITECTURE') == 'AMD64' or os.environ.get('PROCESSOR_ARCHITEW6432') == 'AMD64'): - # VS2013 non-Express has a x64-x86 cross that we want to prefer. + # VS2013 and later, non-Express have a x64-x86 cross that we want + # to prefer. return [os.path.normpath( os.path.join(self.path, 'VC/vcvarsall.bat')), 'amd64_x86'] # Otherwise, the standard x86 compiler. @@ -105,6 +108,14 @@ class VisualStudioVersion(object): return [os.path.normpath( os.path.join(self.path, 'VC/vcvarsall.bat')), arg] + def SetupScript(self, target_arch): + script_data = self._SetupScriptInternal(target_arch) + script_path = script_data[0] + if not os.path.exists(script_path): + raise Exception('%s is missing - make sure VC++ tools are installed.' % + script_path) + return script_data + def _RegistryQueryBase(sysdir, key, value): """Use reg.exe to read a particular key. @@ -138,7 +149,7 @@ def _RegistryQueryBase(sysdir, key, value): def _RegistryQuery(key, value=None): - """Use reg.exe to read a particular key through _RegistryQueryBase. + r"""Use reg.exe to read a particular key through _RegistryQueryBase. First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If that fails, it falls back to System32. Sysnative is available on Vista and @@ -165,8 +176,33 @@ def _RegistryQuery(key, value=None): return text +def _RegistryGetValueUsingWinReg(key, value): + """Use the _winreg module to obtain the value of a registry key. + + Args: + key: The registry key. + value: The particular registry value to read. + Return: + contents of the registry key's value, or None on failure. Throws + ImportError if _winreg is unavailable. + """ + import _winreg + try: + root, subkey = key.split('\\', 1) + assert root == 'HKLM' # Only need HKLM for now. + with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey) as hkey: + return _winreg.QueryValueEx(hkey, value)[0] + except WindowsError: + return None + + def _RegistryGetValue(key, value): - """Use reg.exe to obtain the value of a registry key. + """Use _winreg or reg.exe to obtain the value of a registry key. + + Using _winreg is preferable because it solves an issue on some corporate + environments where access to reg.exe is locked down. However, we still need + to fallback to reg.exe for the case where the _winreg module is not available + (for example in cygwin python). Args: key: The registry key. @@ -174,6 +210,12 @@ def _RegistryGetValue(key, value): Return: contents of the registry key's value, or None on failure. """ + try: + return _RegistryGetValueUsingWinReg(key, value) + except ImportError: + pass + + # Fallback to reg.exe if we fail to import _winreg. text = _RegistryQuery(key, value) if not text: return None @@ -184,19 +226,6 @@ def _RegistryGetValue(key, value): return match.group(1) -def _RegistryKeyExists(key): - """Use reg.exe to see if a key exists. - - Args: - key: The registry key to check. - Return: - True if the key exists - """ - if not _RegistryQuery(key): - return False - return True - - def _CreateVersion(name, path, sdk_based=False): """Sets up MSVS project generation. @@ -207,6 +236,15 @@ def _CreateVersion(name, path, sdk_based=False): if path: path = os.path.normpath(path) versions = { + '2015': VisualStudioVersion('2015', + 'Visual Studio 2015', + solution_version='12.00', + project_version='14.0', + flat_sln=False, + uses_vcxproj=True, + path=path, + sdk_based=sdk_based, + default_toolset='v140'), '2013': VisualStudioVersion('2013', 'Visual Studio 2013', solution_version='13.00', @@ -316,7 +354,8 @@ def _DetectVisualStudioVersions(versions_to_check, force_express): 2008(e) - Visual Studio 2008 (9) 2010(e) - Visual Studio 2010 (10) 2012(e) - Visual Studio 2012 (11) - 2013(e) - Visual Studio 2013 (11) + 2013(e) - Visual Studio 2013 (12) + 2015 - Visual Studio 2015 (14) Where (e) is e for express editions of MSVS and blank otherwise. """ version_to_year = { @@ -325,6 +364,7 @@ def _DetectVisualStudioVersions(versions_to_check, force_express): '10.0': '2010', '11.0': '2012', '12.0': '2013', + '14.0': '2015', } versions = [] for version in versions_to_check: @@ -361,13 +401,14 @@ def _DetectVisualStudioVersions(versions_to_check, force_express): if not path: continue path = _ConvertToCygpath(path) - versions.append(_CreateVersion(version_to_year[version] + 'e', - os.path.join(path, '..'), sdk_based=True)) + if version != '14.0': # There is no Express edition for 2015. + versions.append(_CreateVersion(version_to_year[version] + 'e', + os.path.join(path, '..'), sdk_based=True)) return versions -def SelectVisualStudioVersion(version='auto'): +def SelectVisualStudioVersion(version='auto', allow_fallback=True): """Select which version of Visual Studio projects to generate. Arguments: @@ -379,7 +420,7 @@ def SelectVisualStudioVersion(version='auto'): if version == 'auto': version = os.environ.get('GYP_MSVS_VERSION', 'auto') version_map = { - 'auto': ('10.0', '12.0', '9.0', '8.0', '11.0'), + 'auto': ('14.0', '12.0', '10.0', '9.0', '8.0', '11.0'), '2005': ('8.0',), '2005e': ('8.0',), '2008': ('9.0',), @@ -390,6 +431,7 @@ def SelectVisualStudioVersion(version='auto'): '2012e': ('11.0',), '2013': ('12.0',), '2013e': ('12.0',), + '2015': ('14.0',), } override_path = os.environ.get('GYP_MSVS_OVERRIDE_PATH') if override_path: @@ -401,6 +443,8 @@ def SelectVisualStudioVersion(version='auto'): version = str(version) versions = _DetectVisualStudioVersions(version_map[version], 'e' in version) if not versions: + if not allow_fallback: + raise ValueError('Could not locate Visual Studio installation.') if version == 'auto': # Default to 2005 if we couldn't find anything return _CreateVersion('2005', None) diff --git a/third_party/gyp/__init__.py b/third_party/gyp/__init__.py index 30edea56..668f38b6 100755 --- a/third_party/gyp/__init__.py +++ b/third_party/gyp/__init__.py @@ -49,7 +49,7 @@ def FindBuildFiles(): def Load(build_files, format, default_variables={}, includes=[], depth='.', params=None, check=False, - circular_check=True): + circular_check=True, duplicate_basename_check=True): """ Loads one or more specified build files. default_variables and includes will be copied before use. @@ -59,7 +59,6 @@ def Load(build_files, format, default_variables={}, if params is None: params = {} - flavor = None if '-' in format: format, params['flavor'] = format.split('-', 1) @@ -69,6 +68,7 @@ def Load(build_files, format, default_variables={}, # named WITH_CAPITAL_LETTERS to provide a distinct "best practice" namespace, # avoiding collisions with user and automatic variables. default_variables['GENERATOR'] = format + default_variables['GENERATOR_FLAVOR'] = params.get('flavor', '') # Format can be a custom python file, or by default the name of a module # within gyp.generator. @@ -126,6 +126,7 @@ def Load(build_files, format, default_variables={}, # Process the input specific to this generator. result = gyp.input.Load(build_files, default_variables, includes[:], depth, generator_input_info, check, circular_check, + duplicate_basename_check, params['parallel'], params['root_targets']) return [generator] + result @@ -324,6 +325,16 @@ def gyp_main(args): parser.add_option('--no-circular-check', dest='circular_check', action='store_false', default=True, regenerate=False, help="don't check for circular relationships between files") + # --no-duplicate-basename-check disables the check for duplicate basenames + # in a static_library/shared_library project. Visual C++ 2008 generator + # doesn't support this configuration. Libtool on Mac also generates warnings + # when duplicate basenames are passed into Make generator on Mac. + # TODO(yukawa): Remove this option when these legacy generators are + # deprecated. + parser.add_option('--no-duplicate-basename-check', + dest='duplicate_basename_check', action='store_false', + default=True, regenerate=False, + help="don't check for duplicate basenames") parser.add_option('--no-parallel', action='store_true', default=False, help='Disable multiprocessing') parser.add_option('-S', '--suffix', dest='suffix', default='', @@ -371,7 +382,7 @@ def gyp_main(args): if options.use_environment: generate_formats = os.environ.get('GYP_GENERATORS', []) if generate_formats: - generate_formats = re.split('[\s,]', generate_formats) + generate_formats = re.split(r'[\s,]', generate_formats) if generate_formats: options.formats = generate_formats else: @@ -493,14 +504,14 @@ def gyp_main(args): 'gyp_binary': sys.argv[0], 'home_dot_gyp': home_dot_gyp, 'parallel': options.parallel, - 'root_targets': options.root_targets} + 'root_targets': options.root_targets, + 'target_arch': cmdline_default_variables.get('target_arch', '')} # Start with the default variables from the command line. - [generator, flat_list, targets, data] = Load(build_files, format, - cmdline_default_variables, - includes, options.depth, - params, options.check, - options.circular_check) + [generator, flat_list, targets, data] = Load( + build_files, format, cmdline_default_variables, includes, options.depth, + params, options.check, options.circular_check, + options.duplicate_basename_check) # TODO(mark): Pass |data| for now because the generator needs a list of # build files that came in. In the future, maybe it should just accept diff --git a/third_party/gyp/common.py b/third_party/gyp/common.py index f9c6c6f3..a1e1db5f 100644 --- a/third_party/gyp/common.py +++ b/third_party/gyp/common.py @@ -4,6 +4,7 @@ from __future__ import with_statement +import collections import errno import filecmp import os.path @@ -130,13 +131,20 @@ def QualifiedTarget(build_file, target, toolset): @memoize -def RelativePath(path, relative_to): +def RelativePath(path, relative_to, follow_path_symlink=True): # Assuming both |path| and |relative_to| are relative to the current # directory, returns a relative path that identifies path relative to # relative_to. + # If |follow_symlink_path| is true (default) and |path| is a symlink, then + # this method returns a path to the real file represented by |path|. If it is + # false, this method returns a path to the symlink. If |path| is not a + # symlink, this option has no effect. # Convert to normalized (and therefore absolute paths). - path = os.path.realpath(path) + if follow_path_symlink: + path = os.path.realpath(path) + else: + path = os.path.abspath(path) relative_to = os.path.realpath(relative_to) # On Windows, we can't create a relative path to a different drive, so just @@ -328,7 +336,7 @@ def WriteOnDiff(filename): the target if it differs (on close). """ - class Writer: + class Writer(object): """Wrapper around file which only covers the target if it differs.""" def __init__(self): # Pick temporary file. @@ -417,13 +425,15 @@ def GetFlavor(params): return 'freebsd' if sys.platform.startswith('openbsd'): return 'openbsd' + if sys.platform.startswith('netbsd'): + return 'netbsd' if sys.platform.startswith('aix'): return 'aix' return 'linux' -def CopyTool(flavor, out_path): +def CopyTool(flavor, out_path, generator_flags={}): """Finds (flock|mac|win)_tool.gyp in the gyp directory and copies it to |out_path|.""" # aix and solaris just need flock emulation. mac and win use more complicated @@ -443,11 +453,18 @@ def CopyTool(flavor, out_path): with open(source_path) as source_file: source = source_file.readlines() + # Set custom header flags. + header = '# Generated by gyp. Do not edit.\n' + mac_toolchain_dir = generator_flags.get('mac_toolchain_dir', None) + if flavor == 'mac' and mac_toolchain_dir: + header += "import os;\nos.environ['DEVELOPER_DIR']='%s'\n" \ + % mac_toolchain_dir + # Add header and write it out. tool_path = os.path.join(out_path, 'gyp-%s-tool' % prefix) with open(tool_path, 'w') as tool_file: tool_file.write( - ''.join([source[0], '# Generated by gyp. Do not edit.\n'] + source[1:])) + ''.join([source[0], header] + source[1:])) # Make file executable. os.chmod(tool_path, 0755) @@ -472,6 +489,72 @@ def uniquer(seq, idfun=None): return result +# Based on http://code.activestate.com/recipes/576694/. +class OrderedSet(collections.MutableSet): + def __init__(self, iterable=None): + self.end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.map = {} # key --> [key, prev, next] + if iterable is not None: + self |= iterable + + def __len__(self): + return len(self.map) + + def __contains__(self, key): + return key in self.map + + def add(self, key): + if key not in self.map: + end = self.end + curr = end[1] + curr[2] = end[1] = self.map[key] = [key, curr, end] + + def discard(self, key): + if key in self.map: + key, prev_item, next_item = self.map.pop(key) + prev_item[2] = next_item + next_item[1] = prev_item + + def __iter__(self): + end = self.end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + # The second argument is an addition that causes a pylint warning. + def pop(self, last=True): # pylint: disable=W0221 + if not self: + raise KeyError('set is empty') + key = self.end[1][0] if last else self.end[2][0] + self.discard(key) + return key + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, list(self)) + + def __eq__(self, other): + if isinstance(other, OrderedSet): + return len(self) == len(other) and list(self) == list(other) + return set(self) == set(other) + + # Extensions to the recipe. + def update(self, iterable): + for i in iterable: + if i not in self: + self.add(i) + + class CycleError(Exception): """An exception raised when an unexpected cycle is detected.""" def __init__(self, nodes): @@ -481,7 +564,7 @@ class CycleError(Exception): def TopologicallySorted(graph, get_edges): - """Topologically sort based on a user provided edge definition. + r"""Topologically sort based on a user provided edge definition. Args: graph: A list of node names. @@ -519,3 +602,14 @@ def TopologicallySorted(graph, get_edges): for node in sorted(graph): Visit(node) return ordered_nodes + +def CrossCompileRequested(): + # TODO: figure out how to not build extra host objects in the + # non-cross-compile case when this is enabled, and enable unconditionally. + return (os.environ.get('GYP_CROSSCOMPILE') or + os.environ.get('AR_host') or + os.environ.get('CC_host') or + os.environ.get('CXX_host') or + os.environ.get('AR_target') or + os.environ.get('CC_target') or + os.environ.get('CXX_target')) diff --git a/third_party/gyp/flock_tool.py b/third_party/gyp/flock_tool.py index 3e7efff2..b38d8660 100755 --- a/third_party/gyp/flock_tool.py +++ b/third_party/gyp/flock_tool.py @@ -40,7 +40,12 @@ class FlockTool(object): # with EBADF, that's why we use this F_SETLK # hack instead. fd = os.open(lockfile, os.O_WRONLY|os.O_NOCTTY|os.O_CREAT, 0666) - op = struct.pack('hhllhhl', fcntl.F_WRLCK, 0, 0, 0, 0, 0, 0) + if sys.platform.startswith('aix'): + # Python on AIX is compiled with LARGEFILE support, which changes the + # struct size. + op = struct.pack('hhIllqq', fcntl.F_WRLCK, 0, 0, 0, 0, 0, 0) + else: + op = struct.pack('hhllhhl', fcntl.F_WRLCK, 0, 0, 0, 0, 0, 0) fcntl.fcntl(fd, fcntl.F_SETLK, op) return subprocess.call(cmd_list) diff --git a/third_party/gyp/generator/analyzer.py b/third_party/gyp/generator/analyzer.py new file mode 100644 index 00000000..921c1a6b --- /dev/null +++ b/third_party/gyp/generator/analyzer.py @@ -0,0 +1,741 @@ +# Copyright (c) 2014 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +This script is intended for use as a GYP_GENERATOR. It takes as input (by way of +the generator flag config_path) the path of a json file that dictates the files +and targets to search for. The following keys are supported: +files: list of paths (relative) of the files to search for. +test_targets: unqualified target names to search for. Any target in this list +that depends upon a file in |files| is output regardless of the type of target +or chain of dependencies. +additional_compile_targets: Unqualified targets to search for in addition to +test_targets. Targets in the combined list that depend upon a file in |files| +are not necessarily output. For example, if the target is of type none then the +target is not output (but one of the descendants of the target will be). + +The following is output: +error: only supplied if there is an error. +compile_targets: minimal set of targets that directly or indirectly (for + targets of type none) depend on the files in |files| and is one of the + supplied targets or a target that one of the supplied targets depends on. + The expectation is this set of targets is passed into a build step. This list + always contains the output of test_targets as well. +test_targets: set of targets from the supplied |test_targets| that either + directly or indirectly depend upon a file in |files|. This list if useful + if additional processing needs to be done for certain targets after the + build, such as running tests. +status: outputs one of three values: none of the supplied files were found, + one of the include files changed so that it should be assumed everything + changed (in this case test_targets and compile_targets are not output) or at + least one file was found. +invalid_targets: list of supplied targets that were not found. + +Example: +Consider a graph like the following: + A D + / \ +B C +A depends upon both B and C, A is of type none and B and C are executables. +D is an executable, has no dependencies and nothing depends on it. +If |additional_compile_targets| = ["A"], |test_targets| = ["B", "C"] and +files = ["b.cc", "d.cc"] (B depends upon b.cc and D depends upon d.cc), then +the following is output: +|compile_targets| = ["B"] B must built as it depends upon the changed file b.cc +and the supplied target A depends upon it. A is not output as a build_target +as it is of type none with no rules and actions. +|test_targets| = ["B"] B directly depends upon the change file b.cc. + +Even though the file d.cc, which D depends upon, has changed D is not output +as it was not supplied by way of |additional_compile_targets| or |test_targets|. + +If the generator flag analyzer_output_path is specified, output is written +there. Otherwise output is written to stdout. + +In Gyp the "all" target is shorthand for the root targets in the files passed +to gyp. For example, if file "a.gyp" contains targets "a1" and +"a2", and file "b.gyp" contains targets "b1" and "b2" and "a2" has a dependency +on "b2" and gyp is supplied "a.gyp" then "all" consists of "a1" and "a2". +Notice that "b1" and "b2" are not in the "all" target as "b.gyp" was not +directly supplied to gyp. OTOH if both "a.gyp" and "b.gyp" are supplied to gyp +then the "all" target includes "b1" and "b2". +""" + +import gyp.common +import gyp.ninja_syntax as ninja_syntax +import json +import os +import posixpath +import sys + +debug = False + +found_dependency_string = 'Found dependency' +no_dependency_string = 'No dependencies' +# Status when it should be assumed that everything has changed. +all_changed_string = 'Found dependency (all)' + +# MatchStatus is used indicate if and how a target depends upon the supplied +# sources. +# The target's sources contain one of the supplied paths. +MATCH_STATUS_MATCHES = 1 +# The target has a dependency on another target that contains one of the +# supplied paths. +MATCH_STATUS_MATCHES_BY_DEPENDENCY = 2 +# The target's sources weren't in the supplied paths and none of the target's +# dependencies depend upon a target that matched. +MATCH_STATUS_DOESNT_MATCH = 3 +# The target doesn't contain the source, but the dependent targets have not yet +# been visited to determine a more specific status yet. +MATCH_STATUS_TBD = 4 + +generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested() + +generator_wants_static_library_dependencies_adjusted = False + +generator_default_variables = { +} +for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR', + 'LIB_DIR', 'SHARED_LIB_DIR']: + generator_default_variables[dirname] = '!!!' + +for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME', + 'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT', + 'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX', + 'STATIC_LIB_PREFIX', 'STATIC_LIB_SUFFIX', + 'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX', + 'CONFIGURATION_NAME']: + generator_default_variables[unused] = '' + + +def _ToGypPath(path): + """Converts a path to the format used by gyp.""" + if os.sep == '\\' and os.altsep == '/': + return path.replace('\\', '/') + return path + + +def _ResolveParent(path, base_path_components): + """Resolves |path|, which starts with at least one '../'. Returns an empty + string if the path shouldn't be considered. See _AddSources() for a + description of |base_path_components|.""" + depth = 0 + while path.startswith('../'): + depth += 1 + path = path[3:] + # Relative includes may go outside the source tree. For example, an action may + # have inputs in /usr/include, which are not in the source tree. + if depth > len(base_path_components): + return '' + if depth == len(base_path_components): + return path + return '/'.join(base_path_components[0:len(base_path_components) - depth]) + \ + '/' + path + + +def _AddSources(sources, base_path, base_path_components, result): + """Extracts valid sources from |sources| and adds them to |result|. Each + source file is relative to |base_path|, but may contain '..'. To make + resolving '..' easier |base_path_components| contains each of the + directories in |base_path|. Additionally each source may contain variables. + Such sources are ignored as it is assumed dependencies on them are expressed + and tracked in some other means.""" + # NOTE: gyp paths are always posix style. + for source in sources: + if not len(source) or source.startswith('!!!') or source.startswith('$'): + continue + # variable expansion may lead to //. + org_source = source + source = source[0] + source[1:].replace('//', '/') + if source.startswith('../'): + source = _ResolveParent(source, base_path_components) + if len(source): + result.append(source) + continue + result.append(base_path + source) + if debug: + print 'AddSource', org_source, result[len(result) - 1] + + +def _ExtractSourcesFromAction(action, base_path, base_path_components, + results): + if 'inputs' in action: + _AddSources(action['inputs'], base_path, base_path_components, results) + + +def _ToLocalPath(toplevel_dir, path): + """Converts |path| to a path relative to |toplevel_dir|.""" + if path == toplevel_dir: + return '' + if path.startswith(toplevel_dir + '/'): + return path[len(toplevel_dir) + len('/'):] + return path + + +def _ExtractSources(target, target_dict, toplevel_dir): + # |target| is either absolute or relative and in the format of the OS. Gyp + # source paths are always posix. Convert |target| to a posix path relative to + # |toplevel_dir_|. This is done to make it easy to build source paths. + base_path = posixpath.dirname(_ToLocalPath(toplevel_dir, _ToGypPath(target))) + base_path_components = base_path.split('/') + + # Add a trailing '/' so that _AddSources() can easily build paths. + if len(base_path): + base_path += '/' + + if debug: + print 'ExtractSources', target, base_path + + results = [] + if 'sources' in target_dict: + _AddSources(target_dict['sources'], base_path, base_path_components, + results) + # Include the inputs from any actions. Any changes to these affect the + # resulting output. + if 'actions' in target_dict: + for action in target_dict['actions']: + _ExtractSourcesFromAction(action, base_path, base_path_components, + results) + if 'rules' in target_dict: + for rule in target_dict['rules']: + _ExtractSourcesFromAction(rule, base_path, base_path_components, results) + + return results + + +class Target(object): + """Holds information about a particular target: + deps: set of Targets this Target depends upon. This is not recursive, only the + direct dependent Targets. + match_status: one of the MatchStatus values. + back_deps: set of Targets that have a dependency on this Target. + visited: used during iteration to indicate whether we've visited this target. + This is used for two iterations, once in building the set of Targets and + again in _GetBuildTargets(). + name: fully qualified name of the target. + requires_build: True if the target type is such that it needs to be built. + See _DoesTargetTypeRequireBuild for details. + added_to_compile_targets: used when determining if the target was added to the + set of targets that needs to be built. + in_roots: true if this target is a descendant of one of the root nodes. + is_executable: true if the type of target is executable. + is_static_library: true if the type of target is static_library. + is_or_has_linked_ancestor: true if the target does a link (eg executable), or + if there is a target in back_deps that does a link.""" + def __init__(self, name): + self.deps = set() + self.match_status = MATCH_STATUS_TBD + self.back_deps = set() + self.name = name + # TODO(sky): I don't like hanging this off Target. This state is specific + # to certain functions and should be isolated there. + self.visited = False + self.requires_build = False + self.added_to_compile_targets = False + self.in_roots = False + self.is_executable = False + self.is_static_library = False + self.is_or_has_linked_ancestor = False + + +class Config(object): + """Details what we're looking for + files: set of files to search for + targets: see file description for details.""" + def __init__(self): + self.files = [] + self.targets = set() + self.additional_compile_target_names = set() + self.test_target_names = set() + + def Init(self, params): + """Initializes Config. This is a separate method as it raises an exception + if there is a parse error.""" + generator_flags = params.get('generator_flags', {}) + config_path = generator_flags.get('config_path', None) + if not config_path: + return + try: + f = open(config_path, 'r') + config = json.load(f) + f.close() + except IOError: + raise Exception('Unable to open file ' + config_path) + except ValueError as e: + raise Exception('Unable to parse config file ' + config_path + str(e)) + if not isinstance(config, dict): + raise Exception('config_path must be a JSON file containing a dictionary') + self.files = config.get('files', []) + self.additional_compile_target_names = set( + config.get('additional_compile_targets', [])) + self.test_target_names = set(config.get('test_targets', [])) + + +def _WasBuildFileModified(build_file, data, files, toplevel_dir): + """Returns true if the build file |build_file| is either in |files| or + one of the files included by |build_file| is in |files|. |toplevel_dir| is + the root of the source tree.""" + if _ToLocalPath(toplevel_dir, _ToGypPath(build_file)) in files: + if debug: + print 'gyp file modified', build_file + return True + + # First element of included_files is the file itself. + if len(data[build_file]['included_files']) <= 1: + return False + + for include_file in data[build_file]['included_files'][1:]: + # |included_files| are relative to the directory of the |build_file|. + rel_include_file = \ + _ToGypPath(gyp.common.UnrelativePath(include_file, build_file)) + if _ToLocalPath(toplevel_dir, rel_include_file) in files: + if debug: + print 'included gyp file modified, gyp_file=', build_file, \ + 'included file=', rel_include_file + return True + return False + + +def _GetOrCreateTargetByName(targets, target_name): + """Creates or returns the Target at targets[target_name]. If there is no + Target for |target_name| one is created. Returns a tuple of whether a new + Target was created and the Target.""" + if target_name in targets: + return False, targets[target_name] + target = Target(target_name) + targets[target_name] = target + return True, target + + +def _DoesTargetTypeRequireBuild(target_dict): + """Returns true if the target type is such that it needs to be built.""" + # If a 'none' target has rules or actions we assume it requires a build. + return bool(target_dict['type'] != 'none' or + target_dict.get('actions') or target_dict.get('rules')) + + +def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, + build_files): + """Returns a tuple of the following: + . A dictionary mapping from fully qualified name to Target. + . A list of the targets that have a source file in |files|. + . Targets that constitute the 'all' target. See description at top of file + for details on the 'all' target. + This sets the |match_status| of the targets that contain any of the source + files in |files| to MATCH_STATUS_MATCHES. + |toplevel_dir| is the root of the source tree.""" + # Maps from target name to Target. + name_to_target = {} + + # Targets that matched. + matching_targets = [] + + # Queue of targets to visit. + targets_to_visit = target_list[:] + + # Maps from build file to a boolean indicating whether the build file is in + # |files|. + build_file_in_files = {} + + # Root targets across all files. + roots = set() + + # Set of Targets in |build_files|. + build_file_targets = set() + + while len(targets_to_visit) > 0: + target_name = targets_to_visit.pop() + created_target, target = _GetOrCreateTargetByName(name_to_target, + target_name) + if created_target: + roots.add(target) + elif target.visited: + continue + + target.visited = True + target.requires_build = _DoesTargetTypeRequireBuild( + target_dicts[target_name]) + target_type = target_dicts[target_name]['type'] + target.is_executable = target_type == 'executable' + target.is_static_library = target_type == 'static_library' + target.is_or_has_linked_ancestor = (target_type == 'executable' or + target_type == 'shared_library') + + build_file = gyp.common.ParseQualifiedTarget(target_name)[0] + if not build_file in build_file_in_files: + build_file_in_files[build_file] = \ + _WasBuildFileModified(build_file, data, files, toplevel_dir) + + if build_file in build_files: + build_file_targets.add(target) + + # If a build file (or any of its included files) is modified we assume all + # targets in the file are modified. + if build_file_in_files[build_file]: + print 'matching target from modified build file', target_name + target.match_status = MATCH_STATUS_MATCHES + matching_targets.append(target) + else: + sources = _ExtractSources(target_name, target_dicts[target_name], + toplevel_dir) + for source in sources: + if _ToGypPath(os.path.normpath(source)) in files: + print 'target', target_name, 'matches', source + target.match_status = MATCH_STATUS_MATCHES + matching_targets.append(target) + break + + # Add dependencies to visit as well as updating back pointers for deps. + for dep in target_dicts[target_name].get('dependencies', []): + targets_to_visit.append(dep) + + created_dep_target, dep_target = _GetOrCreateTargetByName(name_to_target, + dep) + if not created_dep_target: + roots.discard(dep_target) + + target.deps.add(dep_target) + dep_target.back_deps.add(target) + + return name_to_target, matching_targets, roots & build_file_targets + + +def _GetUnqualifiedToTargetMapping(all_targets, to_find): + """Returns a tuple of the following: + . mapping (dictionary) from unqualified name to Target for all the + Targets in |to_find|. + . any target names not found. If this is empty all targets were found.""" + result = {} + if not to_find: + return {}, [] + to_find = set(to_find) + for target_name in all_targets.keys(): + extracted = gyp.common.ParseQualifiedTarget(target_name) + if len(extracted) > 1 and extracted[1] in to_find: + to_find.remove(extracted[1]) + result[extracted[1]] = all_targets[target_name] + if not to_find: + return result, [] + return result, [x for x in to_find] + + +def _DoesTargetDependOnMatchingTargets(target): + """Returns true if |target| or any of its dependencies is one of the + targets containing the files supplied as input to analyzer. This updates + |matches| of the Targets as it recurses. + target: the Target to look for.""" + if target.match_status == MATCH_STATUS_DOESNT_MATCH: + return False + if target.match_status == MATCH_STATUS_MATCHES or \ + target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY: + return True + for dep in target.deps: + if _DoesTargetDependOnMatchingTargets(dep): + target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY + print '\t', target.name, 'matches by dep', dep.name + return True + target.match_status = MATCH_STATUS_DOESNT_MATCH + return False + + +def _GetTargetsDependingOnMatchingTargets(possible_targets): + """Returns the list of Targets in |possible_targets| that depend (either + directly on indirectly) on at least one of the targets containing the files + supplied as input to analyzer. + possible_targets: targets to search from.""" + found = [] + print 'Targets that matched by dependency:' + for target in possible_targets: + if _DoesTargetDependOnMatchingTargets(target): + found.append(target) + return found + + +def _AddCompileTargets(target, roots, add_if_no_ancestor, result): + """Recurses through all targets that depend on |target|, adding all targets + that need to be built (and are in |roots|) to |result|. + roots: set of root targets. + add_if_no_ancestor: If true and there are no ancestors of |target| then add + |target| to |result|. |target| must still be in |roots|. + result: targets that need to be built are added here.""" + if target.visited: + return + + target.visited = True + target.in_roots = target in roots + + for back_dep_target in target.back_deps: + _AddCompileTargets(back_dep_target, roots, False, result) + target.added_to_compile_targets |= back_dep_target.added_to_compile_targets + target.in_roots |= back_dep_target.in_roots + target.is_or_has_linked_ancestor |= ( + back_dep_target.is_or_has_linked_ancestor) + + # Always add 'executable' targets. Even though they may be built by other + # targets that depend upon them it makes detection of what is going to be + # built easier. + # And always add static_libraries that have no dependencies on them from + # linkables. This is necessary as the other dependencies on them may be + # static libraries themselves, which are not compile time dependencies. + if target.in_roots and \ + (target.is_executable or + (not target.added_to_compile_targets and + (add_if_no_ancestor or target.requires_build)) or + (target.is_static_library and add_if_no_ancestor and + not target.is_or_has_linked_ancestor)): + print '\t\tadding to compile targets', target.name, 'executable', \ + target.is_executable, 'added_to_compile_targets', \ + target.added_to_compile_targets, 'add_if_no_ancestor', \ + add_if_no_ancestor, 'requires_build', target.requires_build, \ + 'is_static_library', target.is_static_library, \ + 'is_or_has_linked_ancestor', target.is_or_has_linked_ancestor + result.add(target) + target.added_to_compile_targets = True + + +def _GetCompileTargets(matching_targets, supplied_targets): + """Returns the set of Targets that require a build. + matching_targets: targets that changed and need to be built. + supplied_targets: set of targets supplied to analyzer to search from.""" + result = set() + for target in matching_targets: + print 'finding compile targets for match', target.name + _AddCompileTargets(target, supplied_targets, True, result) + return result + + +def _WriteOutput(params, **values): + """Writes the output, either to stdout or a file is specified.""" + if 'error' in values: + print 'Error:', values['error'] + if 'status' in values: + print values['status'] + if 'targets' in values: + values['targets'].sort() + print 'Supplied targets that depend on changed files:' + for target in values['targets']: + print '\t', target + if 'invalid_targets' in values: + values['invalid_targets'].sort() + print 'The following targets were not found:' + for target in values['invalid_targets']: + print '\t', target + if 'build_targets' in values: + values['build_targets'].sort() + print 'Targets that require a build:' + for target in values['build_targets']: + print '\t', target + if 'compile_targets' in values: + values['compile_targets'].sort() + print 'Targets that need to be built:' + for target in values['compile_targets']: + print '\t', target + if 'test_targets' in values: + values['test_targets'].sort() + print 'Test targets:' + for target in values['test_targets']: + print '\t', target + + output_path = params.get('generator_flags', {}).get( + 'analyzer_output_path', None) + if not output_path: + print json.dumps(values) + return + try: + f = open(output_path, 'w') + f.write(json.dumps(values) + '\n') + f.close() + except IOError as e: + print 'Error writing to output file', output_path, str(e) + + +def _WasGypIncludeFileModified(params, files): + """Returns true if one of the files in |files| is in the set of included + files.""" + if params['options'].includes: + for include in params['options'].includes: + if _ToGypPath(os.path.normpath(include)) in files: + print 'Include file modified, assuming all changed', include + return True + return False + + +def _NamesNotIn(names, mapping): + """Returns a list of the values in |names| that are not in |mapping|.""" + return [name for name in names if name not in mapping] + + +def _LookupTargets(names, mapping): + """Returns a list of the mapping[name] for each value in |names| that is in + |mapping|.""" + return [mapping[name] for name in names if name in mapping] + + +def CalculateVariables(default_variables, params): + """Calculate additional variables for use in the build (called by gyp).""" + flavor = gyp.common.GetFlavor(params) + if flavor == 'mac': + default_variables.setdefault('OS', 'mac') + elif flavor == 'win': + default_variables.setdefault('OS', 'win') + # Copy additional generator configuration data from VS, which is shared + # by the Windows Ninja generator. + import gyp.generator.msvs as msvs_generator + generator_additional_non_configuration_keys = getattr(msvs_generator, + 'generator_additional_non_configuration_keys', []) + generator_additional_path_sections = getattr(msvs_generator, + 'generator_additional_path_sections', []) + + gyp.msvs_emulation.CalculateCommonVariables(default_variables, params) + else: + operating_system = flavor + if flavor == 'android': + operating_system = 'linux' # Keep this legacy behavior for now. + default_variables.setdefault('OS', operating_system) + + +class TargetCalculator(object): + """Calculates the matching test_targets and matching compile_targets.""" + def __init__(self, files, additional_compile_target_names, test_target_names, + data, target_list, target_dicts, toplevel_dir, build_files): + self._additional_compile_target_names = set(additional_compile_target_names) + self._test_target_names = set(test_target_names) + self._name_to_target, self._changed_targets, self._root_targets = ( + _GenerateTargets(data, target_list, target_dicts, toplevel_dir, + frozenset(files), build_files)) + self._unqualified_mapping, self.invalid_targets = ( + _GetUnqualifiedToTargetMapping(self._name_to_target, + self._supplied_target_names_no_all())) + + def _supplied_target_names(self): + return self._additional_compile_target_names | self._test_target_names + + def _supplied_target_names_no_all(self): + """Returns the supplied test targets without 'all'.""" + result = self._supplied_target_names(); + result.discard('all') + return result + + def is_build_impacted(self): + """Returns true if the supplied files impact the build at all.""" + return self._changed_targets + + def find_matching_test_target_names(self): + """Returns the set of output test targets.""" + assert self.is_build_impacted() + # Find the test targets first. 'all' is special cased to mean all the + # root targets. To deal with all the supplied |test_targets| are expanded + # to include the root targets during lookup. If any of the root targets + # match, we remove it and replace it with 'all'. + test_target_names_no_all = set(self._test_target_names) + test_target_names_no_all.discard('all') + test_targets_no_all = _LookupTargets(test_target_names_no_all, + self._unqualified_mapping) + test_target_names_contains_all = 'all' in self._test_target_names + if test_target_names_contains_all: + test_targets = [x for x in (set(test_targets_no_all) | + set(self._root_targets))] + else: + test_targets = [x for x in test_targets_no_all] + print 'supplied test_targets' + for target_name in self._test_target_names: + print '\t', target_name + print 'found test_targets' + for target in test_targets: + print '\t', target.name + print 'searching for matching test targets' + matching_test_targets = _GetTargetsDependingOnMatchingTargets(test_targets) + matching_test_targets_contains_all = (test_target_names_contains_all and + set(matching_test_targets) & + set(self._root_targets)) + if matching_test_targets_contains_all: + # Remove any of the targets for all that were not explicitly supplied, + # 'all' is subsequentely added to the matching names below. + matching_test_targets = [x for x in (set(matching_test_targets) & + set(test_targets_no_all))] + print 'matched test_targets' + for target in matching_test_targets: + print '\t', target.name + matching_target_names = [gyp.common.ParseQualifiedTarget(target.name)[1] + for target in matching_test_targets] + if matching_test_targets_contains_all: + matching_target_names.append('all') + print '\tall' + return matching_target_names + + def find_matching_compile_target_names(self): + """Returns the set of output compile targets.""" + assert self.is_build_impacted(); + # Compile targets are found by searching up from changed targets. + # Reset the visited status for _GetBuildTargets. + for target in self._name_to_target.itervalues(): + target.visited = False + + supplied_targets = _LookupTargets(self._supplied_target_names_no_all(), + self._unqualified_mapping) + if 'all' in self._supplied_target_names(): + supplied_targets = [x for x in (set(supplied_targets) | + set(self._root_targets))] + print 'Supplied test_targets & compile_targets' + for target in supplied_targets: + print '\t', target.name + print 'Finding compile targets' + compile_targets = _GetCompileTargets(self._changed_targets, + supplied_targets) + return [gyp.common.ParseQualifiedTarget(target.name)[1] + for target in compile_targets] + + +def GenerateOutput(target_list, target_dicts, data, params): + """Called by gyp as the final stage. Outputs results.""" + config = Config() + try: + config.Init(params) + + if not config.files: + raise Exception('Must specify files to analyze via config_path generator ' + 'flag') + + toplevel_dir = _ToGypPath(os.path.abspath(params['options'].toplevel_dir)) + if debug: + print 'toplevel_dir', toplevel_dir + + if _WasGypIncludeFileModified(params, config.files): + result_dict = { 'status': all_changed_string, + 'test_targets': list(config.test_target_names), + 'compile_targets': list( + config.additional_compile_target_names | + config.test_target_names) } + _WriteOutput(params, **result_dict) + return + + calculator = TargetCalculator(config.files, + config.additional_compile_target_names, + config.test_target_names, data, + target_list, target_dicts, toplevel_dir, + params['build_files']) + if not calculator.is_build_impacted(): + result_dict = { 'status': no_dependency_string, + 'test_targets': [], + 'compile_targets': [] } + if calculator.invalid_targets: + result_dict['invalid_targets'] = calculator.invalid_targets + _WriteOutput(params, **result_dict) + return + + test_target_names = calculator.find_matching_test_target_names() + compile_target_names = calculator.find_matching_compile_target_names() + found_at_least_one_target = compile_target_names or test_target_names + result_dict = { 'test_targets': test_target_names, + 'status': found_dependency_string if + found_at_least_one_target else no_dependency_string, + 'compile_targets': list( + set(compile_target_names) | + set(test_target_names)) } + if calculator.invalid_targets: + result_dict['invalid_targets'] = calculator.invalid_targets + _WriteOutput(params, **result_dict) + + except Exception as e: + _WriteOutput(params, error=str(e)) diff --git a/third_party/gyp/generator/android.py b/third_party/gyp/generator/android.py deleted file mode 100644 index 41346e2b..00000000 --- a/third_party/gyp/generator/android.py +++ /dev/null @@ -1,1069 +0,0 @@ -# Copyright (c) 2012 Google Inc. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -# Notes: -# -# This generates makefiles suitable for inclusion into the Android build system -# via an Android.mk file. It is based on make.py, the standard makefile -# generator. -# -# The code below generates a separate .mk file for each target, but -# all are sourced by the top-level GypAndroid.mk. This means that all -# variables in .mk-files clobber one another, and furthermore that any -# variables set potentially clash with other Android build system variables. -# Try to avoid setting global variables where possible. - -import gyp -import gyp.common -import gyp.generator.make as make # Reuse global functions from make backend. -import os -import re -import subprocess - -generator_default_variables = { - 'OS': 'android', - 'EXECUTABLE_PREFIX': '', - 'EXECUTABLE_SUFFIX': '', - 'STATIC_LIB_PREFIX': 'lib', - 'SHARED_LIB_PREFIX': 'lib', - 'STATIC_LIB_SUFFIX': '.a', - 'SHARED_LIB_SUFFIX': '.so', - 'INTERMEDIATE_DIR': '$(gyp_intermediate_dir)', - 'SHARED_INTERMEDIATE_DIR': '$(gyp_shared_intermediate_dir)', - 'PRODUCT_DIR': '$(gyp_shared_intermediate_dir)', - 'SHARED_LIB_DIR': '$(builddir)/lib.$(TOOLSET)', - 'LIB_DIR': '$(obj).$(TOOLSET)', - 'RULE_INPUT_ROOT': '%(INPUT_ROOT)s', # This gets expanded by Python. - 'RULE_INPUT_DIRNAME': '%(INPUT_DIRNAME)s', # This gets expanded by Python. - 'RULE_INPUT_PATH': '$(RULE_SOURCES)', - 'RULE_INPUT_EXT': '$(suffix $<)', - 'RULE_INPUT_NAME': '$(notdir $<)', - 'CONFIGURATION_NAME': '$(GYP_CONFIGURATION)', -} - -# Make supports multiple toolsets -generator_supports_multiple_toolsets = True - - -# Generator-specific gyp specs. -generator_additional_non_configuration_keys = [ - # Boolean to declare that this target does not want its name mangled. - 'android_unmangled_name', -] -generator_additional_path_sections = [] -generator_extra_sources_for_rules = [] - - -SHARED_FOOTER = """\ -# "gyp_all_modules" is a concatenation of the "gyp_all_modules" targets from -# all the included sub-makefiles. This is just here to clarify. -gyp_all_modules: -""" - -header = """\ -# This file is generated by gyp; do not edit. - -""" - -android_standard_include_paths = set([ - # JNI_H_INCLUDE in build/core/binary.mk - 'dalvik/libnativehelper/include/nativehelper', - # from SRC_HEADERS in build/core/config.mk - 'system/core/include', - 'hardware/libhardware/include', - 'hardware/libhardware_legacy/include', - 'hardware/ril/include', - 'dalvik/libnativehelper/include', - 'frameworks/native/include', - 'frameworks/native/opengl/include', - 'frameworks/base/include', - 'frameworks/base/opengl/include', - 'frameworks/base/native/include', - 'external/skia/include', - # TARGET_C_INCLUDES in build/core/combo/TARGET_linux-arm.mk - 'bionic/libc/arch-arm/include', - 'bionic/libc/include', - 'bionic/libstdc++/include', - 'bionic/libc/kernel/common', - 'bionic/libc/kernel/arch-arm', - 'bionic/libm/include', - 'bionic/libm/include/arm', - 'bionic/libthread_db/include', - ]) - - -# Map gyp target types to Android module classes. -MODULE_CLASSES = { - 'static_library': 'STATIC_LIBRARIES', - 'shared_library': 'SHARED_LIBRARIES', - 'executable': 'EXECUTABLES', -} - - -def IsCPPExtension(ext): - return make.COMPILABLE_EXTENSIONS.get(ext) == 'cxx' - - -def Sourceify(path): - """Convert a path to its source directory form. The Android backend does not - support options.generator_output, so this function is a noop.""" - return path - - -# Map from qualified target to path to output. -# For Android, the target of these maps is a tuple ('static', 'modulename'), -# ('dynamic', 'modulename'), or ('path', 'some/path') instead of a string, -# since we link by module. -target_outputs = {} -# Map from qualified target to any linkable output. A subset -# of target_outputs. E.g. when mybinary depends on liba, we want to -# include liba in the linker line; when otherbinary depends on -# mybinary, we just want to build mybinary first. -target_link_deps = {} - - -class AndroidMkWriter(object): - """AndroidMkWriter packages up the writing of one target-specific Android.mk. - - Its only real entry point is Write(), and is mostly used for namespacing. - """ - - def __init__(self, android_top_dir): - self.android_top_dir = android_top_dir - - def Write(self, qualified_target, relative_target, base_path, output_filename, - spec, configs, part_of_all): - """The main entry point: writes a .mk file for a single target. - - Arguments: - qualified_target: target we're generating - relative_target: qualified target name relative to the root - base_path: path relative to source root we're building in, used to resolve - target-relative paths - output_filename: output .mk file name to write - spec, configs: gyp info - part_of_all: flag indicating this target is part of 'all' - """ - gyp.common.EnsureDirExists(output_filename) - - self.fp = open(output_filename, 'w') - - self.fp.write(header) - - self.qualified_target = qualified_target - self.relative_target = relative_target - self.path = base_path - self.target = spec['target_name'] - self.type = spec['type'] - self.toolset = spec['toolset'] - - deps, link_deps = self.ComputeDeps(spec) - - # Some of the generation below can add extra output, sources, or - # link dependencies. All of the out params of the functions that - # follow use names like extra_foo. - extra_outputs = [] - extra_sources = [] - - self.android_class = MODULE_CLASSES.get(self.type, 'GYP') - self.android_module = self.ComputeAndroidModule(spec) - (self.android_stem, self.android_suffix) = self.ComputeOutputParts(spec) - self.output = self.output_binary = self.ComputeOutput(spec) - - # Standard header. - self.WriteLn('include $(CLEAR_VARS)\n') - - # Module class and name. - self.WriteLn('LOCAL_MODULE_CLASS := ' + self.android_class) - self.WriteLn('LOCAL_MODULE := ' + self.android_module) - # Only emit LOCAL_MODULE_STEM if it's different to LOCAL_MODULE. - # The library module classes fail if the stem is set. ComputeOutputParts - # makes sure that stem == modulename in these cases. - if self.android_stem != self.android_module: - self.WriteLn('LOCAL_MODULE_STEM := ' + self.android_stem) - self.WriteLn('LOCAL_MODULE_SUFFIX := ' + self.android_suffix) - self.WriteLn('LOCAL_MODULE_TAGS := optional') - if self.toolset == 'host': - self.WriteLn('LOCAL_IS_HOST_MODULE := true') - - # Grab output directories; needed for Actions and Rules. - self.WriteLn('gyp_intermediate_dir := $(call local-intermediates-dir)') - self.WriteLn('gyp_shared_intermediate_dir := ' - '$(call intermediates-dir-for,GYP,shared)') - self.WriteLn() - - # List files this target depends on so that actions/rules/copies/sources - # can depend on the list. - # TODO: doesn't pull in things through transitive link deps; needed? - target_dependencies = [x[1] for x in deps if x[0] == 'path'] - self.WriteLn('# Make sure our deps are built first.') - self.WriteList(target_dependencies, 'GYP_TARGET_DEPENDENCIES', - local_pathify=True) - - # Actions must come first, since they can generate more OBJs for use below. - if 'actions' in spec: - self.WriteActions(spec['actions'], extra_sources, extra_outputs) - - # Rules must be early like actions. - if 'rules' in spec: - self.WriteRules(spec['rules'], extra_sources, extra_outputs) - - if 'copies' in spec: - self.WriteCopies(spec['copies'], extra_outputs) - - # GYP generated outputs. - self.WriteList(extra_outputs, 'GYP_GENERATED_OUTPUTS', local_pathify=True) - - # Set LOCAL_ADDITIONAL_DEPENDENCIES so that Android's build rules depend - # on both our dependency targets and our generated files. - self.WriteLn('# Make sure our deps and generated files are built first.') - self.WriteLn('LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) ' - '$(GYP_GENERATED_OUTPUTS)') - self.WriteLn() - - # Sources. - if spec.get('sources', []) or extra_sources: - self.WriteSources(spec, configs, extra_sources) - - self.WriteTarget(spec, configs, deps, link_deps, part_of_all) - - # Update global list of target outputs, used in dependency tracking. - target_outputs[qualified_target] = ('path', self.output_binary) - - # Update global list of link dependencies. - if self.type == 'static_library': - target_link_deps[qualified_target] = ('static', self.android_module) - elif self.type == 'shared_library': - target_link_deps[qualified_target] = ('shared', self.android_module) - - self.fp.close() - return self.android_module - - - def WriteActions(self, actions, extra_sources, extra_outputs): - """Write Makefile code for any 'actions' from the gyp input. - - extra_sources: a list that will be filled in with newly generated source - files, if any - extra_outputs: a list that will be filled in with any outputs of these - actions (used to make other pieces dependent on these - actions) - """ - for action in actions: - name = make.StringToMakefileVariable('%s_%s' % (self.relative_target, - action['action_name'])) - self.WriteLn('### Rules for action "%s":' % action['action_name']) - inputs = action['inputs'] - outputs = action['outputs'] - - # Build up a list of outputs. - # Collect the output dirs we'll need. - dirs = set() - for out in outputs: - if not out.startswith('$'): - print ('WARNING: Action for target "%s" writes output to local path ' - '"%s".' % (self.target, out)) - dir = os.path.split(out)[0] - if dir: - dirs.add(dir) - if int(action.get('process_outputs_as_sources', False)): - extra_sources += outputs - - # Prepare the actual command. - command = gyp.common.EncodePOSIXShellList(action['action']) - if 'message' in action: - quiet_cmd = 'Gyp action: %s ($@)' % action['message'] - else: - quiet_cmd = 'Gyp action: %s ($@)' % name - if len(dirs) > 0: - command = 'mkdir -p %s' % ' '.join(dirs) + '; ' + command - - cd_action = 'cd $(gyp_local_path)/%s; ' % self.path - command = cd_action + command - - # The makefile rules are all relative to the top dir, but the gyp actions - # are defined relative to their containing dir. This replaces the gyp_* - # variables for the action rule with an absolute version so that the - # output goes in the right place. - # Only write the gyp_* rules for the "primary" output (:1); - # it's superfluous for the "extra outputs", and this avoids accidentally - # writing duplicate dummy rules for those outputs. - main_output = make.QuoteSpaces(self.LocalPathify(outputs[0])) - self.WriteLn('%s: gyp_local_path := $(LOCAL_PATH)' % main_output) - self.WriteLn('%s: gyp_intermediate_dir := ' - '$(abspath $(gyp_intermediate_dir))' % main_output) - self.WriteLn('%s: gyp_shared_intermediate_dir := ' - '$(abspath $(gyp_shared_intermediate_dir))' % main_output) - - # Android's envsetup.sh adds a number of directories to the path including - # the built host binary directory. This causes actions/rules invoked by - # gyp to sometimes use these instead of system versions, e.g. bison. - # The built host binaries may not be suitable, and can cause errors. - # So, we remove them from the PATH using the ANDROID_BUILD_PATHS variable - # set by envsetup. - self.WriteLn('%s: export PATH := $(subst $(ANDROID_BUILD_PATHS),,$(PATH))' - % main_output) - - for input in inputs: - assert ' ' not in input, ( - "Spaces in action input filenames not supported (%s)" % input) - for output in outputs: - assert ' ' not in output, ( - "Spaces in action output filenames not supported (%s)" % output) - - self.WriteLn('%s: %s $(GYP_TARGET_DEPENDENCIES)' % - (main_output, ' '.join(map(self.LocalPathify, inputs)))) - self.WriteLn('\t@echo "%s"' % quiet_cmd) - self.WriteLn('\t$(hide)%s\n' % command) - for output in outputs[1:]: - # Make each output depend on the main output, with an empty command - # to force make to notice that the mtime has changed. - self.WriteLn('%s: %s ;' % (self.LocalPathify(output), main_output)) - - extra_outputs += outputs - self.WriteLn() - - self.WriteLn() - - - def WriteRules(self, rules, extra_sources, extra_outputs): - """Write Makefile code for any 'rules' from the gyp input. - - extra_sources: a list that will be filled in with newly generated source - files, if any - extra_outputs: a list that will be filled in with any outputs of these - rules (used to make other pieces dependent on these rules) - """ - if len(rules) == 0: - return - rule_trigger = '%s_rule_trigger' % self.android_module - - did_write_rule = False - for rule in rules: - if len(rule.get('rule_sources', [])) == 0: - continue - did_write_rule = True - name = make.StringToMakefileVariable('%s_%s' % (self.relative_target, - rule['rule_name'])) - self.WriteLn('\n### Generated for rule "%s":' % name) - self.WriteLn('# "%s":' % rule) - - inputs = rule.get('inputs') - for rule_source in rule.get('rule_sources', []): - (rule_source_dirname, rule_source_basename) = os.path.split(rule_source) - (rule_source_root, rule_source_ext) = \ - os.path.splitext(rule_source_basename) - - outputs = [self.ExpandInputRoot(out, rule_source_root, - rule_source_dirname) - for out in rule['outputs']] - - dirs = set() - for out in outputs: - if not out.startswith('$'): - print ('WARNING: Rule for target %s writes output to local path %s' - % (self.target, out)) - dir = os.path.dirname(out) - if dir: - dirs.add(dir) - extra_outputs += outputs - if int(rule.get('process_outputs_as_sources', False)): - extra_sources.extend(outputs) - - components = [] - for component in rule['action']: - component = self.ExpandInputRoot(component, rule_source_root, - rule_source_dirname) - if '$(RULE_SOURCES)' in component: - component = component.replace('$(RULE_SOURCES)', - rule_source) - components.append(component) - - command = gyp.common.EncodePOSIXShellList(components) - cd_action = 'cd $(gyp_local_path)/%s; ' % self.path - command = cd_action + command - if dirs: - command = 'mkdir -p %s' % ' '.join(dirs) + '; ' + command - - # We set up a rule to build the first output, and then set up - # a rule for each additional output to depend on the first. - outputs = map(self.LocalPathify, outputs) - main_output = outputs[0] - self.WriteLn('%s: gyp_local_path := $(LOCAL_PATH)' % main_output) - self.WriteLn('%s: gyp_intermediate_dir := ' - '$(abspath $(gyp_intermediate_dir))' % main_output) - self.WriteLn('%s: gyp_shared_intermediate_dir := ' - '$(abspath $(gyp_shared_intermediate_dir))' % main_output) - - # See explanation in WriteActions. - self.WriteLn('%s: export PATH := ' - '$(subst $(ANDROID_BUILD_PATHS),,$(PATH))' % main_output) - - main_output_deps = self.LocalPathify(rule_source) - if inputs: - main_output_deps += ' ' - main_output_deps += ' '.join([self.LocalPathify(f) for f in inputs]) - - self.WriteLn('%s: %s $(GYP_TARGET_DEPENDENCIES)' % - (main_output, main_output_deps)) - self.WriteLn('\t%s\n' % command) - for output in outputs[1:]: - # Make each output depend on the main output, with an empty command - # to force make to notice that the mtime has changed. - self.WriteLn('%s: %s ;' % (output, main_output)) - self.WriteLn('.PHONY: %s' % (rule_trigger)) - self.WriteLn('%s: %s' % (rule_trigger, main_output)) - self.WriteLn('') - if did_write_rule: - extra_sources.append(rule_trigger) # Force all rules to run. - self.WriteLn('### Finished generating for all rules') - self.WriteLn('') - - - def WriteCopies(self, copies, extra_outputs): - """Write Makefile code for any 'copies' from the gyp input. - - extra_outputs: a list that will be filled in with any outputs of this action - (used to make other pieces dependent on this action) - """ - self.WriteLn('### Generated for copy rule.') - - variable = make.StringToMakefileVariable(self.relative_target + '_copies') - outputs = [] - for copy in copies: - for path in copy['files']: - # The Android build system does not allow generation of files into the - # source tree. The destination should start with a variable, which will - # typically be $(gyp_intermediate_dir) or - # $(gyp_shared_intermediate_dir). Note that we can't use an assertion - # because some of the gyp tests depend on this. - if not copy['destination'].startswith('$'): - print ('WARNING: Copy rule for target %s writes output to ' - 'local path %s' % (self.target, copy['destination'])) - - # LocalPathify() calls normpath, stripping trailing slashes. - path = Sourceify(self.LocalPathify(path)) - filename = os.path.split(path)[1] - output = Sourceify(self.LocalPathify(os.path.join(copy['destination'], - filename))) - - self.WriteLn('%s: %s $(GYP_TARGET_DEPENDENCIES) | $(ACP)' % - (output, path)) - self.WriteLn('\t@echo Copying: $@') - self.WriteLn('\t$(hide) mkdir -p $(dir $@)') - self.WriteLn('\t$(hide) $(ACP) -rpf $< $@') - self.WriteLn() - outputs.append(output) - self.WriteLn('%s = %s' % (variable, - ' '.join(map(make.QuoteSpaces, outputs)))) - extra_outputs.append('$(%s)' % variable) - self.WriteLn() - - - def WriteSourceFlags(self, spec, configs): - """Write out the flags and include paths used to compile source files for - the current target. - - Args: - spec, configs: input from gyp. - """ - for configname, config in sorted(configs.iteritems()): - extracted_includes = [] - - self.WriteLn('\n# Flags passed to both C and C++ files.') - cflags, includes_from_cflags = self.ExtractIncludesFromCFlags( - config.get('cflags', []) + config.get('cflags_c', [])) - extracted_includes.extend(includes_from_cflags) - self.WriteList(cflags, 'MY_CFLAGS_%s' % configname) - - self.WriteList(config.get('defines'), 'MY_DEFS_%s' % configname, - prefix='-D', quoter=make.EscapeCppDefine) - - self.WriteLn('\n# Include paths placed before CFLAGS/CPPFLAGS') - includes = list(config.get('include_dirs', [])) - includes.extend(extracted_includes) - includes = map(Sourceify, map(self.LocalPathify, includes)) - includes = self.NormalizeIncludePaths(includes) - self.WriteList(includes, 'LOCAL_C_INCLUDES_%s' % configname) - - self.WriteLn('\n# Flags passed to only C++ (and not C) files.') - self.WriteList(config.get('cflags_cc'), 'LOCAL_CPPFLAGS_%s' % configname) - - self.WriteLn('\nLOCAL_CFLAGS := $(MY_CFLAGS_$(GYP_CONFIGURATION)) ' - '$(MY_DEFS_$(GYP_CONFIGURATION))') - # Undefine ANDROID for host modules - # TODO: the source code should not use macro ANDROID to tell if it's host - # or target module. - if self.toolset == 'host': - self.WriteLn('# Undefine ANDROID for host modules') - self.WriteLn('LOCAL_CFLAGS += -UANDROID') - self.WriteLn('LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) ' - '$(LOCAL_C_INCLUDES_$(GYP_CONFIGURATION))') - self.WriteLn('LOCAL_CPPFLAGS := $(LOCAL_CPPFLAGS_$(GYP_CONFIGURATION))') - - - def WriteSources(self, spec, configs, extra_sources): - """Write Makefile code for any 'sources' from the gyp input. - These are source files necessary to build the current target. - We need to handle shared_intermediate directory source files as - a special case by copying them to the intermediate directory and - treating them as a genereated sources. Otherwise the Android build - rules won't pick them up. - - Args: - spec, configs: input from gyp. - extra_sources: Sources generated from Actions or Rules. - """ - sources = filter(make.Compilable, spec.get('sources', [])) - generated_not_sources = [x for x in extra_sources if not make.Compilable(x)] - extra_sources = filter(make.Compilable, extra_sources) - - # Determine and output the C++ extension used by these sources. - # We simply find the first C++ file and use that extension. - all_sources = sources + extra_sources - local_cpp_extension = '.cpp' - for source in all_sources: - (root, ext) = os.path.splitext(source) - if IsCPPExtension(ext): - local_cpp_extension = ext - break - if local_cpp_extension != '.cpp': - self.WriteLn('LOCAL_CPP_EXTENSION := %s' % local_cpp_extension) - - # We need to move any non-generated sources that are coming from the - # shared intermediate directory out of LOCAL_SRC_FILES and put them - # into LOCAL_GENERATED_SOURCES. We also need to move over any C++ files - # that don't match our local_cpp_extension, since Android will only - # generate Makefile rules for a single LOCAL_CPP_EXTENSION. - local_files = [] - for source in sources: - (root, ext) = os.path.splitext(source) - if '$(gyp_shared_intermediate_dir)' in source: - extra_sources.append(source) - elif '$(gyp_intermediate_dir)' in source: - extra_sources.append(source) - elif IsCPPExtension(ext) and ext != local_cpp_extension: - extra_sources.append(source) - else: - local_files.append(os.path.normpath(os.path.join(self.path, source))) - - # For any generated source, if it is coming from the shared intermediate - # directory then we add a Make rule to copy them to the local intermediate - # directory first. This is because the Android LOCAL_GENERATED_SOURCES - # must be in the local module intermediate directory for the compile rules - # to work properly. If the file has the wrong C++ extension, then we add - # a rule to copy that to intermediates and use the new version. - final_generated_sources = [] - # If a source file gets copied, we still need to add the orginal source - # directory as header search path, for GCC searches headers in the - # directory that contains the source file by default. - origin_src_dirs = [] - for source in extra_sources: - local_file = source - if not '$(gyp_intermediate_dir)/' in local_file: - basename = os.path.basename(local_file) - local_file = '$(gyp_intermediate_dir)/' + basename - (root, ext) = os.path.splitext(local_file) - if IsCPPExtension(ext) and ext != local_cpp_extension: - local_file = root + local_cpp_extension - if local_file != source: - self.WriteLn('%s: %s' % (local_file, self.LocalPathify(source))) - self.WriteLn('\tmkdir -p $(@D); cp $< $@') - origin_src_dirs.append(os.path.dirname(source)) - final_generated_sources.append(local_file) - - # We add back in all of the non-compilable stuff to make sure that the - # make rules have dependencies on them. - final_generated_sources.extend(generated_not_sources) - self.WriteList(final_generated_sources, 'LOCAL_GENERATED_SOURCES') - - origin_src_dirs = gyp.common.uniquer(origin_src_dirs) - origin_src_dirs = map(Sourceify, map(self.LocalPathify, origin_src_dirs)) - self.WriteList(origin_src_dirs, 'GYP_COPIED_SOURCE_ORIGIN_DIRS') - - self.WriteList(local_files, 'LOCAL_SRC_FILES') - - # Write out the flags used to compile the source; this must be done last - # so that GYP_COPIED_SOURCE_ORIGIN_DIRS can be used as an include path. - self.WriteSourceFlags(spec, configs) - - - def ComputeAndroidModule(self, spec): - """Return the Android module name used for a gyp spec. - - We use the complete qualified target name to avoid collisions between - duplicate targets in different directories. We also add a suffix to - distinguish gyp-generated module names. - """ - - if int(spec.get('android_unmangled_name', 0)): - assert self.type != 'shared_library' or self.target.startswith('lib') - return self.target - - if self.type == 'shared_library': - # For reasons of convention, the Android build system requires that all - # shared library modules are named 'libfoo' when generating -l flags. - prefix = 'lib_' - else: - prefix = '' - - if spec['toolset'] == 'host': - suffix = '_host_gyp' - else: - suffix = '_gyp' - - if self.path: - name = '%s%s_%s%s' % (prefix, self.path, self.target, suffix) - else: - name = '%s%s%s' % (prefix, self.target, suffix) - - return make.StringToMakefileVariable(name) - - - def ComputeOutputParts(self, spec): - """Return the 'output basename' of a gyp spec, split into filename + ext. - - Android libraries must be named the same thing as their module name, - otherwise the linker can't find them, so product_name and so on must be - ignored if we are building a library, and the "lib" prepending is - not done for Android. - """ - assert self.type != 'loadable_module' # TODO: not supported? - - target = spec['target_name'] - target_prefix = '' - target_ext = '' - if self.type == 'static_library': - target = self.ComputeAndroidModule(spec) - target_ext = '.a' - elif self.type == 'shared_library': - target = self.ComputeAndroidModule(spec) - target_ext = '.so' - elif self.type == 'none': - target_ext = '.stamp' - elif self.type != 'executable': - print ("ERROR: What output file should be generated?", - "type", self.type, "target", target) - - if self.type != 'static_library' and self.type != 'shared_library': - target_prefix = spec.get('product_prefix', target_prefix) - target = spec.get('product_name', target) - product_ext = spec.get('product_extension') - if product_ext: - target_ext = '.' + product_ext - - target_stem = target_prefix + target - return (target_stem, target_ext) - - - def ComputeOutputBasename(self, spec): - """Return the 'output basename' of a gyp spec. - - E.g., the loadable module 'foobar' in directory 'baz' will produce - 'libfoobar.so' - """ - return ''.join(self.ComputeOutputParts(spec)) - - - def ComputeOutput(self, spec): - """Return the 'output' (full output path) of a gyp spec. - - E.g., the loadable module 'foobar' in directory 'baz' will produce - '$(obj)/baz/libfoobar.so' - """ - if self.type == 'executable' and self.toolset == 'host': - # We install host executables into shared_intermediate_dir so they can be - # run by gyp rules that refer to PRODUCT_DIR. - path = '$(gyp_shared_intermediate_dir)' - elif self.type == 'shared_library': - if self.toolset == 'host': - path = '$(HOST_OUT_INTERMEDIATE_LIBRARIES)' - else: - path = '$(TARGET_OUT_INTERMEDIATE_LIBRARIES)' - else: - # Other targets just get built into their intermediate dir. - if self.toolset == 'host': - path = '$(call intermediates-dir-for,%s,%s,true)' % (self.android_class, - self.android_module) - else: - path = '$(call intermediates-dir-for,%s,%s)' % (self.android_class, - self.android_module) - - assert spec.get('product_dir') is None # TODO: not supported? - return os.path.join(path, self.ComputeOutputBasename(spec)) - - def NormalizeIncludePaths(self, include_paths): - """ Normalize include_paths. - Convert absolute paths to relative to the Android top directory; - filter out include paths that are already brought in by the Android build - system. - - Args: - include_paths: A list of unprocessed include paths. - Returns: - A list of normalized include paths. - """ - normalized = [] - for path in include_paths: - if path[0] == '/': - path = gyp.common.RelativePath(path, self.android_top_dir) - - # Filter out the Android standard search path. - if path not in android_standard_include_paths: - normalized.append(path) - return normalized - - def ExtractIncludesFromCFlags(self, cflags): - """Extract includes "-I..." out from cflags - - Args: - cflags: A list of compiler flags, which may be mixed with "-I.." - Returns: - A tuple of lists: (clean_clfags, include_paths). "-I.." is trimmed. - """ - clean_cflags = [] - include_paths = [] - for flag in cflags: - if flag.startswith('-I'): - include_paths.append(flag[2:]) - else: - clean_cflags.append(flag) - - return (clean_cflags, include_paths) - - def ComputeAndroidLibraryModuleNames(self, libraries): - """Compute the Android module names from libraries, ie spec.get('libraries') - - Args: - libraries: the value of spec.get('libraries') - Returns: - A tuple (static_lib_modules, dynamic_lib_modules) - """ - static_lib_modules = [] - dynamic_lib_modules = [] - for libs in libraries: - # Libs can have multiple words. - for lib in libs.split(): - # Filter the system libraries, which are added by default by the Android - # build system. - if (lib == '-lc' or lib == '-lstdc++' or lib == '-lm' or - lib.endswith('libgcc.a')): - continue - match = re.search(r'([^/]+)\.a$', lib) - if match: - static_lib_modules.append(match.group(1)) - continue - match = re.search(r'([^/]+)\.so$', lib) - if match: - dynamic_lib_modules.append(match.group(1)) - continue - # "-lstlport" -> libstlport - if lib.startswith('-l'): - if lib.endswith('_static'): - static_lib_modules.append('lib' + lib[2:]) - else: - dynamic_lib_modules.append('lib' + lib[2:]) - return (static_lib_modules, dynamic_lib_modules) - - - def ComputeDeps(self, spec): - """Compute the dependencies of a gyp spec. - - Returns a tuple (deps, link_deps), where each is a list of - filenames that will need to be put in front of make for either - building (deps) or linking (link_deps). - """ - deps = [] - link_deps = [] - if 'dependencies' in spec: - deps.extend([target_outputs[dep] for dep in spec['dependencies'] - if target_outputs[dep]]) - for dep in spec['dependencies']: - if dep in target_link_deps: - link_deps.append(target_link_deps[dep]) - deps.extend(link_deps) - return (gyp.common.uniquer(deps), gyp.common.uniquer(link_deps)) - - - def WriteTargetFlags(self, spec, configs, link_deps): - """Write Makefile code to specify the link flags and library dependencies. - - spec, configs: input from gyp. - link_deps: link dependency list; see ComputeDeps() - """ - for configname, config in sorted(configs.iteritems()): - ldflags = list(config.get('ldflags', [])) - self.WriteLn('') - self.WriteList(ldflags, 'LOCAL_LDFLAGS_%s' % configname) - self.WriteLn('\nLOCAL_LDFLAGS := $(LOCAL_LDFLAGS_$(GYP_CONFIGURATION))') - - # Libraries (i.e. -lfoo) - libraries = gyp.common.uniquer(spec.get('libraries', [])) - static_libs, dynamic_libs = self.ComputeAndroidLibraryModuleNames( - libraries) - - # Link dependencies (i.e. libfoo.a, libfoo.so) - static_link_deps = [x[1] for x in link_deps if x[0] == 'static'] - shared_link_deps = [x[1] for x in link_deps if x[0] == 'shared'] - self.WriteLn('') - self.WriteList(static_libs + static_link_deps, - 'LOCAL_STATIC_LIBRARIES') - self.WriteLn('# Enable grouping to fix circular references') - self.WriteLn('LOCAL_GROUP_STATIC_LIBRARIES := true') - self.WriteLn('') - self.WriteList(dynamic_libs + shared_link_deps, - 'LOCAL_SHARED_LIBRARIES') - - - def WriteTarget(self, spec, configs, deps, link_deps, part_of_all): - """Write Makefile code to produce the final target of the gyp spec. - - spec, configs: input from gyp. - deps, link_deps: dependency lists; see ComputeDeps() - part_of_all: flag indicating this target is part of 'all' - """ - self.WriteLn('### Rules for final target.') - - if self.type != 'none': - self.WriteTargetFlags(spec, configs, link_deps) - - # Add to the set of targets which represent the gyp 'all' target. We use the - # name 'gyp_all_modules' as the Android build system doesn't allow the use - # of the Make target 'all' and because 'all_modules' is the equivalent of - # the Make target 'all' on Android. - if part_of_all: - self.WriteLn('# Add target alias to "gyp_all_modules" target.') - self.WriteLn('.PHONY: gyp_all_modules') - self.WriteLn('gyp_all_modules: %s' % self.android_module) - self.WriteLn('') - - # Add an alias from the gyp target name to the Android module name. This - # simplifies manual builds of the target, and is required by the test - # framework. - if self.target != self.android_module: - self.WriteLn('# Alias gyp target name.') - self.WriteLn('.PHONY: %s' % self.target) - self.WriteLn('%s: %s' % (self.target, self.android_module)) - self.WriteLn('') - - # Add the command to trigger build of the target type depending - # on the toolset. Ex: BUILD_STATIC_LIBRARY vs. BUILD_HOST_STATIC_LIBRARY - # NOTE: This has to come last! - modifier = '' - if self.toolset == 'host': - modifier = 'HOST_' - if self.type == 'static_library': - self.WriteLn('include $(BUILD_%sSTATIC_LIBRARY)' % modifier) - elif self.type == 'shared_library': - self.WriteLn('LOCAL_PRELINK_MODULE := false') - self.WriteLn('include $(BUILD_%sSHARED_LIBRARY)' % modifier) - elif self.type == 'executable': - if self.toolset == 'host': - self.WriteLn('LOCAL_MODULE_PATH := $(gyp_shared_intermediate_dir)') - else: - # Don't install target executables for now, as it results in them being - # included in ROM. This can be revisited if there's a reason to install - # them later. - self.WriteLn('LOCAL_UNINSTALLABLE_MODULE := true') - self.WriteLn('include $(BUILD_%sEXECUTABLE)' % modifier) - else: - self.WriteLn('LOCAL_MODULE_PATH := $(PRODUCT_OUT)/gyp_stamp') - self.WriteLn('LOCAL_UNINSTALLABLE_MODULE := true') - self.WriteLn() - self.WriteLn('include $(BUILD_SYSTEM)/base_rules.mk') - self.WriteLn() - self.WriteLn('$(LOCAL_BUILT_MODULE): $(LOCAL_ADDITIONAL_DEPENDENCIES)') - self.WriteLn('\t$(hide) echo "Gyp timestamp: $@"') - self.WriteLn('\t$(hide) mkdir -p $(dir $@)') - self.WriteLn('\t$(hide) touch $@') - - - def WriteList(self, value_list, variable=None, prefix='', - quoter=make.QuoteIfNecessary, local_pathify=False): - """Write a variable definition that is a list of values. - - E.g. WriteList(['a','b'], 'foo', prefix='blah') writes out - foo = blaha blahb - but in a pretty-printed style. - """ - values = '' - if value_list: - value_list = [quoter(prefix + l) for l in value_list] - if local_pathify: - value_list = [self.LocalPathify(l) for l in value_list] - values = ' \\\n\t' + ' \\\n\t'.join(value_list) - self.fp.write('%s :=%s\n\n' % (variable, values)) - - - def WriteLn(self, text=''): - self.fp.write(text + '\n') - - - def LocalPathify(self, path): - """Convert a subdirectory-relative path into a normalized path which starts - with the make variable $(LOCAL_PATH) (i.e. the top of the project tree). - Absolute paths, or paths that contain variables, are just normalized.""" - if '$(' in path or os.path.isabs(path): - # path is not a file in the project tree in this case, but calling - # normpath is still important for trimming trailing slashes. - return os.path.normpath(path) - local_path = os.path.join('$(LOCAL_PATH)', self.path, path) - local_path = os.path.normpath(local_path) - # Check that normalizing the path didn't ../ itself out of $(LOCAL_PATH) - # - i.e. that the resulting path is still inside the project tree. The - # path may legitimately have ended up containing just $(LOCAL_PATH), though, - # so we don't look for a slash. - assert local_path.startswith('$(LOCAL_PATH)'), ( - 'Path %s attempts to escape from gyp path %s !)' % (path, self.path)) - return local_path - - - def ExpandInputRoot(self, template, expansion, dirname): - if '%(INPUT_ROOT)s' not in template and '%(INPUT_DIRNAME)s' not in template: - return template - path = template % { - 'INPUT_ROOT': expansion, - 'INPUT_DIRNAME': dirname, - } - return path - - -def PerformBuild(data, configurations, params): - # The android backend only supports the default configuration. - options = params['options'] - makefile = os.path.abspath(os.path.join(options.toplevel_dir, - 'GypAndroid.mk')) - env = dict(os.environ) - env['ONE_SHOT_MAKEFILE'] = makefile - arguments = ['make', '-C', os.environ['ANDROID_BUILD_TOP'], 'gyp_all_modules'] - print 'Building: %s' % arguments - subprocess.check_call(arguments, env=env) - - -def GenerateOutput(target_list, target_dicts, data, params): - options = params['options'] - generator_flags = params.get('generator_flags', {}) - builddir_name = generator_flags.get('output_dir', 'out') - limit_to_target_all = generator_flags.get('limit_to_target_all', False) - android_top_dir = os.environ.get('ANDROID_BUILD_TOP') - assert android_top_dir, '$ANDROID_BUILD_TOP not set; you need to run lunch.' - - def CalculateMakefilePath(build_file, base_name): - """Determine where to write a Makefile for a given gyp file.""" - # Paths in gyp files are relative to the .gyp file, but we want - # paths relative to the source root for the master makefile. Grab - # the path of the .gyp file as the base to relativize against. - # E.g. "foo/bar" when we're constructing targets for "foo/bar/baz.gyp". - base_path = gyp.common.RelativePath(os.path.dirname(build_file), - options.depth) - # We write the file in the base_path directory. - output_file = os.path.join(options.depth, base_path, base_name) - assert not options.generator_output, ( - 'The Android backend does not support options.generator_output.') - base_path = gyp.common.RelativePath(os.path.dirname(build_file), - options.toplevel_dir) - return base_path, output_file - - # TODO: search for the first non-'Default' target. This can go - # away when we add verification that all targets have the - # necessary configurations. - default_configuration = None - toolsets = set([target_dicts[target]['toolset'] for target in target_list]) - for target in target_list: - spec = target_dicts[target] - if spec['default_configuration'] != 'Default': - default_configuration = spec['default_configuration'] - break - if not default_configuration: - default_configuration = 'Default' - - srcdir = '.' - makefile_name = 'GypAndroid' + options.suffix + '.mk' - makefile_path = os.path.join(options.toplevel_dir, makefile_name) - assert not options.generator_output, ( - 'The Android backend does not support options.generator_output.') - gyp.common.EnsureDirExists(makefile_path) - root_makefile = open(makefile_path, 'w') - - root_makefile.write(header) - - # We set LOCAL_PATH just once, here, to the top of the project tree. This - # allows all the other paths we use to be relative to the Android.mk file, - # as the Android build system expects. - root_makefile.write('\nLOCAL_PATH := $(call my-dir)\n') - - # Find the list of targets that derive from the gyp file(s) being built. - needed_targets = set() - for build_file in params['build_files']: - for target in gyp.common.AllTargets(target_list, target_dicts, build_file): - needed_targets.add(target) - - build_files = set() - include_list = set() - android_modules = {} - for qualified_target in target_list: - build_file, target, toolset = gyp.common.ParseQualifiedTarget( - qualified_target) - relative_build_file = gyp.common.RelativePath(build_file, - options.toplevel_dir) - build_files.add(relative_build_file) - included_files = data[build_file]['included_files'] - for included_file in included_files: - # The included_files entries are relative to the dir of the build file - # that included them, so we have to undo that and then make them relative - # to the root dir. - relative_include_file = gyp.common.RelativePath( - gyp.common.UnrelativePath(included_file, build_file), - options.toplevel_dir) - abs_include_file = os.path.abspath(relative_include_file) - # If the include file is from the ~/.gyp dir, we should use absolute path - # so that relocating the src dir doesn't break the path. - if (params['home_dot_gyp'] and - abs_include_file.startswith(params['home_dot_gyp'])): - build_files.add(abs_include_file) - else: - build_files.add(relative_include_file) - - base_path, output_file = CalculateMakefilePath(build_file, - target + '.' + toolset + options.suffix + '.mk') - - spec = target_dicts[qualified_target] - configs = spec['configurations'] - - part_of_all = (qualified_target in needed_targets and - not int(spec.get('suppress_wildcard', False))) - if limit_to_target_all and not part_of_all: - continue - - relative_target = gyp.common.QualifiedTarget(relative_build_file, target, - toolset) - writer = AndroidMkWriter(android_top_dir) - android_module = writer.Write(qualified_target, relative_target, base_path, - output_file, spec, configs, - part_of_all=part_of_all) - if android_module in android_modules: - print ('ERROR: Android module names must be unique. The following ' - 'targets both generate Android module name %s.\n %s\n %s' % - (android_module, android_modules[android_module], - qualified_target)) - return - android_modules[android_module] = qualified_target - - # Our root_makefile lives at the source root. Compute the relative path - # from there to the output_file for including. - mkfile_rel_path = gyp.common.RelativePath(output_file, - os.path.dirname(makefile_path)) - include_list.add(mkfile_rel_path) - - root_makefile.write('GYP_CONFIGURATION ?= %s\n' % default_configuration) - - # Write out the sorted list of includes. - root_makefile.write('\n') - for include_file in sorted(include_list): - root_makefile.write('include $(LOCAL_PATH)/' + include_file + '\n') - root_makefile.write('\n') - - root_makefile.write(SHARED_FOOTER) - - root_makefile.close() diff --git a/third_party/gyp/generator/cmake.py b/third_party/gyp/generator/cmake.py index 10d015ee..a2b96291 100644 --- a/third_party/gyp/generator/cmake.py +++ b/third_party/gyp/generator/cmake.py @@ -34,6 +34,7 @@ import signal import string import subprocess import gyp.common +import gyp.xcode_emulation generator_default_variables = { 'EXECUTABLE_PREFIX': '', @@ -55,7 +56,7 @@ generator_default_variables = { 'CONFIGURATION_NAME': '${configuration}', } -FULL_PATH_VARS = ('${CMAKE_SOURCE_DIR}', '${builddir}', '${obj}') +FULL_PATH_VARS = ('${CMAKE_CURRENT_LIST_DIR}', '${builddir}', '${obj}') generator_supports_multiple_toolsets = True generator_wants_static_library_dependencies_adjusted = True @@ -103,7 +104,7 @@ def NormjoinPathForceCMakeSource(base_path, rel_path): if any([rel_path.startswith(var) for var in FULL_PATH_VARS]): return rel_path # TODO: do we need to check base_path for absolute variables as well? - return os.path.join('${CMAKE_SOURCE_DIR}', + return os.path.join('${CMAKE_CURRENT_LIST_DIR}', os.path.normpath(os.path.join(base_path, rel_path))) @@ -150,20 +151,17 @@ def SetFileProperty(output, source_name, property_name, values, sep): output.write('")\n') -def SetFilesProperty(output, source_names, property_name, values, sep): +def SetFilesProperty(output, variable, property_name, values, sep): """Given a set of source files, sets the given property on them.""" - output.write('set_source_files_properties(\n') - for source_name in source_names: - output.write(' ') - output.write(source_name) - output.write('\n') - output.write(' PROPERTIES\n ') + output.write('set_source_files_properties(') + WriteVariable(output, variable) + output.write(' PROPERTIES ') output.write(property_name) output.write(' "') for value in values: output.write(CMakeStringEscape(value)) output.write(sep) - output.write('"\n)\n') + output.write('")\n') def SetTargetProperty(output, target_name, property_name, values, sep=''): @@ -216,7 +214,7 @@ def WriteVariable(output, variable_name, prepend=None): output.write('}') -class CMakeTargetType: +class CMakeTargetType(object): def __init__(self, command, modifier, property_modifier): self.command = command self.modifier = modifier @@ -236,11 +234,11 @@ def StringToCMakeTargetName(a): """Converts the given string 'a' to a valid CMake target name. All invalid characters are replaced by '_'. - Invalid for cmake: ' ', '/', '(', ')' + Invalid for cmake: ' ', '/', '(', ')', '"' Invalid for make: ':' Invalid for unknown reasons but cause failures: '.' """ - return a.translate(string.maketrans(' /():.', '______')) + return a.translate(string.maketrans(' /():."', '_______')) def WriteActions(target_name, actions, extra_sources, extra_deps, @@ -296,7 +294,7 @@ def WriteActions(target_name, actions, extra_sources, extra_deps, WriteVariable(output, inputs_name) output.write('\n') - output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') + output.write(' WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') output.write(path_to_gyp) output.write('\n') @@ -401,9 +399,9 @@ def WriteRules(target_name, rules, extra_sources, extra_deps, output.write(NormjoinPath(path_to_gyp, rule_source)) output.write('\n') - # CMAKE_SOURCE_DIR is where the CMakeLists.txt lives. + # CMAKE_CURRENT_LIST_DIR is where the CMakeLists.txt lives. # The cwd is the current build directory. - output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') + output.write(' WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') output.write(path_to_gyp) output.write('\n') @@ -464,7 +462,7 @@ def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output): extra_deps.append(copy_name) return - class Copy: + class Copy(object): def __init__(self, ext, command): self.cmake_inputs = [] self.cmake_outputs = [] @@ -488,7 +486,7 @@ def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output): copy = file_copy if os.path.basename(src) else dir_copy - copy.cmake_inputs.append(NormjoinPath(path_to_gyp, src)) + copy.cmake_inputs.append(NormjoinPathForceCMakeSource(path_to_gyp, src)) copy.cmake_outputs.append(NormjoinPathForceCMakeSource(path_to_gyp, dst)) copy.gyp_inputs.append(src) copy.gyp_outputs.append(dst) @@ -525,7 +523,7 @@ def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output): WriteVariable(output, copy.inputs_name, ' ') output.write('\n') - output.write('WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') + output.write('WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') output.write(path_to_gyp) output.write('\n') @@ -611,8 +609,8 @@ class CMakeNamer(object): def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, - options, generator_flags, all_qualified_targets, output): - + options, generator_flags, all_qualified_targets, flavor, + output): # The make generator does this always. # TODO: It would be nice to be able to tell CMake all dependencies. circular_libs = generator_flags.get('circular', True) @@ -636,10 +634,20 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, spec = target_dicts.get(qualified_target, {}) config = spec.get('configurations', {}).get(config_to_use, {}) + xcode_settings = None + if flavor == 'mac': + xcode_settings = gyp.xcode_emulation.XcodeSettings(spec) + target_name = spec.get('target_name', '') target_type = spec.get('type', '') target_toolset = spec.get('toolset') + cmake_target_type = cmake_target_type_from_gyp_target_type.get(target_type) + if cmake_target_type is None: + print ('Target %s has unknown target type %s, skipping.' % + ( target_name, target_type ) ) + return + SetVariable(output, 'TARGET', target_name) SetVariable(output, 'TOOLSET', target_toolset) @@ -667,27 +675,89 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, srcs = spec.get('sources', []) # Gyp separates the sheep from the goats based on file extensions. - def partition(l, p): - return reduce(lambda x, e: x[not p(e)].append(e) or x, l, ([], [])) - compilable_srcs, other_srcs = partition(srcs, Compilable) + # A full separation is done here because of flag handing (see below). + s_sources = [] + c_sources = [] + cxx_sources = [] + linkable_sources = [] + other_sources = [] + for src in srcs: + _, ext = os.path.splitext(src) + src_type = COMPILABLE_EXTENSIONS.get(ext, None) + src_norm_path = NormjoinPath(path_from_cmakelists_to_gyp, src); + + if src_type == 's': + s_sources.append(src_norm_path) + elif src_type == 'cc': + c_sources.append(src_norm_path) + elif src_type == 'cxx': + cxx_sources.append(src_norm_path) + elif Linkable(ext): + linkable_sources.append(src_norm_path) + else: + other_sources.append(src_norm_path) + + for extra_source in extra_sources: + src, real_source = extra_source + _, ext = os.path.splitext(real_source) + src_type = COMPILABLE_EXTENSIONS.get(ext, None) + + if src_type == 's': + s_sources.append(src) + elif src_type == 'cc': + c_sources.append(src) + elif src_type == 'cxx': + cxx_sources.append(src) + elif Linkable(ext): + linkable_sources.append(src) + else: + other_sources.append(src) + + s_sources_name = None + if s_sources: + s_sources_name = cmake_target_name + '__asm_srcs' + SetVariableList(output, s_sources_name, s_sources) + + c_sources_name = None + if c_sources: + c_sources_name = cmake_target_name + '__c_srcs' + SetVariableList(output, c_sources_name, c_sources) + + cxx_sources_name = None + if cxx_sources: + cxx_sources_name = cmake_target_name + '__cxx_srcs' + SetVariableList(output, cxx_sources_name, cxx_sources) + + linkable_sources_name = None + if linkable_sources: + linkable_sources_name = cmake_target_name + '__linkable_srcs' + SetVariableList(output, linkable_sources_name, linkable_sources) + + other_sources_name = None + if other_sources: + other_sources_name = cmake_target_name + '__other_srcs' + SetVariableList(output, other_sources_name, other_sources) # CMake gets upset when executable targets provide no sources. - if target_type == 'executable' and not compilable_srcs and not extra_sources: - print ('Executable %s has no complilable sources, treating as "none".' % - target_name ) - target_type = 'none' + # http://www.cmake.org/pipermail/cmake/2010-July/038461.html + dummy_sources_name = None + has_sources = (s_sources_name or + c_sources_name or + cxx_sources_name or + linkable_sources_name or + other_sources_name) + if target_type == 'executable' and not has_sources: + dummy_sources_name = cmake_target_name + '__dummy_srcs' + SetVariable(output, dummy_sources_name, + "${obj}.${TOOLSET}/${TARGET}/genc/dummy.c") + output.write('if(NOT EXISTS "') + WriteVariable(output, dummy_sources_name) + output.write('")\n') + output.write(' file(WRITE "') + WriteVariable(output, dummy_sources_name) + output.write('" "")\n') + output.write("endif()\n") - cmake_target_type = cmake_target_type_from_gyp_target_type.get(target_type) - if cmake_target_type is None: - print ('Target %s has unknown target type %s, skipping.' % - ( target_name, target_type ) ) - return - - other_srcs_name = None - if other_srcs: - other_srcs_name = cmake_target_name + '__other_srcs' - SetVariableList(output, other_srcs_name, - [NormjoinPath(path_from_cmakelists_to_gyp, src) for src in other_srcs]) # CMake is opposed to setting linker directories and considers the practice # of setting linker directories dangerous. Instead, it favors the use of @@ -713,37 +783,54 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, output.write(' ') output.write(cmake_target_type.modifier) - if other_srcs_name: - WriteVariable(output, other_srcs_name, ' ') - - output.write('\n') - - for src in compilable_srcs: - output.write(' ') - output.write(NormjoinPath(path_from_cmakelists_to_gyp, src)) - output.write('\n') - for extra_source in extra_sources: - output.write(' ') - src, _ = extra_source - output.write(NormjoinPath(path_from_cmakelists_to_gyp, src)) - output.write('\n') + if s_sources_name: + WriteVariable(output, s_sources_name, ' ') + if c_sources_name: + WriteVariable(output, c_sources_name, ' ') + if cxx_sources_name: + WriteVariable(output, cxx_sources_name, ' ') + if linkable_sources_name: + WriteVariable(output, linkable_sources_name, ' ') + if other_sources_name: + WriteVariable(output, other_sources_name, ' ') + if dummy_sources_name: + WriteVariable(output, dummy_sources_name, ' ') output.write(')\n') + # Let CMake know if the 'all' target should depend on this target. + exclude_from_all = ('TRUE' if qualified_target not in all_qualified_targets + else 'FALSE') + SetTargetProperty(output, cmake_target_name, + 'EXCLUDE_FROM_ALL', exclude_from_all) + for extra_target_name in extra_deps: + SetTargetProperty(output, extra_target_name, + 'EXCLUDE_FROM_ALL', exclude_from_all) + # Output name and location. if target_type != 'none': + # Link as 'C' if there are no other files + if not c_sources and not cxx_sources: + SetTargetProperty(output, cmake_target_name, 'LINKER_LANGUAGE', ['C']) + # Mark uncompiled sources as uncompiled. - if other_srcs_name: + if other_sources_name: output.write('set_source_files_properties(') - WriteVariable(output, other_srcs_name, '') + WriteVariable(output, other_sources_name, '') output.write(' PROPERTIES HEADER_FILE_ONLY "TRUE")\n') + # Mark object sources as linkable. + if linkable_sources_name: + output.write('set_source_files_properties(') + WriteVariable(output, other_sources_name, '') + output.write(' PROPERTIES EXTERNAL_OBJECT "TRUE")\n') + # Output directory target_output_directory = spec.get('product_dir') if target_output_directory is None: if target_type in ('executable', 'loadable_module'): target_output_directory = generator_default_variables['PRODUCT_DIR'] - elif target_type in ('shared_library'): + elif target_type == 'shared_library': target_output_directory = '${builddir}/lib.${TOOLSET}' elif spec.get('standalone_static_library', False): target_output_directory = generator_default_variables['PRODUCT_DIR'] @@ -804,122 +891,98 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, cmake_target_output_basename) SetFileProperty(output, cmake_target_output, 'GENERATED', ['TRUE'], '') - # Let CMake know if the 'all' target should depend on this target. - exclude_from_all = ('TRUE' if qualified_target not in all_qualified_targets - else 'FALSE') - SetTargetProperty(output, cmake_target_name, - 'EXCLUDE_FROM_ALL', exclude_from_all) - for extra_target_name in extra_deps: - SetTargetProperty(output, extra_target_name, - 'EXCLUDE_FROM_ALL', exclude_from_all) + # Includes + includes = config.get('include_dirs') + if includes: + # This (target include directories) is what requires CMake 2.8.8 + includes_name = cmake_target_name + '__include_dirs' + SetVariableList(output, includes_name, + [NormjoinPathForceCMakeSource(path_from_cmakelists_to_gyp, include) + for include in includes]) + output.write('set_property(TARGET ') + output.write(cmake_target_name) + output.write(' APPEND PROPERTY INCLUDE_DIRECTORIES ') + WriteVariable(output, includes_name, '') + output.write(')\n') - # Includes - includes = config.get('include_dirs') - if includes: - # This (target include directories) is what requires CMake 2.8.8 - includes_name = cmake_target_name + '__include_dirs' - SetVariableList(output, includes_name, - [NormjoinPathForceCMakeSource(path_from_cmakelists_to_gyp, include) - for include in includes]) - output.write('set_property(TARGET ') - output.write(cmake_target_name) - output.write(' APPEND PROPERTY INCLUDE_DIRECTORIES ') - WriteVariable(output, includes_name, '') - output.write(')\n') - - # Defines - defines = config.get('defines') - if defines is not None: - SetTargetProperty(output, + # Defines + defines = config.get('defines') + if defines is not None: + SetTargetProperty(output, cmake_target_name, 'COMPILE_DEFINITIONS', defines, ';') - # Compile Flags - http://www.cmake.org/Bug/view.php?id=6493 - # CMake currently does not have target C and CXX flags. - # So, instead of doing... + # Compile Flags - http://www.cmake.org/Bug/view.php?id=6493 + # CMake currently does not have target C and CXX flags. + # So, instead of doing... - # cflags_c = config.get('cflags_c') - # if cflags_c is not None: - # SetTargetProperty(output, cmake_target_name, - # 'C_COMPILE_FLAGS', cflags_c, ' ') + # cflags_c = config.get('cflags_c') + # if cflags_c is not None: + # SetTargetProperty(output, cmake_target_name, + # 'C_COMPILE_FLAGS', cflags_c, ' ') - # cflags_cc = config.get('cflags_cc') - # if cflags_cc is not None: - # SetTargetProperty(output, cmake_target_name, - # 'CXX_COMPILE_FLAGS', cflags_cc, ' ') + # cflags_cc = config.get('cflags_cc') + # if cflags_cc is not None: + # SetTargetProperty(output, cmake_target_name, + # 'CXX_COMPILE_FLAGS', cflags_cc, ' ') - # Instead we must... - s_sources = [] - c_sources = [] - cxx_sources = [] - for src in srcs: - _, ext = os.path.splitext(src) - src_type = COMPILABLE_EXTENSIONS.get(ext, None) + # Instead we must... + cflags = config.get('cflags', []) + cflags_c = config.get('cflags_c', []) + cflags_cxx = config.get('cflags_cc', []) + if xcode_settings: + cflags = xcode_settings.GetCflags(config_to_use) + cflags_c = xcode_settings.GetCflagsC(config_to_use) + cflags_cxx = xcode_settings.GetCflagsCC(config_to_use) + #cflags_objc = xcode_settings.GetCflagsObjC(config_to_use) + #cflags_objcc = xcode_settings.GetCflagsObjCC(config_to_use) - if src_type == 's': - s_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) + if (not cflags_c or not c_sources) and (not cflags_cxx or not cxx_sources): + SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', cflags, ' ') - if src_type == 'cc': - c_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) - - if src_type == 'cxx': - cxx_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) - - for extra_source in extra_sources: - src, real_source = extra_source - _, ext = os.path.splitext(real_source) - src_type = COMPILABLE_EXTENSIONS.get(ext, None) - - if src_type == 's': - s_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) - - if src_type == 'cc': - c_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) - - if src_type == 'cxx': - cxx_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) - - cflags = config.get('cflags', []) - cflags_c = config.get('cflags_c', []) - cflags_cxx = config.get('cflags_cc', []) - if c_sources and not (s_sources or cxx_sources): - flags = [] - flags.extend(cflags) - flags.extend(cflags_c) - SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') - - elif cxx_sources and not (s_sources or c_sources): - flags = [] - flags.extend(cflags) - flags.extend(cflags_cxx) - SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') - - else: - if s_sources and cflags: - SetFilesProperty(output, s_sources, 'COMPILE_FLAGS', cflags, ' ') - - if c_sources and (cflags or cflags_c): + elif c_sources and not (s_sources or cxx_sources): flags = [] flags.extend(cflags) flags.extend(cflags_c) - SetFilesProperty(output, c_sources, 'COMPILE_FLAGS', flags, ' ') + SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') - if cxx_sources and (cflags or cflags_cxx): + elif cxx_sources and not (s_sources or c_sources): flags = [] flags.extend(cflags) flags.extend(cflags_cxx) - SetFilesProperty(output, cxx_sources, 'COMPILE_FLAGS', flags, ' ') + SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') - # Have assembly link as c if there are no other files - if not c_sources and not cxx_sources and s_sources: - SetTargetProperty(output, cmake_target_name, 'LINKER_LANGUAGE', ['C']) + else: + # TODO: This is broken, one cannot generally set properties on files, + # as other targets may require different properties on the same files. + if s_sources and cflags: + SetFilesProperty(output, s_sources_name, 'COMPILE_FLAGS', cflags, ' ') - # Linker flags - ldflags = config.get('ldflags') - if ldflags is not None: - SetTargetProperty(output, cmake_target_name, 'LINK_FLAGS', ldflags, ' ') + if c_sources and (cflags or cflags_c): + flags = [] + flags.extend(cflags) + flags.extend(cflags_c) + SetFilesProperty(output, c_sources_name, 'COMPILE_FLAGS', flags, ' ') + + if cxx_sources and (cflags or cflags_cxx): + flags = [] + flags.extend(cflags) + flags.extend(cflags_cxx) + SetFilesProperty(output, cxx_sources_name, 'COMPILE_FLAGS', flags, ' ') + + # Linker flags + ldflags = config.get('ldflags') + if ldflags is not None: + SetTargetProperty(output, cmake_target_name, 'LINK_FLAGS', ldflags, ' ') + + # XCode settings + xcode_settings = config.get('xcode_settings', {}) + for xcode_setting, xcode_value in xcode_settings.viewitems(): + SetTargetProperty(output, cmake_target_name, + "XCODE_ATTRIBUTE_%s" % xcode_setting, xcode_value, + '' if isinstance(xcode_value, str) else ' ') # Note on Dependencies and Libraries: # CMake wants to handle link order, resolving the link line up front. @@ -985,7 +1048,7 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, output.write(cmake_target_name) output.write('\n') if static_deps: - write_group = circular_libs and len(static_deps) > 1 + write_group = circular_libs and len(static_deps) > 1 and flavor != 'mac' if write_group: output.write('-Wl,--start-group\n') for dep in gyp.common.uniquer(static_deps): @@ -1001,9 +1064,9 @@ def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, output.write('\n') if external_libs: for lib in gyp.common.uniquer(external_libs): - output.write(' ') - output.write(lib) - output.write('\n') + output.write(' "') + output.write(RemovePrefix(lib, "$(SDKROOT)")) + output.write('"\n') output.write(')\n') @@ -1015,6 +1078,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_to_use): options = params['options'] generator_flags = params['generator_flags'] + flavor = gyp.common.GetFlavor(params) # generator_dir: relative path from pwd to where make puts build files. # Makes migrating from make to cmake easier, cmake doesn't put anything here. @@ -1040,20 +1104,49 @@ def GenerateOutputForConfig(target_list, target_dicts, data, output.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n') output.write('cmake_policy(VERSION 2.8.8)\n') - _, project_target, _ = gyp.common.ParseQualifiedTarget(target_list[-1]) + gyp_file, project_target, _ = gyp.common.ParseQualifiedTarget(target_list[-1]) output.write('project(') output.write(project_target) output.write(')\n') SetVariable(output, 'configuration', config_to_use) + ar = None + cc = None + cxx = None + + make_global_settings = data[gyp_file].get('make_global_settings', []) + build_to_top = gyp.common.InvertRelativePath(build_dir, + options.toplevel_dir) + for key, value in make_global_settings: + if key == 'AR': + ar = os.path.join(build_to_top, value) + if key == 'CC': + cc = os.path.join(build_to_top, value) + if key == 'CXX': + cxx = os.path.join(build_to_top, value) + + ar = gyp.common.GetEnvironFallback(['AR_target', 'AR'], ar) + cc = gyp.common.GetEnvironFallback(['CC_target', 'CC'], cc) + cxx = gyp.common.GetEnvironFallback(['CXX_target', 'CXX'], cxx) + + if ar: + SetVariable(output, 'CMAKE_AR', ar) + if cc: + SetVariable(output, 'CMAKE_C_COMPILER', cc) + if cxx: + SetVariable(output, 'CMAKE_CXX_COMPILER', cxx) + # The following appears to be as-yet undocumented. # http://public.kitware.com/Bug/view.php?id=8392 output.write('enable_language(ASM)\n') # ASM-ATT does not support .S files. # output.write('enable_language(ASM-ATT)\n') - SetVariable(output, 'builddir', '${CMAKE_BINARY_DIR}') + if cc: + SetVariable(output, 'CMAKE_ASM_COMPILER', cc) + + SetVariable(output, 'builddir', '${CMAKE_CURRENT_BINARY_DIR}') SetVariable(output, 'obj', '${builddir}/obj') output.write('\n') @@ -1066,6 +1159,13 @@ def GenerateOutputForConfig(target_list, target_dicts, data, output.write('set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)\n') output.write('\n') + # Force ninja to use rsp files. Otherwise link and ar lines can get too long, + # resulting in 'Argument list too long' errors. + # However, rsp files don't work correctly on Mac. + if flavor != 'mac': + output.write('set(CMAKE_NINJA_FORCE_RESPONSE_FILE 1)\n') + output.write('\n') + namer = CMakeNamer(target_list) # The list of targets upon which the 'all' target should depend. @@ -1078,8 +1178,13 @@ def GenerateOutputForConfig(target_list, target_dicts, data, all_qualified_targets.add(qualified_target) for qualified_target in target_list: + if flavor == 'mac': + gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target) + spec = target_dicts[qualified_target] + gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(data[gyp_file], spec) + WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, - options, generator_flags, all_qualified_targets, output) + options, generator_flags, all_qualified_targets, flavor, output) output.close() diff --git a/third_party/gyp/generator/dump_dependency_json.py b/third_party/gyp/generator/dump_dependency_json.py index 927ba6eb..160eafe2 100644 --- a/third_party/gyp/generator/dump_dependency_json.py +++ b/third_party/gyp/generator/dump_dependency_json.py @@ -14,6 +14,9 @@ generator_supports_multiple_toolsets = True generator_wants_static_library_dependencies_adjusted = False +generator_filelist_paths = { +} + generator_default_variables = { } for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR', @@ -56,6 +59,17 @@ def CalculateGeneratorInputInfo(params): global generator_wants_static_library_dependencies_adjusted generator_wants_static_library_dependencies_adjusted = True + toplevel = params['options'].toplevel_dir + generator_dir = os.path.relpath(params['options'].generator_output or '.') + # output_dir: relative path from generator_dir to the build directory. + output_dir = generator_flags.get('output_dir', 'out') + qualified_out_dir = os.path.normpath(os.path.join( + toplevel, generator_dir, output_dir, 'gypfiles')) + global generator_filelist_paths + generator_filelist_paths = { + 'toplevel': toplevel, + 'qualified_out_dir': qualified_out_dir, + } def GenerateOutput(target_list, target_dicts, data, params): # Map of target -> list of targets it depends on. @@ -74,7 +88,11 @@ def GenerateOutput(target_list, target_dicts, data, params): edges[target].append(dep) targets_to_visit.append(dep) - filename = 'dump.json' + try: + filepath = params['generator_flags']['output_dir'] + except KeyError: + filepath = '.' + filename = os.path.join(filepath, 'dump.json') f = open(filename, 'w') json.dump(edges, f) f.close() diff --git a/third_party/gyp/generator/eclipse.py b/third_party/gyp/generator/eclipse.py index 8d08f57e..3544347b 100644 --- a/third_party/gyp/generator/eclipse.py +++ b/third_party/gyp/generator/eclipse.py @@ -24,6 +24,7 @@ import gyp import gyp.common import gyp.msvs_emulation import shlex +import xml.etree.cElementTree as ET generator_wants_static_library_dependencies_adjusted = False @@ -31,8 +32,8 @@ generator_default_variables = { } for dirname in ['INTERMEDIATE_DIR', 'PRODUCT_DIR', 'LIB_DIR', 'SHARED_LIB_DIR']: - # Some gyp steps fail if these are empty(!). - generator_default_variables[dirname] = 'dir' + # Some gyp steps fail if these are empty(!), so we convert them to variables + generator_default_variables[dirname] = '$' + dirname for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME', 'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT', @@ -165,7 +166,7 @@ def GetAllIncludeDirectories(target_list, target_dicts, return all_includes_list -def GetCompilerPath(target_list, data): +def GetCompilerPath(target_list, data, options): """Determine a command that can be used to invoke the compiler. Returns: @@ -173,13 +174,12 @@ def GetCompilerPath(target_list, data): the compiler from that. Otherwise, see if a compiler was specified via the CC_target environment variable. """ - # First, see if the compiler is configured in make's settings. build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0]) make_global_settings_dict = data[build_file].get('make_global_settings', {}) for key, value in make_global_settings_dict: if key in ['CC', 'CXX']: - return value + return os.path.join(options.toplevel_dir, value) # Check to see if the compiler was specified as an environment variable. for key in ['CC_target', 'CC', 'CXX']: @@ -295,33 +295,123 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, shared_intermediate_dirs = [os.path.join(toplevel_build, 'obj', 'gen'), os.path.join(toplevel_build, 'gen')] - out_name = os.path.join(toplevel_build, 'eclipse-cdt-settings.xml') + GenerateCdtSettingsFile(target_list, + target_dicts, + data, + params, + config_name, + os.path.join(toplevel_build, + 'eclipse-cdt-settings.xml'), + options, + shared_intermediate_dirs) + GenerateClasspathFile(target_list, + target_dicts, + options.toplevel_dir, + toplevel_build, + os.path.join(toplevel_build, + 'eclipse-classpath.xml')) + + +def GenerateCdtSettingsFile(target_list, target_dicts, data, params, + config_name, out_name, options, + shared_intermediate_dirs): gyp.common.EnsureDirExists(out_name) - out = open(out_name, 'w') + with open(out_name, 'w') as out: + out.write('\n') + out.write('\n') - out.write('\n') - out.write('\n') + eclipse_langs = ['C++ Source File', 'C Source File', 'Assembly Source File', + 'GNU C++', 'GNU C', 'Assembly'] + compiler_path = GetCompilerPath(target_list, data, options) + include_dirs = GetAllIncludeDirectories(target_list, target_dicts, + shared_intermediate_dirs, + config_name, params, compiler_path) + WriteIncludePaths(out, eclipse_langs, include_dirs) + defines = GetAllDefines(target_list, target_dicts, data, config_name, + params, compiler_path) + WriteMacros(out, eclipse_langs, defines) - eclipse_langs = ['C++ Source File', 'C Source File', 'Assembly Source File', - 'GNU C++', 'GNU C', 'Assembly'] - compiler_path = GetCompilerPath(target_list, data) - include_dirs = GetAllIncludeDirectories(target_list, target_dicts, - shared_intermediate_dirs, config_name, - params, compiler_path) - WriteIncludePaths(out, eclipse_langs, include_dirs) - defines = GetAllDefines(target_list, target_dicts, data, config_name, params, - compiler_path) - WriteMacros(out, eclipse_langs, defines) + out.write('\n') - out.write('\n') - out.close() + +def GenerateClasspathFile(target_list, target_dicts, toplevel_dir, + toplevel_build, out_name): + '''Generates a classpath file suitable for symbol navigation and code + completion of Java code (such as in Android projects) by finding all + .java and .jar files used as action inputs.''' + gyp.common.EnsureDirExists(out_name) + result = ET.Element('classpath') + + def AddElements(kind, paths): + # First, we need to normalize the paths so they are all relative to the + # toplevel dir. + rel_paths = set() + for path in paths: + if os.path.isabs(path): + rel_paths.add(os.path.relpath(path, toplevel_dir)) + else: + rel_paths.add(path) + + for path in sorted(rel_paths): + entry_element = ET.SubElement(result, 'classpathentry') + entry_element.set('kind', kind) + entry_element.set('path', path) + + AddElements('lib', GetJavaJars(target_list, target_dicts, toplevel_dir)) + AddElements('src', GetJavaSourceDirs(target_list, target_dicts, toplevel_dir)) + # Include the standard JRE container and a dummy out folder + AddElements('con', ['org.eclipse.jdt.launching.JRE_CONTAINER']) + # Include a dummy out folder so that Eclipse doesn't use the default /bin + # folder in the root of the project. + AddElements('output', [os.path.join(toplevel_build, '.eclipse-java-build')]) + + ET.ElementTree(result).write(out_name) + + +def GetJavaJars(target_list, target_dicts, toplevel_dir): + '''Generates a sequence of all .jars used as inputs.''' + for target_name in target_list: + target = target_dicts[target_name] + for action in target.get('actions', []): + for input_ in action['inputs']: + if os.path.splitext(input_)[1] == '.jar' and not input_.startswith('$'): + if os.path.isabs(input_): + yield input_ + else: + yield os.path.join(os.path.dirname(target_name), input_) + + +def GetJavaSourceDirs(target_list, target_dicts, toplevel_dir): + '''Generates a sequence of all likely java package root directories.''' + for target_name in target_list: + target = target_dicts[target_name] + for action in target.get('actions', []): + for input_ in action['inputs']: + if (os.path.splitext(input_)[1] == '.java' and + not input_.startswith('$')): + dir_ = os.path.dirname(os.path.join(os.path.dirname(target_name), + input_)) + # If there is a parent 'src' or 'java' folder, navigate up to it - + # these are canonical package root names in Chromium. This will + # break if 'src' or 'java' exists in the package structure. This + # could be further improved by inspecting the java file for the + # package name if this proves to be too fragile in practice. + parent_search = dir_ + while os.path.basename(parent_search) not in ['src', 'java']: + parent_search, _ = os.path.split(parent_search) + if not parent_search or parent_search == toplevel_dir: + # Didn't find a known root, just return the original path + yield dir_ + break + else: + yield parent_search def GenerateOutput(target_list, target_dicts, data, params): """Generate an XML settings file that can be imported into a CDT project.""" if params['options'].generator_output: - raise NotImplementedError, "--generator_output not implemented for eclipse" + raise NotImplementedError("--generator_output not implemented for eclipse") user_config = params.get('generator_flags', {}).get('config', None) if user_config: diff --git a/third_party/gyp/generator/gypd.py b/third_party/gyp/generator/gypd.py index 22ef57f8..3efdb996 100644 --- a/third_party/gyp/generator/gypd.py +++ b/third_party/gyp/generator/gypd.py @@ -39,9 +39,11 @@ import pprint # These variables should just be spit back out as variable references. _generator_identity_variables = [ + 'CONFIGURATION_NAME', 'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX', 'INTERMEDIATE_DIR', + 'LIB_DIR', 'PRODUCT_DIR', 'RULE_INPUT_ROOT', 'RULE_INPUT_DIRNAME', @@ -49,6 +51,11 @@ _generator_identity_variables = [ 'RULE_INPUT_NAME', 'RULE_INPUT_PATH', 'SHARED_INTERMEDIATE_DIR', + 'SHARED_LIB_DIR', + 'SHARED_LIB_PREFIX', + 'SHARED_LIB_SUFFIX', + 'STATIC_LIB_PREFIX', + 'STATIC_LIB_SUFFIX', ] # gypd doesn't define a default value for OS like many other generator diff --git a/third_party/gyp/generator/make.py b/third_party/gyp/generator/make.py index b88a433d..b7da768f 100644 --- a/third_party/gyp/generator/make.py +++ b/third_party/gyp/generator/make.py @@ -29,6 +29,7 @@ import gyp import gyp.common import gyp.xcode_emulation from gyp.common import GetEnvironFallback +from gyp.common import GypError generator_default_variables = { 'EXECUTABLE_PREFIX': '', @@ -210,10 +211,10 @@ cmd_solink_module_host = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(T LINK_COMMANDS_AIX = """\ quiet_cmd_alink = AR($(TOOLSET)) $@ -cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) crs $@ $(filter %.o,$^) +cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) -X32_64 crs $@ $(filter %.o,$^) quiet_cmd_alink_thin = AR($(TOOLSET)) $@ -cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) crs $@ $(filter %.o,$^) +cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) -X32_64 crs $@ $(filter %.o,$^) quiet_cmd_link = LINK($(TOOLSET)) $@ cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(LD_INPUTS) $(LIBS) @@ -272,30 +273,22 @@ all_deps := %(make_global_settings)s CC.target ?= %(CC.target)s -CFLAGS.target ?= $(CFLAGS) +CFLAGS.target ?= $(CPPFLAGS) $(CFLAGS) CXX.target ?= %(CXX.target)s -CXXFLAGS.target ?= $(CXXFLAGS) +CXXFLAGS.target ?= $(CPPFLAGS) $(CXXFLAGS) LINK.target ?= %(LINK.target)s LDFLAGS.target ?= $(LDFLAGS) AR.target ?= $(AR) # C++ apps need to be linked with g++. -# -# Note: flock is used to seralize linking. Linking is a memory-intensive -# process so running parallel links can often lead to thrashing. To disable -# the serialization, override LINK via an envrionment variable as follows: -# -# export LINK=g++ -# -# This will allow make to invoke N linker processes as specified in -jN. -LINK ?= %(flock)s $(builddir)/linker.lock $(CXX.target) +LINK ?= $(CXX.target) # TODO(evan): move all cross-compilation logic to gyp-time so we don't need # to replicate this environment fallback in make as well. CC.host ?= %(CC.host)s -CFLAGS.host ?= +CFLAGS.host ?= $(CPPFLAGS_host) $(CFLAGS_host) CXX.host ?= %(CXX.host)s -CXXFLAGS.host ?= +CXXFLAGS.host ?= $(CPPFLAGS_host) $(CXXFLAGS_host) LINK.host ?= %(LINK.host)s LDFLAGS.host ?= AR.host ?= %(AR.host)s @@ -372,7 +365,7 @@ cmd_touch = touch $@ quiet_cmd_copy = COPY $@ # send stderr to /dev/null to ignore messages when linking directories. -cmd_copy = ln -f "$<" "$@" 2>/dev/null || (rm -rf "$@" && cp -af "$<" "$@") +cmd_copy = ln -f "$<" "$@" 2>/dev/null || (rm -rf "$@" && cp %(copy_archive_args)s "$<" "$@") %(link_commands)s """ @@ -631,6 +624,38 @@ def QuoteSpaces(s, quote=r'\ '): return s.replace(' ', quote) +# TODO: Avoid code duplication with _ValidateSourcesForMSVSProject in msvs.py. +def _ValidateSourcesForOSX(spec, all_sources): + """Makes sure if duplicate basenames are not specified in the source list. + + Arguments: + spec: The target dictionary containing the properties of the target. + """ + if spec.get('type', None) != 'static_library': + return + + basenames = {} + for source in all_sources: + name, ext = os.path.splitext(source) + is_compiled_file = ext in [ + '.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.s', '.S'] + if not is_compiled_file: + continue + basename = os.path.basename(name) # Don't include extension. + basenames.setdefault(basename, []).append(source) + + error = '' + for basename, files in basenames.iteritems(): + if len(files) > 1: + error += ' %s: %s\n' % (basename, ' '.join(files)) + + if error: + print('static library %s has several files with the same basename:\n' % + spec['target_name'] + error + 'libtool on OS X will generate' + + ' warnings for them.') + raise GypError('Duplicate basenames in sources section, see list above') + + # Map from qualified target to path to output. target_outputs = {} # Map from qualified target to any linkable output. A subset @@ -640,7 +665,7 @@ target_outputs = {} target_link_deps = {} -class MakefileWriter: +class MakefileWriter(object): """MakefileWriter packages up the writing of one target-specific foobar.mk. Its only real entry point is Write(), and is mostly used for namespacing. @@ -758,6 +783,10 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # Sources. all_sources = spec.get('sources', []) + extra_sources if all_sources: + if self.flavor == 'mac': + # libtool on OS X generates warnings for duplicate basenames in the same + # target. + _ValidateSourcesForOSX(spec, all_sources) self.WriteSources( configs, deps, all_sources, extra_outputs, extra_link_deps, part_of_all, @@ -990,7 +1019,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # accidentally writing duplicate dummy rules for those outputs. self.WriteLn('%s: obj := $(abs_obj)' % outputs[0]) self.WriteLn('%s: builddir := $(abs_builddir)' % outputs[0]) - self.WriteMakeRule(outputs, inputs + ['FORCE_DO_CMD'], actions) + self.WriteMakeRule(outputs, inputs, actions, + command="%s_%d" % (name, count)) # Spaces in rule filenames are not supported, but rule variables have # spaces in them (e.g. RULE_INPUT_PATH expands to '$(abspath $<)'). # The spaces within the variables are valid, so remove the variables @@ -1101,9 +1131,12 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD for output, res in gyp.xcode_emulation.GetMacBundleResources( generator_default_variables['PRODUCT_DIR'], self.xcode_settings, map(Sourceify, map(self.Absolutify, resources))): - self.WriteDoCmd([output], [res], 'mac_tool,,,copy-bundle-resource', - part_of_all=True) - bundle_deps.append(output) + _, ext = os.path.splitext(output) + if ext != '.xcassets': + # Make does not supports '.xcassets' emulation. + self.WriteDoCmd([output], [res], 'mac_tool,,,copy-bundle-resource', + part_of_all=True) + bundle_deps.append(output) def WriteMacInfoPlist(self, bundle_deps): @@ -1546,7 +1579,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD for link_dep in link_deps: assert ' ' not in link_dep, ( "Spaces in alink input filenames not supported (%s)" % link_dep) - if (self.flavor not in ('mac', 'openbsd', 'win') and not + if (self.flavor not in ('mac', 'openbsd', 'netbsd', 'win') and not self.is_standalone_static_library): self.WriteDoCmd([self.output_binary], link_deps, 'alink_thin', part_of_all, postbuilds=postbuilds) @@ -1656,6 +1689,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.WriteMakeRule(outputs, inputs, actions = ['$(call do_cmd,%s%s)' % (command, suffix)], comment = comment, + command = command, force = True) # Add our outputs to the list of targets we read depfiles from. # all_deps is only used for deps file reading, and for deps files we replace @@ -1666,7 +1700,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD def WriteMakeRule(self, outputs, inputs, actions=None, comment=None, - order_only=False, force=False, phony=False): + order_only=False, force=False, phony=False, command=None): """Write a Makefile rule, with some extra tricks. outputs: a list of outputs for the rule (note: this is not directly @@ -1679,6 +1713,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD force: if true, include FORCE_DO_CMD as an order-only dep phony: if true, the rule does not actually generate the named output, the output is just a name to run the rule + command: (optional) command name to generate unambiguous labels """ outputs = map(QuoteSpaces, outputs) inputs = map(QuoteSpaces, inputs) @@ -1687,44 +1722,38 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.WriteLn('# ' + comment) if phony: self.WriteLn('.PHONY: ' + ' '.join(outputs)) - # TODO(evanm): just make order_only a list of deps instead of these hacks. - if order_only: - order_insert = '| ' - pick_output = ' '.join(outputs) - else: - order_insert = '' - pick_output = outputs[0] - if force: - force_append = ' FORCE_DO_CMD' - else: - force_append = '' if actions: self.WriteLn("%s: TOOLSET := $(TOOLSET)" % outputs[0]) - self.WriteLn('%s: %s%s%s' % (pick_output, order_insert, ' '.join(inputs), - force_append)) + force_append = ' FORCE_DO_CMD' if force else '' + + if order_only: + # Order only rule: Just write a simple rule. + # TODO(evanm): just make order_only a list of deps instead of this hack. + self.WriteLn('%s: | %s%s' % + (' '.join(outputs), ' '.join(inputs), force_append)) + elif len(outputs) == 1: + # Regular rule, one output: Just write a simple rule. + self.WriteLn('%s: %s%s' % (outputs[0], ' '.join(inputs), force_append)) + else: + # Regular rule, more than one output: Multiple outputs are tricky in + # make. We will write three rules: + # - All outputs depend on an intermediate file. + # - Make .INTERMEDIATE depend on the intermediate. + # - The intermediate file depends on the inputs and executes the + # actual command. + # - The intermediate recipe will 'touch' the intermediate file. + # - The multi-output rule will have an do-nothing recipe. + intermediate = "%s.intermediate" % (command if command else self.target) + self.WriteLn('%s: %s' % (' '.join(outputs), intermediate)) + self.WriteLn('\t%s' % '@:'); + self.WriteLn('%s: %s' % ('.INTERMEDIATE', intermediate)) + self.WriteLn('%s: %s%s' % + (intermediate, ' '.join(inputs), force_append)) + actions.insert(0, '$(call do_cmd,touch)') + if actions: for action in actions: self.WriteLn('\t%s' % action) - if not order_only and len(outputs) > 1: - # If we have more than one output, a rule like - # foo bar: baz - # that for *each* output we must run the action, potentially - # in parallel. That is not what we're trying to write -- what - # we want is that we run the action once and it generates all - # the files. - # http://www.gnu.org/software/hello/manual/automake/Multiple-Outputs.html - # discusses this problem and has this solution: - # 1) Write the naive rule that would produce parallel runs of - # the action. - # 2) Make the outputs seralized on each other, so we won't start - # a parallel run until the first run finishes, at which point - # we'll have generated all the outputs and we're done. - self.WriteLn('%s: %s' % (' '.join(outputs[1:]), outputs[0])) - # Add a dummy command to the "extra outputs" rule, otherwise make seems to - # think these outputs haven't (couldn't have?) changed, and thus doesn't - # flag them as changed (i.e. include in '$?') when evaluating dependent - # rules, which in turn causes do_cmd() to skip running dependent commands. - self.WriteLn('%s: ;' % (' '.join(outputs[1:]))) self.WriteLn() @@ -1981,6 +2010,7 @@ def GenerateOutput(target_list, target_dicts, data, params): srcdir_prefix = '$(srcdir)/' flock_command= 'flock' + copy_archive_arguments = '-af' header_params = { 'default_target': default_target, 'builddir': builddir_name, @@ -1990,6 +2020,7 @@ def GenerateOutput(target_list, target_dicts, data, params): 'link_commands': LINK_COMMANDS_LINUX, 'extra_commands': '', 'srcdir': srcdir, + 'copy_archive_args': copy_archive_arguments, } if flavor == 'mac': flock_command = './gyp-mac-tool flock' @@ -2013,8 +2044,15 @@ def GenerateOutput(target_list, target_dicts, data, params): header_params.update({ 'flock': 'lockf', }) - elif flavor == 'aix': + elif flavor == 'openbsd': + copy_archive_arguments = '-pPRf' header_params.update({ + 'copy_archive_args': copy_archive_arguments, + }) + elif flavor == 'aix': + copy_archive_arguments = '-pPRf' + header_params.update({ + 'copy_archive_args': copy_archive_arguments, 'link_commands': LINK_COMMANDS_AIX, 'flock': './gyp-flock-tool flock', 'flock_index': 2, @@ -2034,7 +2072,6 @@ def GenerateOutput(target_list, target_dicts, data, params): build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0]) make_global_settings_array = data[build_file].get('make_global_settings', []) wrappers = {} - wrappers['LINK'] = '%s $(builddir)/linker.lock' % flock_command for key, value in make_global_settings_array: if key.endswith('_wrapper'): wrappers[key[:-len('_wrapper')]] = '$(abspath %s)' % value diff --git a/third_party/gyp/generator/msvs.py b/third_party/gyp/generator/msvs.py index 9dcdab6c..e60c0256 100644 --- a/third_party/gyp/generator/msvs.py +++ b/third_party/gyp/generator/msvs.py @@ -2,7 +2,6 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import collections import copy import ntpath import os @@ -13,6 +12,7 @@ import sys import gyp.common import gyp.easy_xml as easy_xml +import gyp.generator.ninja as ninja_generator import gyp.MSVSNew as MSVSNew import gyp.MSVSProject as MSVSProject import gyp.MSVSSettings as MSVSSettings @@ -21,6 +21,7 @@ import gyp.MSVSUserFile as MSVSUserFile import gyp.MSVSUtil as MSVSUtil import gyp.MSVSVersion as MSVSVersion from gyp.common import GypError +from gyp.common import OrderedSet # TODO: Remove once bots are on 2.7, http://crbug.com/241769 def _import_OrderedDict(): @@ -41,7 +42,7 @@ OrderedDict = _import_OrderedDict() # if IncrediBuild is executed from inside Visual Studio. This regex # validates that the string looks like a GUID with all uppercase hex # letters. -VALID_MSVS_GUID_CHARS = re.compile('^[A-F0-9\-]+$') +VALID_MSVS_GUID_CHARS = re.compile(r'^[A-F0-9\-]+$') generator_default_variables = { @@ -81,8 +82,16 @@ generator_additional_non_configuration_keys = [ 'msvs_external_builder_out_dir', 'msvs_external_builder_build_cmd', 'msvs_external_builder_clean_cmd', + 'msvs_external_builder_clcompile_cmd', + 'msvs_enable_winrt', + 'msvs_requires_importlibrary', + 'msvs_enable_winphone', + 'msvs_application_type_revision', + 'msvs_target_platform_version', + 'msvs_target_platform_minversion', ] +generator_filelist_paths = None # List of precompiled header related keys. precomp_keys = [ @@ -97,46 +106,6 @@ cached_username = None cached_domain = None -# Based on http://code.activestate.com/recipes/576694/. -class OrderedSet(collections.MutableSet): - def __init__(self, iterable=None): - self.end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.map = {} # key --> [key, prev, next] - if iterable is not None: - self |= iterable - - def __len__(self): - return len(self.map) - - def discard(self, key): - if key in self.map: - key, prev, next = self.map.pop(key) - prev[2] = next - next[1] = prev - - def __contains__(self, key): - return key in self.map - - def add(self, key): - if key not in self.map: - end = self.end - curr = end[1] - curr[2] = end[1] = self.map[key] = [key, curr, end] - - def update(self, iterable): - for i in iterable: - if i not in self: - self.add(i) - - def __iter__(self): - end = self.end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - # TODO(gspencer): Switch the os.environ calls to be # win32api.GetDomainName() and win32api.GetUserName() once the # python version in depot_tools has been updated to work on Vista @@ -153,11 +122,11 @@ def _GetDomainAndUserName(): call = subprocess.Popen(['net', 'config', 'Workstation'], stdout=subprocess.PIPE) config = call.communicate()[0] - username_re = re.compile('^User name\s+(\S+)', re.MULTILINE) + username_re = re.compile(r'^User name\s+(\S+)', re.MULTILINE) username_match = username_re.search(config) if username_match: username = username_match.group(1) - domain_re = re.compile('^Logon domain\s+(\S+)', re.MULTILINE) + domain_re = re.compile(r'^Logon domain\s+(\S+)', re.MULTILINE) domain_match = domain_re.search(config) if domain_match: domain = domain_match.group(1) @@ -288,6 +257,8 @@ def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False): if not tools.get(tool_name): tools[tool_name] = dict() tool = tools[tool_name] + if 'CompileAsWinRT' == setting: + return if tool.get(setting): if only_if_unset: return if type(tool[setting]) == list and type(value) == list: @@ -317,13 +288,28 @@ def _ConfigFullName(config_name, config_data): return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name) +def _ConfigWindowsTargetPlatformVersion(config_data): + ver = config_data.get('msvs_windows_sdk_version') + + for key in [r'HKLM\Software\Microsoft\Microsoft SDKs\Windows\%s', + r'HKLM\Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows\%s']: + sdk_dir = MSVSVersion._RegistryGetValue(key % ver, 'InstallationFolder') + if not sdk_dir: + continue + version = MSVSVersion._RegistryGetValue(key % ver, 'ProductVersion') or '' + # Find a matching entry in sdk_dir\include. + names = sorted([x for x in os.listdir(r'%s\include' % sdk_dir) + if x.startswith(version)], reverse=True) + return names[0] + + def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path, quote_cmd, do_setup_env): if [x for x in cmd if '$(InputDir)' in x]: input_dir_preamble = ( 'set INPUTDIR=$(InputDir)\n' - 'set INPUTDIR=%INPUTDIR:$(ProjectDir)=%\n' + 'if NOT DEFINED INPUTDIR set INPUTDIR=.\\\n' 'set INPUTDIR=%INPUTDIR:~0,-1%\n' ) else: @@ -852,23 +838,27 @@ def _GenerateRulesForMSVS(p, output_dir, options, spec, if rules_external: _GenerateExternalRules(rules_external, output_dir, spec, sources, options, actions_to_add) - _AdjustSourcesForRules(spec, rules, sources, excluded_sources) + _AdjustSourcesForRules(rules, sources, excluded_sources, False) -def _AdjustSourcesForRules(spec, rules, sources, excluded_sources): +def _AdjustSourcesForRules(rules, sources, excluded_sources, is_msbuild): # Add outputs generated by each rule (if applicable). for rule in rules: - # Done if not processing outputs as sources. - if int(rule.get('process_outputs_as_sources', False)): - # Add in the outputs from this rule. - trigger_files = _FindRuleTriggerFiles(rule, sources) - for trigger_file in trigger_files: + # Add in the outputs from this rule. + trigger_files = _FindRuleTriggerFiles(rule, sources) + for trigger_file in trigger_files: + # Remove trigger_file from excluded_sources to let the rule be triggered + # (e.g. rule trigger ax_enums.idl is added to excluded_sources + # because it's also in an action's inputs in the same project) + excluded_sources.discard(_FixPath(trigger_file)) + # Done if not processing outputs as sources. + if int(rule.get('process_outputs_as_sources', False)): inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file) inputs = OrderedSet(_FixPaths(inputs)) outputs = OrderedSet(_FixPaths(outputs)) inputs.remove(_FixPath(trigger_file)) sources.update(inputs) - if not spec.get('msvs_external_builder'): + if not is_msbuild: excluded_sources.update(inputs) sources.update(outputs) @@ -955,6 +945,42 @@ def _GenerateProject(project, options, version, generator_flags): return _GenerateMSVSProject(project, options, version, generator_flags) +# TODO: Avoid code duplication with _ValidateSourcesForOSX in make.py. +def _ValidateSourcesForMSVSProject(spec, version): + """Makes sure if duplicate basenames are not specified in the source list. + + Arguments: + spec: The target dictionary containing the properties of the target. + version: The VisualStudioVersion object. + """ + # This validation should not be applied to MSVC2010 and later. + assert not version.UsesVcxproj() + + # TODO: Check if MSVC allows this for loadable_module targets. + if spec.get('type', None) not in ('static_library', 'shared_library'): + return + sources = spec.get('sources', []) + basenames = {} + for source in sources: + name, ext = os.path.splitext(source) + is_compiled_file = ext in [ + '.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.s', '.S'] + if not is_compiled_file: + continue + basename = os.path.basename(name) # Don't include extension. + basenames.setdefault(basename, []).append(source) + + error = '' + for basename, files in basenames.iteritems(): + if len(files) > 1: + error += ' %s: %s\n' % (basename, ' '.join(files)) + + if error: + print('static library %s has several files with the same basename:\n' % + spec['target_name'] + error + 'MSVC08 cannot handle that.') + raise GypError('Duplicate basenames in sources section, see list above') + + def _GenerateMSVSProject(project, options, version, generator_flags): """Generates a .vcproj file. It may create .rules and .user files too. @@ -980,6 +1006,11 @@ def _GenerateMSVSProject(project, options, version, generator_flags): for config_name, config in spec['configurations'].iteritems(): _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config) + # MSVC08 and prior version cannot handle duplicate basenames in the same + # target. + # TODO: Take excluded sources into consideration if possible. + _ValidateSourcesForMSVSProject(spec, version) + # Prepare list of sources and excluded sources. gyp_file = os.path.split(project.build_file)[1] sources, excluded_sources = _PrepareListOfSources(spec, generator_flags, @@ -1099,7 +1130,8 @@ def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config): for this configuration. """ # Get the information for this configuration - include_dirs, resource_include_dirs = _GetIncludeDirs(config) + include_dirs, midl_include_dirs, resource_include_dirs = \ + _GetIncludeDirs(config) libraries = _GetLibraries(spec) library_dirs = _GetLibraryDirs(config) out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False) @@ -1127,6 +1159,8 @@ def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config): # Add the information to the appropriate tool _ToolAppend(tools, 'VCCLCompilerTool', 'AdditionalIncludeDirectories', include_dirs) + _ToolAppend(tools, 'VCMIDLTool', + 'AdditionalIncludeDirectories', midl_include_dirs) _ToolAppend(tools, 'VCResourceCompilerTool', 'AdditionalIncludeDirectories', resource_include_dirs) # Add in libraries. @@ -1182,10 +1216,14 @@ def _GetIncludeDirs(config): include_dirs = ( config.get('include_dirs', []) + config.get('msvs_system_include_dirs', [])) + midl_include_dirs = ( + config.get('midl_include_dirs', []) + + config.get('msvs_system_include_dirs', [])) resource_include_dirs = config.get('resource_include_dirs', include_dirs) include_dirs = _FixPaths(include_dirs) + midl_include_dirs = _FixPaths(midl_include_dirs) resource_include_dirs = _FixPaths(resource_include_dirs) - return include_dirs, resource_include_dirs + return include_dirs, midl_include_dirs, resource_include_dirs def _GetLibraryDirs(config): @@ -1219,7 +1257,7 @@ def _GetLibraries(spec): found = OrderedSet() unique_libraries_list = [] for entry in reversed(libraries): - library = re.sub('^\-l', '', entry) + library = re.sub(r'^\-l', '', entry) if not os.path.splitext(library)[1]: library += '.lib' if library not in found: @@ -1479,8 +1517,14 @@ def _AdjustSourcesAndConvertToFilterHierarchy( # Prune filters with a single child to flatten ugly directory structures # such as ../../src/modules/module1 etc. - while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter): - sources = sources[0].contents + if version.UsesVcxproj(): + while all([isinstance(s, MSVSProject.Filter) for s in sources]) \ + and len(set([s.name for s in sources])) == 1: + assert all([len(s.contents) == 1 for s in sources]) + sources = [s.contents[0] for s in sources] + else: + while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter): + sources = sources[0].contents return sources, excluded_sources, excluded_idl @@ -1816,7 +1860,7 @@ def _CreateProjectObjects(target_list, target_dicts, options, msvs_version): return projects -def _InitNinjaFlavor(options, target_list, target_dicts): +def _InitNinjaFlavor(params, target_list, target_dicts): """Initialize targets for the ninja flavor. This sets up the necessary variables in the targets to generate msvs projects @@ -1824,7 +1868,7 @@ def _InitNinjaFlavor(options, target_list, target_dicts): if they have not been set. This allows individual specs to override the default values initialized here. Arguments: - options: Options provided to the generator. + params: Params provided to the generator. target_list: List of target pairs: 'base/base.gyp:base'. target_dicts: Dict of target properties keyed on target pair. """ @@ -1838,8 +1882,15 @@ def _InitNinjaFlavor(options, target_list, target_dicts): spec['msvs_external_builder'] = 'ninja' if not spec.get('msvs_external_builder_out_dir'): - spec['msvs_external_builder_out_dir'] = \ - options.depth + '/out/$(Configuration)' + gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target) + gyp_dir = os.path.dirname(gyp_file) + configuration = '$(Configuration)' + if params.get('target_arch') == 'x64': + configuration += '_x64' + spec['msvs_external_builder_out_dir'] = os.path.join( + gyp.common.RelativePath(params['options'].toplevel_dir, gyp_dir), + ninja_generator.ComputeOutputDir(params), + configuration) if not spec.get('msvs_external_builder_build_cmd'): spec['msvs_external_builder_build_cmd'] = [ path_to_ninja, @@ -1852,8 +1903,7 @@ def _InitNinjaFlavor(options, target_list, target_dicts): path_to_ninja, '-C', '$(OutDir)', - '-t', - 'clean', + '-tclean', '$(ProjectName)', ] @@ -1905,6 +1955,19 @@ def PerformBuild(data, configurations, params): rtn = subprocess.check_call(arguments) +def CalculateGeneratorInputInfo(params): + if params.get('flavor') == 'ninja': + toplevel = params['options'].toplevel_dir + qualified_out_dir = os.path.normpath(os.path.join( + toplevel, ninja_generator.ComputeOutputDir(params), + 'gypfiles-msvs-ninja')) + + global generator_filelist_paths + generator_filelist_paths = { + 'toplevel': toplevel, + 'qualified_out_dir': qualified_out_dir, + } + def GenerateOutput(target_list, target_dicts, data, params): """Generate .sln and .vcproj files. @@ -1934,7 +1997,7 @@ def GenerateOutput(target_list, target_dicts, data, params): # Optionally configure each spec to use ninja as the external builder. if params.get('flavor') == 'ninja': - _InitNinjaFlavor(options, target_list, target_dicts) + _InitNinjaFlavor(params, target_list, target_dicts) # Prepare the set of configurations. configs = set() @@ -1987,7 +2050,7 @@ def GenerateOutput(target_list, target_dicts, data, params): def _GenerateMSBuildFiltersFile(filters_path, source_files, - extension_to_rule_name): + rule_dependencies, extension_to_rule_name): """Generate the filters file. This file is used by Visual Studio to organize the presentation of source @@ -2000,8 +2063,8 @@ def _GenerateMSBuildFiltersFile(filters_path, source_files, """ filter_group = [] source_group = [] - _AppendFiltersForMSBuild('', source_files, extension_to_rule_name, - filter_group, source_group) + _AppendFiltersForMSBuild('', source_files, rule_dependencies, + extension_to_rule_name, filter_group, source_group) if filter_group: content = ['Project', {'ToolsVersion': '4.0', @@ -2016,7 +2079,7 @@ def _GenerateMSBuildFiltersFile(filters_path, source_files, os.unlink(filters_path) -def _AppendFiltersForMSBuild(parent_filter_name, sources, +def _AppendFiltersForMSBuild(parent_filter_name, sources, rule_dependencies, extension_to_rule_name, filter_group, source_group): """Creates the list of filters and sources to be added in the filter file. @@ -2042,11 +2105,12 @@ def _AppendFiltersForMSBuild(parent_filter_name, sources, ['UniqueIdentifier', MSVSNew.MakeGuid(source.name)]]) # Recurse and add its dependents. _AppendFiltersForMSBuild(filter_name, source.contents, - extension_to_rule_name, + rule_dependencies, extension_to_rule_name, filter_group, source_group) else: # It's a source. Create a source entry. - _, element = _MapFileToMsBuildSourceType(source, extension_to_rule_name) + _, element = _MapFileToMsBuildSourceType(source, rule_dependencies, + extension_to_rule_name) source_entry = [element, {'Include': source}] # Specify the filter it is part of, if any. if parent_filter_name: @@ -2054,7 +2118,8 @@ def _AppendFiltersForMSBuild(parent_filter_name, sources, source_group.append(source_entry) -def _MapFileToMsBuildSourceType(source, extension_to_rule_name): +def _MapFileToMsBuildSourceType(source, rule_dependencies, + extension_to_rule_name): """Returns the group and element type of the source file. Arguments: @@ -2077,9 +2142,15 @@ def _MapFileToMsBuildSourceType(source, extension_to_rule_name): elif ext == '.rc': group = 'resource' element = 'ResourceCompile' + elif ext == '.asm': + group = 'masm' + element = 'MASM' elif ext == '.idl': group = 'midl' element = 'Midl' + elif source in rule_dependencies: + group = 'rule_dependency' + element = 'CustomBuild' else: group = 'none' element = 'None' @@ -2089,7 +2160,8 @@ def _MapFileToMsBuildSourceType(source, extension_to_rule_name): def _GenerateRulesForMSBuild(output_dir, options, spec, sources, excluded_sources, props_files_of_rules, targets_files_of_rules, - actions_to_add, extension_to_rule_name): + actions_to_add, rule_dependencies, + extension_to_rule_name): # MSBuild rules are implemented using three files: an XML file, a .targets # file and a .props file. # See http://blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx @@ -2105,6 +2177,7 @@ def _GenerateRulesForMSBuild(output_dir, options, spec, continue msbuild_rule = MSBuildRule(rule, spec) msbuild_rules.append(msbuild_rule) + rule_dependencies.update(msbuild_rule.additional_dependencies.split(';')) extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name if msbuild_rules: base = spec['target_name'] + options.suffix @@ -2126,7 +2199,7 @@ def _GenerateRulesForMSBuild(output_dir, options, spec, if rules_external: _GenerateExternalRules(rules_external, output_dir, spec, sources, options, actions_to_add) - _AdjustSourcesForRules(spec, rules, sources, excluded_sources) + _AdjustSourcesForRules(rules, sources, excluded_sources, True) class MSBuildRule(object): @@ -2305,6 +2378,9 @@ def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules): rule_name, {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != " "'true'" % (rule_name, rule_name), + 'EchoOff': 'true', + 'StandardOutputImportance': 'High', + 'StandardErrorImportance': 'High', 'CommandLineTemplate': '%%(%s.CommandLineTemplate)' % rule_name, 'AdditionalOptions': '%%(%s.AdditionalOptions)' % rule_name, 'Inputs': rule_inputs @@ -2579,14 +2655,60 @@ def _GetMSBuildProjectConfigurations(configurations): def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name): namespace = os.path.splitext(gyp_file_name)[0] - return [ + properties = [ ['PropertyGroup', {'Label': 'Globals'}, - ['ProjectGuid', guid], - ['Keyword', 'Win32Proj'], - ['RootNamespace', namespace], + ['ProjectGuid', guid], + ['Keyword', 'Win32Proj'], + ['RootNamespace', namespace], + ['IgnoreWarnCompileDuplicatedFilename', 'true'], ] - ] + ] + if os.environ.get('PROCESSOR_ARCHITECTURE') == 'AMD64' or \ + os.environ.get('PROCESSOR_ARCHITEW6432') == 'AMD64': + properties[0].append(['PreferredToolArchitecture', 'x64']) + + if spec.get('msvs_enable_winrt'): + properties[0].append(['DefaultLanguage', 'en-US']) + properties[0].append(['AppContainerApplication', 'true']) + if spec.get('msvs_application_type_revision'): + app_type_revision = spec.get('msvs_application_type_revision') + properties[0].append(['ApplicationTypeRevision', app_type_revision]) + else: + properties[0].append(['ApplicationTypeRevision', '8.1']) + + if spec.get('msvs_target_platform_version'): + target_platform_version = spec.get('msvs_target_platform_version') + properties[0].append(['WindowsTargetPlatformVersion', + target_platform_version]) + if spec.get('msvs_target_platform_minversion'): + target_platform_minversion = spec.get('msvs_target_platform_minversion') + properties[0].append(['WindowsTargetPlatformMinVersion', + target_platform_minversion]) + else: + properties[0].append(['WindowsTargetPlatformMinVersion', + target_platform_version]) + if spec.get('msvs_enable_winphone'): + properties[0].append(['ApplicationType', 'Windows Phone']) + else: + properties[0].append(['ApplicationType', 'Windows Store']) + + platform_name = None + msvs_windows_sdk_version = None + for configuration in spec['configurations'].itervalues(): + platform_name = platform_name or _ConfigPlatform(configuration) + msvs_windows_sdk_version = (msvs_windows_sdk_version or + _ConfigWindowsTargetPlatformVersion(configuration)) + if platform_name and msvs_windows_sdk_version: + break + + if platform_name == 'ARM': + properties[0].append(['WindowsSDKDesktopARMSupport', 'true']) + if msvs_windows_sdk_version: + properties[0].append(['WindowsTargetPlatformVersion', + str(msvs_windows_sdk_version)]) + + return properties def _GetMSBuildConfigurationDetails(spec, build_file): properties = {} @@ -2597,8 +2719,9 @@ def _GetMSBuildConfigurationDetails(spec, build_file): _AddConditionalProperty(properties, condition, 'ConfigurationType', msbuild_attributes['ConfigurationType']) if character_set: - _AddConditionalProperty(properties, condition, 'CharacterSet', - character_set) + if 'msvs_enable_winrt' not in spec : + _AddConditionalProperty(properties, condition, 'CharacterSet', + character_set) return _GetMSBuildPropertyGroup(spec, 'Configuration', properties) @@ -2813,7 +2936,7 @@ def _AddConditionalProperty(properties, condition, name, value): # Regex for msvs variable references ( i.e. $(FOO) ). -MSVS_VARIABLE_REFERENCE = re.compile('\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)') +MSVS_VARIABLE_REFERENCE = re.compile(r'\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)') def _GetMSBuildPropertyGroup(spec, label, properties): @@ -2897,7 +3020,8 @@ def _FinalizeMSBuildSettings(spec, configuration): converted = True msvs_settings = configuration.get('msvs_settings', {}) msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings) - include_dirs, resource_include_dirs = _GetIncludeDirs(configuration) + include_dirs, midl_include_dirs, resource_include_dirs = \ + _GetIncludeDirs(configuration) libraries = _GetLibraries(spec) library_dirs = _GetLibraryDirs(configuration) out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True) @@ -2927,6 +3051,8 @@ def _FinalizeMSBuildSettings(spec, configuration): # if you don't have any resources. _ToolAppend(msbuild_settings, 'ClCompile', 'AdditionalIncludeDirectories', include_dirs) + _ToolAppend(msbuild_settings, 'Midl', + 'AdditionalIncludeDirectories', midl_include_dirs) _ToolAppend(msbuild_settings, 'ResourceCompile', 'AdditionalIncludeDirectories', resource_include_dirs) # Add in libraries, note that even for empty libraries, we want this @@ -2957,6 +3083,13 @@ def _FinalizeMSBuildSettings(spec, configuration): 'PrecompiledHeaderFile', precompiled_header) _ToolAppend(msbuild_settings, 'ClCompile', 'ForcedIncludeFiles', [precompiled_header]) + else: + _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'NotUsing') + # Turn off WinRT compilation + _ToolAppend(msbuild_settings, 'ClCompile', 'CompileAsWinRT', 'false') + # Turn on import libraries if appropriate + if spec.get('msvs_requires_importlibrary'): + _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'false') # Loadable modules don't generate import libraries; # tell dependent projects to not expect one. if spec['type'] == 'loadable_module': @@ -3024,15 +3157,18 @@ def _VerifySourcesExist(sources, root_dir): return missing_sources -def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name, - actions_spec, sources_handled_by_action, list_excluded): - groups = ['none', 'midl', 'include', 'compile', 'resource', 'rule'] +def _GetMSBuildSources(spec, sources, exclusions, rule_dependencies, + extension_to_rule_name, actions_spec, + sources_handled_by_action, list_excluded): + groups = ['none', 'masm', 'midl', 'include', 'compile', 'resource', 'rule', + 'rule_dependency'] grouped_sources = {} for g in groups: grouped_sources[g] = [] _AddSources2(spec, sources, exclusions, grouped_sources, - extension_to_rule_name, sources_handled_by_action, list_excluded) + rule_dependencies, extension_to_rule_name, + sources_handled_by_action, list_excluded) sources = [] for g in groups: if grouped_sources[g]: @@ -3043,13 +3179,15 @@ def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name, def _AddSources2(spec, sources, exclusions, grouped_sources, - extension_to_rule_name, sources_handled_by_action, + rule_dependencies, extension_to_rule_name, + sources_handled_by_action, list_excluded): extensions_excluded_from_precompile = [] for source in sources: if isinstance(source, MSVSProject.Filter): _AddSources2(spec, source.contents, exclusions, grouped_sources, - extension_to_rule_name, sources_handled_by_action, + rule_dependencies, extension_to_rule_name, + sources_handled_by_action, list_excluded) else: if not source in sources_handled_by_action: @@ -3092,7 +3230,7 @@ def _AddSources2(spec, sources, exclusions, grouped_sources, detail.append(['PrecompiledHeader', '']) detail.append(['ForcedIncludeFiles', '']) - group, element = _MapFileToMsBuildSourceType(source, + group, element = _MapFileToMsBuildSourceType(source, rule_dependencies, extension_to_rule_name) grouped_sources[group].append([element, {'Include': source}] + detail) @@ -3136,6 +3274,7 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): actions_to_add = {} props_files_of_rules = set() targets_files_of_rules = set() + rule_dependencies = set() extension_to_rule_name = {} list_excluded = generator_flags.get('msvs_list_excluded_files', True) @@ -3144,10 +3283,11 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): _GenerateRulesForMSBuild(project_dir, options, spec, sources, excluded_sources, props_files_of_rules, targets_files_of_rules, - actions_to_add, extension_to_rule_name) + actions_to_add, rule_dependencies, + extension_to_rule_name) else: rules = spec.get('rules', []) - _AdjustSourcesForRules(spec, rules, sources, excluded_sources) + _AdjustSourcesForRules(rules, sources, excluded_sources, True) sources, excluded_sources, excluded_idl = ( _AdjustSourcesAndConvertToFilterHierarchy(spec, options, @@ -3170,6 +3310,7 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): spec, actions_to_add) _GenerateMSBuildFiltersFile(project.path + '.filters', sources, + rule_dependencies, extension_to_rule_name) missing_sources = _VerifySourcesExist(sources, project_dir) @@ -3184,6 +3325,12 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.props'}]] import_cpp_targets_section = [ ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.targets'}]] + import_masm_props_section = [ + ['Import', + {'Project': r'$(VCTargetsPath)\BuildCustomizations\masm.props'}]] + import_masm_targets_section = [ + ['Import', + {'Project': r'$(VCTargetsPath)\BuildCustomizations\masm.targets'}]] macro_section = [['PropertyGroup', {'Label': 'UserMacros'}]] content = [ @@ -3197,8 +3344,12 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name) content += import_default_section content += _GetMSBuildConfigurationDetails(spec, project.build_file) - content += _GetMSBuildLocalProperties(project.msbuild_toolset) + if spec.get('msvs_enable_winphone'): + content += _GetMSBuildLocalProperties('v120_wp81') + else: + content += _GetMSBuildLocalProperties(project.msbuild_toolset) content += import_cpp_props_section + content += import_masm_props_section content += _GetMSBuildExtensions(props_files_of_rules) content += _GetMSBuildPropertySheets(configurations) content += macro_section @@ -3206,10 +3357,11 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): project.build_file) content += _GetMSBuildToolSettingsSections(spec, configurations) content += _GetMSBuildSources( - spec, sources, exclusions, extension_to_rule_name, actions_spec, - sources_handled_by_action, list_excluded) + spec, sources, exclusions, rule_dependencies, extension_to_rule_name, + actions_spec, sources_handled_by_action, list_excluded) content += _GetMSBuildProjectReferences(project) content += import_cpp_targets_section + content += import_masm_targets_section content += _GetMSBuildExtensionTargets(targets_files_of_rules) if spec.get('msvs_external_builder'): @@ -3226,7 +3378,9 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): def _GetMSBuildExternalBuilderTargets(spec): """Return a list of MSBuild targets for external builders. - Right now, only "Build" and "Clean" targets are generated. + The "Build" and "Clean" targets are always generated. If the spec contains + 'msvs_external_builder_clcompile_cmd', then the "ClCompile" target will also + be generated, to support building selected C/C++ files. Arguments: spec: The gyp target spec. @@ -3245,7 +3399,17 @@ def _GetMSBuildExternalBuilderTargets(spec): clean_target = ['Target', {'Name': 'Clean'}] clean_target.append(['Exec', {'Command': clean_cmd}]) - return [build_target, clean_target] + targets = [build_target, clean_target] + + if spec.get('msvs_external_builder_clcompile_cmd'): + clcompile_cmd = _BuildCommandLineForRuleRaw( + spec, spec['msvs_external_builder_clcompile_cmd'], + False, False, False, False) + clcompile_target = ['Target', {'Name': 'ClCompile'}] + clcompile_target.append(['Exec', {'Command': clcompile_cmd}]) + targets.append(clcompile_target) + + return targets def _GetMSBuildExtensions(props_files_of_rules): @@ -3299,8 +3463,8 @@ def _GenerateActionsForMSBuild(spec, actions_to_add): # get too long. See also _AddActions: cygwin's setup_env mustn't be called # for every invocation or the command that sets the PATH will grow too # long. - command = ( - '\r\nif %errorlevel% neq 0 exit /b %errorlevel%\r\n'.join(commands)) + command = '\r\n'.join([c + '\r\nif %errorlevel% neq 0 exit /b %errorlevel%' + for c in commands]) _AddMSBuildAction(spec, primary_input, inputs, diff --git a/third_party/gyp/generator/ninja.py b/third_party/gyp/generator/ninja.py index 1ed23f64..9cfc7060 100644 --- a/third_party/gyp/generator/ninja.py +++ b/third_party/gyp/generator/ninja.py @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import collections import copy import hashlib import json @@ -13,6 +14,7 @@ import subprocess import sys import gyp import gyp.common +from gyp.common import OrderedSet import gyp.msvs_emulation import gyp.MSVSUtil as MSVSUtil import gyp.xcode_emulation @@ -60,17 +62,7 @@ generator_additional_path_sections = [] generator_extra_sources_for_rules = [] generator_filelist_paths = None -# TODO: figure out how to not build extra host objects in the non-cross-compile -# case when this is enabled, and enable unconditionally. -generator_supports_multiple_toolsets = ( - os.environ.get('GYP_CROSSCOMPILE') or - os.environ.get('AR_host') or - os.environ.get('CC_host') or - os.environ.get('CXX_host') or - os.environ.get('AR_target') or - os.environ.get('CC_target') or - os.environ.get('CXX_target')) - +generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested() def StripPrefix(arg, prefix): if arg.startswith(prefix): @@ -106,7 +98,7 @@ def AddArch(output, arch): return '%s.%s%s' % (output, arch, extension) -class Target: +class Target(object): """Target represents the paths used within a single gyp target. Conceptually, building a single target A is a series of steps: @@ -147,8 +139,11 @@ class Target: self.bundle = None # On Windows, incremental linking requires linking against all the .objs # that compose a .lib (rather than the .lib itself). That list is stored - # here. + # here. In this case, we also need to save the compile_deps for the target, + # so that the the target that directly depends on the .objs can also depend + # on those. self.component_objs = None + self.compile_deps = None # Windows only. The import .lib is the output of a build step, but # because dependents only link against the lib (not both the lib and the # dll) we keep track of the import library here. @@ -210,8 +205,8 @@ class Target: # an output file; the result can be namespaced such that it is unique # to the input file name as well as the output target name. -class NinjaWriter: - def __init__(self, qualified_target, target_outputs, base_dir, build_dir, +class NinjaWriter(object): + def __init__(self, hash_for_rules, target_outputs, base_dir, build_dir, output_file, toplevel_build, output_file_name, flavor, toplevel_dir=None): """ @@ -221,7 +216,7 @@ class NinjaWriter: toplevel_dir: path to the toplevel directory """ - self.qualified_target = qualified_target + self.hash_for_rules = hash_for_rules self.target_outputs = target_outputs self.base_dir = base_dir self.build_dir = build_dir @@ -338,12 +333,15 @@ class NinjaWriter: obj += '.' + self.toolset path_dir, path_basename = os.path.split(path) + assert not os.path.isabs(path_dir), ( + "'%s' can not be absolute path (see crbug.com/462153)." % path_dir) + if qualified: path_basename = self.name + '.' + path_basename return os.path.normpath(os.path.join(obj, self.base_dir, path_dir, path_basename)) - def WriteCollapsedDependencies(self, name, targets): + def WriteCollapsedDependencies(self, name, targets, order_only=None): """Given a list of targets, return a path for a single file representing the result of building all the targets or None. @@ -351,10 +349,11 @@ class NinjaWriter: assert targets == filter(None, targets), targets if len(targets) == 0: + assert not order_only return None - if len(targets) > 1: + if len(targets) > 1 or order_only: stamp = self.GypPathToUniqueOutput(name + '.stamp') - targets = self.ninja.build(stamp, 'stamp', targets) + targets = self.ninja.build(stamp, 'stamp', targets, order_only=order_only) self.ninja.newline() return targets[0] @@ -380,10 +379,16 @@ class NinjaWriter: # should be used for linking. self.uses_cpp = False + self.target_rpath = generator_flags.get('target_rpath', r'\$$ORIGIN/lib/') + self.is_mac_bundle = gyp.xcode_emulation.IsMacBundle(self.flavor, spec) self.xcode_settings = self.msvs_settings = None if self.flavor == 'mac': self.xcode_settings = gyp.xcode_emulation.XcodeSettings(spec) + mac_toolchain_dir = generator_flags.get('mac_toolchain_dir', None) + if mac_toolchain_dir: + self.xcode_settings.mac_toolchain_dir = mac_toolchain_dir + if self.flavor == 'win': self.msvs_settings = gyp.msvs_emulation.MsvsSettings(spec, generator_flags) @@ -391,6 +396,9 @@ class NinjaWriter: self.ninja.variable('arch', self.win_env[arch]) self.ninja.variable('cc', '$cl_' + arch) self.ninja.variable('cxx', '$cl_' + arch) + self.ninja.variable('cc_host', '$cl_' + arch) + self.ninja.variable('cxx_host', '$cl_' + arch) + self.ninja.variable('asm', '$ml_' + arch) if self.flavor == 'mac': self.archs = self.xcode_settings.GetActiveArchs(config_name) @@ -472,17 +480,20 @@ class NinjaWriter: else: print "Warning: Actions/rules writing object files don't work with " \ "multiarch targets, dropping. (target %s)" % spec['target_name'] + elif self.flavor == 'mac' and len(self.archs) > 1: + link_deps = collections.defaultdict(list) - + compile_deps = self.target.actions_stamp or actions_depends if self.flavor == 'win' and self.target.type == 'static_library': self.target.component_objs = link_deps + self.target.compile_deps = compile_deps # Write out a link step, if needed. output = None is_empty_bundle = not link_deps and not mac_bundle_depends if link_deps or self.target.actions_stamp or actions_depends: output = self.WriteTarget(spec, config_name, config, link_deps, - self.target.actions_stamp or actions_depends) + compile_deps) if self.is_mac_bundle: mac_bundle_depends.append(output) @@ -523,7 +534,7 @@ class NinjaWriter: def WriteWinIdlFiles(self, spec, prebuild): """Writes rules to match MSVS's implicit idl handling.""" assert self.flavor == 'win' - if self.msvs_settings.HasExplicitIdlRules(spec): + if self.msvs_settings.HasExplicitIdlRulesOrActions(spec): return [] outputs = [] for source in filter(lambda x: x.endswith('.idl'), spec['sources']): @@ -554,12 +565,16 @@ class NinjaWriter: if 'sources' in spec and self.flavor == 'win': outputs += self.WriteWinIdlFiles(spec, prebuild) + if self.xcode_settings and self.xcode_settings.IsIosFramework(): + self.WriteiOSFrameworkHeaders(spec, outputs, prebuild) + stamp = self.WriteCollapsedDependencies('actions_rules_copies', outputs) if self.is_mac_bundle: - self.WriteMacBundleResources( + xcassets = self.WriteMacBundleResources( extra_mac_bundle_resources + mac_bundle_resources, mac_bundle_depends) - self.WriteMacInfoPlist(mac_bundle_depends) + partial_info_plist = self.WriteMacXCassets(xcassets, mac_bundle_depends) + self.WriteMacInfoPlist(partial_info_plist, mac_bundle_depends) return stamp @@ -580,23 +595,24 @@ class NinjaWriter: def WriteActions(self, actions, extra_sources, prebuild, extra_mac_bundle_resources): # Actions cd into the base directory. - env = self.GetSortedXcodeEnv() - if self.flavor == 'win': - env = self.msvs_settings.GetVSMacroEnv( - '$!PRODUCT_DIR', config=self.config_name) + env = self.GetToolchainEnv() all_outputs = [] for action in actions: # First write out a rule for the action. - name = '%s_%s' % (action['action_name'], - hashlib.md5(self.qualified_target).hexdigest()) + name = '%s_%s' % (action['action_name'], self.hash_for_rules) description = self.GenerateDescription('ACTION', action.get('message', None), name) is_cygwin = (self.msvs_settings.IsRuleRunUnderCygwin(action) if self.flavor == 'win' else False) args = action['action'] + depfile = action.get('depfile', None) + if depfile: + depfile = self.ExpandSpecial(depfile, self.base_to_build) + pool = 'console' if int(action.get('ninja_use_console', 0)) else None rule_name, _ = self.WriteNewNinjaRule(name, args, description, - is_cygwin, env=env) + is_cygwin, env, pool, + depfile=depfile) inputs = [self.GypPathToNinja(i, env) for i in action['inputs']] if int(action.get('process_outputs_as_sources', False)): @@ -616,15 +632,16 @@ class NinjaWriter: def WriteRules(self, rules, extra_sources, prebuild, mac_bundle_resources, extra_mac_bundle_resources): - env = self.GetSortedXcodeEnv() + env = self.GetToolchainEnv() all_outputs = [] for rule in rules: - # First write out a rule for the rule action. - name = '%s_%s' % (rule['rule_name'], - hashlib.md5(self.qualified_target).hexdigest()) # Skip a rule with no action and no inputs. if 'action' not in rule and not rule.get('rule_sources', []): continue + + # First write out a rule for the rule action. + name = '%s_%s' % (rule['rule_name'], self.hash_for_rules) + args = rule['action'] description = self.GenerateDescription( 'RULE', @@ -632,8 +649,9 @@ class NinjaWriter: ('%s ' + generator_default_variables['RULE_INPUT_PATH']) % name) is_cygwin = (self.msvs_settings.IsRuleRunUnderCygwin(rule) if self.flavor == 'win' else False) + pool = 'console' if int(rule.get('ninja_use_console', 0)) else None rule_name, args = self.WriteNewNinjaRule( - name, args, description, is_cygwin, env=env) + name, args, description, is_cygwin, env, pool) # TODO: if the command references the outputs directly, we should # simplify it to just use $out. @@ -645,16 +663,32 @@ class NinjaWriter: needed_variables = set(['source']) for argument in args: for var in special_locals: - if ('${%s}' % var) in argument: + if '${%s}' % var in argument: needed_variables.add(var) + needed_variables = sorted(needed_variables) def cygwin_munge(path): + # pylint: disable=cell-var-from-loop if is_cygwin: return path.replace('\\', '/') return path + inputs = [self.GypPathToNinja(i, env) for i in rule.get('inputs', [])] + + # If there are n source files matching the rule, and m additional rule + # inputs, then adding 'inputs' to each build edge written below will + # write m * n inputs. Collapsing reduces this to m + n. + sources = rule.get('rule_sources', []) + num_inputs = len(inputs) + if prebuild: + num_inputs += 1 + if num_inputs > 2 and len(sources) > 2: + inputs = [self.WriteCollapsedDependencies( + rule['rule_name'], inputs, order_only=prebuild)] + prebuild = [] + # For each source file, write an edge that generates all the outputs. - for source in rule.get('rule_sources', []): + for source in sources: source = os.path.normpath(source) dirname, basename = os.path.split(source) root, ext = os.path.splitext(basename) @@ -663,9 +697,6 @@ class NinjaWriter: outputs = [self.ExpandRuleVariables(o, root, dirname, source, ext, basename) for o in rule['outputs']] - inputs = [self.ExpandRuleVariables(i, root, dirname, - source, ext, basename) - for i in rule.get('inputs', [])] if int(rule.get('process_outputs_as_sources', False)): extra_sources += outputs @@ -703,10 +734,12 @@ class NinjaWriter: else: assert var == None, repr(var) - inputs = [self.GypPathToNinja(i, env) for i in inputs] outputs = [self.GypPathToNinja(o, env) for o in outputs] - extra_bindings.append(('unique_name', - hashlib.md5(outputs[0]).hexdigest())) + if self.flavor == 'win': + # WriteNewNinjaRule uses unique_name for creating an rsp file on win. + extra_bindings.append(('unique_name', + hashlib.md5(outputs[0]).hexdigest())) + self.ninja.build(outputs, rule_name, self.GypPathToNinja(source), implicit=inputs, order_only=prebuild, @@ -718,7 +751,11 @@ class NinjaWriter: def WriteCopies(self, copies, prebuild, mac_bundle_depends): outputs = [] - env = self.GetSortedXcodeEnv() + if self.xcode_settings: + extra_env = self.xcode_settings.GetPerTargetSettings() + env = self.GetToolchainEnv(additional_settings=extra_env) + else: + env = self.GetToolchainEnv() for copy in copies: for path in copy['files']: # Normalize the path so trailing slashes don't confuse us. @@ -740,17 +777,90 @@ class NinjaWriter: return outputs + def WriteiOSFrameworkHeaders(self, spec, outputs, prebuild): + """Prebuild steps to generate hmap files and copy headers to destination.""" + framework = self.ComputeMacBundleOutput() + all_sources = spec['sources'] + copy_headers = spec['mac_framework_headers'] + output = self.GypPathToUniqueOutput('headers.hmap') + self.xcode_settings.header_map_path = output + all_headers = map(self.GypPathToNinja, + filter(lambda x:x.endswith(('.h')), all_sources)) + variables = [('framework', framework), + ('copy_headers', map(self.GypPathToNinja, copy_headers))] + outputs.extend(self.ninja.build( + output, 'compile_ios_framework_headers', all_headers, + variables=variables, order_only=prebuild)) + def WriteMacBundleResources(self, resources, bundle_depends): """Writes ninja edges for 'mac_bundle_resources'.""" + xcassets = [] + + extra_env = self.xcode_settings.GetPerTargetSettings() + env = self.GetSortedXcodeEnv(additional_settings=extra_env) + env = self.ComputeExportEnvString(env) + isBinary = self.xcode_settings.IsBinaryOutputFormat(self.config_name) + for output, res in gyp.xcode_emulation.GetMacBundleResources( generator_default_variables['PRODUCT_DIR'], self.xcode_settings, map(self.GypPathToNinja, resources)): output = self.ExpandSpecial(output) - self.ninja.build(output, 'mac_tool', res, - variables=[('mactool_cmd', 'copy-bundle-resource')]) - bundle_depends.append(output) + if os.path.splitext(output)[-1] != '.xcassets': + self.ninja.build(output, 'mac_tool', res, + variables=[('mactool_cmd', 'copy-bundle-resource'), \ + ('env', env), ('binary', isBinary)]) + bundle_depends.append(output) + else: + xcassets.append(res) + return xcassets - def WriteMacInfoPlist(self, bundle_depends): + def WriteMacXCassets(self, xcassets, bundle_depends): + """Writes ninja edges for 'mac_bundle_resources' .xcassets files. + + This add an invocation of 'actool' via the 'mac_tool.py' helper script. + It assumes that the assets catalogs define at least one imageset and + thus an Assets.car file will be generated in the application resources + directory. If this is not the case, then the build will probably be done + at each invocation of ninja.""" + if not xcassets: + return + + extra_arguments = {} + settings_to_arg = { + 'XCASSETS_APP_ICON': 'app-icon', + 'XCASSETS_LAUNCH_IMAGE': 'launch-image', + } + settings = self.xcode_settings.xcode_settings[self.config_name] + for settings_key, arg_name in settings_to_arg.iteritems(): + value = settings.get(settings_key) + if value: + extra_arguments[arg_name] = value + + partial_info_plist = None + if extra_arguments: + partial_info_plist = self.GypPathToUniqueOutput( + 'assetcatalog_generated_info.plist') + extra_arguments['output-partial-info-plist'] = partial_info_plist + + outputs = [] + outputs.append( + os.path.join( + self.xcode_settings.GetBundleResourceFolder(), + 'Assets.car')) + if partial_info_plist: + outputs.append(partial_info_plist) + + keys = QuoteShellArgument(json.dumps(extra_arguments), self.flavor) + extra_env = self.xcode_settings.GetPerTargetSettings() + env = self.GetSortedXcodeEnv(additional_settings=extra_env) + env = self.ComputeExportEnvString(env) + + bundle_depends.extend(self.ninja.build( + outputs, 'compile_xcassets', xcassets, + variables=[('env', env), ('keys', keys)])) + return partial_info_plist + + def WriteMacInfoPlist(self, partial_info_plist, bundle_depends): """Write build rules for bundle Info.plist files.""" info_plist, out, defines, extra_env = gyp.xcode_emulation.GetMacInfoPlist( generator_default_variables['PRODUCT_DIR'], @@ -770,10 +880,18 @@ class NinjaWriter: env = self.GetSortedXcodeEnv(additional_settings=extra_env) env = self.ComputeExportEnvString(env) + if partial_info_plist: + intermediate_plist = self.GypPathToUniqueOutput('merged_info.plist') + info_plist = self.ninja.build( + intermediate_plist, 'merge_infoplist', + [partial_info_plist, info_plist]) + keys = self.xcode_settings.GetExtraPlistItems(self.config_name) keys = QuoteShellArgument(json.dumps(keys), self.flavor) + isBinary = self.xcode_settings.IsBinaryOutputFormat(self.config_name) self.ninja.build(out, 'copy_infoplist', info_plist, - variables=[('env', env), ('keys', keys)]) + variables=[('env', env), ('keys', keys), + ('binary', isBinary)]) bundle_depends.append(out) def WriteSources(self, ninja_file, config_name, config, sources, predepends, @@ -785,6 +903,8 @@ class NinjaWriter: self.ninja.variable('cxx', '$cxx_host') self.ninja.variable('ld', '$ld_host') self.ninja.variable('ldxx', '$ldxx_host') + self.ninja.variable('nm', '$nm_host') + self.ninja.variable('readelf', '$readelf_host') if self.flavor != 'mac' or len(self.archs) == 1: return self.WriteSourcesForArch( @@ -810,6 +930,7 @@ class NinjaWriter: cflags_objcc = ['$cflags_cc'] + \ self.xcode_settings.GetCflagsObjCC(config_name) elif self.flavor == 'win': + asmflags = self.msvs_settings.GetAsmflags(config_name) cflags = self.msvs_settings.GetCflags(config_name) cflags_c = self.msvs_settings.GetCflagsC(config_name) cflags_cc = self.msvs_settings.GetCflagsCC(config_name) @@ -839,27 +960,41 @@ class NinjaWriter: os.environ.get('CFLAGS', '').split() + cflags_c) cflags_cc = (os.environ.get('CPPFLAGS', '').split() + os.environ.get('CXXFLAGS', '').split() + cflags_cc) + elif self.toolset == 'host': + cflags_c = (os.environ.get('CPPFLAGS_host', '').split() + + os.environ.get('CFLAGS_host', '').split() + cflags_c) + cflags_cc = (os.environ.get('CPPFLAGS_host', '').split() + + os.environ.get('CXXFLAGS_host', '').split() + cflags_cc) defines = config.get('defines', []) + extra_defines self.WriteVariableList(ninja_file, 'defines', [Define(d, self.flavor) for d in defines]) if self.flavor == 'win': + self.WriteVariableList(ninja_file, 'asmflags', + map(self.ExpandSpecial, asmflags)) self.WriteVariableList(ninja_file, 'rcflags', [QuoteShellArgument(self.ExpandSpecial(f), self.flavor) for f in self.msvs_settings.GetRcflags(config_name, self.GypPathToNinja)]) include_dirs = config.get('include_dirs', []) - env = self.GetSortedXcodeEnv() + + env = self.GetToolchainEnv() if self.flavor == 'win': - env = self.msvs_settings.GetVSMacroEnv('$!PRODUCT_DIR', - config=config_name) include_dirs = self.msvs_settings.AdjustIncludeDirs(include_dirs, config_name) self.WriteVariableList(ninja_file, 'includes', [QuoteShellArgument('-I' + self.GypPathToNinja(i, env), self.flavor) for i in include_dirs]) + if self.flavor == 'win': + midl_include_dirs = config.get('midl_include_dirs', []) + midl_include_dirs = self.msvs_settings.AdjustMidlIncludeDirs( + midl_include_dirs, config_name) + self.WriteVariableList(ninja_file, 'midl_includes', + [QuoteShellArgument('-I' + self.GypPathToNinja(i, env), self.flavor) + for i in midl_include_dirs]) + pch_commands = precompiled_header.GetPchBuildCommands(arch) if self.flavor == 'mac': # Most targets use no precompiled headers, so only write these if needed. @@ -868,6 +1003,8 @@ class NinjaWriter: include = precompiled_header.GetInclude(ext, arch) if include: ninja_file.variable(var, include) + arflags = config.get('arflags', []) + self.WriteVariableList(ninja_file, 'cflags', map(self.ExpandSpecial, cflags)) self.WriteVariableList(ninja_file, 'cflags_c', @@ -879,6 +1016,8 @@ class NinjaWriter: map(self.ExpandSpecial, cflags_objc)) self.WriteVariableList(ninja_file, 'cflags_objcc', map(self.ExpandSpecial, cflags_objcc)) + self.WriteVariableList(ninja_file, 'arflags', + map(self.ExpandSpecial, arflags)) ninja_file.newline() outputs = [] has_rc_source = False @@ -894,9 +1033,7 @@ class NinjaWriter: elif ext == 's' and self.flavor != 'win': # Doesn't generate .o.d files. command = 'cc_s' elif (self.flavor == 'win' and ext == 'asm' and - self.msvs_settings.GetArch(config_name) == 'x86' and not self.msvs_settings.HasExplicitAsmRules(spec)): - # Asm files only get auto assembled for x86 (not x64). command = 'asm' # Add the _asm suffix as msvs is capable of handling .cc and # .asm files of the same name without collision. @@ -956,25 +1093,35 @@ class NinjaWriter: cmd = map.get(lang) ninja_file.build(gch, cmd, input, variables=[(var_name, lang_flag)]) - def WriteLink(self, spec, config_name, config, link_deps): + def WriteLink(self, spec, config_name, config, link_deps, compile_deps): """Write out a link step. Fills out target.binary. """ if self.flavor != 'mac' or len(self.archs) == 1: return self.WriteLinkForArch( - self.ninja, spec, config_name, config, link_deps) + self.ninja, spec, config_name, config, link_deps, compile_deps) else: output = self.ComputeOutput(spec) inputs = [self.WriteLinkForArch(self.arch_subninjas[arch], spec, config_name, config, link_deps[arch], - arch=arch) + compile_deps, arch=arch) for arch in self.archs] extra_bindings = [] + build_output = output if not self.is_mac_bundle: self.AppendPostbuildVariable(extra_bindings, spec, output, output) - self.ninja.build(output, 'lipo', inputs, variables=extra_bindings) + + # TODO(yyanagisawa): more work needed to fix: + # https://code.google.com/p/gyp/issues/detail?id=411 + if (spec['type'] in ('shared_library', 'loadable_module') and + not self.is_mac_bundle): + extra_bindings.append(('lib', output)) + self.ninja.build([output, output + '.TOC'], 'solipo', inputs, + variables=extra_bindings) + else: + self.ninja.build(build_output, 'lipo', inputs, variables=extra_bindings) return output def WriteLinkForArch(self, ninja_file, spec, config_name, config, - link_deps, arch=None): + link_deps, compile_deps, arch=None): """Write out a link step. Fills out target.binary. """ command = { 'executable': 'link', @@ -985,6 +1132,15 @@ class NinjaWriter: implicit_deps = set() solibs = set() + order_deps = set() + + if compile_deps: + # Normally, the compiles of the target already depend on compile_deps, + # but a shared_library target might have no sources and only link together + # a few static_library deps, so the link step also needs to depend + # on compile_deps to make sure actions in the shared_library target + # get run before the link. + order_deps.add(compile_deps) if 'dependencies' in spec: # Two kinds of dependencies: @@ -1003,6 +1159,8 @@ class NinjaWriter: target.component_objs and self.msvs_settings.IsUseLibraryDependencyInputs(config_name)): new_deps = target.component_objs + if target.compile_deps: + order_deps.add(target.compile_deps) elif self.flavor == 'win' and target.import_lib: new_deps = [target.import_lib] elif target.UsesToc(self.flavor): @@ -1063,10 +1221,12 @@ class NinjaWriter: rpath = 'lib/' if self.toolset != 'target': rpath += self.toolset - ldflags.append('-Wl,-rpath=\$$ORIGIN/%s' % rpath) + ldflags.append(r'-Wl,-rpath=\$$ORIGIN/%s' % rpath) + else: + ldflags.append('-Wl,-rpath=%s' % self.target_rpath) ldflags.append('-Wl,-rpath-link=%s' % rpath) self.WriteVariableList(ninja_file, 'ldflags', - gyp.common.uniquer(map(self.ExpandSpecial, ldflags))) + map(self.ExpandSpecial, ldflags)) library_dirs = config.get('library_dirs', []) if self.flavor == 'win': @@ -1095,9 +1255,27 @@ class NinjaWriter: extra_bindings.append(('soname', os.path.split(output)[1])) extra_bindings.append(('lib', gyp.common.EncodePOSIXShellArgument(output))) + if self.flavor != 'win': + link_file_list = output + if self.is_mac_bundle: + # 'Dependency Framework.framework/Versions/A/Dependency Framework' -> + # 'Dependency Framework.framework.rsp' + link_file_list = self.xcode_settings.GetWrapperName() + if arch: + link_file_list += '.' + arch + link_file_list += '.rsp' + # If an rspfile contains spaces, ninja surrounds the filename with + # quotes around it and then passes it to open(), creating a file with + # quotes in its name (and when looking for the rsp file, the name + # makes it through bash which strips the quotes) :-/ + link_file_list = link_file_list.replace(' ', '_') + extra_bindings.append( + ('link_file_list', + gyp.common.EncodePOSIXShellArgument(link_file_list))) if self.flavor == 'win': extra_bindings.append(('binary', output)) - if '/NOENTRY' not in ldflags: + if ('/NOENTRY' not in ldflags and + not self.msvs_settings.GetNoImportLibrary(config_name)): self.target.import_lib = output + '.lib' extra_bindings.append(('implibflag', '/IMPLIB:%s' % self.target.import_lib)) @@ -1119,10 +1297,12 @@ class NinjaWriter: if len(solibs): - extra_bindings.append(('solibs', gyp.common.EncodePOSIXShellList(solibs))) + extra_bindings.append(('solibs', + gyp.common.EncodePOSIXShellList(sorted(solibs)))) ninja_file.build(output, command + command_suffix, link_deps, - implicit=list(implicit_deps), + implicit=sorted(implicit_deps), + order_only=list(order_deps), variables=extra_bindings) return linked_binary @@ -1137,7 +1317,7 @@ class NinjaWriter: self.target.type = 'none' elif spec['type'] == 'static_library': self.target.binary = self.ComputeOutput(spec) - if (self.flavor not in ('mac', 'openbsd', 'win') and not + if (self.flavor not in ('mac', 'openbsd', 'netbsd', 'win') and not self.is_standalone_static_library): self.ninja.build(self.target.binary, 'alink_thin', link_deps, order_only=compile_deps) @@ -1174,7 +1354,8 @@ class NinjaWriter: # needed. variables=variables) else: - self.target.binary = self.WriteLink(spec, config_name, config, link_deps) + self.target.binary = self.WriteLink(spec, config_name, config, link_deps, + compile_deps) return self.target.binary def WriteMacBundle(self, spec, mac_bundle_depends, is_empty): @@ -1187,15 +1368,32 @@ class NinjaWriter: self.AppendPostbuildVariable(variables, spec, output, self.target.binary, is_command_start=not package_framework) if package_framework and not is_empty: - variables.append(('version', self.xcode_settings.GetFrameworkVersion())) - self.ninja.build(output, 'package_framework', mac_bundle_depends, - variables=variables) + if spec['type'] == 'shared_library' and self.xcode_settings.isIOS: + self.ninja.build(output, 'package_ios_framework', mac_bundle_depends, + variables=variables) + else: + variables.append(('version', self.xcode_settings.GetFrameworkVersion())) + self.ninja.build(output, 'package_framework', mac_bundle_depends, + variables=variables) else: self.ninja.build(output, 'stamp', mac_bundle_depends, variables=variables) self.target.bundle = output return output + def GetToolchainEnv(self, additional_settings=None): + """Returns the variables toolchain would set for build steps.""" + env = self.GetSortedXcodeEnv(additional_settings=additional_settings) + if self.flavor == 'win': + env = self.GetMsvsToolchainEnv( + additional_settings=additional_settings) + return env + + def GetMsvsToolchainEnv(self, additional_settings=None): + """Returns the variables Visual Studio would set for build steps.""" + return self.msvs_settings.GetVSMacroEnv('$!PRODUCT_DIR', + config=self.config_name) + def GetSortedXcodeEnv(self, additional_settings=None): """Returns the variables Xcode would set for build steps.""" assert self.abs_build_dir @@ -1377,7 +1575,8 @@ class NinjaWriter: values = [] ninja_file.variable(var, ' '.join(values)) - def WriteNewNinjaRule(self, name, args, description, is_cygwin, env): + def WriteNewNinjaRule(self, name, args, description, is_cygwin, env, pool, + depfile=None): """Write out a new ninja "rule" statement for a given command. Returns the name of the new rule, and a copy of |args| with variables @@ -1435,7 +1634,8 @@ class NinjaWriter: # GYP rules/actions express being no-ops by not touching their outputs. # Avoid executing downstream dependencies in this case by specifying # restat=1 to ninja. - self.ninja.rule(rule_name, command, description, restat=True, + self.ninja.rule(rule_name, command, description, depfile=depfile, + restat=True, pool=pool, rspfile=rspfile, rspfile_content=rspfile_content) self.ninja.newline() @@ -1466,12 +1666,13 @@ def CalculateVariables(default_variables, params): generator_extra_sources_for_rules = getattr(xcode_generator, 'generator_extra_sources_for_rules', []) elif flavor == 'win': + exts = gyp.MSVSUtil.TARGET_TYPE_EXT default_variables.setdefault('OS', 'win') - default_variables['EXECUTABLE_SUFFIX'] = '.exe' + default_variables['EXECUTABLE_SUFFIX'] = '.' + exts['executable'] default_variables['STATIC_LIB_PREFIX'] = '' - default_variables['STATIC_LIB_SUFFIX'] = '.lib' + default_variables['STATIC_LIB_SUFFIX'] = '.' + exts['static_library'] default_variables['SHARED_LIB_PREFIX'] = '' - default_variables['SHARED_LIB_SUFFIX'] = '.dll' + default_variables['SHARED_LIB_SUFFIX'] = '.' + exts['shared_library'] # Copy additional generator configuration data from VS, which is shared # by the Windows Ninja generator. @@ -1535,6 +1736,10 @@ def CommandWithWrapper(cmd, wrappers, prog): def GetDefaultConcurrentLinks(): """Returns a best-guess for a number of concurrent links.""" + pool_size = int(os.environ.get('GYP_LINK_CONCURRENCY', 0)) + if pool_size: + return pool_size + if sys.platform in ('win32', 'cygwin'): import ctypes @@ -1555,18 +1760,21 @@ def GetDefaultConcurrentLinks(): stat.dwLength = ctypes.sizeof(stat) ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat)) - mem_limit = max(1, stat.ullTotalPhys / (4 * (2 ** 30))) # total / 4GB - hard_cap = max(1, int(os.getenv('GYP_LINK_CONCURRENCY_MAX', 2**32))) + # VS 2015 uses 20% more working set than VS 2013 and can consume all RAM + # on a 64 GB machine. + mem_limit = max(1, stat.ullTotalPhys / (5 * (2 ** 30))) # total / 5GB + hard_cap = max(1, int(os.environ.get('GYP_LINK_CONCURRENCY_MAX', 2**32))) return min(mem_limit, hard_cap) elif sys.platform.startswith('linux'): - with open("/proc/meminfo") as meminfo: - memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB') - for line in meminfo: - match = memtotal_re.match(line) - if not match: - continue - # Allow 8Gb per link on Linux because Gold is quite memory hungry - return max(1, int(match.group(1)) / (8 * (2 ** 20))) + if os.path.exists("/proc/meminfo"): + with open("/proc/meminfo") as meminfo: + memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB') + for line in meminfo: + match = memtotal_re.match(line) + if not match: + continue + # Allow 8Gb per link on Linux because Gold is quite memory hungry + return max(1, int(match.group(1)) / (8 * (2 ** 20))) return 1 elif sys.platform == 'darwin': try: @@ -1653,7 +1861,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, master_ninja = ninja_syntax.Writer(master_ninja_file, width=120) # Put build-time support tools in out/{config_name}. - gyp.common.CopyTool(flavor, toplevel_build) + gyp.common.CopyTool(flavor, toplevel_build, generator_flags) # Grab make settings for CC/CXX. # The rules are @@ -1663,14 +1871,15 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, # 'CC_host'/'CXX_host' enviroment variable, cc_host/cxx_host should be set # to cc/cxx. if flavor == 'win': - # Overridden by local arch choice in the use_deps case. - # Chromium's ffmpeg c99conv.py currently looks for a 'cc =' line in - # build.ninja so needs something valid here. http://crbug.com/233985 - cc = 'cl.exe' - cxx = 'cl.exe' + ar = 'lib.exe' + # cc and cxx must be set to the correct architecture by overriding with one + # of cl_x86 or cl_x64 below. + cc = 'UNSET' + cxx = 'UNSET' ld = 'link.exe' ld_host = '$ld' else: + ar = 'ar' cc = 'cc' cxx = 'c++' ld = '$cc' @@ -1678,11 +1887,16 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, ld_host = '$cc_host' ldxx_host = '$cxx_host' + ar_host = ar cc_host = None cxx_host = None cc_host_global_setting = None cxx_host_global_setting = None clang_cl = None + nm = 'nm' + nm_host = 'nm' + readelf = 'readelf' + readelf_host = 'readelf' build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0]) make_global_settings = data[build_file].get('make_global_settings', []) @@ -1690,6 +1904,10 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, options.toplevel_dir) wrappers = {} for key, value in make_global_settings: + if key == 'AR': + ar = os.path.join(build_to_root, value) + if key == 'AR.host': + ar_host = os.path.join(build_to_root, value) if key == 'CC': cc = os.path.join(build_to_root, value) if cc.endswith('clang-cl'): @@ -1702,6 +1920,18 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, if key == 'CXX.host': cxx_host = os.path.join(build_to_root, value) cxx_host_global_setting = value + if key == 'LD': + ld = os.path.join(build_to_root, value) + if key == 'LD.host': + ld_host = os.path.join(build_to_root, value) + if key == 'NM': + nm = os.path.join(build_to_root, value) + if key == 'NM.host': + nm_host = os.path.join(build_to_root, value) + if key == 'READELF': + readelf = os.path.join(build_to_root, value) + if key == 'READELF.host': + readelf_host = os.path.join(build_to_root, value) if key.endswith('_wrapper'): wrappers[key[:-len('_wrapper')]] = os.path.join(build_to_root, value) @@ -1712,10 +1942,21 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, key_prefix = re.sub(r'\.HOST$', '.host', key_prefix) wrappers[key_prefix] = os.path.join(build_to_root, value) + mac_toolchain_dir = generator_flags.get('mac_toolchain_dir', None) + if mac_toolchain_dir: + wrappers['LINK'] = "export DEVELOPER_DIR='%s' &&" % mac_toolchain_dir + if flavor == 'win': + configs = [target_dicts[qualified_target]['configurations'][config_name] + for qualified_target in target_list] + shared_system_includes = None + if not generator_flags.get('ninja_use_custom_environment_files', 0): + shared_system_includes = \ + gyp.msvs_emulation.ExtractSharedMSVSSystemIncludes( + configs, generator_flags) cl_paths = gyp.msvs_emulation.GenerateEnvironmentFiles( - toplevel_build, generator_flags, OpenOutput) - for arch, path in cl_paths.iteritems(): + toplevel_build, generator_flags, shared_system_includes, OpenOutput) + for arch, path in sorted(cl_paths.iteritems()): if clang_cl: # If we have selected clang-cl, use that instead. path = clang_cl @@ -1734,14 +1975,22 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, if flavor == 'win': master_ninja.variable('ld', ld) master_ninja.variable('idl', 'midl.exe') - master_ninja.variable('ar', 'lib.exe') + master_ninja.variable('ar', ar) master_ninja.variable('rc', 'rc.exe') - master_ninja.variable('asm', 'ml.exe') + master_ninja.variable('ml_x86', 'ml.exe') + master_ninja.variable('ml_x64', 'ml64.exe') master_ninja.variable('mt', 'mt.exe') else: master_ninja.variable('ld', CommandWithWrapper('LINK', wrappers, ld)) master_ninja.variable('ldxx', CommandWithWrapper('LINK', wrappers, ldxx)) - master_ninja.variable('ar', GetEnvironFallback(['AR_target', 'AR'], 'ar')) + master_ninja.variable('ar', GetEnvironFallback(['AR_target', 'AR'], ar)) + if flavor != 'mac': + # Mac does not use readelf/nm for .TOC generation, so avoiding polluting + # the master ninja with extra unused variables. + master_ninja.variable( + 'nm', GetEnvironFallback(['NM_target', 'NM'], nm)) + master_ninja.variable( + 'readelf', GetEnvironFallback(['READELF_target', 'READELF'], readelf)) if generator_supports_multiple_toolsets: if not cc_host: @@ -1749,7 +1998,10 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, if not cxx_host: cxx_host = cxx - master_ninja.variable('ar_host', GetEnvironFallback(['AR_host'], 'ar')) + master_ninja.variable('ar_host', GetEnvironFallback(['AR_host'], ar_host)) + master_ninja.variable('nm_host', GetEnvironFallback(['NM_host'], nm_host)) + master_ninja.variable('readelf_host', + GetEnvironFallback(['READELF_host'], readelf_host)) cc_host = GetEnvironFallback(['CC_host'], cc_host) cxx_host = GetEnvironFallback(['CXX_host'], cxx_host) @@ -1832,7 +2084,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, description='IDL $in', command=('%s gyp-win-tool midl-wrapper $arch $outdir ' '$tlb $h $dlldata $iid $proxy $in ' - '$idlflags' % sys.executable)) + '$midl_includes $idlflags' % sys.executable)) master_ninja.rule( 'rc', description='RC $in', @@ -1842,20 +2094,20 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, sys.executable)) master_ninja.rule( 'asm', - description='ASM $in', + description='ASM $out', command=('%s gyp-win-tool asm-wrapper ' - '$arch $asm $defines $includes /c /Fo $out $in' % + '$arch $asm $defines $includes $asmflags /c /Fo $out $in' % sys.executable)) if flavor != 'mac' and flavor != 'win': master_ninja.rule( 'alink', description='AR $out', - command='rm -f $out && $ar rcs $out $in') + command='rm -f $out && $ar rcs $arflags $out $in') master_ninja.rule( 'alink_thin', description='AR $out', - command='rm -f $out && $ar rcsT $out $in') + command='rm -f $out && $ar rcsT $arflags $out $in') # This allows targets that only need to depend on $lib's API to declare an # order-only dependency on $lib.TOC and avoid relinking such downstream @@ -1863,38 +2115,39 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, # The resulting string leaves an uninterpolated %{suffix} which # is used in the final substitution below. mtime_preserving_solink_base = ( - 'if [ ! -e $lib -o ! -e ${lib}.TOC ]; then ' - '%(solink)s && %(extract_toc)s > ${lib}.TOC; else ' - '%(solink)s && %(extract_toc)s > ${lib}.tmp && ' - 'if ! cmp -s ${lib}.tmp ${lib}.TOC; then mv ${lib}.tmp ${lib}.TOC ; ' + 'if [ ! -e $lib -o ! -e $lib.TOC ]; then ' + '%(solink)s && %(extract_toc)s > $lib.TOC; else ' + '%(solink)s && %(extract_toc)s > $lib.tmp && ' + 'if ! cmp -s $lib.tmp $lib.TOC; then mv $lib.tmp $lib.TOC ; ' 'fi; fi' % { 'solink': '$ld -shared $ldflags -o $lib -Wl,-soname=$soname %(suffix)s', 'extract_toc': - ('{ readelf -d ${lib} | grep SONAME ; ' - 'nm -gD -f p ${lib} | cut -f1-2 -d\' \'; }')}) + ('{ $readelf -d $lib | grep SONAME ; ' + '$nm -gD -f p $lib | cut -f1-2 -d\' \'; }')}) master_ninja.rule( 'solink', description='SOLINK $lib', restat=True, - command=(mtime_preserving_solink_base % { - 'suffix': '-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive ' - '$libs'}), + command=mtime_preserving_solink_base % {'suffix': '@$link_file_list'}, + rspfile='$link_file_list', + rspfile_content= + '-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive $libs', pool='link_pool') master_ninja.rule( 'solink_module', description='SOLINK(module) $lib', restat=True, - command=(mtime_preserving_solink_base % { - 'suffix': '-Wl,--start-group $in $solibs -Wl,--end-group ' - '$libs'}), + command=mtime_preserving_solink_base % {'suffix': '@$link_file_list'}, + rspfile='$link_file_list', + rspfile_content='-Wl,--start-group $in -Wl,--end-group $solibs $libs', pool='link_pool') master_ninja.rule( 'link', description='LINK $out', command=('$ld $ldflags -o $out ' - '-Wl,--start-group $in $solibs -Wl,--end-group $libs'), + '-Wl,--start-group $in -Wl,--end-group $solibs $libs'), pool='link_pool') elif flavor == 'win': master_ninja.rule( @@ -1933,21 +2186,31 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, 'lipo', description='LIPO $out, POSTBUILDS', command='rm -f $out && lipo -create $in -output $out$postbuilds') + master_ninja.rule( + 'solipo', + description='SOLIPO $out, POSTBUILDS', + command=( + 'rm -f $lib $lib.TOC && lipo -create $in -output $lib$postbuilds &&' + '%(extract_toc)s > $lib.TOC' + % { 'extract_toc': + '{ otool -l $lib | grep LC_ID_DYLIB -A 5; ' + 'nm -gP $lib | cut -f1-2 -d\' \' | grep -v U$$; true; }'})) + # Record the public interface of $lib in $lib.TOC. See the corresponding # comment in the posix section above for details. solink_base = '$ld %(type)s $ldflags -o $lib %(suffix)s' mtime_preserving_solink_base = ( - 'if [ ! -e $lib -o ! -e ${lib}.TOC ] || ' + 'if [ ! -e $lib -o ! -e $lib.TOC ] || ' # Always force dependent targets to relink if this library # reexports something. Handling this correctly would require # recursive TOC dumping but this is rare in practice, so punt. 'otool -l $lib | grep -q LC_REEXPORT_DYLIB ; then ' - '%(solink)s && %(extract_toc)s > ${lib}.TOC; ' + '%(solink)s && %(extract_toc)s > $lib.TOC; ' 'else ' - '%(solink)s && %(extract_toc)s > ${lib}.tmp && ' - 'if ! cmp -s ${lib}.tmp ${lib}.TOC; then ' - 'mv ${lib}.tmp ${lib}.TOC ; ' + '%(solink)s && %(extract_toc)s > $lib.tmp && ' + 'if ! cmp -s $lib.tmp $lib.TOC; then ' + 'mv $lib.tmp $lib.TOC ; ' 'fi; ' 'fi' % { 'solink': solink_base, @@ -1955,34 +2218,42 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, '{ otool -l $lib | grep LC_ID_DYLIB -A 5; ' 'nm -gP $lib | cut -f1-2 -d\' \' | grep -v U$$; true; }'}) - solink_suffix = '$in $solibs $libs$postbuilds' + + solink_suffix = '@$link_file_list$postbuilds' master_ninja.rule( 'solink', description='SOLINK $lib, POSTBUILDS', restat=True, command=mtime_preserving_solink_base % {'suffix': solink_suffix, 'type': '-shared'}, + rspfile='$link_file_list', + rspfile_content='$in $solibs $libs', pool='link_pool') master_ninja.rule( 'solink_notoc', description='SOLINK $lib, POSTBUILDS', restat=True, command=solink_base % {'suffix':solink_suffix, 'type': '-shared'}, + rspfile='$link_file_list', + rspfile_content='$in $solibs $libs', pool='link_pool') - solink_module_suffix = '$in $solibs $libs$postbuilds' master_ninja.rule( 'solink_module', description='SOLINK(module) $lib, POSTBUILDS', restat=True, - command=mtime_preserving_solink_base % {'suffix': solink_module_suffix, + command=mtime_preserving_solink_base % {'suffix': solink_suffix, 'type': '-bundle'}, + rspfile='$link_file_list', + rspfile_content='$in $solibs $libs', pool='link_pool') master_ninja.rule( 'solink_module_notoc', description='SOLINK(module) $lib, POSTBUILDS', restat=True, - command=solink_base % {'suffix': solink_module_suffix, 'type': '-bundle'}, + command=solink_base % {'suffix': solink_suffix, 'type': '-bundle'}, + rspfile='$link_file_list', + rspfile_content='$in $solibs $libs', pool='link_pool') master_ninja.rule( @@ -1999,16 +2270,35 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, master_ninja.rule( 'copy_infoplist', description='COPY INFOPLIST $in', - command='$env ./gyp-mac-tool copy-info-plist $in $out $keys') + command='$env ./gyp-mac-tool copy-info-plist $in $out $binary $keys') + master_ninja.rule( + 'merge_infoplist', + description='MERGE INFOPLISTS $in', + command='$env ./gyp-mac-tool merge-info-plist $out $in') + master_ninja.rule( + 'compile_xcassets', + description='COMPILE XCASSETS $in', + command='$env ./gyp-mac-tool compile-xcassets $keys $in') + master_ninja.rule( + 'compile_ios_framework_headers', + description='COMPILE HEADER MAPS AND COPY FRAMEWORK HEADERS $in', + command='$env ./gyp-mac-tool compile-ios-framework-header-map $out ' + '$framework $in && $env ./gyp-mac-tool ' + 'copy-ios-framework-headers $framework $copy_headers') master_ninja.rule( 'mac_tool', description='MACTOOL $mactool_cmd $in', - command='$env ./gyp-mac-tool $mactool_cmd $in $out') + command='$env ./gyp-mac-tool $mactool_cmd $in $out $binary') master_ninja.rule( 'package_framework', description='PACKAGE FRAMEWORK $out, POSTBUILDS', command='./gyp-mac-tool package-framework $out $version$postbuilds ' '&& touch $out') + master_ninja.rule( + 'package_ios_framework', + description='PACKAGE IOS FRAMEWORK $out, POSTBUILDS', + command='./gyp-mac-tool package-ios-framework $out $postbuilds ' + '&& touch $out') if flavor == 'win': master_ninja.rule( 'stamp', @@ -2043,6 +2333,15 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, # objects. target_short_names = {} + # short name of targets that were skipped because they didn't contain anything + # interesting. + # NOTE: there may be overlap between this an non_empty_target_names. + empty_target_names = set() + + # Set of non-empty short target names. + # NOTE: there may be overlap between this an empty_target_names. + non_empty_target_names = set() + for qualified_target in target_list: # qualified_target is like: third_party/icu/icu.gyp:icui18n#target build_file, name, toolset = \ @@ -2057,7 +2356,15 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, if flavor == 'mac': gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(data[build_file], spec) - build_file = gyp.common.RelativePath(build_file, options.toplevel_dir) + # If build_file is a symlink, we must not follow it because there's a chance + # it could point to a path above toplevel_dir, and we cannot correctly deal + # with that case at the moment. + build_file = gyp.common.RelativePath(build_file, options.toplevel_dir, + False) + + qualified_target_for_hash = gyp.common.QualifiedTarget(build_file, name, + toolset) + hash_for_rules = hashlib.md5(qualified_target_for_hash).hexdigest() base_path = os.path.dirname(build_file) obj = 'obj' @@ -2066,7 +2373,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, output_file = os.path.join(obj, base_path, name + '.ninja') ninja_output = StringIO() - writer = NinjaWriter(qualified_target, target_outputs, base_path, build_dir, + writer = NinjaWriter(hash_for_rules, target_outputs, base_path, build_dir, ninja_output, toplevel_build, output_file, flavor, toplevel_dir=options.toplevel_dir) @@ -2086,6 +2393,9 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, target_outputs[qualified_target] = target if qualified_target in all_targets: all_outputs.add(target.FinalOutput()) + non_empty_target_names.add(name) + else: + empty_target_names.add(name) if target_short_names: # Write a short name to build this target. This benefits both the @@ -2093,13 +2403,23 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, # able to run actions and build libraries by their short name. master_ninja.newline() master_ninja.comment('Short names for targets.') - for short_name in target_short_names: + for short_name in sorted(target_short_names): master_ninja.build(short_name, 'phony', [x.FinalOutput() for x in target_short_names[short_name]]) + # Write phony targets for any empty targets that weren't written yet. As + # short names are not necessarily unique only do this for short names that + # haven't already been output for another target. + empty_target_names = empty_target_names - non_empty_target_names + if empty_target_names: + master_ninja.newline() + master_ninja.comment('Empty targets (output for completeness).') + for name in sorted(empty_target_names): + master_ninja.build(name, 'phony') + if all_outputs: master_ninja.newline() - master_ninja.build('all', 'phony', list(all_outputs)) + master_ninja.build('all', 'phony', sorted(all_outputs)) master_ninja.default(generator_flags.get('default_target', 'all')) master_ninja_file.close() diff --git a/third_party/gyp/generator/ninja_test.py b/third_party/gyp/generator/ninja_test.py index 52661bcd..1767b2f4 100644 --- a/third_party/gyp/generator/ninja_test.py +++ b/third_party/gyp/generator/ninja_test.py @@ -15,15 +15,18 @@ import TestCommon class TestPrefixesAndSuffixes(unittest.TestCase): def test_BinaryNamesWindows(self): - writer = ninja.NinjaWriter('foo', 'wee', '.', '.', 'build.ninja', '.', - 'build.ninja', 'win') - spec = { 'target_name': 'wee' } - self.assertTrue(writer.ComputeOutputFileName(spec, 'executable'). - endswith('.exe')) - self.assertTrue(writer.ComputeOutputFileName(spec, 'shared_library'). - endswith('.dll')) - self.assertTrue(writer.ComputeOutputFileName(spec, 'static_library'). - endswith('.lib')) + # These cannot run on non-Windows as they require a VS installation to + # correctly handle variable expansion. + if sys.platform.startswith('win'): + writer = ninja.NinjaWriter('foo', 'wee', '.', '.', 'build.ninja', '.', + 'build.ninja', 'win') + spec = { 'target_name': 'wee' } + self.assertTrue(writer.ComputeOutputFileName(spec, 'executable'). + endswith('.exe')) + self.assertTrue(writer.ComputeOutputFileName(spec, 'shared_library'). + endswith('.dll')) + self.assertTrue(writer.ComputeOutputFileName(spec, 'static_library'). + endswith('.lib')) def test_BinaryNamesLinux(self): writer = ninja.NinjaWriter('foo', 'wee', '.', '.', 'build.ninja', '.', diff --git a/third_party/gyp/generator/xcode.py b/third_party/gyp/generator/xcode.py index 331e78ba..db99d6ab 100644 --- a/third_party/gyp/generator/xcode.py +++ b/third_party/gyp/generator/xcode.py @@ -5,6 +5,7 @@ import filecmp import gyp.common import gyp.xcodeproj_file +import gyp.xcode_ninja import errno import os import sys @@ -68,11 +69,15 @@ generator_additional_path_sections = [ # The Xcode-specific keys that exist on targets and aren't moved down to # configurations. generator_additional_non_configuration_keys = [ + 'ios_app_extension', + 'ios_watch_app', + 'ios_watchkit_extension', 'mac_bundle', 'mac_bundle_resources', 'mac_framework_headers', 'mac_framework_private_headers', 'mac_xctest_bundle', + 'mac_xcuitest_bundle', 'xcode_create_dependents_test_runner', ] @@ -83,6 +88,8 @@ generator_extra_sources_for_rules = [ 'mac_framework_private_headers', ] +generator_filelist_paths = None + # Xcode's standard set of library directories, which don't need to be duplicated # in LIBRARY_SEARCH_PATHS. This list is not exhaustive, but that's okay. xcode_standard_library_dirs = frozenset([ @@ -484,7 +491,7 @@ sys.exit(subprocess.call(sys.argv[1:]))" """ def AddSourceToTarget(source, type, pbxp, xct): # TODO(mark): Perhaps source_extensions and library_extensions can be made a # little bit fancier. - source_extensions = ['c', 'cc', 'cpp', 'cxx', 'm', 'mm', 's'] + source_extensions = ['c', 'cc', 'cpp', 'cxx', 'm', 'mm', 's', 'swift'] # .o is conceptually more of a "source" than a "library," but Xcode thinks # of "sources" as things to compile and "libraries" (or "frameworks") as @@ -520,7 +527,7 @@ def AddHeaderToTarget(header, pbxp, xct, is_public): xct.HeadersPhase().AddFile(header, settings) -_xcode_variable_re = re.compile('(\$\((.*?)\))') +_xcode_variable_re = re.compile(r'(\$\((.*?)\))') def ExpandXcodeVariables(string, expansions): """Expands Xcode-style $(VARIABLES) in string per the expansions dict. @@ -574,13 +581,47 @@ def PerformBuild(data, configurations, params): subprocess.check_call(arguments) +def CalculateGeneratorInputInfo(params): + toplevel = params['options'].toplevel_dir + if params.get('flavor') == 'ninja': + generator_dir = os.path.relpath(params['options'].generator_output or '.') + output_dir = params.get('generator_flags', {}).get('output_dir', 'out') + output_dir = os.path.normpath(os.path.join(generator_dir, output_dir)) + qualified_out_dir = os.path.normpath(os.path.join( + toplevel, output_dir, 'gypfiles-xcode-ninja')) + else: + output_dir = os.path.normpath(os.path.join(toplevel, 'xcodebuild')) + qualified_out_dir = os.path.normpath(os.path.join( + toplevel, output_dir, 'gypfiles')) + + global generator_filelist_paths + generator_filelist_paths = { + 'toplevel': toplevel, + 'qualified_out_dir': qualified_out_dir, + } + + def GenerateOutput(target_list, target_dicts, data, params): + # Optionally configure each spec to use ninja as the external builder. + ninja_wrapper = params.get('flavor') == 'ninja' + if ninja_wrapper: + (target_list, target_dicts, data) = \ + gyp.xcode_ninja.CreateWrapper(target_list, target_dicts, data, params) + options = params['options'] generator_flags = params.get('generator_flags', {}) parallel_builds = generator_flags.get('xcode_parallel_builds', True) serialize_all_tests = \ generator_flags.get('xcode_serialize_all_test_runs', True) - project_version = generator_flags.get('xcode_project_version', None) + upgrade_check_project_version = \ + generator_flags.get('xcode_upgrade_check_project_version', None) + + # Format upgrade_check_project_version with leading zeros as needed. + if upgrade_check_project_version: + upgrade_check_project_version = str(upgrade_check_project_version) + while len(upgrade_check_project_version) < 4: + upgrade_check_project_version = '0' + upgrade_check_project_version + skip_excluded_files = \ not generator_flags.get('xcode_list_excluded_files', True) xcode_projects = {} @@ -595,11 +636,17 @@ def GenerateOutput(target_list, target_dicts, data, params): xcode_projects[build_file] = xcp pbxp = xcp.project + # Set project-level attributes from multiple options + project_attributes = {}; if parallel_builds: - pbxp.SetProperty('attributes', - {'BuildIndependentTargetsInParallel': 'YES'}) - if project_version: - xcp.project_file.SetXcodeVersion(project_version) + project_attributes['BuildIndependentTargetsInParallel'] = 'YES' + if upgrade_check_project_version: + project_attributes['LastUpgradeCheck'] = upgrade_check_project_version + project_attributes['LastTestingUpgradeCheck'] = \ + upgrade_check_project_version + project_attributes['LastSwiftUpdateCheck'] = \ + upgrade_check_project_version + pbxp.SetProperty('attributes', project_attributes) # Add gyp/gypi files to project if not generator_flags.get('standalone'): @@ -637,14 +684,22 @@ def GenerateOutput(target_list, target_dicts, data, params): # com.googlecode.gyp.xcode.bundle, a pseudo-type that xcode.py interprets # to create a single-file mh_bundle. _types = { - 'executable': 'com.apple.product-type.tool', - 'loadable_module': 'com.googlecode.gyp.xcode.bundle', - 'shared_library': 'com.apple.product-type.library.dynamic', - 'static_library': 'com.apple.product-type.library.static', - 'executable+bundle': 'com.apple.product-type.application', - 'loadable_module+bundle': 'com.apple.product-type.bundle', - 'loadable_module+xctest': 'com.apple.product-type.bundle.unit-test', - 'shared_library+bundle': 'com.apple.product-type.framework', + 'executable': 'com.apple.product-type.tool', + 'loadable_module': 'com.googlecode.gyp.xcode.bundle', + 'shared_library': 'com.apple.product-type.library.dynamic', + 'static_library': 'com.apple.product-type.library.static', + 'mac_kernel_extension': 'com.apple.product-type.kernel-extension', + 'executable+bundle': 'com.apple.product-type.application', + 'loadable_module+bundle': 'com.apple.product-type.bundle', + 'loadable_module+xctest': 'com.apple.product-type.bundle.unit-test', + 'loadable_module+xcuitest': 'com.apple.product-type.bundle.ui-testing', + 'shared_library+bundle': 'com.apple.product-type.framework', + 'executable+extension+bundle': 'com.apple.product-type.app-extension', + 'executable+watch+extension+bundle': + 'com.apple.product-type.watchkit-extension', + 'executable+watch+bundle': + 'com.apple.product-type.application.watchapp', + 'mac_kernel_extension+bundle': 'com.apple.product-type.kernel-extension', } target_properties = { @@ -654,14 +709,35 @@ def GenerateOutput(target_list, target_dicts, data, params): type = spec['type'] is_xctest = int(spec.get('mac_xctest_bundle', 0)) + is_xcuitest = int(spec.get('mac_xcuitest_bundle', 0)) is_bundle = int(spec.get('mac_bundle', 0)) or is_xctest + is_app_extension = int(spec.get('ios_app_extension', 0)) + is_watchkit_extension = int(spec.get('ios_watchkit_extension', 0)) + is_watch_app = int(spec.get('ios_watch_app', 0)) if type != 'none': type_bundle_key = type - if is_xctest: + if is_xcuitest: + type_bundle_key += '+xcuitest' + assert type == 'loadable_module', ( + 'mac_xcuitest_bundle targets must have type loadable_module ' + '(target %s)' % target_name) + elif is_xctest: type_bundle_key += '+xctest' assert type == 'loadable_module', ( 'mac_xctest_bundle targets must have type loadable_module ' '(target %s)' % target_name) + elif is_app_extension: + assert is_bundle, ('ios_app_extension flag requires mac_bundle ' + '(target %s)' % target_name) + type_bundle_key += '+extension+bundle' + elif is_watchkit_extension: + assert is_bundle, ('ios_watchkit_extension flag requires mac_bundle ' + '(target %s)' % target_name) + type_bundle_key += '+watch+extension+bundle' + elif is_watch_app: + assert is_bundle, ('ios_watch_app flag requires mac_bundle ' + '(target %s)' % target_name) + type_bundle_key += '+watch+bundle' elif is_bundle: type_bundle_key += '+bundle' @@ -677,6 +753,9 @@ def GenerateOutput(target_list, target_dicts, data, params): assert not is_bundle, ( 'mac_bundle targets cannot have type none (target "%s")' % target_name) + assert not is_xcuitest, ( + 'mac_xcuitest_bundle targets cannot have type none (target "%s")' % + target_name) assert not is_xctest, ( 'mac_xctest_bundle targets cannot have type none (target "%s")' % target_name) @@ -703,11 +782,16 @@ def GenerateOutput(target_list, target_dicts, data, params): # and is made a dependency of this target. This way the work is done # before the dependency checks for what should be recompiled. support_xct = None - if type != 'none' and (spec_actions or spec_rules): + # The Xcode "issues" don't affect xcode-ninja builds, since the dependency + # logic all happens in ninja. Don't bother creating the extra targets in + # that case. + if type != 'none' and (spec_actions or spec_rules) and not ninja_wrapper: support_xccl = CreateXCConfigurationList(configuration_names); + support_target_suffix = generator_flags.get( + 'support_target_suffix', ' Support') support_target_properties = { 'buildConfigurationList': support_xccl, - 'name': target_name + ' Support', + 'name': target_name + support_target_suffix, } if target_product_name: support_target_properties['productName'] = \ @@ -1096,6 +1180,9 @@ exit 1 # Relative paths are relative to $(SRCROOT). dest = '$(SRCROOT)/' + dest + code_sign = int(copy_group.get('xcode_code_sign', 0)) + settings = (None, '{ATTRIBUTES = (CodeSignOnCopy, ); }')[code_sign]; + # Coalesce multiple "copies" sections in the same target with the same # "destination" property into the same PBXCopyFilesBuildPhase, otherwise # they'll wind up with ID collisions. @@ -1114,7 +1201,7 @@ exit 1 pbxcp_dict[dest] = pbxcp for file in copy_group['files']: - pbxcp.AddFile(file) + pbxcp.AddFile(file, settings) # Excluded files can also go into the project file. if not skip_excluded_files: diff --git a/third_party/gyp/input.py b/third_party/gyp/input.py index 6472912d..22eb333d 100644 --- a/third_party/gyp/input.py +++ b/third_party/gyp/input.py @@ -10,8 +10,8 @@ from compiler.ast import Module from compiler.ast import Node from compiler.ast import Stmt import compiler -import copy import gyp.common +import gyp.simple_copy import multiprocessing import optparse import os.path @@ -24,10 +24,16 @@ import threading import time import traceback from gyp.common import GypError +from gyp.common import OrderedSet # A list of types that are treated as linkable. -linkable_types = ['executable', 'shared_library', 'loadable_module'] +linkable_types = [ + 'executable', + 'shared_library', + 'loadable_module', + 'mac_kernel_extension', +] # A list of sections that contain links to other targets. dependency_sections = ['dependencies', 'export_dependent_settings'] @@ -45,18 +51,36 @@ base_path_sections = [ 'outputs', 'sources', ] -path_sections = [] +path_sections = set() -is_path_section_charset = set('=+?!') -is_path_section_match_re = re.compile('_(dir|file|path)s?$') +# These per-process dictionaries are used to cache build file data when loading +# in parallel mode. +per_process_data = {} +per_process_aux_data = {} def IsPathSection(section): - # If section ends in one of these characters, it's applied to a 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, # because there's no way for a regular expression to be treated as a path. - while section[-1:] in is_path_section_charset: + while section and section[-1:] in '=+?!': section = section[:-1] - return section in path_sections or is_path_section_match_re.search(section) + + if section in path_sections: + return True + + # Sections mathing the regexp '_(dir|file|path)s?$' are also + # considered PathSections. Using manual string matching since that + # is much faster than the regexp and this can be called hundreds of + # thousands of times so micro performance matters. + if "_" in section: + tail = section[-6:] + if tail[-1] == 's': + tail = tail[:-1] + if tail[-5:] in ('_file', '_path'): + return True + return tail[-4:] == '_dir' + + return False # base_non_configuration_keys is a list of key names that belong in the target # itself and should not be propagated into its configurations. It is merged @@ -196,11 +220,11 @@ def CheckNode(node, keypath): elif isinstance(node, Const): return node.getChildren()[0] else: - raise TypeError, "Unknown AST node at key path '" + '.'.join(keypath) + \ - "': " + repr(node) + raise TypeError("Unknown AST node at key path '" + '.'.join(keypath) + + "': " + repr(node)) -def LoadOneBuildFile(build_file_path, data, aux_data, variables, includes, +def LoadOneBuildFile(build_file_path, data, aux_data, includes, is_target, check): if build_file_path in data: return data[build_file_path] @@ -224,7 +248,7 @@ def LoadOneBuildFile(build_file_path, data, aux_data, variables, includes, gyp.common.ExceptionAppend(e, 'while reading ' + build_file_path) raise - if not isinstance(build_file_data, dict): + if type(build_file_data) is not dict: raise GypError("%s does not evaluate to a dictionary." % build_file_path) data[build_file_path] = build_file_data @@ -236,10 +260,10 @@ def LoadOneBuildFile(build_file_path, data, aux_data, variables, includes, try: if is_target: LoadBuildFileIncludesIntoDict(build_file_data, build_file_path, data, - aux_data, variables, includes, check) + aux_data, includes, check) else: LoadBuildFileIncludesIntoDict(build_file_data, build_file_path, data, - aux_data, variables, None, check) + aux_data, None, check) except Exception, e: gyp.common.ExceptionAppend(e, 'while reading includes of ' + build_file_path) @@ -249,7 +273,7 @@ def LoadOneBuildFile(build_file_path, data, aux_data, variables, includes, def LoadBuildFileIncludesIntoDict(subdict, subdict_path, data, aux_data, - variables, includes, check): + includes, check): includes_list = [] if includes != None: includes_list.extend(includes) @@ -273,30 +297,27 @@ def LoadBuildFileIncludesIntoDict(subdict, subdict_path, data, aux_data, gyp.DebugOutput(gyp.DEBUG_INCLUDES, "Loading Included File: '%s'", include) MergeDicts(subdict, - LoadOneBuildFile(include, data, aux_data, variables, None, - False, check), + LoadOneBuildFile(include, data, aux_data, None, False, check), subdict_path, include) # Recurse into subdictionaries. for k, v in subdict.iteritems(): - if v.__class__ == dict: - LoadBuildFileIncludesIntoDict(v, subdict_path, data, aux_data, variables, + if type(v) is dict: + LoadBuildFileIncludesIntoDict(v, subdict_path, data, aux_data, None, check) - elif v.__class__ == list: - LoadBuildFileIncludesIntoList(v, subdict_path, data, aux_data, variables, + elif type(v) is list: + LoadBuildFileIncludesIntoList(v, subdict_path, data, aux_data, check) # This recurses into lists so that it can look for dicts. -def LoadBuildFileIncludesIntoList(sublist, sublist_path, data, aux_data, - variables, check): +def LoadBuildFileIncludesIntoList(sublist, sublist_path, data, aux_data, check): for item in sublist: - if item.__class__ == dict: + if type(item) is dict: LoadBuildFileIncludesIntoDict(item, sublist_path, data, aux_data, - variables, None, check) - elif item.__class__ == list: - LoadBuildFileIncludesIntoList(item, sublist_path, data, aux_data, - variables, check) + None, check) + elif type(item) is list: + LoadBuildFileIncludesIntoList(item, sublist_path, data, aux_data, check) # Processes toolsets in all the targets. This recurses into condition entries # since they can contain toolsets as well. @@ -320,7 +341,7 @@ def ProcessToolsetsInDict(data): if len(toolsets) > 0: # Optimization: only do copies if more than one toolset is specified. for build in toolsets[1:]: - new_target = copy.deepcopy(target) + new_target = gyp.simple_copy.deepcopy(target) new_target['toolset'] = build new_target_list.append(new_target) target['toolset'] = toolsets[0] @@ -328,9 +349,10 @@ def ProcessToolsetsInDict(data): data['targets'] = new_target_list if 'conditions' in data: for condition in data['conditions']: - if isinstance(condition, list): + if type(condition) is list: for condition_dict in condition[1:]: - ProcessToolsetsInDict(condition_dict) + if type(condition_dict) is dict: + ProcessToolsetsInDict(condition_dict) # TODO(mark): I don't love this name. It just means that it's going to load @@ -350,15 +372,22 @@ def LoadTargetBuildFile(build_file_path, data, aux_data, variables, includes, else: variables['DEPTH'] = d.replace('\\', '/') - if build_file_path in data['target_build_files']: - # Already loaded. - return False - data['target_build_files'].add(build_file_path) + # The 'target_build_files' key is only set when loading target build files in + # the non-parallel code path, where LoadTargetBuildFile is called + # recursively. In the parallel code path, we don't need to check whether the + # |build_file_path| has already been loaded, because the 'scheduled' set in + # ParallelState guarantees that we never load the same |build_file_path| + # twice. + if 'target_build_files' in data: + if build_file_path in data['target_build_files']: + # Already loaded. + return False + data['target_build_files'].add(build_file_path) gyp.DebugOutput(gyp.DEBUG_INCLUDES, "Loading Target Build File '%s'", build_file_path) - build_file_data = LoadOneBuildFile(build_file_path, data, aux_data, variables, + build_file_data = LoadOneBuildFile(build_file_path, data, aux_data, includes, True, check) # Store DEPTH for later use in generators. @@ -408,7 +437,8 @@ def LoadTargetBuildFile(build_file_path, data, aux_data, variables, includes, # copy with the target-specific data merged into it as the replacement # target dict. old_target_dict = build_file_data['targets'][index] - new_target_dict = copy.deepcopy(build_file_data['target_defaults']) + new_target_dict = gyp.simple_copy.deepcopy( + build_file_data['target_defaults']) MergeDicts(new_target_dict, old_target_dict, build_file_path, build_file_path) build_file_data['targets'][index] = new_target_dict @@ -443,10 +473,8 @@ def LoadTargetBuildFile(build_file_path, data, aux_data, variables, includes, else: return (build_file_path, dependencies) - def CallLoadTargetBuildFile(global_flags, - build_file_path, data, - aux_data, variables, + build_file_path, variables, includes, depth, check, generator_input_info): """Wrapper around LoadTargetBuildFile for parallel processing. @@ -462,35 +490,24 @@ def CallLoadTargetBuildFile(global_flags, for key, value in global_flags.iteritems(): globals()[key] = value - # Save the keys so we can return data that changed. - data_keys = set(data) - aux_data_keys = set(aux_data) - SetGeneratorGlobals(generator_input_info) - result = LoadTargetBuildFile(build_file_path, data, - aux_data, variables, + result = LoadTargetBuildFile(build_file_path, per_process_data, + per_process_aux_data, variables, includes, depth, check, False) if not result: return result (build_file_path, dependencies) = result - data_out = {} - for key in data: - if key == 'target_build_files': - continue - if key not in data_keys: - data_out[key] = data[key] - aux_data_out = {} - for key in aux_data: - if key not in aux_data_keys: - aux_data_out[key] = aux_data[key] + # We can safely pop the build_file_data from per_process_data because it + # will never be referenced by this process again, so we don't need to keep + # it in the cache. + build_file_data = per_process_data.pop(build_file_path) # This gets serialized and sent back to the main process via a pipe. # It's handled in LoadTargetBuildFileCallback. return (build_file_path, - data_out, - aux_data_out, + build_file_data, dependencies) except GypError, e: sys.stderr.write("gyp: %s\n" % e) @@ -521,8 +538,6 @@ class ParallelState(object): self.condition = None # The "data" dict that was passed to LoadTargetBuildFileParallel self.data = None - # The "aux_data" dict that was passed to LoadTargetBuildFileParallel - self.aux_data = None # The number of parallel calls outstanding; decremented when a response # was received. self.pending = 0 @@ -543,12 +558,9 @@ class ParallelState(object): self.condition.notify() self.condition.release() return - (build_file_path0, data0, aux_data0, dependencies0) = result + (build_file_path0, build_file_data0, dependencies0) = result + self.data[build_file_path0] = build_file_data0 self.data['target_build_files'].add(build_file_path0) - for key in data0: - self.data[key] = data0[key] - for key in aux_data0: - self.aux_data[key] = aux_data0[key] for new_dependency in dependencies0: if new_dependency not in self.scheduled: self.scheduled.add(new_dependency) @@ -558,9 +570,8 @@ class ParallelState(object): self.condition.release() -def LoadTargetBuildFilesParallel(build_files, data, aux_data, - variables, includes, depth, check, - generator_input_info): +def LoadTargetBuildFilesParallel(build_files, data, variables, includes, depth, + check, generator_input_info): parallel_state = ParallelState() parallel_state.condition = threading.Condition() # Make copies of the build_files argument that we can modify while working. @@ -568,7 +579,6 @@ def LoadTargetBuildFilesParallel(build_files, data, aux_data, parallel_state.scheduled = set(build_files) parallel_state.pending = 0 parallel_state.data = data - parallel_state.aux_data = aux_data try: parallel_state.condition.acquire() @@ -582,20 +592,16 @@ def LoadTargetBuildFilesParallel(build_files, data, aux_data, dependency = parallel_state.dependencies.pop() parallel_state.pending += 1 - data_in = {} - data_in['target_build_files'] = data['target_build_files'] - aux_data_in = {} global_flags = { 'path_sections': globals()['path_sections'], 'non_configuration_keys': globals()['non_configuration_keys'], 'multiple_toolsets': globals()['multiple_toolsets']} if not parallel_state.pool: - parallel_state.pool = multiprocessing.Pool(8) + parallel_state.pool = multiprocessing.Pool(multiprocessing.cpu_count()) parallel_state.pool.apply_async( CallLoadTargetBuildFile, args = (global_flags, dependency, - data_in, aux_data_in, variables, includes, depth, check, generator_input_info), callback = parallel_state.LoadTargetBuildFileCallback) except KeyboardInterrupt, e: @@ -636,39 +642,50 @@ def FindEnclosingBracketGroup(input_str): return (-1, -1) -canonical_int_re = re.compile('(0|-?[1-9][0-9]*)$') - - def IsStrCanonicalInt(string): """Returns True if |string| is in its canonical integer form. The canonical form is such that str(int(string)) == string. """ - return isinstance(string, str) and canonical_int_re.match(string) + if type(string) is str: + # 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. + if string: + if string == "0": + return True + if string[0] == "-": + string = string[1:] + if not string: + return False + if '1' <= string[0] <= '9': + return string.isdigit() + + return False # This matches things like "<(asdf)", "(?P<(?:(?:!?@?)|\|)?)' - '(?P[-a-zA-Z0-9_.]+)?' - '\((?P\s*\[?)' - '(?P.*?)(\]?)\))') + r'(?P(?P<(?:(?:!?@?)|\|)?)' + r'(?P[-a-zA-Z0-9_.]+)?' + r'\((?P\s*\[?)' + r'(?P.*?)(\]?)\))') # This matches the same as early_variable_re, but with '>' instead of '<'. late_variable_re = re.compile( - '(?P(?P>(?:(?:!?@?)|\|)?)' - '(?P[-a-zA-Z0-9_.]+)?' - '\((?P\s*\[?)' - '(?P.*?)(\]?)\))') + r'(?P(?P>(?:(?:!?@?)|\|)?)' + r'(?P[-a-zA-Z0-9_.]+)?' + r'\((?P\s*\[?)' + r'(?P.*?)(\]?)\))') # This matches the same as early_variable_re, but with '^' instead of '<'. latelate_variable_re = re.compile( - '(?P(?P[\^](?:(?:!?@?)|\|)?)' - '(?P[-a-zA-Z0-9_.]+)?' - '\((?P\s*\[?)' - '(?P.*?)(\]?)\))') + r'(?P(?P[\^](?:(?:!?@?)|\|)?)' + r'(?P[-a-zA-Z0-9_.]+)?' + r'\((?P\s*\[?)' + r'(?P.*?)(\]?)\))') # Global cache of results from running commands so they don't have to be run # more then once. @@ -677,7 +694,7 @@ cached_command_results = {} def FixupPlatformCommand(cmd): if sys.platform == 'win32': - if type(cmd) == list: + if type(cmd) is list: cmd = [re.sub('^cat ', 'type ', cmd[0])] + cmd[1:] else: cmd = re.sub('^cat ', 'type ', cmd) @@ -767,7 +784,7 @@ def ExpandVariables(input, phase, variables, build_file): # contexts. However, since filtration has no chance to run on <|(), # this seems like the only obvious way to give them access to filters. if file_list: - processed_variables = copy.deepcopy(variables) + processed_variables = gyp.simple_copy.deepcopy(variables) ProcessListFiltersInDict(contents, processed_variables) # Recurse to expand variables in the contents contents = ExpandVariables(contents, phase, @@ -804,7 +821,7 @@ def ExpandVariables(input, phase, variables, build_file): # This works around actions/rules which have more inputs than will # fit on the command line. if file_list: - if type(contents) == list: + if type(contents) is list: contents_list = contents else: contents_list = contents.split(' ') @@ -837,17 +854,15 @@ def ExpandVariables(input, phase, variables, build_file): use_shell = False # Check for a cached value to avoid executing commands, or generating - # file lists more than once. - # TODO(http://code.google.com/p/gyp/issues/detail?id=112): It is - # possible that the command being invoked depends on the current - # directory. For that case the syntax needs to be extended so that the - # directory is also used in cache_key (it becomes a tuple). + # file lists more than once. The cache key contains the command to be + # run as well as the directory to run it from, to account for commands + # that depend on their current directory. # TODO(http://code.google.com/p/gyp/issues/detail?id=111): In theory, # someone could author a set of GYP files where each time the command # is invoked it produces different output by design. When the need # arises, the syntax should be extended to support no caching off a # command's output so it is run every time. - cache_key = str(contents) + cache_key = (str(contents), build_file_dir) cached_value = cached_command_results.get(cache_key, None) if cached_value is None: gyp.DebugOutput(gyp.DEBUG_VARIABLES, @@ -883,11 +898,15 @@ def ExpandVariables(input, phase, variables, build_file): else: # Fix up command with platform specific workarounds. contents = FixupPlatformCommand(contents) - p = subprocess.Popen(contents, shell=use_shell, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - stdin=subprocess.PIPE, - cwd=build_file_dir) + try: + p = subprocess.Popen(contents, shell=use_shell, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + cwd=build_file_dir) + except Exception, e: + raise GypError("%s while executing command '%s' in %s" % + (e, contents, build_file)) p_stdout, p_stderr = p.communicate('') @@ -895,8 +914,8 @@ def ExpandVariables(input, phase, variables, build_file): sys.stderr.write(p_stderr) # Simulate check_call behavior, since check_call only exists # in python 2.5 and later. - raise GypError("Call to '%s' returned exit status %d." % - (contents, p.returncode)) + raise GypError("Call to '%s' returned exit status %d while in %s." % + (contents, p.returncode, build_file)) replacement = p_stdout.rstrip() cached_command_results[cache_key] = replacement @@ -925,10 +944,9 @@ def ExpandVariables(input, phase, variables, build_file): else: replacement = variables[contents] - if isinstance(replacement, list): + if type(replacement) is list: for item in replacement: - if (not contents[-1] == '/' and - not isinstance(item, str) and not isinstance(item, int)): + if not contents[-1] == '/' and type(item) not in (str, int): raise GypError('Variable ' + contents + ' must expand to a string or list of strings; ' + 'list contains a ' + @@ -938,8 +956,7 @@ def ExpandVariables(input, phase, variables, build_file): # with conditions sections. ProcessVariablesAndConditionsInList(replacement, phase, variables, build_file) - elif not isinstance(replacement, str) and \ - not isinstance(replacement, int): + elif type(replacement) not in (str, int): raise GypError('Variable ' + contents + ' must expand to a string or list of strings; ' + 'found a ' + replacement.__class__.__name__) @@ -948,7 +965,7 @@ def ExpandVariables(input, phase, variables, build_file): # Expanding in list context. It's guaranteed that there's only one # replacement to do in |input_str| and that it's this replacement. See # above. - if isinstance(replacement, list): + if type(replacement) is list: # If it's already a list, make a copy. output = replacement[:] else: @@ -957,7 +974,7 @@ def ExpandVariables(input, phase, variables, build_file): else: # Expanding in string context. encoded_replacement = '' - if isinstance(replacement, list): + if type(replacement) is list: # When expanding a list into string context, turn the list items # into a string in a way that will work with a subprocess call. # @@ -975,26 +992,32 @@ def ExpandVariables(input, phase, variables, build_file): # Prepare for the next match iteration. input_str = output - # Look for more matches now that we've replaced some, to deal with - # expanding local variables (variables defined in the same - # variables block as this one). - gyp.DebugOutput(gyp.DEBUG_VARIABLES, "Found output %r, recursing.", output) - if isinstance(output, list): - if output and isinstance(output[0], list): - # Leave output alone if it's a list of lists. - # We don't want such lists to be stringified. - pass - else: - new_output = [] - for item in output: - new_output.append( - ExpandVariables(item, phase, variables, build_file)) - output = new_output + if output == input: + gyp.DebugOutput(gyp.DEBUG_VARIABLES, + "Found only identity matches on %r, avoiding infinite " + "recursion.", + output) else: - output = ExpandVariables(output, phase, variables, build_file) + # Look for more matches now that we've replaced some, to deal with + # expanding local variables (variables defined in the same + # variables block as this one). + gyp.DebugOutput(gyp.DEBUG_VARIABLES, "Found output %r, recursing.", output) + if type(output) is list: + if output and type(output[0]) is list: + # Leave output alone if it's a list of lists. + # We don't want such lists to be stringified. + pass + else: + new_output = [] + for item in output: + new_output.append( + ExpandVariables(item, phase, variables, build_file)) + output = new_output + else: + output = ExpandVariables(output, phase, variables, build_file) # Convert all strings that are canonically-represented integers into integers. - if isinstance(output, list): + if type(output) is list: for index in xrange(0, len(output)): if IsStrCanonicalInt(output[index]): output[index] = int(output[index]) @@ -1003,6 +1026,80 @@ def ExpandVariables(input, phase, variables, build_file): return output +# The same condition is often evaluated over and over again so it +# makes sense to cache as much as possible between evaluations. +cached_conditions_asts = {} + +def EvalCondition(condition, conditions_key, phase, variables, build_file): + """Returns the dict that should be used or None if the result was + that nothing should be used.""" + if type(condition) is not list: + raise GypError(conditions_key + ' must be a list') + if len(condition) < 2: + # It's possible that condition[0] won't work in which case this + # attempt will raise its own IndexError. That's probably fine. + raise GypError(conditions_key + ' ' + condition[0] + + ' must be at least length 2, not ' + str(len(condition))) + + i = 0 + result = None + while i < len(condition): + cond_expr = condition[i] + true_dict = condition[i + 1] + if type(true_dict) is not dict: + raise GypError('{} {} must be followed by a dictionary, not {}'.format( + conditions_key, cond_expr, type(true_dict))) + if len(condition) > i + 2 and type(condition[i + 2]) is dict: + false_dict = condition[i + 2] + i = i + 3 + if i != len(condition): + raise GypError('{} {} has {} unexpected trailing items'.format( + conditions_key, cond_expr, len(condition) - i)) + else: + false_dict = None + i = i + 2 + if result == None: + result = EvalSingleCondition( + cond_expr, true_dict, false_dict, phase, variables, build_file) + + return result + + +def EvalSingleCondition( + cond_expr, true_dict, false_dict, phase, variables, build_file): + """Returns true_dict if cond_expr evaluates to true, and false_dict + otherwise.""" + # Do expansions on the condition itself. Since the conditon can naturally + # contain variable references without needing to resort to GYP expansion + # syntax, this is of dubious value for variables, but someone might want to + # 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): + raise ValueError( + 'Variable expansion in this context permits str and int ' + \ + 'only, found ' + cond_expr_expanded.__class__.__name__) + + try: + if cond_expr_expanded in cached_conditions_asts: + ast_code = cached_conditions_asts[cond_expr_expanded] + else: + ast_code = compile(cond_expr_expanded, '', 'eval') + cached_conditions_asts[cond_expr_expanded] = ast_code + if eval(ast_code, {'__builtins__': None}, variables): + return true_dict + return false_dict + except SyntaxError, e: + syntax_error = SyntaxError('%s while evaluating condition \'%s\' in %s ' + 'at character %d.' % + (str(e.args[0]), e.text, build_file, e.offset), + e.filename, e.lineno, e.offset, e.text) + raise syntax_error + except NameError, e: + gyp.common.ExceptionAppend(e, 'while evaluating condition \'%s\' in %s' % + (cond_expr_expanded, build_file)) + raise GypError(e) + def ProcessConditionsInDict(the_dict, phase, variables, build_file): # Process a 'conditions' or 'target_conditions' section in the_dict, @@ -1038,48 +1135,8 @@ def ProcessConditionsInDict(the_dict, phase, variables, build_file): del the_dict[conditions_key] for condition in conditions_list: - if not isinstance(condition, list): - raise GypError(conditions_key + ' must be a list') - if len(condition) != 2 and len(condition) != 3: - # It's possible that condition[0] won't work in which case this - # attempt will raise its own IndexError. That's probably fine. - raise GypError(conditions_key + ' ' + condition[0] + - ' must be length 2 or 3, not ' + str(len(condition))) - - [cond_expr, true_dict] = condition[0:2] - false_dict = None - if len(condition) == 3: - false_dict = condition[2] - - # Do expansions on the condition itself. Since the conditon can naturally - # contain variable references without needing to resort to GYP expansion - # syntax, this is of dubious value for variables, but someone might want to - # use a command expansion directly inside a condition. - cond_expr_expanded = ExpandVariables(cond_expr, phase, variables, - build_file) - if not isinstance(cond_expr_expanded, str) and \ - not isinstance(cond_expr_expanded, int): - raise ValueError, \ - 'Variable expansion in this context permits str and int ' + \ - 'only, found ' + expanded.__class__.__name__ - - try: - ast_code = compile(cond_expr_expanded, '', 'eval') - - if eval(ast_code, {'__builtins__': None}, variables): - merge_dict = true_dict - else: - merge_dict = false_dict - except SyntaxError, e: - syntax_error = SyntaxError('%s while evaluating condition \'%s\' in %s ' - 'at character %d.' % - (str(e.args[0]), e.text, build_file, e.offset), - e.filename, e.lineno, e.offset, e.text) - raise syntax_error - except NameError, e: - gyp.common.ExceptionAppend(e, 'while evaluating condition \'%s\' in %s' % - (cond_expr_expanded, build_file)) - raise GypError(e) + merge_dict = EvalCondition(condition, conditions_key, phase, variables, + build_file) if merge_dict != None: # Expand variables and nested conditinals in the merge_dict before @@ -1094,8 +1151,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.iteritems(): - if isinstance(value, str) or isinstance(value, int) or \ - isinstance(value, list): + if type(value) in (str, int, list): variables['_' + key] = value @@ -1108,8 +1164,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', {}).iteritems(): - if not isinstance(value, str) and not isinstance(value, int) and \ - not isinstance(value, list): + if type(value) not in (str, int, list): continue if key.endswith('%'): @@ -1162,12 +1217,12 @@ def ProcessVariablesAndConditionsInDict(the_dict, phase, variables_in, for key, value in the_dict.iteritems(): # Skip "variables", which was already processed if present. - if key != 'variables' and isinstance(value, str): + if key != 'variables' and type(value) is str: expanded = ExpandVariables(value, phase, variables, build_file) - if not isinstance(expanded, str) and not isinstance(expanded, int): - raise ValueError, \ + if type(expanded) not in (str, int): + raise ValueError( 'Variable expansion in this context permits str and int ' + \ - 'only, found ' + expanded.__class__.__name__ + ' for ' + key + 'only, found ' + expanded.__class__.__name__ + ' for ' + key) the_dict[key] = expanded # Variable expansion may have resulted in changes to automatics. Reload. @@ -1221,23 +1276,23 @@ def ProcessVariablesAndConditionsInDict(the_dict, phase, variables_in, for key, value in the_dict.iteritems(): # Skip "variables" and string values, which were already processed if # present. - if key == 'variables' or isinstance(value, str): + if key == 'variables' or type(value) is str: continue - if isinstance(value, dict): + if type(value) is dict: # Pass a copy of the variables dict so that subdicts can't influence # parents. ProcessVariablesAndConditionsInDict(value, phase, variables, build_file, key) - elif isinstance(value, list): + elif type(value) is list: # The list itself can't influence the variables dict, and # ProcessVariablesAndConditionsInList will make copies of the variables # dict if it needs to pass it to something that can influence it. No # copy is necessary here. ProcessVariablesAndConditionsInList(value, phase, variables, build_file) - elif not isinstance(value, int): - raise TypeError, 'Unknown type ' + value.__class__.__name__ + \ - ' for ' + key + elif type(value) is not int: + raise TypeError('Unknown type ' + value.__class__.__name__ + \ + ' for ' + key) def ProcessVariablesAndConditionsInList(the_list, phase, variables, @@ -1246,17 +1301,17 @@ def ProcessVariablesAndConditionsInList(the_list, phase, variables, index = 0 while index < len(the_list): item = the_list[index] - if isinstance(item, dict): + if type(item) is dict: # Make a copy of the variables dict so that it won't influence anything # outside of its own scope. ProcessVariablesAndConditionsInDict(item, phase, variables, build_file) - elif isinstance(item, list): + elif type(item) is list: ProcessVariablesAndConditionsInList(item, phase, variables, build_file) - elif isinstance(item, str): + elif type(item) is str: expanded = ExpandVariables(item, phase, variables, build_file) - if isinstance(expanded, str) or isinstance(expanded, int): + if type(expanded) in (str, int): the_list[index] = expanded - elif isinstance(expanded, list): + elif type(expanded) is list: the_list[index:index+1] = expanded index += len(expanded) @@ -1264,13 +1319,13 @@ def ProcessVariablesAndConditionsInList(the_list, phase, variables, # without falling into the index increment below. continue else: - raise ValueError, \ + raise ValueError( 'Variable expansion in this context permits strings and ' + \ 'lists only, found ' + expanded.__class__.__name__ + ' at ' + \ - index - elif not isinstance(item, int): - raise TypeError, 'Unknown type ' + item.__class__.__name__ + \ - ' at index ' + index + index) + elif type(item) is not int: + raise TypeError('Unknown type ' + item.__class__.__name__ + \ + ' at index ' + index) index = index + 1 @@ -1443,6 +1498,20 @@ def RemoveSelfDependencies(targets): target_dict[dependency_key] = Filter(dependencies, target_name) +def RemoveLinkDependenciesFromNoneTargets(targets): + """Remove dependencies having the 'link_dependency' attribute from the 'none' + targets.""" + for target_name, target_dict in targets.iteritems(): + for dependency_key in dependency_sections: + dependencies = target_dict.get(dependency_key, []) + if dependencies: + for t in dependencies: + if target_dict.get('type', None) == 'none': + if targets[t].get('variables', {}).get('link_dependency', 0): + target_dict[dependency_key] = \ + Filter(target_dict[dependency_key], t) + + class DependencyGraphNode(object): """ @@ -1468,13 +1537,17 @@ class DependencyGraphNode(object): # are the "ref" attributes of DependencyGraphNodes. Every target will # appear in flat_list after all of its dependencies, and before all of its # dependents. - flat_list = [] + flat_list = OrderedSet() + + def ExtractNodeRef(node): + """Extracts the object that the node represents from the given node.""" + return node.ref # in_degree_zeros is the list of DependencyGraphNodes that have no # dependencies not in flat_list. Initially, it is a copy of the children # of this node, because when the graph was built, nodes with no # dependencies were made implicit dependents of the root node. - in_degree_zeros = set(self.dependents[:]) + in_degree_zeros = sorted(self.dependents[:], key=ExtractNodeRef) while in_degree_zeros: # Nodes in in_degree_zeros have no dependencies not in flat_list, so they @@ -1482,13 +1555,17 @@ class DependencyGraphNode(object): # as work progresses, so that the next node to process from the list can # always be accessed at a consistent position. node = in_degree_zeros.pop() - flat_list.append(node.ref) + flat_list.add(node.ref) # Look at dependents of the node just added to flat_list. Some of them # may now belong in in_degree_zeros. - for node_dependent in node.dependents: + for node_dependent in sorted(node.dependents, key=ExtractNodeRef): is_in_degree_zero = True - for node_dependent_dependency in node_dependent.dependencies: + # TODO: We want to check through the + # node_dependent.dependencies list but if it's long and we + # always start at the beginning, then we get O(n^2) behaviour. + for node_dependent_dependency in (sorted(node_dependent.dependencies, + key=ExtractNodeRef)): if not node_dependent_dependency.ref in flat_list: # The dependent one or more dependencies not in flat_list. There # will be more chances to add it to flat_list when examining @@ -1501,30 +1578,29 @@ class DependencyGraphNode(object): # All of the dependent's dependencies are already in flat_list. Add # it to in_degree_zeros where it will be processed in a future # iteration of the outer loop. - in_degree_zeros.add(node_dependent) + in_degree_zeros += [node_dependent] - return flat_list + return list(flat_list) - def FindCycles(self, path=None): + def FindCycles(self): """ Returns a list of cycles in the graph, where each cycle is its own list. """ - if path is None: - path = [self] - results = [] - for node in self.dependents: - if node in path: - cycle = [node] - for part in path: - cycle.append(part) - if part == node: - break - results.append(tuple(cycle)) - else: - results.extend(node.FindCycles([node] + path)) + visited = set() - return list(set(results)) + def Visit(node, path): + for child in node.dependents: + if child in path: + results.append([child] + path[:path.index(child) + 1]) + elif not child in visited: + visited.add(child) + Visit(child, [child] + path) + + visited.add(self) + Visit(self, [self]) + + return results def DirectDependencies(self, dependencies=None): """Returns a list of just direct dependencies.""" @@ -1589,21 +1665,26 @@ class DependencyGraphNode(object): return self._AddImportedDependencies(targets, dependencies) def DeepDependencies(self, dependencies=None): - """Returns a list of all of a target's dependencies, recursively.""" - if dependencies == None: - dependencies = [] + """Returns an OrderedSet of all of a target's dependencies, recursively.""" + if dependencies is None: + # Using a list to get ordered output and a set to do fast "is it + # already added" checks. + dependencies = OrderedSet() for dependency in self.dependencies: # Check for None, corresponding to the root node. - if dependency.ref != None and dependency.ref not in dependencies: - dependencies.append(dependency.ref) + if dependency.ref is None: + continue + if dependency.ref not in dependencies: dependency.DeepDependencies(dependencies) + dependencies.add(dependency.ref) return dependencies def _LinkDependenciesInternal(self, targets, include_shared_libraries, dependencies=None, initial=True): - """Returns a list of dependency targets that are linked into this target. + """Returns an OrderedSet of dependency targets that are linked + into this target. This function has a split personality, depending on the setting of |initial|. Outside callers should always leave |initial| at its default @@ -1616,11 +1697,13 @@ class DependencyGraphNode(object): If |include_shared_libraries| is False, the resulting dependencies will not include shared_library targets that are linked into this target. """ - if dependencies == None: - dependencies = [] + if dependencies is None: + # Using a list to get ordered output and a set to do fast "is it + # already added" checks. + dependencies = OrderedSet() # Check for None, corresponding to the root node. - if self.ref == None: + if self.ref is None: return dependencies # It's kind of sucky that |targets| has to be passed into this function, @@ -1648,15 +1731,15 @@ class DependencyGraphNode(object): # Don't traverse 'none' targets if explicitly excluded. if (target_type == 'none' and not targets[self.ref].get('dependencies_traverse', True)): - if self.ref not in dependencies: - dependencies.append(self.ref) + dependencies.add(self.ref) return dependencies - # Executables and loadable modules are already fully and finally linked. - # Nothing else can be a link dependency of them, there can only be - # dependencies in the sense that a dependent target might run an - # executable or load the loadable_module. - if not initial and target_type in ('executable', 'loadable_module'): + # Executables, mac kernel extensions and loadable modules are already fully + # and finally linked. Nothing else can be a link dependency of them, there + # can only be dependencies in the sense that a dependent target might run + # an executable or load the loadable_module. + if not initial and target_type in ('executable', 'loadable_module', + 'mac_kernel_extension'): return dependencies # Shared libraries are already fully linked. They should only be included @@ -1671,7 +1754,7 @@ class DependencyGraphNode(object): # The target is linkable, add it to the list of link dependencies. if self.ref not in dependencies: - dependencies.append(self.ref) + dependencies.add(self.ref) if initial or not is_linkable: # If this is a subsequent target and it's linkable, don't look any # further for linkable dependencies, as they'll already be linked into @@ -1735,12 +1818,22 @@ def BuildDependencyList(targets): flat_list = root_node.FlattenToList() # If there's anything left unvisited, there must be a circular dependency - # (cycle). If you need to figure out what's wrong, look for elements of - # targets that are not in flat_list. + # (cycle). if len(flat_list) != len(targets): + if not root_node.dependents: + # If all targets have dependencies, add the first target as a dependent + # of root_node so that the cycle can be discovered from root_node. + target = targets.keys()[0] + target_node = dependency_nodes[target] + target_node.dependencies.append(root_node) + root_node.dependents.append(target_node) + + cycles = [] + for cycle in root_node.FindCycles(): + paths = [node.ref for node in cycle] + cycles.append('Cycle: %s' % ' -> '.join(paths)) raise DependencyGraphNode.CircularException( - 'Some targets not reachable, cycle in dependency graph detected: ' + - ' '.join(set(flat_list) ^ set(targets))) + 'Cycles in dependency graph detected:\n' + '\n'.join(cycles)) return [dependency_nodes, flat_list] @@ -1790,20 +1883,18 @@ def VerifyNoGYPFileCircularDependencies(targets): # If there's anything left unvisited, there must be a circular dependency # (cycle). if len(flat_list) != len(dependency_nodes): - bad_files = [] - for file in dependency_nodes.iterkeys(): - if not file in flat_list: - bad_files.append(file) - common_path_prefix = os.path.commonprefix(dependency_nodes) + if not root_node.dependents: + # If all files have dependencies, add the first file as a dependent + # of root_node so that the cycle can be discovered from root_node. + file_node = dependency_nodes.values()[0] + file_node.dependencies.append(root_node) + root_node.dependents.append(file_node) cycles = [] for cycle in root_node.FindCycles(): - simplified_paths = [] - for node in cycle: - assert(node.ref.startswith(common_path_prefix)) - simplified_paths.append(node.ref[len(common_path_prefix):]) - cycles.append('Cycle: %s' % ' -> '.join(simplified_paths)) - raise DependencyGraphNode.CircularException, \ - 'Cycles in .gyp file dependency graph detected:\n' + '\n'.join(cycles) + paths = [node.ref for node in cycle] + cycles.append('Cycle: %s' % ' -> '.join(paths)) + raise DependencyGraphNode.CircularException( + 'Cycles in .gyp file dependency graph detected:\n' + '\n'.join(cycles)) def DoDependentSettings(key, flat_list, targets, dependency_nodes): @@ -1966,25 +2057,25 @@ 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 isinstance(item, str) or isinstance(item, int): + if type(item) in (str, int): # The cheap and easy case. if is_paths: to_item = MakePathRelative(to_file, fro_file, item) else: to_item = item - if not isinstance(item, str) or not item.startswith('-'): + if not (type(item) is str 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. singleton = True - elif isinstance(item, dict): + elif type(item) is dict: # Make a copy of the dictionary, continuing to look for paths to fix. # The other intelligent aspects of merge processing won't apply because # item is being merged into an empty dict. to_item = {} MergeDicts(to_item, item, to_file, fro_file) - elif isinstance(item, list): + elif type(item) is list: # Recurse, making a copy of the list. If the list contains any # descendant dicts, path fixing will occur. Note that here, custom # values for is_paths and append are dropped; those are only to be @@ -1993,9 +2084,9 @@ def MergeLists(to, fro, to_file, fro_file, is_paths=False, append=True): to_item = [] MergeLists(to_item, item, to_file, fro_file) else: - raise TypeError, \ + raise TypeError( 'Attempt to merge list item of unsupported type ' + \ - item.__class__.__name__ + item.__class__.__name__) if append: # If appending a singleton that's already in the list, don't append. @@ -2030,30 +2121,30 @@ def MergeDicts(to, fro, to_file, fro_file): # modified. if k in to: bad_merge = False - if isinstance(v, str) or isinstance(v, int): - if not (isinstance(to[k], str) or isinstance(to[k], int)): + if type(v) in (str, int): + if type(to[k]) not in (str, int): bad_merge = True - elif v.__class__ != to[k].__class__: + elif type(v) is not type(to[k]): bad_merge = True if bad_merge: - raise TypeError, \ + raise TypeError( 'Attempt to merge dict value of type ' + v.__class__.__name__ + \ ' into incompatible type ' + to[k].__class__.__name__ + \ - ' for key ' + k - if isinstance(v, str) or isinstance(v, int): + ' for key ' + k) + if type(v) in (str, int): # Overwrite the existing value, if any. Cheap and easy. is_path = IsPathSection(k) if is_path: to[k] = MakePathRelative(to_file, fro_file, v) else: to[k] = v - elif isinstance(v, dict): + elif type(v) is dict: # Recurse, guaranteeing copies will be made of objects that require it. if not k in to: to[k] = {} MergeDicts(to[k], v, to_file, fro_file) - elif isinstance(v, list): + elif type(v) is list: # Lists in dicts can be merged with different policies, depending on # how the key in the "from" dict (k, the from-key) is written. # @@ -2096,13 +2187,13 @@ def MergeDicts(to, fro, to_file, fro_file): # If the key ends in "?", the list will only be merged if it doesn't # already exist. continue - if not isinstance(to[list_base], list): + elif type(to[list_base]) is not list: # This may not have been checked above if merging in a list with an # extension character. - raise TypeError, \ + raise TypeError( 'Attempt to merge dict value of type ' + v.__class__.__name__ + \ ' into incompatible type ' + to[list_base].__class__.__name__ + \ - ' for key ' + list_base + '(' + k + ')' + ' for key ' + list_base + '(' + k + ')') else: to[list_base] = [] @@ -2114,9 +2205,9 @@ def MergeDicts(to, fro, to_file, fro_file): is_paths = IsPathSection(list_base) MergeLists(to[list_base], v, to_file, fro_file, is_paths, append) else: - raise TypeError, \ + raise TypeError( 'Attempt to merge dict value of unsupported type ' + \ - v.__class__.__name__ + ' for key ' + k + v.__class__.__name__ + ' for key ' + k) def MergeConfigWithInheritance(new_configuration_dict, build_file, @@ -2157,43 +2248,39 @@ def SetUpConfigurations(target, target_dict): if not 'configurations' in target_dict: target_dict['configurations'] = {'Default': {}} if not 'default_configuration' in target_dict: - concrete = [i for i in target_dict['configurations'].iterkeys() - if not target_dict['configurations'][i].get('abstract')] + concrete = [i for (i, config) in target_dict['configurations'].iteritems() + if not config.get('abstract')] target_dict['default_configuration'] = sorted(concrete)[0] - for configuration in target_dict['configurations'].keys(): - old_configuration_dict = target_dict['configurations'][configuration] + merged_configurations = {} + configs = target_dict['configurations'] + for (configuration, old_configuration_dict) in configs.iteritems(): # Skip abstract configurations (saves work only). if old_configuration_dict.get('abstract'): continue # Configurations inherit (most) settings from the enclosing target scope. # Get the inheritance relationship right by making a copy of the target # dict. - new_configuration_dict = copy.deepcopy(target_dict) - - # Take out the bits that don't belong in a "configurations" section. - # Since configuration setup is done before conditional, exclude, and rules - # processing, be careful with handling of the suffix characters used in - # those phases. - delete_keys = [] - for key in new_configuration_dict: + new_configuration_dict = {} + for (key, target_val) in target_dict.iteritems(): key_ext = key[-1:] if key_ext in key_suffixes: key_base = key[:-1] else: key_base = key - if key_base in non_configuration_keys: - delete_keys.append(key) - - for key in delete_keys: - del new_configuration_dict[key] + if not key_base in non_configuration_keys: + new_configuration_dict[key] = gyp.simple_copy.deepcopy(target_val) # Merge in configuration (with all its parents first). MergeConfigWithInheritance(new_configuration_dict, build_file, target_dict, configuration, []) - # Put the new result back into the target dict as a configuration. - target_dict['configurations'][configuration] = new_configuration_dict + merged_configurations[configuration] = new_configuration_dict + + # Put the new configurations back into the target dict as a configuration. + for configuration in merged_configurations.keys(): + target_dict['configurations'][configuration] = ( + merged_configurations[configuration]) # Now drop all the abstract ones. for configuration in target_dict['configurations'].keys(): @@ -2264,9 +2351,9 @@ def ProcessListFiltersInDict(name, the_dict): if operation != '!' and operation != '/': continue - if not isinstance(value, list): - raise ValueError, name + ' key ' + key + ' must be list, not ' + \ - value.__class__.__name__ + if type(value) is not list: + raise ValueError(name + ' key ' + key + ' must be list, not ' + \ + value.__class__.__name__) list_key = key[:-1] if list_key not in the_dict: @@ -2276,12 +2363,12 @@ def ProcessListFiltersInDict(name, the_dict): del_lists.append(key) continue - if not isinstance(the_dict[list_key], list): + if type(the_dict[list_key]) is not list: value = the_dict[list_key] - raise ValueError, name + ' key ' + list_key + \ - ' must be list, not ' + \ - value.__class__.__name__ + ' when applying ' + \ - {'!': 'exclusion', '/': 'regex'}[operation] + raise ValueError(name + ' key ' + list_key + \ + ' must be list, not ' + \ + value.__class__.__name__ + ' when applying ' + \ + {'!': 'exclusion', '/': 'regex'}[operation]) if not list_key in lists: lists.append(list_key) @@ -2330,8 +2417,8 @@ def ProcessListFiltersInDict(name, the_dict): action_value = 1 else: # This is an action that doesn't make any sense. - raise ValueError, 'Unrecognized action ' + action + ' in ' + name + \ - ' key ' + regex_key + raise ValueError('Unrecognized action ' + action + ' in ' + name + \ + ' key ' + regex_key) for index in xrange(0, len(the_list)): list_item = the_list[index] @@ -2378,17 +2465,17 @@ def ProcessListFiltersInDict(name, the_dict): # Now recurse into subdicts and lists that may contain dicts. for key, value in the_dict.iteritems(): - if isinstance(value, dict): + if type(value) is dict: ProcessListFiltersInDict(key, value) - elif isinstance(value, list): + elif type(value) is list: ProcessListFiltersInList(key, value) def ProcessListFiltersInList(name, the_list): for item in the_list: - if isinstance(item, dict): + if type(item) is dict: ProcessListFiltersInDict(name, item) - elif isinstance(item, list): + elif type(item) is list: ProcessListFiltersInList(name, item) @@ -2403,7 +2490,7 @@ def ValidateTargetType(target, target_dict): """ VALID_TARGET_TYPES = ('executable', 'loadable_module', 'static_library', 'shared_library', - 'none') + 'mac_kernel_extension', 'none') target_type = target_dict.get('type', None) if target_type not in VALID_TARGET_TYPES: raise GypError("Target %s has an invalid target type '%s'. " @@ -2416,9 +2503,11 @@ def ValidateTargetType(target, target_dict): target_type)) -def ValidateSourcesInTarget(target, target_dict, build_file): - # TODO: Check if MSVC allows this for loadable_module targets. - if target_dict.get('type', None) not in ('static_library', 'shared_library'): +def ValidateSourcesInTarget(target, target_dict, build_file, + duplicate_basename_check): + if not duplicate_basename_check: + return + if target_dict.get('type', None) != 'static_library': return sources = target_dict.get('sources', []) basenames = {} @@ -2438,8 +2527,8 @@ def ValidateSourcesInTarget(target, target_dict, build_file): if error: print('static library %s has several files with the same basename:\n' % - target + error + 'Some build systems, e.g. MSVC08, ' - 'cannot handle that.') + target + error + 'libtool on Mac cannot handle that. Use ' + '--no-duplicate-basename-check to disable this validation.') raise GypError('Duplicate basenames in sources section, see list above') @@ -2506,7 +2595,7 @@ def ValidateRunAsInTarget(target, target_dict, build_file): run_as = target_dict.get('run_as') if not run_as: return - if not isinstance(run_as, dict): + if type(run_as) is not dict: raise GypError("The 'run_as' in target %s from file %s should be a " "dictionary." % (target_name, build_file)) @@ -2515,17 +2604,17 @@ def ValidateRunAsInTarget(target, target_dict, build_file): raise GypError("The 'run_as' in target %s from file %s must have an " "'action' section." % (target_name, build_file)) - if not isinstance(action, list): + if type(action) is not list: raise GypError("The 'action' for 'run_as' in target %s from file %s " "must be a list." % (target_name, build_file)) working_directory = run_as.get('working_directory') - if working_directory and not isinstance(working_directory, str): + if working_directory and type(working_directory) is not str: raise GypError("The 'working_directory' for 'run_as' in target %s " "in file %s should be a string." % (target_name, build_file)) environment = run_as.get('environment') - if environment and not isinstance(environment, dict): + if environment and type(environment) is not dict: raise GypError("The 'environment' for 'run_as' in target %s " "in file %s should be a dictionary." % (target_name, build_file)) @@ -2555,17 +2644,17 @@ 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 isinstance(v, int): + if type(v) is int: v = str(v) the_dict[k] = v - elif isinstance(v, dict): + elif type(v) is dict: TurnIntIntoStrInDict(v) - elif isinstance(v, list): + elif type(v) is list: TurnIntIntoStrInList(v) - if isinstance(k, int): - the_dict[str(k)] = v + if type(k) is int: del the_dict[k] + the_dict[str(k)] = v def TurnIntIntoStrInList(the_list): @@ -2573,11 +2662,11 @@ def TurnIntIntoStrInList(the_list): """ for index in xrange(0, len(the_list)): item = the_list[index] - if isinstance(item, int): + if type(item) is int: the_list[index] = str(item) - elif isinstance(item, dict): + elif type(item) is dict: TurnIntIntoStrInDict(item) - elif isinstance(item, list): + elif type(item) is list: TurnIntIntoStrInList(item) @@ -2647,8 +2736,8 @@ def SetGeneratorGlobals(generator_input_info): # Set up path_sections and non_configuration_keys with the default data plus # the generator-specific data. global path_sections - path_sections = base_path_sections[:] - path_sections.extend(generator_input_info['path_sections']) + path_sections = set(base_path_sections) + path_sections.update(generator_input_info['path_sections']) global non_configuration_keys non_configuration_keys = base_non_configuration_keys[:] @@ -2663,7 +2752,7 @@ def SetGeneratorGlobals(generator_input_info): def Load(build_files, variables, includes, depth, generator_input_info, check, - circular_check, parallel, root_targets): + circular_check, duplicate_basename_check, parallel, root_targets): SetGeneratorGlobals(generator_input_info) # A generator can have other lists (in addition to sources) be processed # for rules. @@ -2677,15 +2766,14 @@ def Load(build_files, variables, includes, depth, generator_input_info, check, # well as meta-data (e.g. 'included_files' key). 'target_build_files' keeps # track of the keys corresponding to "target" files. data = {'target_build_files': set()} - aux_data = {} # Normalize paths everywhere. This is important because paths will be # used as keys to the data dict and for references between input files. build_files = set(map(os.path.normpath, build_files)) if parallel: - LoadTargetBuildFilesParallel(build_files, data, aux_data, - variables, includes, depth, check, - generator_input_info) + LoadTargetBuildFilesParallel(build_files, data, variables, includes, depth, + check, generator_input_info) else: + aux_data = {} for build_file in build_files: try: LoadTargetBuildFile(build_file, data, aux_data, @@ -2707,6 +2795,10 @@ def Load(build_files, variables, includes, depth, generator_input_info, check, # Expand dependencies specified as build_file:*. ExpandWildcardDependencies(targets, data) + # Remove all dependencies marked as 'link_dependency' from the targets of + # type 'none'. + RemoveLinkDependenciesFromNoneTargets(targets) + # Apply exclude (!) and regex (/) list filters only for dependency_sections. for target_name, target_dict in targets.iteritems(): tmp_dict = {} @@ -2792,10 +2884,8 @@ def Load(build_files, variables, includes, depth, generator_input_info, check, target_dict = targets[target] build_file = gyp.common.BuildFile(target) ValidateTargetType(target, target_dict) - # TODO(thakis): Get vpx_scale/arm/scalesystemdependent.c to be renamed to - # scalesystemdependent_arm_additions.c or similar. - if 'arm' not in variables.get('target_arch', ''): - ValidateSourcesInTarget(target, target_dict, build_file) + ValidateSourcesInTarget(target, target_dict, build_file, + duplicate_basename_check) ValidateRulesInTarget(target, target_dict, extra_sources_for_rules) ValidateRunAsInTarget(target, target_dict, build_file) ValidateActionsInTarget(target, target_dict, build_file) diff --git a/third_party/gyp/input_test.py b/third_party/gyp/input_test.py index cdbf6b2f..4234fbb8 100755 --- a/third_party/gyp/input_test.py +++ b/third_party/gyp/input_test.py @@ -44,16 +44,16 @@ class TestFindCycles(unittest.TestCase): def test_cycle_self_reference(self): self._create_dependency(self.nodes['a'], self.nodes['a']) - self.assertEquals([(self.nodes['a'], self.nodes['a'])], + self.assertEquals([[self.nodes['a'], self.nodes['a']]], self.nodes['a'].FindCycles()) def test_cycle_two_nodes(self): self._create_dependency(self.nodes['a'], self.nodes['b']) self._create_dependency(self.nodes['b'], self.nodes['a']) - self.assertEquals([(self.nodes['a'], self.nodes['b'], self.nodes['a'])], + self.assertEquals([[self.nodes['a'], self.nodes['b'], self.nodes['a']]], self.nodes['a'].FindCycles()) - self.assertEquals([(self.nodes['b'], self.nodes['a'], self.nodes['b'])], + self.assertEquals([[self.nodes['b'], self.nodes['a'], self.nodes['b']]], self.nodes['b'].FindCycles()) def test_two_cycles(self): @@ -65,9 +65,9 @@ class TestFindCycles(unittest.TestCase): cycles = self.nodes['a'].FindCycles() self.assertTrue( - (self.nodes['a'], self.nodes['b'], self.nodes['a']) in cycles) + [self.nodes['a'], self.nodes['b'], self.nodes['a']] in cycles) self.assertTrue( - (self.nodes['b'], self.nodes['c'], self.nodes['b']) in cycles) + [self.nodes['b'], self.nodes['c'], self.nodes['b']] in cycles) self.assertEquals(2, len(cycles)) def test_big_cycle(self): @@ -77,12 +77,12 @@ class TestFindCycles(unittest.TestCase): self._create_dependency(self.nodes['d'], self.nodes['e']) self._create_dependency(self.nodes['e'], self.nodes['a']) - self.assertEquals([(self.nodes['a'], + self.assertEquals([[self.nodes['a'], self.nodes['b'], self.nodes['c'], self.nodes['d'], self.nodes['e'], - self.nodes['a'])], + self.nodes['a']]], self.nodes['a'].FindCycles()) diff --git a/third_party/gyp/mac_tool.py b/third_party/gyp/mac_tool.py index c61a3ef6..055d79cf 100755 --- a/third_party/gyp/mac_tool.py +++ b/third_party/gyp/mac_tool.py @@ -17,6 +17,7 @@ import plistlib import re import shutil import string +import struct import subprocess import sys import tempfile @@ -45,9 +46,10 @@ class MacTool(object): """Transforms a tool name like copy-info-plist to CopyInfoPlist""" return name_string.title().replace('-', '') - def ExecCopyBundleResource(self, source, dest): + def ExecCopyBundleResource(self, source, dest, convert_to_binary): """Copies a resource file to the bundle/Resources directory, performing any necessary compilation on each resource.""" + convert_to_binary = convert_to_binary == 'True' extension = os.path.splitext(source)[1].lower() if os.path.isdir(source): # Copy tree. @@ -61,11 +63,16 @@ class MacTool(object): return self._CopyXIBFile(source, dest) elif extension == '.storyboard': return self._CopyXIBFile(source, dest) - elif extension == '.strings': + elif extension == '.strings' and not convert_to_binary: self._CopyStringsFile(source, dest) else: + if os.path.exists(dest): + os.unlink(dest) shutil.copy(source, dest) + if convert_to_binary and extension in ('.plist', '.strings'): + self._ConvertToBinary(dest) + def _CopyXIBFile(self, source, dest): """Compiles a XIB file with ibtool into a binary plist in the bundle.""" @@ -76,8 +83,26 @@ class MacTool(object): if os.path.relpath(dest): dest = os.path.join(base, dest) - args = ['xcrun', 'ibtool', '--errors', '--warnings', '--notices', - '--output-format', 'human-readable-text', '--compile', dest, source] + args = ['xcrun', 'ibtool', '--errors', '--warnings', '--notices'] + + if os.environ['XCODE_VERSION_ACTUAL'] > '0700': + args.extend(['--auto-activate-custom-fonts']) + if 'IPHONEOS_DEPLOYMENT_TARGET' in os.environ: + args.extend([ + '--target-device', 'iphone', '--target-device', 'ipad', + '--minimum-deployment-target', + os.environ['IPHONEOS_DEPLOYMENT_TARGET'], + ]) + else: + args.extend([ + '--target-device', 'mac', + '--minimum-deployment-target', + os.environ['MACOSX_DEPLOYMENT_TARGET'], + ]) + + args.extend(['--output-format', 'human-readable-text', '--compile', dest, + source]) + ibtool_section_re = re.compile(r'/\*.*\*/') ibtool_re = re.compile(r'.*note:.*is clipping its content') ibtoolout = subprocess.Popen(args, stdout=subprocess.PIPE) @@ -92,6 +117,10 @@ class MacTool(object): sys.stdout.write(line) return ibtoolout.returncode + def _ConvertToBinary(self, dest): + subprocess.check_call([ + 'xcrun', 'plutil', '-convert', 'binary1', '-o', dest, dest]) + def _CopyStringsFile(self, source, dest): """Copies a .strings file using iconv to reconvert the input into UTF-16.""" input_code = self._DetectInputEncoding(source) or "UTF-8" @@ -131,7 +160,7 @@ class MacTool(object): else: return None - def ExecCopyInfoPlist(self, source, dest, *keys): + def ExecCopyInfoPlist(self, source, dest, convert_to_binary, *keys): """Copies the |source| Info.plist to the destination directory |dest|.""" # Read the source Info.plist into memory. fd = open(source, 'r') @@ -146,7 +175,7 @@ class MacTool(object): # Go through all the environment variables and replace them as variables in # the file. - IDENT_RE = re.compile('[/\s]') + IDENT_RE = re.compile(r'[_/\s]') for key in os.environ: if key.startswith('_'): continue @@ -185,6 +214,9 @@ class MacTool(object): # "compiled". self._WritePkgInfo(dest) + if convert_to_binary == 'True': + self._ConvertToBinary(dest) + def _WritePkgInfo(self, info_plist): """This writes the PkgInfo file from the data stored in Info.plist.""" plist = plistlib.readPlist(info_plist) @@ -218,14 +250,49 @@ class MacTool(object): def ExecFilterLibtool(self, *cmd_list): """Calls libtool and filters out '/path/to/libtool: file: foo.o has no symbols'.""" - libtool_re = re.compile(r'^.*libtool: file: .* has no symbols$') - libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE) + libtool_re = re.compile(r'^.*libtool: (?:for architecture: \S* )?' + r'file: .* has no symbols$') + libtool_re5 = re.compile( + r'^.*libtool: warning for library: ' + + r'.* the table of contents is empty ' + + r'\(no object file members in the library define global symbols\)$') + env = os.environ.copy() + # Ref: + # http://www.opensource.apple.com/source/cctools/cctools-809/misc/libtool.c + # The problem with this flag is that it resets the file mtime on the file to + # epoch=0, e.g. 1970-1-1 or 1969-12-31 depending on timezone. + env['ZERO_AR_DATE'] = '1' + libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE, env=env) _, err = libtoolout.communicate() for line in err.splitlines(): - if not libtool_re.match(line): + if not libtool_re.match(line) and not libtool_re5.match(line): print >>sys.stderr, line + # Unconditionally touch the output .a file on the command line if present + # and the command succeeded. A bit hacky. + if not libtoolout.returncode: + for i in range(len(cmd_list) - 1): + if cmd_list[i] == "-o" and cmd_list[i+1].endswith('.a'): + os.utime(cmd_list[i+1], None) + break return libtoolout.returncode + def ExecPackageIosFramework(self, framework): + # Find the name of the binary based on the part before the ".framework". + binary = os.path.basename(framework).split('.')[0] + module_path = os.path.join(framework, 'Modules'); + if not os.path.exists(module_path): + os.mkdir(module_path) + module_template = 'framework module %s {\n' \ + ' umbrella header "%s.h"\n' \ + '\n' \ + ' export *\n' \ + ' module * { export * }\n' \ + '}\n' % (binary, binary) + + module_file = open(os.path.join(module_path, 'module.modulemap'), "w") + module_file.write(module_template) + module_file.close() + def ExecPackageFramework(self, framework, version): """Takes a path to Something.framework and the Current version of that and sets up all the symlinks.""" @@ -262,49 +329,105 @@ class MacTool(object): os.remove(link) os.symlink(dest, link) - def ExecCodeSignBundle(self, key, resource_rules, entitlements, provisioning): + def ExecCompileIosFrameworkHeaderMap(self, out, framework, *all_headers): + framework_name = os.path.basename(framework).split('.')[0] + all_headers = map(os.path.abspath, all_headers) + filelist = {} + for header in all_headers: + filename = os.path.basename(header) + filelist[filename] = header + filelist[os.path.join(framework_name, filename)] = header + WriteHmap(out, filelist) + + def ExecCopyIosFrameworkHeaders(self, framework, *copy_headers): + header_path = os.path.join(framework, 'Headers'); + if not os.path.exists(header_path): + os.makedirs(header_path) + for header in copy_headers: + shutil.copy(header, os.path.join(header_path, os.path.basename(header))) + + def ExecCompileXcassets(self, keys, *inputs): + """Compiles multiple .xcassets files into a single .car file. + + This invokes 'actool' to compile all the inputs .xcassets files. The + |keys| arguments is a json-encoded dictionary of extra arguments to + pass to 'actool' when the asset catalogs contains an application icon + or a launch image. + + Note that 'actool' does not create the Assets.car file if the asset + catalogs does not contains imageset. + """ + command_line = [ + 'xcrun', 'actool', '--output-format', 'human-readable-text', + '--compress-pngs', '--notices', '--warnings', '--errors', + ] + is_iphone_target = 'IPHONEOS_DEPLOYMENT_TARGET' in os.environ + if is_iphone_target: + platform = os.environ['CONFIGURATION'].split('-')[-1] + if platform not in ('iphoneos', 'iphonesimulator'): + platform = 'iphonesimulator' + command_line.extend([ + '--platform', platform, '--target-device', 'iphone', + '--target-device', 'ipad', '--minimum-deployment-target', + os.environ['IPHONEOS_DEPLOYMENT_TARGET'], '--compile', + os.path.abspath(os.environ['CONTENTS_FOLDER_PATH']), + ]) + else: + command_line.extend([ + '--platform', 'macosx', '--target-device', 'mac', + '--minimum-deployment-target', os.environ['MACOSX_DEPLOYMENT_TARGET'], + '--compile', + os.path.abspath(os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH']), + ]) + if keys: + keys = json.loads(keys) + for key, value in keys.iteritems(): + arg_name = '--' + key + if isinstance(value, bool): + if value: + command_line.append(arg_name) + elif isinstance(value, list): + for v in value: + command_line.append(arg_name) + command_line.append(str(v)) + else: + command_line.append(arg_name) + command_line.append(str(value)) + # Note: actool crashes if inputs path are relative, so use os.path.abspath + # to get absolute path name for inputs. + command_line.extend(map(os.path.abspath, inputs)) + subprocess.check_call(command_line) + + def ExecMergeInfoPlist(self, output, *inputs): + """Merge multiple .plist files into a single .plist file.""" + merged_plist = {} + for path in inputs: + plist = self._LoadPlistMaybeBinary(path) + self._MergePlist(merged_plist, plist) + plistlib.writePlist(merged_plist, output) + + def ExecCodeSignBundle(self, key, entitlements, provisioning, path, preserve): """Code sign a bundle. This function tries to code sign an iOS bundle, following the same algorithm as Xcode: - 1. copy ResourceRules.plist from the user or the SDK into the bundle, - 2. pick the provisioning profile that best match the bundle identifier, + 1. pick the provisioning profile that best match the bundle identifier, and copy it into the bundle as embedded.mobileprovision, - 3. copy Entitlements.plist from user or SDK next to the bundle, - 4. code sign the bundle. + 2. copy Entitlements.plist from user or SDK next to the bundle, + 3. code sign the bundle. """ - resource_rules_path = self._InstallResourceRules(resource_rules) substitutions, overrides = self._InstallProvisioningProfile( provisioning, self._GetCFBundleIdentifier()) entitlements_path = self._InstallEntitlements( entitlements, substitutions, overrides) - subprocess.check_call([ - 'codesign', '--force', '--sign', key, '--resource-rules', - resource_rules_path, '--entitlements', entitlements_path, - os.path.join( - os.environ['TARGET_BUILD_DIR'], - os.environ['FULL_PRODUCT_NAME'])]) - def _InstallResourceRules(self, resource_rules): - """Installs ResourceRules.plist from user or SDK into the bundle. - - Args: - resource_rules: string, optional, path to the ResourceRules.plist file - to use, default to "${SDKROOT}/ResourceRules.plist" - - Returns: - Path to the copy of ResourceRules.plist into the bundle. - """ - source_path = resource_rules - target_path = os.path.join( - os.environ['BUILT_PRODUCTS_DIR'], - os.environ['CONTENTS_FOLDER_PATH'], - 'ResourceRules.plist') - if not source_path: - source_path = os.path.join( - os.environ['SDKROOT'], 'ResourceRules.plist') - shutil.copy2(source_path, target_path) - return target_path + args = ['codesign', '--force', '--sign', key] + if preserve == 'True': + args.extend(['--deep', '--preserve-metadata=identifier,entitlements']) + else: + args.extend(['--entitlements', entitlements_path]) + args.extend(['--timestamp=none', path]) + subprocess.check_call(args) def _InstallProvisioningProfile(self, profile, bundle_identifier): """Installs embedded.mobileprovision into the bundle. @@ -398,6 +521,19 @@ class MacTool(object): 'security', 'cms', '-D', '-i', profile_path, '-o', temp.name]) return self._LoadPlistMaybeBinary(temp.name) + def _MergePlist(self, merged_plist, plist): + """Merge |plist| into |merged_plist|.""" + for key, value in plist.iteritems(): + if isinstance(value, dict): + merged_value = merged_plist.get(key, {}) + if isinstance(merged_value, dict): + self._MergePlist(merged_value, value) + merged_plist[key] = merged_value + else: + merged_plist[key] = value + else: + merged_plist[key] = value + def _LoadPlistMaybeBinary(self, plist_path): """Loads into a memory a plist possibly encoded in binary format. @@ -506,5 +642,71 @@ class MacTool(object): return {k: self._ExpandVariables(data[k], substitutions) for k in data} return data +def NextGreaterPowerOf2(x): + return 2**(x).bit_length() + +def WriteHmap(output_name, filelist): + """Generates a header map based on |filelist|. + + Per Mark Mentovai: + A header map is structured essentially as a hash table, keyed by names used + in #includes, and providing pathnames to the actual files. + + The implementation below and the comment above comes from inspecting: + http://www.opensource.apple.com/source/distcc/distcc-2503/distcc_dist/include_server/headermap.py?txt + while also looking at the implementation in clang in: + https://llvm.org/svn/llvm-project/cfe/trunk/lib/Lex/HeaderMap.cpp + """ + magic = 1751998832 + version = 1 + _reserved = 0 + count = len(filelist) + capacity = NextGreaterPowerOf2(count) + strings_offset = 24 + (12 * capacity) + max_value_length = len(max(filelist.items(), key=lambda (k,v):len(v))[1]) + + out = open(output_name, "wb") + out.write(struct.pack('.+)') - for key in [MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID]: - try: - output = self._GetStdout(['/usr/sbin/pkgutil', '--pkg-info', key]) - return re.search(regex, output).groupdict()['version'] - except: - continue - - def _XcodeVersion(self): - # `xcodebuild -version` output looks like - # Xcode 4.6.3 - # Build version 4H1503 - # or like - # Xcode 3.2.6 - # Component versions: DevToolsCore-1809.0; DevToolsSupport-1806.0 - # BuildVersion: 10M2518 - # Convert that to '0463', '4H1503'. - if len(XcodeSettings._xcode_version_cache) == 0: - try: - version_list = self._GetStdout(['xcodebuild', '-version']).splitlines() - # In some circumstances xcodebuild exits 0 but doesn't return - # the right results; for example, a user on 10.7 or 10.8 with - # a bogus path set via xcode-select - # In that case this may be a CLT-only install so fall back to - # checking that version. - if len(version_list) < 2: - raise GypError, "xcodebuild returned unexpected results" - except: - version = self._CLTVersion() - if version: - version = re.match('(\d\.\d\.?\d*)', version).groups()[0] - else: - raise GypError, "No Xcode or CLT version detected!" - # The CLT has no build information, so we return an empty string. - version_list = [version, ''] - version = version_list[0] - build = version_list[-1] - # Be careful to convert "4.2" to "0420": - version = version.split()[-1].replace('.', '') - version = (version + '0' * (3 - len(version))).zfill(4) - if build: - build = build.split()[-1] - XcodeSettings._xcode_version_cache = (version, build) - return XcodeSettings._xcode_version_cache + return GetStdout(['sw_vers', '-buildVersion']) def _XcodeIOSDeviceFamily(self, configname): family = self.xcode_settings[configname].get('TARGETED_DEVICE_FAMILY', '1') @@ -944,28 +1153,40 @@ class XcodeSettings(object): cache = {} cache['BuildMachineOSBuild'] = self._BuildMachineOSBuild() - xcode, xcode_build = self._XcodeVersion() + xcode, xcode_build = XcodeVersion() cache['DTXcode'] = xcode cache['DTXcodeBuild'] = xcode_build + compiler = self.xcode_settings[configname].get('GCC_VERSION') + if compiler is not None: + cache['DTCompiler'] = compiler sdk_root = self._SdkRoot(configname) if not sdk_root: sdk_root = self._DefaultSdkRoot() - cache['DTSDKName'] = sdk_root - if xcode >= '0430': + sdk_version = self._GetSdkVersionInfoItem(sdk_root, '--show-sdk-version') + cache['DTSDKName'] = sdk_root + (sdk_version or '') + if xcode >= '0720': cache['DTSDKBuild'] = self._GetSdkVersionInfoItem( - sdk_root, 'ProductBuildVersion') + sdk_root, '--show-sdk-build-version') + elif xcode >= '0430': + cache['DTSDKBuild'] = sdk_version else: cache['DTSDKBuild'] = cache['BuildMachineOSBuild'] if self.isIOS: - cache['DTPlatformName'] = cache['DTSDKName'] + cache['MinimumOSVersion'] = self.xcode_settings[configname].get( + 'IPHONEOS_DEPLOYMENT_TARGET') + cache['DTPlatformName'] = sdk_root + cache['DTPlatformVersion'] = sdk_version + if configname.endswith("iphoneos"): - cache['DTPlatformVersion'] = self._GetSdkVersionInfoItem( - sdk_root, 'ProductVersion') cache['CFBundleSupportedPlatforms'] = ['iPhoneOS'] + cache['DTPlatformBuild'] = cache['DTSDKBuild'] else: cache['CFBundleSupportedPlatforms'] = ['iPhoneSimulator'] + # This is weird, but Xcode sets DTPlatformBuild to an empty field + # for simulator builds. + cache['DTPlatformBuild'] = "" XcodeSettings._plist_cache[configname] = cache # Include extra plist items that are per-target, not per global @@ -982,14 +1203,15 @@ class XcodeSettings(object): project, then the environment variable was empty. Starting with this version, Xcode uses the name of the newest SDK installed. """ - if self._XcodeVersion() < '0500': + xcode_version, xcode_build = XcodeVersion() + if xcode_version < '0500': return '' default_sdk_path = self._XcodeSdkPath('') default_sdk_root = XcodeSettings._sdk_root_cache.get(default_sdk_path) if default_sdk_root: return default_sdk_root try: - all_sdks = self._GetStdout(['xcodebuild', '-showsdks']) + all_sdks = GetStdout(['xcodebuild', '-showsdks']) except: # If xcodebuild fails, there will be no valid SDKs return '' @@ -1002,28 +1224,6 @@ class XcodeSettings(object): return sdk_root return '' - def _DefaultArch(self): - # For Mac projects, Xcode changed the default value used when ARCHS is not - # set from "i386" to "x86_64". - # - # For iOS projects, if ARCHS is unset, it defaults to "armv7 armv7s" when - # building for a device, and the simulator binaries are always build for - # "i386". - # - # For new projects, ARCHS is set to $(ARCHS_STANDARD_INCLUDING_64_BIT), - # which correspond to "armv7 armv7s arm64", and when building the simulator - # the architecture is either "i386" or "x86_64" depending on the simulated - # device (respectively 32-bit or 64-bit device). - # - # Since the value returned by this function is only used when ARCHS is not - # set, then on iOS we return "i386", as the default xcode project generator - # does not set ARCHS if it is not set in the .gyp file. - if self.isIOS: - return 'i386' - version, build = self._XcodeVersion() - if version >= '0500': - return 'x86_64' - return 'i386' class MacPrefixHeader(object): """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature. @@ -1131,6 +1331,81 @@ class MacPrefixHeader(object): ] +def XcodeVersion(): + """Returns a tuple of version and build version of installed Xcode.""" + # `xcodebuild -version` output looks like + # Xcode 4.6.3 + # Build version 4H1503 + # or like + # Xcode 3.2.6 + # Component versions: DevToolsCore-1809.0; DevToolsSupport-1806.0 + # BuildVersion: 10M2518 + # Convert that to '0463', '4H1503'. + global XCODE_VERSION_CACHE + if XCODE_VERSION_CACHE: + return XCODE_VERSION_CACHE + try: + version_list = GetStdout(['xcodebuild', '-version']).splitlines() + # In some circumstances xcodebuild exits 0 but doesn't return + # the right results; for example, a user on 10.7 or 10.8 with + # a bogus path set via xcode-select + # In that case this may be a CLT-only install so fall back to + # checking that version. + if len(version_list) < 2: + raise GypError("xcodebuild returned unexpected results") + except: + version = CLTVersion() + if version: + version = re.match(r'(\d\.\d\.?\d*)', version).groups()[0] + else: + raise GypError("No Xcode or CLT version detected!") + # The CLT has no build information, so we return an empty string. + version_list = [version, ''] + version = version_list[0] + build = version_list[-1] + # Be careful to convert "4.2" to "0420": + version = version.split()[-1].replace('.', '') + version = (version + '0' * (3 - len(version))).zfill(4) + if build: + build = build.split()[-1] + XCODE_VERSION_CACHE = (version, build) + return XCODE_VERSION_CACHE + + +# This function ported from the logic in Homebrew's CLT version check +def CLTVersion(): + """Returns the version of command-line tools from pkgutil.""" + # pkgutil output looks like + # package-id: com.apple.pkg.CLTools_Executables + # version: 5.0.1.0.1.1382131676 + # volume: / + # location: / + # install-time: 1382544035 + # groups: com.apple.FindSystemFiles.pkg-group com.apple.DevToolsBoth.pkg-group com.apple.DevToolsNonRelocatableShared.pkg-group + STANDALONE_PKG_ID = "com.apple.pkg.DeveloperToolsCLILeo" + FROM_XCODE_PKG_ID = "com.apple.pkg.DeveloperToolsCLI" + MAVERICKS_PKG_ID = "com.apple.pkg.CLTools_Executables" + + regex = re.compile('version: (?P.+)') + for key in [MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID]: + try: + output = GetStdout(['/usr/sbin/pkgutil', '--pkg-info', key]) + return re.search(regex, output).groupdict()['version'] + except: + continue + + +def GetStdout(cmdlist): + """Returns the content of standard output returned by invoking |cmdlist|. + Raises |GypError| if the command return with a non-zero return code.""" + job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE) + out = job.communicate()[0] + if job.returncode != 0: + sys.stderr.write(out + '\n') + raise GypError('Error %d running %s' % (job.returncode, cmdlist[0])) + return out.rstrip('\n') + + def MergeGlobalXcodeSettingsToSpec(global_dict, spec): """Merges the global xcode_settings dictionary into each configuration of the target represented by spec. For keys that are both in the global and the local @@ -1153,7 +1428,10 @@ def IsMacBundle(flavor, spec): Bundles are directories with a certain subdirectory structure, instead of just a single file. Bundle rules do not produce a binary but also package resources into that directory.""" - is_mac_bundle = (int(spec.get('mac_bundle', 0)) != 0 and flavor == 'mac') + is_mac_bundle = int(spec.get('mac_xctest_bundle', 0)) != 0 or \ + int(spec.get('mac_xcuitest_bundle', 0)) != 0 or \ + (int(spec.get('mac_bundle', 0)) != 0 and flavor == 'mac') + if is_mac_bundle: assert spec['type'] != 'none', ( 'mac_bundle targets cannot have type none (target "%s")' % @@ -1271,6 +1549,7 @@ def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration, # These are filled in on a as-needed basis. env = { + 'BUILT_FRAMEWORKS_DIR' : built_products_dir, 'BUILT_PRODUCTS_DIR' : built_products_dir, 'CONFIGURATION' : configuration, 'PRODUCT_NAME' : xcode_settings.GetProductName(), @@ -1281,12 +1560,16 @@ def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration, # written for bundles: 'TARGET_BUILD_DIR' : built_products_dir, 'TEMP_DIR' : '${TMPDIR}', + 'XCODE_VERSION_ACTUAL' : XcodeVersion()[0], } if xcode_settings.GetPerConfigSetting('SDKROOT', configuration): env['SDKROOT'] = xcode_settings._SdkPath(configuration) else: env['SDKROOT'] = '' + if xcode_settings.mac_toolchain_dir: + env['DEVELOPER_DIR'] = xcode_settings.mac_toolchain_dir + if spec['type'] in ( 'executable', 'static_library', 'shared_library', 'loadable_module'): env['EXECUTABLE_NAME'] = xcode_settings.GetExecutableName() @@ -1310,6 +1593,11 @@ def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration, install_name_base = xcode_settings.GetInstallNameBase() if install_name_base: env['DYLIB_INSTALL_NAME_BASE'] = install_name_base + if XcodeVersion() >= '0500' and not env.get('SDKROOT'): + sdk_root = xcode_settings._SdkRoot(configuration) + if not sdk_root: + sdk_root = xcode_settings._XcodeSdkPath('') + env['SDKROOT'] = sdk_root if not additional_settings: additional_settings = {} @@ -1420,16 +1708,17 @@ def _HasIOSTarget(targets): def _AddIOSDeviceConfigurations(targets): """Clone all targets and append -iphoneos to the name. Configure these targets - to build for iOS devices.""" - for target_dict in targets.values(): - for config_name in target_dict['configurations'].keys(): - config = target_dict['configurations'][config_name] - new_config_name = config_name + '-iphoneos' - new_config_dict = copy.deepcopy(config) - if target_dict['toolset'] == 'target': - new_config_dict['xcode_settings']['ARCHS'] = ['armv7'] - new_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos' - target_dict['configurations'][new_config_name] = new_config_dict + to build for iOS devices and use correct architectures for those builds.""" + for target_dict in targets.itervalues(): + toolset = target_dict['toolset'] + configs = target_dict['configurations'] + for config_name, simulator_config_dict in dict(configs).iteritems(): + iphoneos_config_dict = copy.deepcopy(simulator_config_dict) + configs[config_name + '-iphoneos'] = iphoneos_config_dict + configs[config_name + '-iphonesimulator'] = simulator_config_dict + if toolset == 'target': + simulator_config_dict['xcode_settings']['SDKROOT'] = 'iphonesimulator' + iphoneos_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos' return targets def CloneConfigurationForDeviceAndEmulator(target_dicts): diff --git a/third_party/gyp/xcode_ninja.py b/third_party/gyp/xcode_ninja.py new file mode 100644 index 00000000..bc76ffff --- /dev/null +++ b/third_party/gyp/xcode_ninja.py @@ -0,0 +1,289 @@ +# Copyright (c) 2014 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Xcode-ninja wrapper project file generator. + +This updates the data structures passed to the Xcode gyp generator to build +with ninja instead. The Xcode project itself is transformed into a list of +executable targets, each with a build step to build with ninja, and a target +with every source and resource file. This appears to sidestep some of the +major performance headaches experienced using complex projects and large number +of targets within Xcode. +""" + +import errno +import gyp.generator.ninja +import os +import re +import xml.sax.saxutils + + +def _WriteWorkspace(main_gyp, sources_gyp, params): + """ Create a workspace to wrap main and sources gyp paths. """ + (build_file_root, build_file_ext) = os.path.splitext(main_gyp) + workspace_path = build_file_root + '.xcworkspace' + options = params['options'] + if options.generator_output: + workspace_path = os.path.join(options.generator_output, workspace_path) + try: + os.makedirs(workspace_path) + except OSError, e: + if e.errno != errno.EEXIST: + raise + output_string = '\n' + \ + '\n' + for gyp_name in [main_gyp, sources_gyp]: + name = os.path.splitext(os.path.basename(gyp_name))[0] + '.xcodeproj' + name = xml.sax.saxutils.quoteattr("group:" + name) + output_string += ' \n' % name + output_string += '\n' + + workspace_file = os.path.join(workspace_path, "contents.xcworkspacedata") + + try: + with open(workspace_file, 'r') as input_file: + input_string = input_file.read() + if input_string == output_string: + return + except IOError: + # Ignore errors if the file doesn't exist. + pass + + with open(workspace_file, 'w') as output_file: + output_file.write(output_string) + +def _TargetFromSpec(old_spec, params): + """ Create fake target for xcode-ninja wrapper. """ + # Determine ninja top level build dir (e.g. /path/to/out). + ninja_toplevel = None + jobs = 0 + if params: + options = params['options'] + ninja_toplevel = \ + os.path.join(options.toplevel_dir, + gyp.generator.ninja.ComputeOutputDir(params)) + jobs = params.get('generator_flags', {}).get('xcode_ninja_jobs', 0) + + target_name = old_spec.get('target_name') + product_name = old_spec.get('product_name', target_name) + product_extension = old_spec.get('product_extension') + + ninja_target = {} + ninja_target['target_name'] = target_name + ninja_target['product_name'] = product_name + if product_extension: + ninja_target['product_extension'] = product_extension + ninja_target['toolset'] = old_spec.get('toolset') + ninja_target['default_configuration'] = old_spec.get('default_configuration') + ninja_target['configurations'] = {} + + # Tell Xcode to look in |ninja_toplevel| for build products. + new_xcode_settings = {} + if ninja_toplevel: + new_xcode_settings['CONFIGURATION_BUILD_DIR'] = \ + "%s/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)" % ninja_toplevel + + if 'configurations' in old_spec: + for config in old_spec['configurations'].iterkeys(): + old_xcode_settings = \ + old_spec['configurations'][config].get('xcode_settings', {}) + if 'IPHONEOS_DEPLOYMENT_TARGET' in old_xcode_settings: + new_xcode_settings['CODE_SIGNING_REQUIRED'] = "NO" + new_xcode_settings['IPHONEOS_DEPLOYMENT_TARGET'] = \ + old_xcode_settings['IPHONEOS_DEPLOYMENT_TARGET'] + for key in ['BUNDLE_LOADER', 'TEST_HOST']: + if key in old_xcode_settings: + new_xcode_settings[key] = old_xcode_settings[key] + + ninja_target['configurations'][config] = {} + ninja_target['configurations'][config]['xcode_settings'] = \ + new_xcode_settings + + ninja_target['mac_bundle'] = old_spec.get('mac_bundle', 0) + ninja_target['mac_xctest_bundle'] = old_spec.get('mac_xctest_bundle', 0) + ninja_target['ios_app_extension'] = old_spec.get('ios_app_extension', 0) + ninja_target['ios_watchkit_extension'] = \ + old_spec.get('ios_watchkit_extension', 0) + ninja_target['ios_watchkit_app'] = old_spec.get('ios_watchkit_app', 0) + ninja_target['type'] = old_spec['type'] + if ninja_toplevel: + ninja_target['actions'] = [ + { + 'action_name': 'Compile and copy %s via ninja' % target_name, + 'inputs': [], + 'outputs': [], + 'action': [ + 'env', + 'PATH=%s' % os.environ['PATH'], + 'ninja', + '-C', + new_xcode_settings['CONFIGURATION_BUILD_DIR'], + target_name, + ], + 'message': 'Compile and copy %s via ninja' % target_name, + }, + ] + if jobs > 0: + ninja_target['actions'][0]['action'].extend(('-j', jobs)) + return ninja_target + +def IsValidTargetForWrapper(target_extras, executable_target_pattern, spec): + """Limit targets for Xcode wrapper. + + Xcode sometimes performs poorly with too many targets, so only include + proper executable targets, with filters to customize. + Arguments: + target_extras: Regular expression to always add, matching any target. + executable_target_pattern: Regular expression limiting executable targets. + spec: Specifications for target. + """ + target_name = spec.get('target_name') + # Always include targets matching target_extras. + if target_extras is not None and re.search(target_extras, target_name): + return True + + # Otherwise just show executable targets and xc_tests. + if (int(spec.get('mac_xctest_bundle', 0)) != 0 or + (spec.get('type', '') == 'executable' and + spec.get('product_extension', '') != 'bundle')): + + # If there is a filter and the target does not match, exclude the target. + if executable_target_pattern is not None: + if not re.search(executable_target_pattern, target_name): + return False + return True + return False + +def CreateWrapper(target_list, target_dicts, data, params): + """Initialize targets for the ninja wrapper. + + This sets up the necessary variables in the targets to generate Xcode projects + that use ninja as an external builder. + Arguments: + target_list: List of target pairs: 'base/base.gyp:base'. + target_dicts: Dict of target properties keyed on target pair. + data: Dict of flattened build files keyed on gyp path. + params: Dict of global options for gyp. + """ + orig_gyp = params['build_files'][0] + for gyp_name, gyp_dict in data.iteritems(): + if gyp_name == orig_gyp: + depth = gyp_dict['_DEPTH'] + + # Check for custom main gyp name, otherwise use the default CHROMIUM_GYP_FILE + # and prepend .ninja before the .gyp extension. + generator_flags = params.get('generator_flags', {}) + main_gyp = generator_flags.get('xcode_ninja_main_gyp', None) + if main_gyp is None: + (build_file_root, build_file_ext) = os.path.splitext(orig_gyp) + main_gyp = build_file_root + ".ninja" + build_file_ext + + # Create new |target_list|, |target_dicts| and |data| data structures. + new_target_list = [] + new_target_dicts = {} + new_data = {} + + # Set base keys needed for |data|. + new_data[main_gyp] = {} + new_data[main_gyp]['included_files'] = [] + new_data[main_gyp]['targets'] = [] + new_data[main_gyp]['xcode_settings'] = \ + data[orig_gyp].get('xcode_settings', {}) + + # Normally the xcode-ninja generator includes only valid executable targets. + # If |xcode_ninja_executable_target_pattern| is set, that list is reduced to + # executable targets that match the pattern. (Default all) + executable_target_pattern = \ + generator_flags.get('xcode_ninja_executable_target_pattern', None) + + # For including other non-executable targets, add the matching target name + # to the |xcode_ninja_target_pattern| regular expression. (Default none) + target_extras = generator_flags.get('xcode_ninja_target_pattern', None) + + for old_qualified_target in target_list: + spec = target_dicts[old_qualified_target] + if IsValidTargetForWrapper(target_extras, executable_target_pattern, spec): + # Add to new_target_list. + target_name = spec.get('target_name') + new_target_name = '%s:%s#target' % (main_gyp, target_name) + new_target_list.append(new_target_name) + + # Add to new_target_dicts. + new_target_dicts[new_target_name] = _TargetFromSpec(spec, params) + + # Add to new_data. + for old_target in data[old_qualified_target.split(':')[0]]['targets']: + if old_target['target_name'] == target_name: + new_data_target = {} + new_data_target['target_name'] = old_target['target_name'] + new_data_target['toolset'] = old_target['toolset'] + new_data[main_gyp]['targets'].append(new_data_target) + + # Create sources target. + sources_target_name = 'sources_for_indexing' + sources_target = _TargetFromSpec( + { 'target_name' : sources_target_name, + 'toolset': 'target', + 'default_configuration': 'Default', + 'mac_bundle': '0', + 'type': 'executable' + }, None) + + # Tell Xcode to look everywhere for headers. + sources_target['configurations'] = {'Default': { 'include_dirs': [ depth ] } } + + # Put excluded files into the sources target so they can be opened in Xcode. + skip_excluded_files = \ + not generator_flags.get('xcode_ninja_list_excluded_files', True) + + sources = [] + for target, target_dict in target_dicts.iteritems(): + base = os.path.dirname(target) + files = target_dict.get('sources', []) + \ + target_dict.get('mac_bundle_resources', []) + + if not skip_excluded_files: + files.extend(target_dict.get('sources_excluded', []) + + target_dict.get('mac_bundle_resources_excluded', [])) + + for action in target_dict.get('actions', []): + files.extend(action.get('inputs', [])) + + if not skip_excluded_files: + files.extend(action.get('inputs_excluded', [])) + + # Remove files starting with $. These are mostly intermediate files for the + # build system. + files = [ file for file in files if not file.startswith('$')] + + # Make sources relative to root build file. + relative_path = os.path.dirname(main_gyp) + sources += [ os.path.relpath(os.path.join(base, file), relative_path) + for file in files ] + + sources_target['sources'] = sorted(set(sources)) + + # Put sources_to_index in it's own gyp. + sources_gyp = \ + os.path.join(os.path.dirname(main_gyp), sources_target_name + ".gyp") + fully_qualified_target_name = \ + '%s:%s#target' % (sources_gyp, sources_target_name) + + # Add to new_target_list, new_target_dicts and new_data. + new_target_list.append(fully_qualified_target_name) + new_target_dicts[fully_qualified_target_name] = sources_target + new_data_target = {} + new_data_target['target_name'] = sources_target['target_name'] + new_data_target['_DEPTH'] = depth + new_data_target['toolset'] = "target" + new_data[sources_gyp] = {} + new_data[sources_gyp]['targets'] = [] + new_data[sources_gyp]['included_files'] = [] + new_data[sources_gyp]['xcode_settings'] = \ + data[orig_gyp].get('xcode_settings', {}) + new_data[sources_gyp]['targets'].append(new_data_target) + + # Write workspace to file. + _WriteWorkspace(main_gyp, sources_gyp, params) + return (new_target_list, new_target_dicts, new_data) diff --git a/third_party/gyp/xcodeproj_file.py b/third_party/gyp/xcodeproj_file.py index 79c3abcf..1bc90c7d 100644 --- a/third_party/gyp/xcodeproj_file.py +++ b/third_party/gyp/xcodeproj_file.py @@ -173,7 +173,7 @@ _escaped = re.compile('[\\\\"]|[\x00-\x1f]') # Used by SourceTreeAndPathFromPath -_path_leading_variable = re.compile('^\$\((.*?)\)(/(.*))?$') +_path_leading_variable = re.compile(r'^\$\((.*?)\)(/(.*))?$') def SourceTreeAndPathFromPath(input_path): """Given input_path, returns a tuple with sourceTree and path values. @@ -196,7 +196,7 @@ def SourceTreeAndPathFromPath(input_path): return (source_tree, output_path) def ConvertVariablesToShellSyntax(input_string): - return re.sub('\$\((.*?)\)', '${\\1}', input_string) + return re.sub(r'\$\((.*?)\)', '${\\1}', input_string) class XCObject(object): """The abstract base of all class types used in Xcode project files. @@ -341,13 +341,13 @@ class XCObject(object): elif isinstance(value, dict): # dicts are never strong. if is_strong: - raise TypeError, 'Strong dict for key ' + key + ' in ' + \ - self.__class__.__name__ + raise TypeError('Strong dict for key ' + key + ' in ' + \ + self.__class__.__name__) else: that._properties[key] = value.copy() else: - raise TypeError, 'Unexpected type ' + value.__class__.__name__ + \ - ' for key ' + key + ' in ' + self.__class__.__name__ + raise TypeError('Unexpected type ' + value.__class__.__name__ + \ + ' for key ' + key + ' in ' + self.__class__.__name__) return that @@ -366,8 +366,7 @@ class XCObject(object): ('name' in self._schema and self._schema['name'][3]): return self._properties['name'] - raise NotImplementedError, \ - self.__class__.__name__ + ' must implement Name' + raise NotImplementedError(self.__class__.__name__ + ' must implement Name') def Comment(self): """Return a comment string for the object. @@ -466,10 +465,10 @@ class XCObject(object): for descendant in descendants: if descendant.id in ids: other = ids[descendant.id] - raise KeyError, \ + raise KeyError( 'Duplicate ID %s, objects "%s" and "%s" in "%s"' % \ (descendant.id, str(descendant._properties), - str(other._properties), self._properties['rootObject'].Name()) + str(other._properties), self._properties['rootObject'].Name())) ids[descendant.id] = descendant def Children(self): @@ -630,7 +629,7 @@ class XCObject(object): sep printable += end_tabs + '}' else: - raise TypeError, "Can't make " + value.__class__.__name__ + ' printable' + raise TypeError("Can't make " + value.__class__.__name__ + ' printable') if comment != None: printable += ' ' + self._EncodeComment(comment) @@ -756,31 +755,31 @@ class XCObject(object): for property, value in properties.iteritems(): # Make sure the property is in the schema. if not property in self._schema: - raise KeyError, property + ' not in ' + self.__class__.__name__ + raise KeyError(property + ' not in ' + self.__class__.__name__) # Make sure the property conforms to the schema. (is_list, property_type, is_strong) = self._schema[property][0:3] if is_list: if value.__class__ != list: - raise TypeError, \ + raise TypeError( property + ' of ' + self.__class__.__name__ + \ - ' must be list, not ' + value.__class__.__name__ + ' must be list, not ' + value.__class__.__name__) for item in value: if not isinstance(item, property_type) and \ not (item.__class__ == unicode and property_type == str): # Accept unicode where str is specified. str is treated as # UTF-8-encoded. - raise TypeError, \ + raise TypeError( 'item of ' + property + ' of ' + self.__class__.__name__ + \ ' must be ' + property_type.__name__ + ', not ' + \ - item.__class__.__name__ + item.__class__.__name__) elif not isinstance(value, property_type) and \ not (value.__class__ == unicode and property_type == str): # Accept unicode where str is specified. str is treated as # UTF-8-encoded. - raise TypeError, \ + raise TypeError( property + ' of ' + self.__class__.__name__ + ' must be ' + \ - property_type.__name__ + ', not ' + value.__class__.__name__ + property_type.__name__ + ', not ' + value.__class__.__name__) # Checks passed, perform the assignment. if do_copy: @@ -804,9 +803,9 @@ class XCObject(object): elif isinstance(value, dict): self._properties[property] = value.copy() else: - raise TypeError, "Don't know how to copy a " + \ - value.__class__.__name__ + ' object for ' + \ - property + ' in ' + self.__class__.__name__ + raise TypeError("Don't know how to copy a " + \ + value.__class__.__name__ + ' object for ' + \ + property + ' in ' + self.__class__.__name__) else: self._properties[property] = value @@ -837,15 +836,15 @@ class XCObject(object): # Schema validation. if not key in self._schema: - raise KeyError, key + ' not in ' + self.__class__.__name__ + raise KeyError(key + ' not in ' + self.__class__.__name__) (is_list, property_type, is_strong) = self._schema[key][0:3] if not is_list: - raise TypeError, key + ' of ' + self.__class__.__name__ + ' must be list' + raise TypeError(key + ' of ' + self.__class__.__name__ + ' must be list') if not isinstance(value, property_type): - raise TypeError, 'item of ' + key + ' of ' + self.__class__.__name__ + \ - ' must be ' + property_type.__name__ + ', not ' + \ - value.__class__.__name__ + raise TypeError('item of ' + key + ' of ' + self.__class__.__name__ + \ + ' must be ' + property_type.__name__ + ', not ' + \ + value.__class__.__name__) # If the property doesn't exist yet, create a new empty list to receive the # item. @@ -869,7 +868,7 @@ class XCObject(object): for property, attributes in self._schema.iteritems(): (is_list, property_type, is_strong, is_required) = attributes[0:4] if is_required and not property in self._properties: - raise KeyError, self.__class__.__name__ + ' requires ' + property + raise KeyError(self.__class__.__name__ + ' requires ' + property) def _SetDefaultsFromSchema(self): """Assign object default values according to the schema. This will not @@ -1143,16 +1142,16 @@ class PBXGroup(XCHierarchicalElement): child_path = child.PathFromSourceTreeAndPath() if child_path: if child_path in self._children_by_path: - raise ValueError, 'Found multiple children with path ' + child_path + raise ValueError('Found multiple children with path ' + child_path) self._children_by_path[child_path] = child if isinstance(child, PBXVariantGroup): child_name = child._properties.get('name', None) key = (child_name, child_path) if key in self._variant_children_by_name_and_path: - raise ValueError, 'Found multiple PBXVariantGroup children with ' + \ - 'name ' + str(child_name) + ' and path ' + \ - str(child_path) + raise ValueError('Found multiple PBXVariantGroup children with ' + \ + 'name ' + str(child_name) + ' and path ' + \ + str(child_path)) self._variant_children_by_name_and_path[key] = child def AppendChild(self, child): @@ -1493,6 +1492,7 @@ class PBXFileReference(XCFileLikeElement, XCContainerPortal, XCRemoteObject): 'icns': 'image.icns', 'java': 'sourcecode.java', 'js': 'sourcecode.javascript', + 'kext': 'wrapper.kext', 'm': 'sourcecode.c.objc', 'mm': 'sourcecode.cpp.objcpp', 'nib': 'wrapper.nib', @@ -1508,10 +1508,12 @@ class PBXFileReference(XCFileLikeElement, XCContainerPortal, XCRemoteObject): 's': 'sourcecode.asm', 'storyboard': 'file.storyboard', 'strings': 'text.plist.strings', + 'swift': 'sourcecode.swift', 'ttf': 'file', 'xcassets': 'folder.assetcatalog', 'xcconfig': 'text.xcconfig', 'xcdatamodel': 'wrapper.xcdatamodel', + 'xcdatamodeld':'wrapper.xcdatamodeld', 'xib': 'file.xib', 'y': 'sourcecode.yacc', } @@ -1606,7 +1608,7 @@ class XCConfigurationList(XCObject): if configuration._properties['name'] == name: return configuration - raise KeyError, name + raise KeyError(name) def DefaultConfiguration(self): """Convenience accessor to obtain the default XCBuildConfiguration.""" @@ -1663,7 +1665,7 @@ class XCConfigurationList(XCObject): value = configuration_value else: if value != configuration_value: - raise ValueError, 'Variant values for ' + key + raise ValueError('Variant values for ' + key) return value @@ -1770,8 +1772,8 @@ class XCBuildPhase(XCObject): # added, either as a child or deeper descendant. The second item should # be a boolean indicating whether files should be added into hierarchical # groups or one single flat group. - raise NotImplementedError, \ - self.__class__.__name__ + ' must implement FileGroup' + raise NotImplementedError( + self.__class__.__name__ + ' must implement FileGroup') def _AddPathToDict(self, pbxbuildfile, path): """Adds path to the dict tracking paths belonging to this build phase. @@ -1780,7 +1782,7 @@ class XCBuildPhase(XCObject): """ if path in self._files_by_path: - raise ValueError, 'Found multiple build files with path ' + path + raise ValueError('Found multiple build files with path ' + path) self._files_by_path[path] = pbxbuildfile def _AddBuildFileToDicts(self, pbxbuildfile, path=None): @@ -1835,8 +1837,8 @@ class XCBuildPhase(XCObject): # problem. if xcfilelikeelement in self._files_by_xcfilelikeelement and \ self._files_by_xcfilelikeelement[xcfilelikeelement] != pbxbuildfile: - raise ValueError, 'Found multiple build files for ' + \ - xcfilelikeelement.Name() + raise ValueError('Found multiple build files for ' + \ + xcfilelikeelement.Name()) self._files_by_xcfilelikeelement[xcfilelikeelement] = pbxbuildfile def AppendBuildFile(self, pbxbuildfile, path=None): @@ -1950,6 +1952,7 @@ class PBXCopyFilesBuildPhase(XCBuildPhase): # path_tree_to_subfolder maps names of Xcode variables to the associated # dstSubfolderSpec property value used in a PBXCopyFilesBuildPhase object. path_tree_to_subfolder = { + 'BUILT_FRAMEWORKS_DIR': 10, # Frameworks Directory 'BUILT_PRODUCTS_DIR': 16, # Products Directory # Other types that can be chosen via the Xcode UI. # TODO(mark): Map Xcode variable names to these. @@ -1957,7 +1960,6 @@ class PBXCopyFilesBuildPhase(XCBuildPhase): # : 6, # Executables: 6 # : 7, # Resources # : 15, # Java Resources - # : 10, # Frameworks # : 11, # Shared Frameworks # : 12, # Shared Support # : 13, # PlugIns @@ -2000,8 +2002,8 @@ class PBXCopyFilesBuildPhase(XCBuildPhase): subfolder = 0 relative_path = path[1:] else: - raise ValueError, 'Can\'t use path %s in a %s' % \ - (path, self.__class__.__name__) + raise ValueError('Can\'t use path %s in a %s' % \ + (path, self.__class__.__name__)) self._properties['dstPath'] = relative_path self._properties['dstSubfolderSpec'] = subfolder @@ -2237,10 +2239,16 @@ class PBXNativeTarget(XCTarget): # Mapping from Xcode product-types to settings. The settings are: # filetype : used for explicitFileType in the project file # prefix : the prefix for the file name - # suffix : the suffix for the filen ame + # suffix : the suffix for the file name _product_filetypes = { - 'com.apple.product-type.application': ['wrapper.application', - '', '.app'], + 'com.apple.product-type.application': ['wrapper.application', + '', '.app'], + 'com.apple.product-type.application.watchapp': ['wrapper.application', + '', '.app'], + 'com.apple.product-type.watchkit-extension': ['wrapper.app-extension', + '', '.appex'], + 'com.apple.product-type.app-extension': ['wrapper.app-extension', + '', '.appex'], 'com.apple.product-type.bundle': ['wrapper.cfbundle', '', '.bundle'], 'com.apple.product-type.framework': ['wrapper.framework', @@ -2253,8 +2261,12 @@ class PBXNativeTarget(XCTarget): '', ''], 'com.apple.product-type.bundle.unit-test': ['wrapper.cfbundle', '', '.xctest'], + 'com.apple.product-type.bundle.ui-testing': ['wrapper.cfbundle', + '', '.xctest'], 'com.googlecode.gyp.xcode.bundle': ['compiled.mach-o.dylib', '', '.so'], + 'com.apple.product-type.kernel-extension': ['wrapper.kext', + '', '.kext'], } def __init__(self, properties=None, id=None, parent=None, @@ -2307,17 +2319,19 @@ class PBXNativeTarget(XCTarget): force_extension = suffix[1:] if self._properties['productType'] == \ - 'com.apple.product-type-bundle.unit.test': + 'com.apple.product-type-bundle.unit.test' or \ + self._properties['productType'] == \ + 'com.apple.product-type-bundle.ui-testing': if force_extension is None: force_extension = suffix[1:] if force_extension is not None: # If it's a wrapper (bundle), set WRAPPER_EXTENSION. + # Extension override. + suffix = '.' + force_extension if filetype.startswith('wrapper.'): self.SetBuildSetting('WRAPPER_EXTENSION', force_extension) else: - # Extension override. - suffix = '.' + force_extension self.SetBuildSetting('EXECUTABLE_EXTENSION', force_extension) if filetype.startswith('compiled.mach-o.executable'): @@ -2733,8 +2747,53 @@ class PBXProject(XCContainerPortal): self._SetUpProductReferences(other_pbxproject, product_group, project_ref) + inherit_unique_symroot = self._AllSymrootsUnique(other_pbxproject, False) + targets = other_pbxproject.GetProperty('targets') + if all(self._AllSymrootsUnique(t, inherit_unique_symroot) for t in targets): + dir_path = project_ref._properties['path'] + product_group._hashables.extend(dir_path) + return [product_group, project_ref] + def _AllSymrootsUnique(self, target, inherit_unique_symroot): + # Returns True if all configurations have a unique 'SYMROOT' attribute. + # The value of inherit_unique_symroot decides, if a configuration is assumed + # to inherit a unique 'SYMROOT' attribute from its parent, if it doesn't + # define an explicit value for 'SYMROOT'. + symroots = self._DefinedSymroots(target) + for s in self._DefinedSymroots(target): + if (s is not None and not self._IsUniqueSymrootForTarget(s) or + s is None and not inherit_unique_symroot): + return False + return True if symroots else inherit_unique_symroot + + def _DefinedSymroots(self, target): + # Returns all values for the 'SYMROOT' attribute defined in all + # configurations for this target. If any configuration doesn't define the + # 'SYMROOT' attribute, None is added to the returned set. If all + # configurations don't define the 'SYMROOT' attribute, an empty set is + # returned. + config_list = target.GetProperty('buildConfigurationList') + symroots = set() + for config in config_list.GetProperty('buildConfigurations'): + setting = config.GetProperty('buildSettings') + if 'SYMROOT' in setting: + symroots.add(setting['SYMROOT']) + else: + symroots.add(None) + if len(symroots) == 1 and None in symroots: + return set() + return symroots + + def _IsUniqueSymrootForTarget(self, symroot): + # This method returns True if all configurations in target contain a + # 'SYMROOT' attribute that is unique for the given target. A value is + # unique, if the Xcode macro '$SRCROOT' appears in it in any form. + uniquifier = ['$SRCROOT', '$(SRCROOT)'] + if any(x in symroot for x in uniquifier): + return True + return False + def _SetUpProductReferences(self, other_pbxproject, product_group, project_ref): # TODO(mark): This only adds references to products in other_pbxproject @@ -2803,7 +2862,7 @@ class PBXProject(XCContainerPortal): product_group = ref_dict['ProductGroup'] product_group._properties['children'] = sorted( product_group._properties['children'], - cmp=lambda x, y: CompareProducts(x, y, remote_products)) + cmp=lambda x, y, rp=remote_products: CompareProducts(x, y, rp)) class XCProjectFile(XCObject): @@ -2811,27 +2870,10 @@ class XCProjectFile(XCObject): _schema.update({ 'archiveVersion': [0, int, 0, 1, 1], 'classes': [0, dict, 0, 1, {}], - 'objectVersion': [0, int, 0, 1, 45], + 'objectVersion': [0, int, 0, 1, 46], 'rootObject': [0, PBXProject, 1, 1], }) - def SetXcodeVersion(self, version): - version_to_object_version = { - '2.4': 45, - '3.0': 45, - '3.1': 45, - '3.2': 46, - } - if not version in version_to_object_version: - supported_str = ', '.join(sorted(version_to_object_version.keys())) - raise Exception( - 'Unsupported Xcode version %s (supported: %s)' % - ( version, supported_str ) ) - compatibility_version = 'Xcode %s' % version - self._properties['rootObject'].SetProperty('compatibilityVersion', - compatibility_version) - self.SetProperty('objectVersion', version_to_object_version[version]); - def ComputeIDs(self, recursive=True, overwrite=True, hash=None): # Although XCProjectFile is implemented here as an XCObject, it's not a # proper object in the Xcode sense, and it certainly doesn't have its own diff --git a/third_party/jsmn/LICENSE b/third_party/jsmn/LICENSE new file mode 100644 index 00000000..c84fb2e9 --- /dev/null +++ b/third_party/jsmn/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2010 Serge A. Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/third_party/jsmn/README.md b/third_party/jsmn/README.md new file mode 100644 index 00000000..105897b8 --- /dev/null +++ b/third_party/jsmn/README.md @@ -0,0 +1,167 @@ + +JSMN +==== + +jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be +easily integrated into resource-limited or embedded projects. + +You can find more information about JSON format at [json.org][1] + +Library sources are available at https://github.com/zserge/jsmn + +The web page with some information about jsmn can be found at +[http://zserge.com/jsmn.html][2] + +Philosophy +---------- + +Most JSON parsers offer you a bunch of functions to load JSON data, parse it +and extract any value by its name. jsmn proves that checking the correctness of +every JSON packet or allocating temporary objects to store parsed JSON fields +often is an overkill. + +JSON format itself is extremely simple, so why should we complicate it? + +jsmn is designed to be **robust** (it should work fine even with erroneous +data), **fast** (it should parse data on the fly), **portable** (no superfluous +dependencies or non-standard C extensions). And of course, **simplicity** is a +key feature - simple code style, simple algorithm, simple integration into +other projects. + +Features +-------- + +* compatible with C89 +* no dependencies (even libc!) +* highly portable (tested on x86/amd64, ARM, AVR) +* about 200 lines of code +* extremely small code footprint +* API contains only 2 functions +* no dynamic memory allocation +* incremental single-pass parsing +* library code is covered with unit-tests + +Design +------ + +The rudimentary jsmn object is a **token**. Let's consider a JSON string: + + '{ "name" : "Jack", "age" : 27 }' + +It holds the following tokens: + +* Object: `{ "name" : "Jack", "age" : 27}` (the whole object) +* Strings: `"name"`, `"Jack"`, `"age"` (keys and some values) +* Number: `27` + +In jsmn, tokens do not hold any data, but point to token boundaries in JSON +string instead. In the example above jsmn will create tokens like: Object +[0..31], String [3..7], String [12..16], String [20..23], Number [27..29]. + +Every jsmn token has a type, which indicates the type of corresponding JSON +token. jsmn supports the following token types: + +* Object - a container of key-value pairs, e.g.: + `{ "foo":"bar", "x":0.3 }` +* Array - a sequence of values, e.g.: + `[ 1, 2, 3 ]` +* String - a quoted sequence of chars, e.g.: `"foo"` +* Primitive - a number, a boolean (`true`, `false`) or `null` + +Besides start/end positions, jsmn tokens for complex types (like arrays +or objects) also contain a number of child items, so you can easily follow +object hierarchy. + +This approach provides enough information for parsing any JSON data and makes +it possible to use zero-copy techniques. + +Install +------- + +To clone the repository you should have Git installed. Just run: + + $ git clone https://github.com/zserge/jsmn + +Repository layout is simple: jsmn.c and jsmn.h are library files, tests are in +the jsmn\_test.c, you will also find README, LICENSE and Makefile files inside. + +To build the library, run `make`. It is also recommended to run `make test`. +Let me know, if some tests fail. + +If build was successful, you should get a `libjsmn.a` library. +The header file you should include is called `"jsmn.h"`. + +API +--- + +Token types are described by `jsmntype_t`: + + typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 + } jsmntype_t; + +**Note:** Unlike JSON data types, primitive tokens are not divided into +numbers, booleans and null, because one can easily tell the type using the +first character: + +* 't', 'f' - boolean +* 'n' - null +* '-', '0'..'9' - number + +Token is an object of `jsmntok_t` type: + + typedef struct { + jsmntype_t type; // Token type + int start; // Token start position + int end; // Token end position + int size; // Number of child (nested) tokens + } jsmntok_t; + +**Note:** string tokens point to the first character after +the opening quote and the previous symbol before final quote. This was made +to simplify string extraction from JSON data. + +All job is done by `jsmn_parser` object. You can initialize a new parser using: + + jsmn_parser parser; + jsmntok_t tokens[10]; + + jsmn_init(&parser); + + // js - pointer to JSON string + // tokens - an array of tokens available + // 10 - number of tokens available + jsmn_parse(&parser, js, strlen(js), tokens, 10); + +This will create a parser, and then it tries to parse up to 10 JSON tokens from +the `js` string. + +A non-negative return value of `jsmn_parse` is the number of tokens actually +used by the parser. +Passing NULL instead of the tokens array would not store parsing results, but +instead the function will return the value of tokens needed to parse the given +string. This can be useful if you don't know yet how many tokens to allocate. + +If something goes wrong, you will get an error. Error will be one of these: + +* `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted +* `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large +* `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data + +If you get `JSON_ERROR_NOMEM`, you can re-allocate more tokens and call +`jsmn_parse` once more. If you read json data from the stream, you can +periodically call `jsmn_parse` and check if return value is `JSON_ERROR_PART`. +You will get this error until you reach the end of JSON data. + +Other info +---------- + +This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php), + so feel free to integrate it in your commercial products. + +[1]: http://www.json.org/ +[2]: http://zserge.com/jsmn.html diff --git a/third_party/jsmn/example/jsondump.c b/third_party/jsmn/example/jsondump.c new file mode 100644 index 00000000..cf08c5ca --- /dev/null +++ b/third_party/jsmn/example/jsondump.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include "../jsmn.h" + +/* Function realloc_it() is a wrapper function for standart realloc() + * with one difference - it frees old memory pointer in case of realloc + * failure. Thus, DO NOT use old data pointer in anyway after call to + * realloc_it(). If your code has some kind of fallback algorithm if + * memory can't be re-allocated - use standart realloc() instead. + */ +static inline void *realloc_it(void *ptrmem, size_t size) { + void *p = realloc(ptrmem, size); + if (!p) { + free (ptrmem); + fprintf(stderr, "realloc(): errno=%d\n", errno); + } + return p; +} + +/* + * An example of reading JSON from stdin and printing its content to stdout. + * The output looks like YAML, but I'm not sure if it's really compatible. + */ + +static int dump(const char *js, jsmntok_t *t, size_t count, int indent) { + int i, j, k; + if (count == 0) { + return 0; + } + if (t->type == JSMN_PRIMITIVE) { + printf("%.*s", t->end - t->start, js+t->start); + return 1; + } else if (t->type == JSMN_STRING) { + printf("'%.*s'", t->end - t->start, js+t->start); + return 1; + } else if (t->type == JSMN_OBJECT) { + printf("\n"); + j = 0; + for (i = 0; i < t->size; i++) { + for (k = 0; k < indent; k++) printf(" "); + j += dump(js, t+1+j, count-j, indent+1); + printf(": "); + j += dump(js, t+1+j, count-j, indent+1); + printf("\n"); + } + return j+1; + } else if (t->type == JSMN_ARRAY) { + j = 0; + printf("\n"); + for (i = 0; i < t->size; i++) { + for (k = 0; k < indent-1; k++) printf(" "); + printf(" - "); + j += dump(js, t+1+j, count-j, indent+1); + printf("\n"); + } + return j+1; + } + return 0; +} + +int main() { + int r; + int eof_expected = 0; + char *js = NULL; + size_t jslen = 0; + char buf[BUFSIZ]; + + jsmn_parser p; + jsmntok_t *tok; + size_t tokcount = 2; + + /* Prepare parser */ + jsmn_init(&p); + + /* Allocate some tokens as a start */ + tok = malloc(sizeof(*tok) * tokcount); + if (tok == NULL) { + fprintf(stderr, "malloc(): errno=%d\n", errno); + return 3; + } + + for (;;) { + /* Read another chunk */ + r = fread(buf, 1, sizeof(buf), stdin); + if (r < 0) { + fprintf(stderr, "fread(): %d, errno=%d\n", r, errno); + return 1; + } + if (r == 0) { + if (eof_expected != 0) { + return 0; + } else { + fprintf(stderr, "fread(): unexpected EOF\n"); + return 2; + } + } + + js = realloc_it(js, jslen + r + 1); + if (js == NULL) { + return 3; + } + strncpy(js + jslen, buf, r); + jslen = jslen + r; + +again: + r = jsmn_parse(&p, js, jslen, tok, tokcount); + if (r < 0) { + if (r == JSMN_ERROR_NOMEM) { + tokcount = tokcount * 2; + tok = realloc_it(tok, sizeof(*tok) * tokcount); + if (tok == NULL) { + return 3; + } + goto again; + } + } else { + dump(js, tok, p.toknext, 0); + eof_expected = 1; + } + } + + return 0; +} diff --git a/third_party/jsmn/example/simple.c b/third_party/jsmn/example/simple.c new file mode 100644 index 00000000..de448838 --- /dev/null +++ b/third_party/jsmn/example/simple.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include "../jsmn.h" + +/* + * A small example of jsmn parsing when JSON structure is known and number of + * tokens is predictable. + */ + +const char *JSON_STRING = + "{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n " + "\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}"; + +static int jsoneq(const char *json, jsmntok_t *tok, const char *s) { + if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start && + strncmp(json + tok->start, s, tok->end - tok->start) == 0) { + return 0; + } + return -1; +} + +int main() { + int i; + int r; + jsmn_parser p; + jsmntok_t t[128]; /* We expect no more than 128 tokens */ + + jsmn_init(&p); + r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t, sizeof(t)/sizeof(t[0])); + if (r < 0) { + printf("Failed to parse JSON: %d\n", r); + return 1; + } + + /* Assume the top-level element is an object */ + if (r < 1 || t[0].type != JSMN_OBJECT) { + printf("Object expected\n"); + return 1; + } + + /* Loop over all keys of the root object */ + for (i = 1; i < r; i++) { + if (jsoneq(JSON_STRING, &t[i], "user") == 0) { + /* We may use strndup() to fetch string value */ + printf("- User: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "admin") == 0) { + /* We may additionally check if the value is either "true" or "false" */ + printf("- Admin: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "uid") == 0) { + /* We may want to do strtol() here to get numeric value */ + printf("- UID: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "groups") == 0) { + int j; + printf("- Groups:\n"); + if (t[i+1].type != JSMN_ARRAY) { + continue; /* We expect groups to be an array of strings */ + } + for (j = 0; j < t[i+1].size; j++) { + jsmntok_t *g = &t[i+j+2]; + printf(" * %.*s\n", g->end - g->start, JSON_STRING + g->start); + } + i += t[i+1].size + 1; + } else { + printf("Unexpected key: %.*s\n", t[i].end-t[i].start, + JSON_STRING + t[i].start); + } + } + return 0; +} diff --git a/third_party/jsmn/jsmn.c b/third_party/jsmn/jsmn.c new file mode 100644 index 00000000..e7765eb1 --- /dev/null +++ b/third_party/jsmn/jsmn.c @@ -0,0 +1,311 @@ +#include "jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + diff --git a/third_party/jsmn/jsmn.h b/third_party/jsmn/jsmn.h new file mode 100644 index 00000000..01ca99c8 --- /dev/null +++ b/third_party/jsmn/jsmn.h @@ -0,0 +1,76 @@ +#ifndef __JSMN_H_ +#define __JSMN_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; + +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; + +/** + * JSON token description. + * @param type type (object, array, string etc.) + * @param start start position in JSON data string + * @param end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * a single JSON object. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens); + +#ifdef __cplusplus +} +#endif + +#endif /* __JSMN_H_ */ diff --git a/third_party/jsmn/library.json b/third_party/jsmn/library.json new file mode 100644 index 00000000..8e2f5c25 --- /dev/null +++ b/third_party/jsmn/library.json @@ -0,0 +1,16 @@ +{ + "name": "jsmn", + "keywords": "json", + "description": "Minimalistic JSON parser/tokenizer in C. It can be easily integrated into resource-limited or embedded projects", + "repository": + { + "type": "git", + "url": "https://github.com/zserge/jsmn.git" + }, + "frameworks": "*", + "platforms": "*", + "examples": [ + "example/*.c" + ], + "exclude": "test" +} diff --git a/third_party/jsmn/test/test.h b/third_party/jsmn/test/test.h new file mode 100644 index 00000000..35f704f9 --- /dev/null +++ b/third_party/jsmn/test/test.h @@ -0,0 +1,27 @@ +#ifndef __TEST_H__ +#define __TEST_H__ + +static int test_passed = 0; +static int test_failed = 0; + +/* Terminate current test with error */ +#define fail() return __LINE__ + +/* Successful end of the test case */ +#define done() return 0 + +/* Check single condition */ +#define check(cond) do { if (!(cond)) fail(); } while (0) + +/* Test runner */ +static void test(int (*func)(void), const char *name) { + int r = func(); + if (r == 0) { + test_passed++; + } else { + test_failed++; + printf("FAILED: %s (at line %d)\n", name, r); + } +} + +#endif /* __TEST_H__ */ diff --git a/third_party/jsmn/test/tests.c b/third_party/jsmn/test/tests.c new file mode 100644 index 00000000..a72689ec --- /dev/null +++ b/third_party/jsmn/test/tests.c @@ -0,0 +1,378 @@ +#include +#include +#include +#include + +#include "test.h" +#include "testutil.h" + +int test_empty(void) { + check(parse("{}", 1, 1, + JSMN_OBJECT, 0, 2, 0)); + check(parse("[]", 1, 1, + JSMN_ARRAY, 0, 2, 0)); + check(parse("[{},{}]", 3, 3, + JSMN_ARRAY, 0, 7, 2, + JSMN_OBJECT, 1, 3, 0, + JSMN_OBJECT, 4, 6, 0)); + return 0; +} + +int test_object(void) { + check(parse("{\"a\":0}", 3, 3, + JSMN_OBJECT, 0, 7, 1, + JSMN_STRING, "a", 1, + JSMN_PRIMITIVE, "0")); + check(parse("{\"a\":[]}", 3, 3, + JSMN_OBJECT, 0, 8, 1, + JSMN_STRING, "a", 1, + JSMN_ARRAY, 5, 7, 0)); + check(parse("{\"a\":{},\"b\":{}}", 5, 5, + JSMN_OBJECT, -1, -1, 2, + JSMN_STRING, "a", 1, + JSMN_OBJECT, -1, -1, 0, + JSMN_STRING, "b", 1, + JSMN_OBJECT, -1, -1, 0)); + check(parse("{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }", 7, 7, + JSMN_OBJECT, -1, -1, 3, + JSMN_STRING, "Day", 1, + JSMN_PRIMITIVE, "26", + JSMN_STRING, "Month", 1, + JSMN_PRIMITIVE, "9", + JSMN_STRING, "Year", 1, + JSMN_PRIMITIVE, "12")); + check(parse("{\"a\": 0, \"b\": \"c\"}", 5, 5, + JSMN_OBJECT, -1, -1, 2, + JSMN_STRING, "a", 1, + JSMN_PRIMITIVE, "0", + JSMN_STRING, "b", 1, + JSMN_STRING, "c", 0)); + +#ifdef JSMN_STRICT + check(parse("{\"a\"\n0}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\", 0}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5)); + /* FIXME */ + /*check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2));*/ + /*check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{,}", JSMN_ERROR_INVAL, 4));*/ +#endif + return 0; +} + +int test_array(void) { + /* FIXME */ + /*check(parse("[10}", JSMN_ERROR_INVAL, 3));*/ + /*check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)*/ + check(parse("[10]", 2, 2, + JSMN_ARRAY, -1, -1, 1, + JSMN_PRIMITIVE, "10")); + check(parse("{\"a\": 1]", JSMN_ERROR_INVAL, 3)); + /* FIXME */ + /*check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3));*/ + return 0; +} + +int test_primitive(void) { + check(parse("{\"boolVar\" : true }", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "boolVar", 1, + JSMN_PRIMITIVE, "true")); + check(parse("{\"boolVar\" : false }", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "boolVar", 1, + JSMN_PRIMITIVE, "false")); + check(parse("{\"nullVar\" : null }", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "nullVar", 1, + JSMN_PRIMITIVE, "null")); + check(parse("{\"intVar\" : 12}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "intVar", 1, + JSMN_PRIMITIVE, "12")); + check(parse("{\"floatVar\" : 12.345}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, + JSMN_PRIMITIVE, "12.345")); + return 0; +} + +int test_string(void) { + check(parse("{\"strVar\" : \"hello world\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "strVar", 1, + JSMN_STRING, "hello world", 0)); + check(parse("{\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "strVar", 1, + JSMN_STRING, "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\", 0)); + check(parse("{\"strVar\": \"\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "strVar", 1, + JSMN_STRING, "", 0)); + check(parse("{\"a\":\"\\uAbcD\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_STRING, "\\uAbcD", 0)); + check(parse("{\"a\":\"str\\u0000\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_STRING, "str\\u0000", 0)); + check(parse("{\"a\":\"\\uFFFFstr\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_STRING, "\\uFFFFstr", 0)); + check(parse("{\"a\":[\"\\u0280\"]}", 4, 4, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_ARRAY, -1, -1, 1, + JSMN_STRING, "\\u0280", 0)); + + check(parse("{\"a\":\"str\\uFFGFstr\"}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\":\"str\\u@FfF\"}", JSMN_ERROR_INVAL, 3)); + check(parse("{{\"a\":[\"\\u028\"]}", JSMN_ERROR_INVAL, 4)); + return 0; +} + +int test_partial_string(void) { + int i; + int r; + jsmn_parser p; + jsmntok_t tok[5]; + const char *js = "{\"x\": \"va\\\\ue\", \"y\": \"value y\"}"; + + jsmn_init(&p); + for (i = 1; i <= strlen(js); i++) { + r = jsmn_parse(&p, js, i, tok, sizeof(tok)/sizeof(tok[0])); + if (i == strlen(js)) { + check(r == 5); + check(tokeq(js, tok, 5, + JSMN_OBJECT, -1, -1, 2, + JSMN_STRING, "x", 1, + JSMN_STRING, "va\\\\ue", 0, + JSMN_STRING, "y", 1, + JSMN_STRING, "value y", 0)); + } else { + check(r == JSMN_ERROR_PART); + } + } + return 0; +} + +int test_partial_array(void) { +#ifdef JSMN_STRICT + int r; + int i; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js = "[ 1, true, [123, \"hello\"]]"; + + jsmn_init(&p); + for (i = 1; i <= strlen(js); i++) { + r = jsmn_parse(&p, js, i, tok, sizeof(tok)/sizeof(tok[0])); + if (i == strlen(js)) { + check(r == 6); + check(tokeq(js, tok, 6, + JSMN_ARRAY, -1, -1, 3, + JSMN_PRIMITIVE, "1", + JSMN_PRIMITIVE, "true", + JSMN_ARRAY, -1, -1, 2, + JSMN_PRIMITIVE, "123", + JSMN_STRING, "hello", 0)); + } else { + check(r == JSMN_ERROR_PART); + } + } +#endif + return 0; +} + +int test_array_nomem(void) { + int i; + int r; + jsmn_parser p; + jsmntok_t toksmall[10], toklarge[10]; + const char *js; + + js = " [ 1, true, [123, \"hello\"]]"; + + for (i = 0; i < 6; i++) { + jsmn_init(&p); + memset(toksmall, 0, sizeof(toksmall)); + memset(toklarge, 0, sizeof(toklarge)); + r = jsmn_parse(&p, js, strlen(js), toksmall, i); + check(r == JSMN_ERROR_NOMEM); + + memcpy(toklarge, toksmall, sizeof(toksmall)); + + r = jsmn_parse(&p, js, strlen(js), toklarge, 10); + check(r >= 0); + check(tokeq(js, toklarge, 4, + JSMN_ARRAY, -1, -1, 3, + JSMN_PRIMITIVE, "1", + JSMN_PRIMITIVE, "true", + JSMN_ARRAY, -1, -1, 2, + JSMN_PRIMITIVE, "123", + JSMN_STRING, "hello", 0)); + } + return 0; +} + +int test_unquoted_keys(void) { +#ifndef JSMN_STRICT + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + + jsmn_init(&p); + js = "key1: \"value\"\nkey2 : 123"; + + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0); + check(tokeq(js, tok, 4, + JSMN_PRIMITIVE, "key1", + JSMN_STRING, "value", 0, + JSMN_PRIMITIVE, "key2", + JSMN_PRIMITIVE, "123")); +#endif + return 0; +} + +int test_issue_22(void) { + int r; + jsmn_parser p; + jsmntok_t tokens[128]; + const char *js; + + js = "{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, " + "\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", " + "\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], " + "\"orientation\":\"orthogonal\", \"properties\": { }, \"tileheight\":32, " + "\"tilesets\":[ { \"firstgid\":1, \"image\":\"..\\/images\\/tiles.png\", " + "\"imageheight\":64, \"imagewidth\":160, \"margin\":0, \"name\":\"Tiles\", " + "\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 }], " + "\"tilewidth\":32, \"version\":1, \"width\":10 }"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 128); + check(r >= 0); + return 0; +} + +int test_issue_27(void) { + const char *js = + "{ \"name\" : \"Jack\", \"age\" : 27 } { \"name\" : \"Anna\", "; + check(parse(js, JSMN_ERROR_PART, 8)); + return 0; +} + +int test_input_length(void) { + const char *js; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + + js = "{\"a\": 0}garbage"; + + jsmn_init(&p); + r = jsmn_parse(&p, js, 8, tokens, 10); + check(r == 3); + check(tokeq(js, tokens, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_PRIMITIVE, "0")); + return 0; +} + +int test_count(void) { + jsmn_parser p; + const char *js; + + js = "{}"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + + js = "[]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + + js = "[[]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2); + + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + + js = "[[], [[]], [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + + js = "[\"a\", [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + + js = "[[], \"[], [[]]\", [[]]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + + js = "[1, 2, 3]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4); + + js = "[1, 2, [3, \"a\"], null]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + + return 0; +} + + +int test_nonstrict(void) { +#ifndef JSMN_STRICT + const char *js; + js = "a: 0garbage"; + check(parse(js, 2, 2, + JSMN_PRIMITIVE, "a", + JSMN_PRIMITIVE, "0garbage")); + + js = "Day : 26\nMonth : Sep\n\nYear: 12"; + check(parse(js, 6, 6, + JSMN_PRIMITIVE, "Day", + JSMN_PRIMITIVE, "26", + JSMN_PRIMITIVE, "Month", + JSMN_PRIMITIVE, "Sep", + JSMN_PRIMITIVE, "Year", + JSMN_PRIMITIVE, "12")); +#endif + return 0; +} + +int main(void) { + test(test_empty, "test for a empty JSON objects/arrays"); + test(test_object, "test for a JSON objects"); + test(test_array, "test for a JSON arrays"); + test(test_primitive, "test primitive JSON data types"); + test(test_string, "test string JSON data types"); + + test(test_partial_string, "test partial JSON string parsing"); + test(test_partial_array, "test partial array reading"); + test(test_array_nomem, "test array reading with a smaller number of tokens"); + test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); + test(test_input_length, "test strings that are not null-terminated"); + test(test_issue_22, "test issue #22"); + test(test_issue_27, "test issue #27"); + test(test_count, "test tokens count estimation"); + test(test_nonstrict, "test for non-strict mode"); + printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); + return (test_failed > 0); +} diff --git a/third_party/jsmn/test/testutil.h b/third_party/jsmn/test/testutil.h new file mode 100644 index 00000000..9a1eb2d6 --- /dev/null +++ b/third_party/jsmn/test/testutil.h @@ -0,0 +1,94 @@ +#ifndef __TEST_UTIL_H__ +#define __TEST_UTIL_H__ + +#include "../jsmn.c" + +static int vtokeq(const char *s, jsmntok_t *t, int numtok, va_list ap) { + if (numtok > 0) { + int i, start, end, size; + int type; + char *value; + + size = -1; + value = NULL; + for (i = 0; i < numtok; i++) { + type = va_arg(ap, int); + if (type == JSMN_STRING) { + value = va_arg(ap, char *); + size = va_arg(ap, int); + start = end = -1; + } else if (type == JSMN_PRIMITIVE) { + value = va_arg(ap, char *); + start = end = size = -1; + } else { + start = va_arg(ap, int); + end = va_arg(ap, int); + size = va_arg(ap, int); + value = NULL; + } + if (t[i].type != type) { + printf("token %d type is %d, not %d\n", i, t[i].type, type); + return 0; + } + if (start != -1 && end != -1) { + if (t[i].start != start) { + printf("token %d start is %d, not %d\n", i, t[i].start, start); + return 0; + } + if (t[i].end != end ) { + printf("token %d end is %d, not %d\n", i, t[i].end, end); + return 0; + } + } + if (size != -1 && t[i].size != size) { + printf("token %d size is %d, not %d\n", i, t[i].size, size); + return 0; + } + + if (s != NULL && value != NULL) { + const char *p = s + t[i].start; + if (strlen(value) != t[i].end - t[i].start || + strncmp(p, value, t[i].end - t[i].start) != 0) { + printf("token %d value is %.*s, not %s\n", i, t[i].end-t[i].start, + s+t[i].start, value); + return 0; + } + } + } + } + return 1; +} + +static int tokeq(const char *s, jsmntok_t *tokens, int numtok, ...) { + int ok; + va_list args; + va_start(args, numtok); + ok = vtokeq(s, tokens, numtok, args); + va_end(args); + return ok; +} + +static int parse(const char *s, int status, int numtok, ...) { + int r; + int ok = 1; + va_list args; + jsmn_parser p; + jsmntok_t *t = malloc(numtok * sizeof(jsmntok_t)); + + jsmn_init(&p); + r = jsmn_parse(&p, s, strlen(s), t, numtok); + if (r != status) { + printf("status is %d, not %d\n", r, status); + return 0; + } + + if (status >= 0) { + va_start(args, numtok); + ok = vtokeq(s, t, numtok, args); + va_end(args); + } + free(t); + return ok; +} + +#endif /* __TEST_UTIL_H__ */ diff --git a/third_party/protobuf/gtest/libtool b/third_party/protobuf/gtest/libtool index 1471747b..889db512 100755 --- a/third_party/protobuf/gtest/libtool +++ b/third_party/protobuf/gtest/libtool @@ -2,7 +2,7 @@ # libtool - Provide generalized library-building support services. # Generated automatically by config.status (gtest) 1.6.0 -# Libtool was configured on host horcrux.kir.corp.google.com: +# Libtool was configured on host gmorgan-linux1.kir.corp.google.com: # NOTE: Changes made to this file will be lost: look at ltmain.sh. # # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, diff --git a/third_party/protobuf/libtool b/third_party/protobuf/libtool index df4b2405..9e456720 100755 --- a/third_party/protobuf/libtool +++ b/third_party/protobuf/libtool @@ -2,7 +2,7 @@ # libtool - Provide generalized library-building support services. # Generated automatically by config.status (protobuf) 2.5.0 -# Libtool was configured on host horcrux.kir.corp.google.com: +# Libtool was configured on host gmorgan-linux1.kir.corp.google.com: # NOTE: Changes made to this file will be lost: look at ltmain.sh. # # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, diff --git a/third_party/stringencoders/src/modp_b64.cpp b/third_party/stringencoders/src/modp_b64.cpp new file mode 100644 index 00000000..436143ea --- /dev/null +++ b/third_party/stringencoders/src/modp_b64.cpp @@ -0,0 +1,269 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ +/* vi: set expandtab shiftwidth=4 tabstop=4: */ +/** + * \file modp_b64.c + *
+ * MODP_B64 - High performance base64 encoder/decoder
+ * http://code.google.com/p/stringencoders/
+ *
+ * Copyright © 2005, 2006, 2007  Nick Galbreath -- nickg [at] modp [dot] com
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ *   Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ *   Neither the name of the modp.com nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This is the standard "new" BSD license:
+ * http://www.opensource.org/licenses/bsd-license.php
+ * 
+ */ + +/* public header */ +#include "modp_b64.h" + +/* + * If you are ripping this out of the library, comment out the next + * line and uncomment the next lines as approrpiate + */ +#include "config.h" + +/* if on motoral, sun, ibm; uncomment this */ +/* #define WORDS_BIGENDIAN 1 */ +/* else for Intel, Amd; uncomment this */ +/* #undef WORDS_BIGENDIAN */ + +#include "modp_b64_data.h" + +#define BADCHAR 0x01FFFFFF + +/** + * you can control if we use padding by commenting out this + * next line. However, I highly recommend you use padding and not + * using it should only be for compatability with a 3rd party. + * Also, 'no padding' is not tested! + */ +#define DOPAD 1 + +/* + * if we aren't doing padding + * set the pad character to NULL + */ +#ifndef DOPAD +#undef CHARPAD +#define CHARPAD '\0' +#endif + +int modp_b64_encode(char* dest, const char* str, int len) +{ + int i; + const uint8_t* s = (const uint8_t*) str; + uint8_t* p = (uint8_t*) dest; + + /* unsigned here is important! */ + /* uint8_t is fastest on G4, amd */ + /* uint32_t is fastest on Intel */ + uint32_t t1, t2, t3; + + for (i = 0; i < len - 2; i += 3) { + t1 = s[i]; t2 = s[i+1]; t3 = s[i+2]; + *p++ = e0[t1]; + *p++ = e1[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)]; + *p++ = e1[((t2 & 0x0F) << 2) | ((t3 >> 6) & 0x03)]; + *p++ = e2[t3]; + } + + switch (len - i) { + case 0: + break; + case 1: + t1 = s[i]; + *p++ = e0[t1]; + *p++ = e1[(t1 & 0x03) << 4]; + *p++ = CHARPAD; + *p++ = CHARPAD; + break; + default: /* case 2 */ + t1 = s[i]; t2 = s[i+1]; + *p++ = e0[t1]; + *p++ = e1[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)]; + *p++ = e2[(t2 & 0x0F) << 2]; + *p++ = CHARPAD; + } + + *p = '\0'; + return (int)(p - (uint8_t*)dest); +} + +#ifdef WORDS_BIGENDIAN /* BIG ENDIAN -- SUN / IBM / MOTOROLA */ +int modp_b64_decode(char* dest, const char* src, int len) +{ + int i; + if (len == 0) return 0; + +#ifdef DOPAD + /* if padding is used, then the message must be at least + 4 chars and be a multiple of 4. + there can be at most 2 pad chars at the end */ + if (len < 4 || (len % 4 != 0)) return -1; + if (src[len-1] == CHARPAD) { + len--; + if (src[len -1] == CHARPAD) { + len--; + } + } +#endif /* DOPAD */ + + int leftover = len % 4; + int chunks = (leftover == 0) ? len / 4 - 1 : len /4; + + uint8_t* p = (uint8_t*) dest; + uint32_t x = 0; + uint32_t* destInt = (uint32_t*) p; + uint32_t* srcInt = (uint32_t*) src; + uint32_t y = *srcInt++; + for (i = 0; i < chunks; ++i) { + x = d0[y >> 24 & 0xff] | d1[y >> 16 & 0xff] | + d2[y >> 8 & 0xff] | d3[y & 0xff]; + + if (x >= BADCHAR) return -1; + *destInt = x << 8; + p += 3; + destInt = (uint32_t*)p; + y = *srcInt++; + } + + switch (leftover) { + case 0: + x = d0[y >> 24 & 0xff] | d1[y >> 16 & 0xff] | + d2[y >> 8 & 0xff] | d3[y & 0xff]; + if (x >= BADCHAR) return -1; + *p++ = ((uint8_t*)&x)[1]; + *p++ = ((uint8_t*)&x)[2]; + *p = ((uint8_t*)&x)[3]; + return (chunks+1)*3; +#ifndef DOPAD + case 1: /* with padding this is an impossible case */ + x = d3[y >> 24]; + *p = (uint8_t)x; + break; +#endif + case 2: + x = d3[y >> 24] *64 + d3[(y >> 16) & 0xff]; + *p = (uint8_t)(x >> 4); + break; + default: /* case 3 */ + x = (d3[y >> 24] *64 + d3[(y >> 16) & 0xff])*64 + + d3[(y >> 8) & 0xff]; + *p++ = (uint8_t) (x >> 10); + *p = (uint8_t) (x >> 2); + break; + } + + if (x >= BADCHAR) return -1; + return 3*chunks + (6*leftover)/8; +} + +#else /* LITTLE ENDIAN -- INTEL AND FRIENDS */ + +int modp_b64_decode(char* dest, const char* src, int len) +{ + int i; + if (len == 0) return 0; + +#ifdef DOPAD + /* + * if padding is used, then the message must be at least + * 4 chars and be a multiple of 4 + */ + if (len < 4 || (len % 4 != 0)) return -1; /* error */ + /* there can be at most 2 pad chars at the end */ + if (src[len-1] == CHARPAD) { + len--; + if (src[len -1] == CHARPAD) { + len--; + } + } +#endif + + int leftover = len % 4; + int chunks = (leftover == 0) ? len / 4 - 1 : len /4; + + uint8_t* p = (uint8_t*) dest; + uint32_t x = 0; + uint32_t* destInt = (uint32_t*) p; + uint32_t* srcInt = (uint32_t*) src; + uint32_t y = *srcInt++; + for (i = 0; i < chunks; ++i) { + x = d0[y & 0xff] | + d1[(y >> 8) & 0xff] | + d2[(y >> 16) & 0xff] | + d3[(y >> 24) & 0xff]; + + if (x >= BADCHAR) return -1; + *destInt = x ; + p += 3; + destInt = (uint32_t*)p; + y = *srcInt++;} + + + switch (leftover) { + case 0: + x = d0[y & 0xff] | + d1[(y >> 8) & 0xff] | + d2[(y >> 16) & 0xff] | + d3[(y >> 24) & 0xff]; + + if (x >= BADCHAR) return -1; + *p++ = ((uint8_t*)(&x))[0]; + *p++ = ((uint8_t*)(&x))[1]; + *p = ((uint8_t*)(&x))[2]; + return (chunks+1)*3; + break; +#ifndef DOPAD + case 1: /* with padding this is an impossible case */ + x = d0[y & 0xff]; + *p = *((uint8_t*)(&x)); // i.e. first char/byte in int + break; +#endif + case 2: // * case 2, 1 output byte */ + x = d0[y & 0xff] | d1[y >> 8 & 0xff]; + *p = *((uint8_t*)(&x)); // i.e. first char + break; + default: /* case 3, 2 output bytes */ + x = d0[y & 0xff] | + d1[y >> 8 & 0xff ] | + d2[y >> 16 & 0xff]; /* 0x3c */ + *p++ = ((uint8_t*)(&x))[0]; + *p = ((uint8_t*)(&x))[1]; + break; + } + + if (x >= BADCHAR) return -1; + + return 3*chunks + (6*leftover)/8; +} + +#endif /* if bigendian / else / endif */ diff --git a/third_party/stringencoders/src/modp_b64.h b/third_party/stringencoders/src/modp_b64.h new file mode 100644 index 00000000..3256af7a --- /dev/null +++ b/third_party/stringencoders/src/modp_b64.h @@ -0,0 +1,234 @@ +/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ +/* vi: set expandtab shiftwidth=4 tabstop=4: */ + +/** + * \file + *
+ * High performance base64 encoder / decoder
+ *
+ * Copyright © 2005, 2006, 2007 Nick Galbreath -- nickg [at] modp [dot] com
+ * All rights reserved.
+ *
+ * http://code.google.com/p/stringencoders/
+ *
+ * Released under bsd license.  See modp_b64.c for details.
+ * 
+ * + * This uses the standard base 64 alphabet. If you are planning + * to embed a base 64 encoding inside a URL use modp_b64w instead. + * + */ + +#ifndef COM_MODP_STRINGENCODERS_B64 +#define COM_MODP_STRINGENCODERS_B64 + +#ifdef __cplusplus +#define BEGIN_C extern "C" { +#define END_C } +#else +#define BEGIN_C +#define END_C +#endif + +BEGIN_C + +/** + * Encode a raw binary string into base 64. + * \param[out] dest should be allocated by the caller to contain + * at least modp_b64_encode_len(len) bytes (see below) + * This will contain the null-terminated b64 encoded result + * \param[in] src contains the bytes + * \param[in] len contains the number of bytes in the src + * \return length of the destination string plus the ending null byte + * i.e. the result will be equal to strlen(dest) + 1 + * + * Example + * + * \code + * char* src = ...; + * int srclen = ...; //the length of number of bytes in src + * char* dest = (char*) malloc(modp_b64_encode_len); + * int len = modp_b64_encode(dest, src, sourcelen); + * if (len == -1) { + * printf("Error\n"); + * } else { + * printf("b64 = %s\n", dest); + * } + * \endcode + * + */ +int modp_b64_encode(char* dest, const char* str, int len); + +/** + * Decode a base64 encoded string + * + * \param[out] dest should be allocated by the caller to contain at least + * len * 3 / 4 bytes. The destination cannot be the same as the source + * They must be different buffers. + * \param[in] src should contain exactly len bytes of b64 characters. + * if src contains -any- non-base characters (such as white + * space, -1 is returned. + * \param[in] len is the length of src + * + * \return the length (strlen) of the output, or -1 if unable to + * decode + * + * \code + * char* src = ...; + * int srclen = ...; // or if you don't know use strlen(src) + * char* dest = (char*) malloc(modp_b64_decode_len(srclen)); + * int len = modp_b64_decode(dest, src, sourcelen); + * if (len == -1) { error } + * \endcode + */ +int modp_b64_decode(char* dest, const char* src, int len); + +/** + * Given a source string of length len, this returns the amount of + * memory the destination string should have. + * + * remember, this is integer math + * 3 bytes turn into 4 chars + * ceiling[len / 3] * 4 + 1 + * + * +1 is for any extra null. + */ +#define modp_b64_encode_len(A) ((A+2)/3 * 4 + 1) + +/** + * Given a base64 string of length len, + * this returns the amount of memory required for output string + * It maybe be more than the actual number of bytes written. + * NOTE: remember this is integer math + * this allocates a bit more memory than traditional versions of b64 + * decode 4 chars turn into 3 bytes + * floor[len * 3/4] + 2 + */ +#define modp_b64_decode_len(A) (A / 4 * 3 + 2) + +/** + * Will return the strlen of the output from encoding. + * This may be less than the required number of bytes allocated. + * + * This allows you to 'deserialized' a struct + * \code + * char* b64encoded = "..."; + * int len = strlen(b64encoded); + * + * struct datastuff foo; + * if (modp_b64_encode_strlen(sizeof(struct datastuff)) != len) { + * // wrong size + * return false; + * } else { + * // safe to do; + * if (modp_b64_decode((char*) &foo, b64encoded, len) == -1) { + * // bad characters + * return false; + * } + * } + * // foo is filled out now + * \endcode + */ +#define modp_b64_encode_strlen(A) ((A + 2)/ 3 * 4) + +END_C + +#ifdef __cplusplus +#include +#include + +namespace modp { + /** \brief b64 encode a cstr with len + * + * \param[in] s the input string to encode + * \param[in] len the length of the input string + * \return a newly allocated b64 string. Empty if failed. + */ + inline std::string b64_encode(const char* s, size_t len) + { + std::string x(modp_b64_encode_len(len), '\0'); + int d = modp_b64_encode(const_cast(x.data()), s, + static_cast(len)); + x.erase(d, std::string::npos); + return x; + } + + /** \brief b64 encode a cstr + * + * \param[in] s the input string to encode + * \return a newly allocated b64 string. Empty if failed. + */ + inline std::string b64_encode(const char* s) + { + return b64_encode(s, static_cast(strlen(s))); + } + + /** \brief b64 encode a const std::string + * + * \param[in] s the input string to encode + * \return a newly allocated b64 string. Empty if failed. + */ + inline std::string b64_encode(const std::string& s) + { + return b64_encode(s.data(), s.size()); + } + + /** + * base 64 encode a string (self-modifing) + * + * This function is for C++ only (duh) + * + * \param[in,out] s the string to be decoded + * \return a reference to the input string + */ + inline std::string& b64_encode(std::string& s) + { + std::string x(b64_encode(s.data(), s.size())); + s.swap(x); + return s; + } + + inline std::string b64_decode(const char* src, size_t len) + { + std::string x(modp_b64_decode_len(len)+1, '\0'); + int d = modp_b64_decode(const_cast(x.data()), src, + static_cast(len)); + if (d < 0) { + x.clear(); + } else { + x.erase(d, std::string::npos); + } + return x; + } + + inline std::string b64_decode(const char* src) + { + return b64_decode(src, strlen(src)); + } + + /** + * base 64 decode a string (self-modifing) + * On failure, the string is empty. + * + * This function is for C++ only (duh) + * + * \param[in,out] s the string to be decoded + * \return a reference to the input string + */ + inline std::string& b64_decode(std::string& s) + { + std::string x(b64_decode(s.data(), s.size())); + s.swap(x); + return s; + } + + inline std::string b64_decode(const std::string& s) + { + return b64_decode(s.data(), s.size()); + } + +} + +#endif /* __cplusplus */ + +#endif /* MODP_B64 */ diff --git a/third_party/stringencoders/src/modp_b64_data.h b/third_party/stringencoders/src/modp_b64_data.h new file mode 100644 index 00000000..4fb321c5 --- /dev/null +++ b/third_party/stringencoders/src/modp_b64_data.h @@ -0,0 +1,480 @@ +#include +#define CHAR62 '+' +#define CHAR63 '/' +#define CHARPAD '=' +static const unsigned char e0[256] = { + 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'C', + 'C', 'C', 'D', 'D', 'D', 'D', 'E', 'E', 'E', 'E', + 'F', 'F', 'F', 'F', 'G', 'G', 'G', 'G', 'H', 'H', + 'H', 'H', 'I', 'I', 'I', 'I', 'J', 'J', 'J', 'J', + 'K', 'K', 'K', 'K', 'L', 'L', 'L', 'L', 'M', 'M', + 'M', 'M', 'N', 'N', 'N', 'N', 'O', 'O', 'O', 'O', + 'P', 'P', 'P', 'P', 'Q', 'Q', 'Q', 'Q', 'R', 'R', + 'R', 'R', 'S', 'S', 'S', 'S', 'T', 'T', 'T', 'T', + 'U', 'U', 'U', 'U', 'V', 'V', 'V', 'V', 'W', 'W', + 'W', 'W', 'X', 'X', 'X', 'X', 'Y', 'Y', 'Y', 'Y', + 'Z', 'Z', 'Z', 'Z', 'a', 'a', 'a', 'a', 'b', 'b', + 'b', 'b', 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'd', + 'e', 'e', 'e', 'e', 'f', 'f', 'f', 'f', 'g', 'g', + 'g', 'g', 'h', 'h', 'h', 'h', 'i', 'i', 'i', 'i', + 'j', 'j', 'j', 'j', 'k', 'k', 'k', 'k', 'l', 'l', + 'l', 'l', 'm', 'm', 'm', 'm', 'n', 'n', 'n', 'n', + 'o', 'o', 'o', 'o', 'p', 'p', 'p', 'p', 'q', 'q', + 'q', 'q', 'r', 'r', 'r', 'r', 's', 's', 's', 's', + 't', 't', 't', 't', 'u', 'u', 'u', 'u', 'v', 'v', + 'v', 'v', 'w', 'w', 'w', 'w', 'x', 'x', 'x', 'x', + 'y', 'y', 'y', 'y', 'z', 'z', 'z', 'z', '0', '0', + '0', '0', '1', '1', '1', '1', '2', '2', '2', '2', + '3', '3', '3', '3', '4', '4', '4', '4', '5', '5', + '5', '5', '6', '6', '6', '6', '7', '7', '7', '7', + '8', '8', '8', '8', '9', '9', '9', '9', '+', '+', + '+', '+', '/', '/', '/', '/' +}; + +static const unsigned char e1[256] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' +}; + +static const unsigned char e2[256] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' +}; + + + +#ifdef WORDS_BIGENDIAN + + +/* SPECIAL DECODE TABLES FOR BIG ENDIAN (IBM/MOTOROLA/SUN) CPUS */ + +static const uint32_t d0[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00f80000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00fc0000, +0x00d00000, 0x00d40000, 0x00d80000, 0x00dc0000, 0x00e00000, 0x00e40000, +0x00e80000, 0x00ec0000, 0x00f00000, 0x00f40000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00040000, 0x00080000, 0x000c0000, 0x00100000, 0x00140000, 0x00180000, +0x001c0000, 0x00200000, 0x00240000, 0x00280000, 0x002c0000, 0x00300000, +0x00340000, 0x00380000, 0x003c0000, 0x00400000, 0x00440000, 0x00480000, +0x004c0000, 0x00500000, 0x00540000, 0x00580000, 0x005c0000, 0x00600000, +0x00640000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00680000, 0x006c0000, 0x00700000, 0x00740000, 0x00780000, +0x007c0000, 0x00800000, 0x00840000, 0x00880000, 0x008c0000, 0x00900000, +0x00940000, 0x00980000, 0x009c0000, 0x00a00000, 0x00a40000, 0x00a80000, +0x00ac0000, 0x00b00000, 0x00b40000, 0x00b80000, 0x00bc0000, 0x00c00000, +0x00c40000, 0x00c80000, 0x00cc0000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d1[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0003e000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0003f000, +0x00034000, 0x00035000, 0x00036000, 0x00037000, 0x00038000, 0x00039000, +0x0003a000, 0x0003b000, 0x0003c000, 0x0003d000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000, +0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000, +0x0000d000, 0x0000e000, 0x0000f000, 0x00010000, 0x00011000, 0x00012000, +0x00013000, 0x00014000, 0x00015000, 0x00016000, 0x00017000, 0x00018000, +0x00019000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0001a000, 0x0001b000, 0x0001c000, 0x0001d000, 0x0001e000, +0x0001f000, 0x00020000, 0x00021000, 0x00022000, 0x00023000, 0x00024000, +0x00025000, 0x00026000, 0x00027000, 0x00028000, 0x00029000, 0x0002a000, +0x0002b000, 0x0002c000, 0x0002d000, 0x0002e000, 0x0002f000, 0x00030000, +0x00031000, 0x00032000, 0x00033000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d2[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000f80, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000fc0, +0x00000d00, 0x00000d40, 0x00000d80, 0x00000dc0, 0x00000e00, 0x00000e40, +0x00000e80, 0x00000ec0, 0x00000f00, 0x00000f40, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000040, 0x00000080, 0x000000c0, 0x00000100, 0x00000140, 0x00000180, +0x000001c0, 0x00000200, 0x00000240, 0x00000280, 0x000002c0, 0x00000300, +0x00000340, 0x00000380, 0x000003c0, 0x00000400, 0x00000440, 0x00000480, +0x000004c0, 0x00000500, 0x00000540, 0x00000580, 0x000005c0, 0x00000600, +0x00000640, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000680, 0x000006c0, 0x00000700, 0x00000740, 0x00000780, +0x000007c0, 0x00000800, 0x00000840, 0x00000880, 0x000008c0, 0x00000900, +0x00000940, 0x00000980, 0x000009c0, 0x00000a00, 0x00000a40, 0x00000a80, +0x00000ac0, 0x00000b00, 0x00000b40, 0x00000b80, 0x00000bc0, 0x00000c00, +0x00000c40, 0x00000c80, 0x00000cc0, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d3[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000003e, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0000003f, +0x00000034, 0x00000035, 0x00000036, 0x00000037, 0x00000038, 0x00000039, +0x0000003a, 0x0000003b, 0x0000003c, 0x0000003d, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, +0x00000007, 0x00000008, 0x00000009, 0x0000000a, 0x0000000b, 0x0000000c, +0x0000000d, 0x0000000e, 0x0000000f, 0x00000010, 0x00000011, 0x00000012, +0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, 0x00000018, +0x00000019, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000001a, 0x0000001b, 0x0000001c, 0x0000001d, 0x0000001e, +0x0000001f, 0x00000020, 0x00000021, 0x00000022, 0x00000023, 0x00000024, +0x00000025, 0x00000026, 0x00000027, 0x00000028, 0x00000029, 0x0000002a, +0x0000002b, 0x0000002c, 0x0000002d, 0x0000002e, 0x0000002f, 0x00000030, +0x00000031, 0x00000032, 0x00000033, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +#else + + +/* SPECIAL DECODE TABLES FOR LITTLE ENDIAN (INTEL) CPUS */ + +static const uint32_t d0[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x000000f8, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x000000fc, +0x000000d0, 0x000000d4, 0x000000d8, 0x000000dc, 0x000000e0, 0x000000e4, +0x000000e8, 0x000000ec, 0x000000f0, 0x000000f4, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000004, 0x00000008, 0x0000000c, 0x00000010, 0x00000014, 0x00000018, +0x0000001c, 0x00000020, 0x00000024, 0x00000028, 0x0000002c, 0x00000030, +0x00000034, 0x00000038, 0x0000003c, 0x00000040, 0x00000044, 0x00000048, +0x0000004c, 0x00000050, 0x00000054, 0x00000058, 0x0000005c, 0x00000060, +0x00000064, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000068, 0x0000006c, 0x00000070, 0x00000074, 0x00000078, +0x0000007c, 0x00000080, 0x00000084, 0x00000088, 0x0000008c, 0x00000090, +0x00000094, 0x00000098, 0x0000009c, 0x000000a0, 0x000000a4, 0x000000a8, +0x000000ac, 0x000000b0, 0x000000b4, 0x000000b8, 0x000000bc, 0x000000c0, +0x000000c4, 0x000000c8, 0x000000cc, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d1[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000e003, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0000f003, +0x00004003, 0x00005003, 0x00006003, 0x00007003, 0x00008003, 0x00009003, +0x0000a003, 0x0000b003, 0x0000c003, 0x0000d003, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000, +0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000, +0x0000d000, 0x0000e000, 0x0000f000, 0x00000001, 0x00001001, 0x00002001, +0x00003001, 0x00004001, 0x00005001, 0x00006001, 0x00007001, 0x00008001, +0x00009001, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000a001, 0x0000b001, 0x0000c001, 0x0000d001, 0x0000e001, +0x0000f001, 0x00000002, 0x00001002, 0x00002002, 0x00003002, 0x00004002, +0x00005002, 0x00006002, 0x00007002, 0x00008002, 0x00009002, 0x0000a002, +0x0000b002, 0x0000c002, 0x0000d002, 0x0000e002, 0x0000f002, 0x00000003, +0x00001003, 0x00002003, 0x00003003, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d2[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00800f00, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00c00f00, +0x00000d00, 0x00400d00, 0x00800d00, 0x00c00d00, 0x00000e00, 0x00400e00, +0x00800e00, 0x00c00e00, 0x00000f00, 0x00400f00, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00400000, 0x00800000, 0x00c00000, 0x00000100, 0x00400100, 0x00800100, +0x00c00100, 0x00000200, 0x00400200, 0x00800200, 0x00c00200, 0x00000300, +0x00400300, 0x00800300, 0x00c00300, 0x00000400, 0x00400400, 0x00800400, +0x00c00400, 0x00000500, 0x00400500, 0x00800500, 0x00c00500, 0x00000600, +0x00400600, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00800600, 0x00c00600, 0x00000700, 0x00400700, 0x00800700, +0x00c00700, 0x00000800, 0x00400800, 0x00800800, 0x00c00800, 0x00000900, +0x00400900, 0x00800900, 0x00c00900, 0x00000a00, 0x00400a00, 0x00800a00, +0x00c00a00, 0x00000b00, 0x00400b00, 0x00800b00, 0x00c00b00, 0x00000c00, +0x00400c00, 0x00800c00, 0x00c00c00, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d3[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x003e0000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x003f0000, +0x00340000, 0x00350000, 0x00360000, 0x00370000, 0x00380000, 0x00390000, +0x003a0000, 0x003b0000, 0x003c0000, 0x003d0000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00010000, 0x00020000, 0x00030000, 0x00040000, 0x00050000, 0x00060000, +0x00070000, 0x00080000, 0x00090000, 0x000a0000, 0x000b0000, 0x000c0000, +0x000d0000, 0x000e0000, 0x000f0000, 0x00100000, 0x00110000, 0x00120000, +0x00130000, 0x00140000, 0x00150000, 0x00160000, 0x00170000, 0x00180000, +0x00190000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x001a0000, 0x001b0000, 0x001c0000, 0x001d0000, 0x001e0000, +0x001f0000, 0x00200000, 0x00210000, 0x00220000, 0x00230000, 0x00240000, +0x00250000, 0x00260000, 0x00270000, 0x00280000, 0x00290000, 0x002a0000, +0x002b0000, 0x002c0000, 0x002d0000, 0x002e0000, 0x002f0000, 0x00300000, +0x00310000, 0x00320000, 0x00330000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +#endif