From 1955c9c2c974e05f2df93a2ca7186bf3a0c0c098 Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Mon, 15 Dec 2014 10:35:08 -0800 Subject: [PATCH] Source release v2.2.0-0-903 + third_party libs Change-Id: I03f670eaeb052bc741abb347be06f8ddc58418e7 --- README | 12 +- README.upgrading | 27 +- cdm/cdm.gyp | 15 +- cdm/cdm_unittests.gyp | 18 +- cdm/include/cdm_host_file.h | 75 +- cdm/include/content_decryption_module.h | 575 +++++--- cdm/include/host_4_file_io_client.h | 54 + cdm/include/wv_cdm_version.h | 2 +- cdm/include/wv_client_property_set.h | 73 + ...ule.h => wv_content_decryption_module_1.h} | 110 +- cdm/include/wv_content_decryption_module_4.h | 155 ++ cdm/src/cdm_library_init.cpp | 48 + cdm/src/file_store.cpp | 181 ++- cdm/src/host_4_file_io_client.cpp | 66 + ...cpp => wv_content_decryption_module_1.cpp} | 323 ++--- cdm/src/wv_content_decryption_module_4.cpp | 597 ++++++++ cdm/test/cdm_api_1_test.cpp | 709 +++++++++ cdm/test/cdm_api_4_test.cpp | 922 ++++++++++++ cdm/test/cdm_api_test.cpp | 1289 ----------------- cdm/test/cdm_test_config.h | 18 + cdm/test/cdm_test_main.cpp | 96 ++ cdm/test/device_cert.h | 7 +- cdm/test/gtest.gyp | 16 - cdm/test/test_host_1.cpp | 134 ++ cdm/test/test_host_1.h | 105 ++ cdm/test/test_host_4.cpp | 137 ++ cdm/test/test_host_4.h | 108 ++ cdm/test/test_host_4_file_io.cpp | 31 + cdm/test/test_host_4_file_io.h | 34 + cdm/test/test_util.cpp | 52 + cdm/test/test_util.h | 58 + core/include/buffer_reader.h | 7 +- core/include/cdm_engine.h | 27 +- core/include/cdm_session.h | 5 +- core/include/certificate_provisioning.h | 3 +- core/include/clock.h | 1 - core/include/crypto_key.h | 6 +- core/include/crypto_session.h | 16 +- core/include/device_files.h | 13 +- core/include/file_store.h | 2 +- core/include/initialization_data.h | 2 +- core/include/license.h | 54 +- core/include/lock.h | 16 +- core/include/oemcrypto_adapter.h | 5 +- core/include/privacy_crypto.h | 5 +- core/include/properties.h | 5 + core/include/scoped_ptr.h | 1 + core/src/buffer_reader.cpp | 3 +- core/src/cdm_engine.cpp | 155 +- core/src/cdm_session.cpp | 88 +- core/src/certificate_provisioning.cpp | 40 +- core/src/crypto_session.cpp | 60 +- core/src/device_files.cpp | 152 +- core/src/initialization_data.cpp | 20 +- core/src/license.cpp | 197 +-- core/src/license_protocol.proto | 9 +- core/src/oemcrypto_adapter_static.cpp | 4 + core/src/oemcrypto_adapter_static_v8.cpp | 67 +- core/src/policy_engine.cpp | 85 +- core/src/privacy_crypto_dummy.cpp | 8 +- ..._crypto.cpp => privacy_crypto_openssl.cpp} | 0 core/src/string_conversions.cpp | 17 +- core/test/base64_test.cpp | 32 +- core/test/cdm_engine_test.cpp | 179 +-- core/test/cdm_session_unittest.cpp | 8 +- core/test/config_test_env.cpp | 59 +- core/test/device_files_unittest.cpp | 35 +- core/test/file_store_unittest.cpp | 3 +- core/test/http_socket.cpp | 34 +- core/test/http_socket.h | 9 +- core/test/http_socket_test.cpp | 114 +- core/test/license_request.cpp | 37 +- core/test/license_unittest.cpp | 366 +++-- core/test/policy_engine_unittest.cpp | 1 + core/test/test_printers.cpp | 93 ++ core/test/test_printers.h | 19 + core/test/timer_unittest.cpp | 13 +- core/test/url_request.cpp | 19 +- core/test/url_request.h | 1 + linux/src/log.cpp | 32 +- oemcrypto/include/OEMCryptoCENC.h | 56 +- oemcrypto/test/oemcrypto_test.cpp | 162 ++- platforms/global_config.gypi | 2 +- {cdm/test => third_party}/gmock.gyp | 21 +- third_party/gmock/README.android | 9 +- 85 files changed, 5594 insertions(+), 2830 deletions(-) create mode 100644 cdm/include/host_4_file_io_client.h create mode 100644 cdm/include/wv_client_property_set.h rename cdm/include/{wv_content_decryption_module.h => wv_content_decryption_module_1.h} (50%) create mode 100644 cdm/include/wv_content_decryption_module_4.h create mode 100644 cdm/src/cdm_library_init.cpp create mode 100644 cdm/src/host_4_file_io_client.cpp rename cdm/src/{wv_content_decryption_module.cpp => wv_content_decryption_module_1.cpp} (55%) create mode 100644 cdm/src/wv_content_decryption_module_4.cpp create mode 100644 cdm/test/cdm_api_1_test.cpp create mode 100644 cdm/test/cdm_api_4_test.cpp delete mode 100644 cdm/test/cdm_api_test.cpp create mode 100644 cdm/test/cdm_test_config.h create mode 100644 cdm/test/cdm_test_main.cpp delete mode 100644 cdm/test/gtest.gyp create mode 100644 cdm/test/test_host_1.cpp create mode 100644 cdm/test/test_host_1.h create mode 100644 cdm/test/test_host_4.cpp create mode 100644 cdm/test/test_host_4.h create mode 100644 cdm/test/test_host_4_file_io.cpp create mode 100644 cdm/test/test_host_4_file_io.h create mode 100644 cdm/test/test_util.cpp create mode 100644 cdm/test/test_util.h rename core/src/{privacy_crypto.cpp => privacy_crypto_openssl.cpp} (100%) create mode 100644 core/test/test_printers.cpp create mode 100644 core/test/test_printers.h rename {cdm/test => third_party}/gmock.gyp (54%) diff --git a/README b/README index e2f45f22..6db0104b 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ -README for Widevine CDM Partner Kit v2.1 -Date: 6/03/2014 +README for Widevine CDM Partner Kit v2.2 +Date: 12/16/2014 This document provides additional details on installation, system setup, building, and testing components of the Widevine Content @@ -230,16 +230,12 @@ Here's a current list of GYP files and test-related targets: wvcdm_shared Build CDM shared library cdm/cdm_unittests.gyp wvcdm_shared_api_unittest Build CDM unit tests - cdm/test/gtest.gyp - gtest Provides gtest modules for unit tests - cdm/test/gmock.gyp + third_party/gmock.gyp gmock Provides gmock modules for unit tests gmock_main Provides main routine for unit tests - oemcrypto/mock/oec_mock.gyp - oemcrypto Top level target to build/link OEMCrypto + gtest Provides gtest modules for unit tests oemcrypto/oemcrypto.gyp oec_lib Build OEMCrypto library - oec_mock Build mock OEMCrypto library oec_unittest Build OEMCrypto unit tests oemcrypto/mock/oec_mock.gyp oec_mock Build mock OEMCrypto sources diff --git a/README.upgrading b/README.upgrading index db393d6b..69dd3812 100644 --- a/README.upgrading +++ b/README.upgrading @@ -1,5 +1,5 @@ -README.upgrading for Widevine CDM Partner Kit v2.1 -Date: 7/10/2014 +README.upgrading for Widevine CDM Partner Kit v2.2 +Date: 12/16/2014 This document provides details on important changes between versions of the Widevine CDM. Some upgrades require you to make changes to your application, @@ -9,6 +9,29 @@ NOTE: All gyp variables have default values in platforms/global_config.gypi. You may override these defaults in your platform-specific gypi. +New in v2.2.0: +===== +New gyp variables have been introduced to make the build more configurable: + * privacy_crypto_impl + This replaces the variable 'disable_privacy_crypto'. To achieve the same + effect, set the value to 'dummy'. Defaults to 'openssl'. + +ContentDecryptionModule_1::GenerateKeyRequest did not previously require the +'type' parameter to be supplied. This is now required, and must be set to the +content's MIME type. + +ContentDecryptionModule_1::Decrypt* previously required subsamples. This is no +longer required. WebM content, for example, does not contain subsamples. + +ContentDecryptionModule_1::Decrypt* did not previously set the size of a +decrypted Buffer. This has been fixed. + +New CDM/Host interfaces, CDM_4/Host_4, have been introduced. CDM_1/Host_1 will +continue to be supported until the end of Q1, 2015. The new interface includes +many fixes and improvements, updates to reflect the evolving EME standard, and +the introduction of offline playback APIs. + + New in v2.1.6: ===== The following methods have been removed from the CDM interface (class diff --git a/cdm/cdm.gyp b/cdm/cdm.gyp index 49818e92..588255a7 100644 --- a/cdm/cdm.gyp +++ b/cdm/cdm.gyp @@ -35,6 +35,7 @@ '../third_party/stringencoders/src', ], 'sources': [ + '../cdm/src/host_4_file_io_client.cpp', '../cdm/src/file_store.cpp', '../cdm/src/properties_common.cpp', '../core/src/string_conversions.cpp', @@ -67,9 +68,11 @@ ], 'sources': [ # uses common published api + '../cdm/src/cdm_library_init.cpp', '../cdm/src/clock.cpp', '../cdm/src/host_event_listener.cpp', - '../cdm/src/wv_content_decryption_module.cpp', + '../cdm/src/wv_content_decryption_module_1.cpp', + '../cdm/src/wv_content_decryption_module_4.cpp', '../core/src/buffer_reader.cpp', '../core/src/cdm_engine.cpp', '../core/src/cdm_session.cpp', @@ -80,18 +83,10 @@ '../core/src/license.cpp', '../core/src/oemcrypto_adapter_static.cpp', '../core/src/policy_engine.cpp', - '../core/src/privacy_crypto.cpp', + '../core/src/privacy_crypto_<(privacy_crypto_impl).cpp', '../core/src/properties.cpp', ], 'conditions': [ - ['disable_privacy_crypto=="true"', { - 'sources!': [ # exclude - '../core/src/privacy_crypto.cpp', - ], - 'sources': [ # include - '../core/src/privacy_crypto_dummy.cpp', - ], - }], ['oemcrypto_v8=="true"', { 'sources!': [ # exclude '../core/src/oemcrypto_adapter_static.cpp', diff --git a/cdm/cdm_unittests.gyp b/cdm/cdm_unittests.gyp index e4759972..5a48a771 100644 --- a/cdm/cdm_unittests.gyp +++ b/cdm/cdm_unittests.gyp @@ -9,10 +9,17 @@ 'target_name': 'wvcdm_shared_api_unittest', 'type': 'executable', 'sources': [ - '../cdm/test/cdm_api_test.cpp', + '../cdm/test/cdm_api_1_test.cpp', + '../cdm/test/cdm_api_4_test.cpp', + '../cdm/test/cdm_test_main.cpp', + '../cdm/test/test_host_1.cpp', + '../cdm/test/test_host_4.cpp', + '../cdm/test/test_host_4_file_io.cpp', + '../cdm/test/test_util.cpp', '../core/test/config_test_env.cpp', '../core/test/license_request.cpp', '../core/test/http_socket.cpp', + '../core/test/test_printers.cpp', '../core/test/url_request.cpp', ], 'include_dirs': [ @@ -22,6 +29,9 @@ '../core/test', '../oemcrypto/include', ], + 'defines': [ + 'UNIT_TEST', + ], 'libraries': [ '-lssl', '-lcrypto', @@ -29,9 +39,9 @@ ], 'dependencies': [ 'cdm.gyp:wvcdm_shared', - 'test/gmock.gyp:gmock', - 'test/gmock.gyp:gmock_main', - 'test/gtest.gyp:gtest', + '../third_party/gmock.gyp:gmock', + '../third_party/gmock.gyp:gmock_main', + '../third_party/gmock.gyp:gtest', ], }, ], diff --git a/cdm/include/cdm_host_file.h b/cdm/include/cdm_host_file.h index 93311ae9..c8dc9549 100644 --- a/cdm/include/cdm_host_file.h +++ b/cdm/include/cdm_host_file.h @@ -3,42 +3,87 @@ #ifndef WVCDM_CDM_CDM_HOST_FILE_H_ #define WVCDM_CDM_CDM_HOST_FILE_H_ -#include "file_store.h" #include "content_decryption_module.h" +#include "file_store.h" +#include "host_4_file_io_client.h" +#include "scoped_ptr.h" namespace wvcdm { class IFileFactory; +class IHostFile { + public: + virtual ~IHostFile() {} + virtual bool Open(const std::string& name) = 0; + virtual ssize_t Read(char* buffer, size_t bytes) = 0; + virtual ssize_t Write(const char* buffer, size_t bytes) = 0; + virtual bool Close() = 0; + virtual bool Remove(const std::string& name) = 0; + virtual ssize_t FileSize(const std::string& name) = 0; +}; + +class File1Impl : public IHostFile { + public: + explicit File1Impl(cdm::Host_1* const host_1) : host_1_(host_1) {} + virtual ~File1Impl() {} + virtual bool Open(const std::string& name) OVERRIDE; + virtual ssize_t Read(char* buffer, size_t bytes) OVERRIDE; + virtual ssize_t Write(const char* buffer, size_t bytes) OVERRIDE; + virtual bool Close() OVERRIDE; + virtual bool Remove(const std::string& name) OVERRIDE; + virtual ssize_t FileSize(const std::string& name) OVERRIDE; + private: + cdm::Host_1* const host_1_; + std::string fname_; +}; + +class File4Impl : public IHostFile { + public: + explicit File4Impl(cdm::Host_4* const host_4) : + host_4_(host_4), host_4_file_io_client_(host_4) {} + virtual ~File4Impl() {} + virtual bool Open(const std::string& name) OVERRIDE; + virtual ssize_t Read(char* buffer, size_t bytes) OVERRIDE; + virtual ssize_t Write(const char* buffer, size_t bytes) OVERRIDE; + virtual bool Close() OVERRIDE; + virtual bool Remove(const std::string& name) OVERRIDE; + virtual ssize_t FileSize(const std::string& name) OVERRIDE; + private: + cdm::Host_4* const host_4_; + Host4FileIOClient host_4_file_io_client_; +}; + class File::Impl { public: - explicit Impl(cdm::Host* const host) : host_(host) {} + explicit Impl(cdm::Host_1* const host_1) : + file_api_(new File1Impl(host_1)) {} - static void RegisterFileFactory(IFileFactory* factory) { - factory_ = factory; - } + explicit Impl(cdm::Host_4* const host_4) : + file_api_(new File4Impl(host_4)) {} + + ~Impl() {} + + static void RegisterFileFactory(IFileFactory* factory) { factory_ = factory; } - virtual bool Exists(const std::string& name); virtual bool Open(const std::string& name); - virtual bool Close(); - virtual bool Remove(const std::string& name); virtual ssize_t Read(char* buffer, size_t bytes); virtual ssize_t Write(const char* buffer, size_t bytes); + virtual bool Close(); + virtual bool Exists(const std::string& name); + virtual bool Remove(const std::string& name); virtual ssize_t FileSize(const std::string& name); private: static IFileFactory* factory_; friend class File; - cdm::Host* const host_; - std::string fname_; + scoped_ptr file_api_; }; class IFileFactory { protected: - IFileFactory() { - File::Impl::RegisterFileFactory(this); - } + IFileFactory() { File::Impl::RegisterFileFactory(this); } virtual ~IFileFactory() {} @@ -46,6 +91,6 @@ class IFileFactory { virtual File::Impl* NewFileImpl() = 0; }; -} // namespace wvcdm +} // namespace wvcdm -#endif // WVCDM_CDM_CDM_HOST_FILE_H_ +#endif // WVCDM_CDM_CDM_HOST_FILE_H_ diff --git a/cdm/include/content_decryption_module.h b/cdm/include/content_decryption_module.h index 7fef5f3b..25a7c589 100644 --- a/cdm/include/content_decryption_module.h +++ b/cdm/include/content_decryption_module.h @@ -37,13 +37,11 @@ typedef __int64 int64_t; #endif // defined(WIN32) -// The version number must be rolled when the exported functions are updated! -// If the CDM and the adapter use different versions of these functions, the -// adapter will fail to load or crash! -#define INITIALIZE_CDM_MODULE InitializeCdmModule_1 +// We maintain this macro for backward compatibility only. +#define INITIALIZE_CDM_MODULE InitializeCdmModule extern "C" { -CDM_EXPORT void INITIALIZE_CDM_MODULE(); +CDM_EXPORT void InitializeCdmModule(); CDM_EXPORT void DeinitializeCdmModule(); @@ -62,38 +60,47 @@ typedef void* (*GetCdmHostFunc)(int host_interface_version, void* user_data); // object. CDM_EXPORT void* CreateCdmInstance( int cdm_interface_version, - const char* key_system, int key_system_size, + const char* key_system, uint32_t key_system_size, GetCdmHostFunc get_cdm_host_func, void* user_data); -CDM_EXPORT int GetCdmVersion(); +CDM_EXPORT const char* GetCdmVersion(); } namespace cdm { -class AudioFrames; -class DecryptedBlock; -class VideoFrame; +class Host_1; +class Host_4; enum Status { kSuccess = 0, - kNeedMoreData, // Decoder needs more data to produce a decoded frame/sample. - kNoKey, // The required decryption key is not available. - kSessionError, // Session management error. - kDecryptError, // Decryption failed. - kDecodeError, // Error decoding audio or video. - kRetry, // Buffer temporarily cannot be accepted. Retry after a short delay. - kNeedsDeviceCertificate // Requires Device Certificate for content licensing + kNoKey = 2, // The required decryption key is not available. + kSessionError = 3, // Session management error. + kDecryptError = 4, // Decryption failed. + kDecodeError = 5, // Error decoding audio or video. + kRetry = 6, // Buffer temporarily cannot be accepted, delay and retry. + kNeedsDeviceCertificate = 7 // A certificate is required for licensing. }; // This must be consistent with MediaKeyError defined in the -// Encrypted media Extensions (EME) specification: http://goo.gl/3Df8h +// Encrypted media Extensions (EME) specification: http://goo.gl/IBjNCP enum MediaKeyError { kUnknownError = 1, - kClientError, - kServiceError, - kOutputError, - kHardwareChangeError, - kDomainError + kClientError = 2, + kOutputError = 4, +}; + +// The type of session to create. The valid types are defined in the spec: +// http://goo.gl/vmc3pd +enum SessionType { + kTemporary = 0, + kPersistent = 1, + kProvisioning = 2, +}; + +// The type of stream. Used in DecryptDecodeAndRender. +enum StreamType { + kStreamTypeAudio = 0, + kStreamTypeVideo = 1 }; // An input buffer can be split into several continuous subsamples. @@ -118,11 +125,11 @@ enum MediaKeyError { // | clear1 | decrypted1| clear2 | decrypted2 | clear3 | decrypted3 | // struct SubsampleEntry { - SubsampleEntry(int32_t clear_bytes, int32_t cipher_bytes) + SubsampleEntry(uint32_t clear_bytes, uint32_t cipher_bytes) : clear_bytes(clear_bytes), cipher_bytes(cipher_bytes) {} - int32_t clear_bytes; - int32_t cipher_bytes; + uint32_t clear_bytes; + uint32_t cipher_bytes; }; // Represents an input buffer to be decrypted (and possibly decoded). It @@ -141,116 +148,173 @@ struct InputBuffer { timestamp(0) {} const uint8_t* data; // Pointer to the beginning of the input data. - int32_t data_size; // Size (in bytes) of |data|. + uint32_t data_size; // Size (in bytes) of |data|. - int32_t data_offset; // Number of bytes to be discarded before decryption. + uint32_t data_offset; // Number of bytes to be discarded before decryption. const uint8_t* key_id; // Key ID to identify the decryption key. - int32_t key_id_size; // Size (in bytes) of |key_id|. + uint32_t key_id_size; // Size (in bytes) of |key_id|. const uint8_t* iv; // Initialization vector. - int32_t iv_size; // Size (in bytes) of |iv|. + uint32_t iv_size; // Size (in bytes) of |iv|. const struct SubsampleEntry* subsamples; - int32_t num_subsamples; // Number of subsamples in |subsamples|. + uint32_t num_subsamples; // Number of subsamples in |subsamples|. int64_t timestamp; // Presentation timestamp in microseconds. }; -struct AudioDecoderConfig { - enum AudioCodec { - kUnknownAudioCodec = 0, - kCodecVorbis, - kCodecAac +// Represents a buffer created by the Host. +class Buffer { + public: + // Destroys the buffer in the same context as it was created. + virtual void Destroy() = 0; + + virtual int32_t Capacity() const = 0; + virtual uint8_t* Data() = 0; + virtual void SetSize(int32_t size) = 0; + virtual int32_t Size() const = 0; + + protected: + Buffer() {} + virtual ~Buffer() {} + + private: + Buffer(const Buffer&); + void operator=(const Buffer&); +}; + +// Represents a key-value map. +// Both created and destroyed by the Host. +// Data is filled in by the CDM. +// Need not be implemented if QueryKeyStatus() is not called. +class KeyValueMap { + public: + virtual void Set(const char* key, void* value, size_t value_size) = 0; + + protected: + KeyValueMap() {} + virtual ~KeyValueMap() {} + + private: + KeyValueMap(const KeyValueMap&); + void operator=(const KeyValueMap&); +}; + +// Represents a decrypted block that has not been decoded. +class DecryptedBlock { + public: + virtual void SetDecryptedBuffer(Buffer* buffer) = 0; + virtual Buffer* DecryptedBuffer() = 0; + + virtual void SetTimestamp(int64_t timestamp) = 0; + virtual int64_t Timestamp() const = 0; + + protected: + DecryptedBlock() {} + virtual ~DecryptedBlock() {} +}; + +// The FileIO interface provides a way for the CDM to store data in a file in +// persistent storage. This interface aims only at providing basic read/write +// capabilities and should not be used as a full fledged file IO API. +// +// All methods that report their result via calling a method on FileIOClient +// (currently, this is Open, Read, and Write) must call into FileIOClient on the +// same thread they were called on and must do so before returning. This +// restriction may be lifted in the future. +// +// Each domain (e.g. "example.com") and each CDM has it's own persistent +// storage. All instances of a given CDM associated with a given domain share +// the same persistent storage. +// +// Note to implementors of this interface: +// Per-origin storage and the ability for users to clear it are important. +// See http://www.w3.org/TR/encrypted-media/#privacy-storedinfo. +class FileIO { + public: + // Opens the file with |file_name| for read and write. + // FileIOClient::OnOpenComplete() will be called after the opening + // operation finishes. + // - When the file is opened by a CDM instance, it will be classified as "in + // use". In this case other CDM instances in the same domain may receive + // kInUse status when trying to open it. + // - |file_name| should not include path separators. + virtual void Open(const char* file_name, uint32_t file_name_size) = 0; + + // Reads the contents of the file. FileIOClient::OnReadComplete() will be + // called with the read status. Read() should not be called if a previous + // Read() or Write() call is still pending; otherwise OnReadComplete() will + // be called with kInUse. + virtual void Read() = 0; + + // Writes |data_size| bytes of |data| into the file. + // FileIOClient::OnWriteComplete() will be called with the write status. + // All existing contents in the file will be overwritten. Calling Write() with + // NULL |data| will clear all contents in the file. Write() should not be + // called if a previous Write() or Read() call is still pending; otherwise + // OnWriteComplete() will be called with kInUse. + virtual void Write(const uint8_t* data, uint32_t data_size) = 0; + + // Closes the file if opened, destroys this FileIO object and releases any + // resources allocated. The CDM must call this method when it finished using + // this object. A FileIO object must not be used after Close() is called. + virtual void Close() = 0; + + protected: + FileIO() {} + virtual ~FileIO() {} +}; + +// Responses to FileIO calls. +class FileIOClient { + public: + enum Status { + kSuccess = 0, + kInUse, + kError }; - AudioDecoderConfig() - : codec(kUnknownAudioCodec), - channel_count(0), - bits_per_channel(0), - samples_per_second(0), - extra_data(NULL), - extra_data_size(0) {} + // Response to a FileIO::Open() call with the open |status|. + virtual void OnOpenComplete(Status status) = 0; - AudioCodec codec; - int32_t channel_count; - int32_t bits_per_channel; - int32_t samples_per_second; + // Response to a FileIO::Read() call to provide |data_size| bytes of |data| + // read from the file. + // - kSuccess indicates that all contents of the file has been successfully + // read. In this case, 0 |data_size| means that the file is empty. + // - kInUse indicates that there are other read/write operations pending. + // - kError indicates read failure, e.g. the storage isn't open or cannot be + // fully read. + virtual void OnReadComplete(Status status, + const uint8_t* data, uint32_t data_size) = 0; - // Optional byte data required to initialize audio decoders, such as the - // vorbis setup header. - uint8_t* extra_data; - int32_t extra_data_size; -}; + // Response to a FileIO::Write() call. + // - kSuccess indicates that all the data has been written into the file + // successfully. + // - kInUse indicates that there are other read/write operations pending. + // - kError indicates write failure, e.g. the storage isn't open or cannot be + // fully written. Upon write failure, the contents of the file should be + // regarded as corrupt and should not used. + virtual void OnWriteComplete(Status status) = 0; -// Surface formats based on FOURCC labels, see: http://www.fourcc.org/yuv.php -enum VideoFormat { - kUnknownVideoFormat = 0, // Unknown format value. Used for error reporting. - kYv12, // 12bpp YVU planar 1x1 Y, 2x2 VU samples. - kI420 // 12bpp YVU planar 1x1 Y, 2x2 UV samples. -}; - -struct Size { - Size() : width(0), height(0) {} - Size(int32_t width, int32_t height) : width(width), height(height) {} - - int32_t width; - int32_t height; -}; - -struct VideoDecoderConfig { - enum VideoCodec { - kUnknownVideoCodec = 0, - kCodecVp8, - kCodecH264 - }; - - enum VideoCodecProfile { - kUnknownVideoCodecProfile = 0, - kVp8ProfileMain, - kH264ProfileBaseline, - kH264ProfileMain, - kH264ProfileExtended, - kH264ProfileHigh, - kH264ProfileHigh10, - kH264ProfileHigh422, - kH264ProfileHigh444Predictive - }; - - VideoDecoderConfig() - : codec(kUnknownVideoCodec), - profile(kUnknownVideoCodecProfile), - format(kUnknownVideoFormat), - extra_data(NULL), - extra_data_size(0) {} - - VideoCodec codec; - VideoCodecProfile profile; - VideoFormat format; - - // Width and height of video frame immediately post-decode. Not all pixels - // in this region are valid. - Size coded_size; - - // Optional byte data required to initialize video decoders, such as H.264 - // AAVC data. - uint8_t* extra_data; - int32_t extra_data_size; -}; - -enum StreamType { - kStreamTypeAudio = 0, - kStreamTypeVideo = 1 + protected: + FileIOClient() {} + virtual ~FileIOClient() {} }; // ContentDecryptionModule interface that all CDMs need to implement. -// The interface is versioned for backward compatibility. -// Note: ContentDecryptionModule implementations must use the allocator -// provided in CreateCdmInstance() to allocate any Buffer that needs to -// be passed back to the caller. Implementations must call Buffer::Destroy() -// when a Buffer is created that will never be returned to the caller. +// CDM interfaces are versioned for backward compatibility. +// Note: ContentDecryptionModule implementations must use the Host +// to allocate any Buffer that needs to be passed back to the caller. +// Host implementations must call Buffer::Destroy() when a Buffer is created +// that will never be returned to the caller. + +// Based on chromium's ContentDecryptionModule_1. class ContentDecryptionModule_1 { public: + static const int kVersion = 1002; + typedef Host_1 Host; + // Generates a |key_request| given |type| and |init_data|. // // Returns kSuccess if the key request was successfully generated, in which @@ -296,9 +360,9 @@ class ContentDecryptionModule_1 { virtual Status Decrypt(const InputBuffer& encrypted_buffer, DecryptedBlock* decrypted_buffer) = 0; - // Decrypts the |encrypted_buffer|, decodes the decrypted buffer into a - // video frame, and passes the frame to the rendering FW/HW. No data - // is returned. + // Decrypts the |encrypted_buffer|, decodes the decrypted buffer, and passes + // the video frame or audio samples to the rendering FW/HW. No data is + // returned to the caller. // // Returns kSuccess if decryption, decoding, and rendering all succeeded. // Returns kNoKey if the CDM did not have the necessary decryption key @@ -312,9 +376,9 @@ class ContentDecryptionModule_1 { virtual Status DecryptDecodeAndRenderFrame( const InputBuffer& encrypted_buffer) = 0; - // Decrypts the |encrypted_buffer|, decodes the decrypted buffer into - // audio frames, and passes the samples to the rendering FW/HW. No - // data is returned. + // Decrypts the |encrypted_buffer|, decodes the decrypted buffer into a + // video frame, and passes the frame to the rendering FW/HW. No data + // is returned. // // Returns kSuccess if decryption, decoding, and rendering all succeeded. // Returns kNoKey if the CDM did not have the necessary decryption key @@ -331,11 +395,11 @@ class ContentDecryptionModule_1 { // Destroys the object in the same context as it was created. virtual void Destroy() = 0; - // Provisioning related methods + // Provisioning-related methods. virtual Status GetProvisioningRequest( std::string* request, std::string* default_url) = 0; - virtual cdm::Status HandleProvisioningResponse( + virtual Status HandleProvisioningResponse( std::string& response) = 0; protected: @@ -343,36 +407,139 @@ class ContentDecryptionModule_1 { virtual ~ContentDecryptionModule_1() {} }; -const int kWidevineCdmInterfaceVersion_1 = 1002; - -typedef ContentDecryptionModule_1 ContentDecryptionModule; -const int kCdmInterfaceVersion = kWidevineCdmInterfaceVersion_1; - -// Represents a buffer created by Allocator implementations. -class Buffer { +// Based on chromium's ContentDecryptionModule_4 and ContentDecryptionModule_5. +class ContentDecryptionModule_4 { public: - // Destroys the buffer in the same context as it was created. + static const int kVersion = 1004; + typedef Host_4 Host; + + // The non-decryption methods on this class, such as CreateSession(), + // get passed a |session_id| for a MediaKeySession object. It must be used in + // the reply via Host methods (e.g. Host::OnSessionMessage()). + // Note: |session_id| is different from MediaKeySession's sessionId attribute, + // which is referred to as |web_session_id| in this file. + + // Creates a new session and generates a key request given |init_data| and + // |session_type|. OnSessionCreated() will be called with a web session ID + // once the session exists. OnSessionMessage() will subsequently be called + // with the key request. A session represents hardware crypto resources, + // (if any exist for the platform), which may be in limited supply. + // For sessions of type kProvisioning, |mime_type| and |init_data| will be + // ignored and may be NULL. + virtual void CreateSession(uint32_t session_id, + const char* mime_type, uint32_t mime_type_size, + const uint8_t* init_data, uint32_t init_data_size, + SessionType session_type) = 0; + + // Creates a new session and loads a previous persistent session into it that + // has a web session ID of |web_session_id|. OnSessionCreated() will be + // called once the session is loaded. + virtual void LoadSession( + uint32_t session_id, + const char* web_session_id, uint32_t web_session_id_length) = 0; + + // Updates the session with |response|. + virtual void UpdateSession( + uint32_t session_id, + const uint8_t* response, uint32_t response_size) = 0; + + // Tests whether |key_id| is known to any current session. + virtual bool IsKeyValid(const uint8_t* key_id, int key_id_size) = 0; + + // Releases the resources for the session |session_id|. + // After calling this, it is invalid to refer to this session any more. + // If any hardware crypto resources were being used by this session, they will + // be released. + // If this session was a persistent session, this will NOT delete the + // persisted data. The persisted data will be preserved so that the session + // can be reloaded later with LoadSession(). To delete the persisted session, + // use RemoveSession(). + virtual void ReleaseSession(uint32_t session_id) = 0; + + // Creates a new session and generates a key release request for the + // existing persistent session identified by |web_session_id|. + // OnSessionCreated() will be called once the session exists. + // OnSessionMessage() will subsequently be called with the key release + // request. + virtual void RemoveSession( + uint32_t session_id, + const char* web_session_id, uint32_t web_session_id_length) = 0; + + // Signals to the CDM that it should use server certificates to protect the + // privacy of the user. The primary use of this is when the application + // driving the CDM is untrusted code, such as when a web browser allows a web + // page's JavaScript to access the CDM. By using server certificates to + // encrypt communication with the license server, device-identifying + // information cannot be extracted from the license exchange process by a + // malicious caller. + // Unless you also call SetServerCertificate() to set a pre-cached server + // certificate, the CDM will perform a certificate exchange with the server + // prior to any key exchanges. + // This method may not be called if any sessions are open. It is typically + // called before any sessions have been opened, but may also be called if all + // open sessions have been released. + // Note that calling SetServerCertificate() implicitly calls this method as + // well. + virtual Status UsePrivacyMode() = 0; + + // Provides a server certificate to be used to encrypt messages to the + // license server. Calling this is like calling UsePrivacyMode(), except that + // because the certificate is provided up-front, the CDM does not have to + // perform a certificate exchange with the server. + // This method may not be called if any sessions are open. It is typically + // called before any sessions have been opened, but may also be called if all + // open sessions have been released. + // Note that calling this method also implicitly calls UsePrivacyMode(). + virtual Status SetServerCertificate( + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) = 0; + + // Performs scheduled operation with |context| when the timer fires. + virtual void TimerExpired(void* context) = 0; + + // Decrypts the |encrypted_buffer|. + // + // Returns kSuccess if decryption succeeded, in which case the callee + // should have filled the |decrypted_buffer| and passed the ownership of + // |data| in |decrypted_buffer| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kDecryptError if any other error happened. + // If the return value is not kSuccess, |decrypted_buffer| should be ignored + // by the caller. + virtual Status Decrypt(const InputBuffer& encrypted_buffer, + DecryptedBlock* decrypted_buffer) = 0; + + // Decrypts the |encrypted_buffer|, decodes the decrypted buffer, and passes + // the video or audio frames to the rendering FW/HW. No data is returned to + // the caller. + // + // Returns kSuccess if decryption, decoding, and rendering all succeeded. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kRetry if |encrypted_buffer| cannot be accepted (e.g, video + // pipeline is full). Caller should retry after a short delay. + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + virtual Status DecryptDecodeAndRender(const InputBuffer& encrypted_buffer, + StreamType stream_type) = 0; + + // Destroys the object in the same context as it was created. virtual void Destroy() = 0; - virtual int32_t Capacity() const = 0; - virtual uint8_t* Data() = 0; - virtual void SetSize(int32_t size) = 0; - virtual int32_t Size() const = 0; - protected: - Buffer() {} - virtual ~Buffer() {} - - private: - Buffer(const Buffer&); - void operator=(const Buffer&); + ContentDecryptionModule_4() {} + virtual ~ContentDecryptionModule_4() {} }; // Host interface that the CDM can call into to access browser side services. -// Host interfaces are versioned for backward compatibility. CDM should use -// HostFactory object to request a Host interface of a particular version. +// Host interfaces are versioned for backward compatibility. + +// Based on chromium's Host_1. class Host_1 { public: + static const int kVersion = 1002; + // Returns a Buffer* containing non-zero members upon success, or NULL on // failure. The caller owns the Buffer* after this call. The buffer is not // guaranteed to be zero initialized. The capacity of the allocated Buffer @@ -418,77 +585,69 @@ class Host_1 { virtual ~Host_1() {} }; -const int kWidevineHostInterfaceVersion_1 = 1002; - -typedef Host_1 Host; -const int kHostInterfaceVersion = kWidevineHostInterfaceVersion_1; - -// Represents a decrypted block that has not been decoded. -class DecryptedBlock { +// Based on chromium's Host_4 and Host_5. +class Host_4 { public: - virtual void SetDecryptedBuffer(Buffer* buffer) = 0; - virtual Buffer* DecryptedBuffer() = 0; + static const int kVersion = 1004; - virtual void SetTimestamp(int64_t timestamp) = 0; - virtual int64_t Timestamp() const = 0; + // Returns a Buffer* containing non-zero members upon success, or NULL on + // failure. The caller owns the Buffer* after this call. The buffer is not + // guaranteed to be zero initialized. The capacity of the allocated Buffer + // is guaranteed to be not less than |capacity|. + virtual Buffer* Allocate(uint32_t capacity) = 0; + + // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms| + // from now with |context|. + virtual void SetTimer(int64_t delay_ms, void* context) = 0; + + // Returns the current epoch wall time in seconds. + virtual double GetCurrentWallTimeInSeconds() = 0; + + // Called by the CDM when a session is created or loaded and the value for the + // MediaKeySession's sessionId attribute is available (|web_session_id|). + // This must be called before OnSessionMessage() or OnSessionUpdated() is + // called for |session_id|. |web_session_id_length| should not include null + // termination. + // When called in response to LoadSession(), the |web_session_id| must be the + // same as the |web_session_id| passed in LoadSession(). + virtual void OnSessionCreated( + uint32_t session_id, + const char* web_session_id, uint32_t web_session_id_length) = 0; + + // Called by the CDM when it has a message for session |session_id|. + // Length parameters should not include null termination. + virtual void OnSessionMessage( + uint32_t session_id, + const char* message, uint32_t message_length, + const char* destination_url, uint32_t destination_url_length) = 0; + + // Called by the CDM when session |session_id| has been updated. + virtual void OnSessionUpdated(uint32_t session_id) = 0; + + // Called by the CDM when session |session_id| is closed. + virtual void OnSessionClosed(uint32_t session_id) = 0; + + // Called by the CDM when an error occurs in session |session_id|. + virtual void OnSessionError(uint32_t session_id, + Status error_code, + uint32_t system_code) = 0; + + // Creates a FileIO object from the host to do file IO operation. Returns NULL + // if a FileIO object cannot be obtained. Once a valid FileIO object is + // returned, |client| must be valid until FileIO::Close() is called. The + // CDM can call this method multiple times to operate on different files. + virtual FileIO* CreateFileIO(FileIOClient* client) = 0; protected: - DecryptedBlock() {} - virtual ~DecryptedBlock() {} + Host_4() {} + virtual ~Host_4() {} }; -class VideoFrame { - public: - enum VideoPlane { - kYPlane = 0, - kUPlane = 1, - kVPlane = 2, - kMaxPlanes = 3, - }; +typedef ContentDecryptionModule_1 ContentDecryptionModule; +const int kCdmInterfaceVersion = ContentDecryptionModule::kVersion; - virtual void SetFormat(VideoFormat format) = 0; - virtual VideoFormat Format() const = 0; - - virtual void SetSize(cdm::Size size) = 0; - virtual cdm::Size Size() const = 0; - - virtual void SetFrameBuffer(Buffer* frame_buffer) = 0; - virtual Buffer* FrameBuffer() = 0; - - virtual void SetPlaneOffset(VideoPlane plane, int32_t offset) = 0; - virtual int32_t PlaneOffset(VideoPlane plane) = 0; - - virtual void SetStride(VideoPlane plane, int32_t stride) = 0; - virtual int32_t Stride(VideoPlane plane) = 0; - - virtual void SetTimestamp(int64_t timestamp) = 0; - virtual int64_t Timestamp() const = 0; - - protected: - VideoFrame() {} - virtual ~VideoFrame() {} -}; - -// Represents decrypted and decoded audio frames. AudioFrames can contain -// multiple audio output buffers, which are serialized into this format: -// -// |<------------------- serialized audio buffer ------------------->| -// | int64_t timestamp | int64_t length | length bytes of audio data | -// -// For example, with three audio output buffers, the AudioFrames will look -// like this: -// -// |<----------------- AudioFrames ------------------>| -// | audio buffer 0 | audio buffer 1 | audio buffer 2 | -class AudioFrames { - public: - virtual void SetFrameBuffer(Buffer* buffer) = 0; - virtual Buffer* FrameBuffer() = 0; - - protected: - AudioFrames() {} - virtual ~AudioFrames() {} -}; +typedef ContentDecryptionModule::Host Host; +const int kHostInterfaceVersion = Host::kVersion; } // namespace cdm diff --git a/cdm/include/host_4_file_io_client.h b/cdm/include/host_4_file_io_client.h new file mode 100644 index 00000000..b84eada4 --- /dev/null +++ b/cdm/include/host_4_file_io_client.h @@ -0,0 +1,54 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_HOST_4_FILE_IO_CLIENT_H_ +#define WVCDM_HOST_4_FILE_IO_CLIENT_H_ + +#include "content_decryption_module.h" +#include "wv_cdm_common.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class Host4FileIOClient : public cdm::FileIOClient { + public: + explicit Host4FileIOClient(cdm::Host_4* host) + : host_(host), + file_io_(NULL), + status_(kSuccess), + data_size_(0), + buffer_(NULL), + buffer_size_(0) {} + ~Host4FileIOClient(); + + bool Open(const std::string& name); + bool Read(char* buffer, size_t buffer_size); + bool ReadFileSize() { return Read(NULL, 0); } + bool Write(const char* data, size_t data_size); + bool Close(); + + // cdm::FileIOClient implementation + virtual void OnOpenComplete(Status status) OVERRIDE; + virtual void OnReadComplete(Status status, const uint8_t* data, + uint32_t data_size) OVERRIDE; + virtual void OnWriteComplete(Status status) OVERRIDE; + + // Get the result of the last operation + Status status() const { return status_; } + uint32_t data_size() const { return data_size_; } + + private: + cdm::Host_4* host_; + cdm::FileIO* file_io_; + + // These hold the result of the last operation + Status status_; + uint32_t data_size_; + char* buffer_; + size_t buffer_size_; + + CORE_DISALLOW_COPY_AND_ASSIGN(Host4FileIOClient); +}; + +} // namespace wvcdm + +#endif // WVCDM_HOST_4_FILE_IO_CLIENT_H_ diff --git a/cdm/include/wv_cdm_version.h b/cdm/include/wv_cdm_version.h index 553ecb71..2f46e19d 100644 --- a/cdm/include/wv_cdm_version.h +++ b/cdm/include/wv_cdm_version.h @@ -1,3 +1,3 @@ // Widevine CDM Kit Version - #define WV_CDM_VERSION "v2.1.8-0-829" + #define WV_CDM_VERSION "v2.2.0-0-903" diff --git a/cdm/include/wv_client_property_set.h b/cdm/include/wv_client_property_set.h new file mode 100644 index 00000000..532720ac --- /dev/null +++ b/cdm/include/wv_client_property_set.h @@ -0,0 +1,73 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_WV_CLIENT_PROPERTY_SET_H_ +#define WVCDM_CDM_WV_CLIENT_PROPERTY_SET_H_ + +#include "cdm_client_property_set.h" + +#include "wv_cdm_types.h" + +namespace wvcdm { + +class WVClientPropertySet : public CdmClientPropertySet { + public: + WVClientPropertySet() + : use_privacy_mode_(false) {} + + virtual ~WVClientPropertySet() {} + + void set_security_level(const std::string& securityLevel) { + security_level_ = securityLevel; + } + + virtual const std::string& security_level() const { + return security_level_; + } + + void set_use_privacy_mode(bool usePrivacyMode) { + use_privacy_mode_ = usePrivacyMode; + } + + virtual bool use_privacy_mode() const { + return use_privacy_mode_; + } + + void set_service_certificate(const std::string& serviceCertificate) { + service_certificate_ = serviceCertificate; + } + + virtual const std::string& service_certificate() const { + return service_certificate_; + } + + virtual bool is_session_sharing_enabled() const { + return true; // This is unused by common cdm but we need a definition + // for the pure virtual methods. + } + + void set_is_session_sharing_enabled(bool shareKeys) { + return; // This is unused by common cdm but we need a definition + // for the pure virtual methods. + } + + virtual uint32_t session_sharing_id() const { + return 1; // This is unused by common cdm but we need a + // definition for the pure virtual methods. + } + + virtual void set_session_sharing_id(uint32_t id) { + return; // This is unused by common cdm but we need a + // definition for the pure virtual methods. + } + + private: + CORE_DISALLOW_COPY_AND_ASSIGN(WVClientPropertySet); + + std::string security_level_; + bool use_privacy_mode_; + std::string service_certificate_; +}; + +} // namespace wvcdm + +#endif // WVCDM_CDM_WV_CLIENT_PROPERTY_SET_H_ diff --git a/cdm/include/wv_content_decryption_module.h b/cdm/include/wv_content_decryption_module_1.h similarity index 50% rename from cdm/include/wv_content_decryption_module.h rename to cdm/include/wv_content_decryption_module_1.h index f6cfb2a7..d2539eb3 100644 --- a/cdm/include/wv_content_decryption_module.h +++ b/cdm/include/wv_content_decryption_module_1.h @@ -1,7 +1,7 @@ // Copyright 2013 Google Inc. All Rights Reserved. -#ifndef WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_H_ -#define WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_H_ +#ifndef WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_1_H_ +#define WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_1_H_ #include "cdm_client_property_set.h" #include "cdm_engine.h" @@ -14,77 +14,19 @@ #include "wv_cdm_common.h" #include "wv_cdm_types.h" +#include "wv_client_property_set.h" namespace wvcdm { -class WVClientPropertySet : public wvcdm::CdmClientPropertySet { +class WvContentDecryptionModule_1 : public cdm::ContentDecryptionModule_1, + public IFileFactory, + public IClock { public: - WVClientPropertySet() - : use_privacy_mode_(false) {} + explicit WvContentDecryptionModule_1(cdm::Host_1* host); - virtual ~WVClientPropertySet() {} + virtual ~WvContentDecryptionModule_1(); - void set_security_level(const std::string& securityLevel) { - security_level_ = securityLevel; - } - - virtual const std::string& security_level() const { - return security_level_; - } - - void set_use_privacy_mode(bool usePrivacyMode) { - use_privacy_mode_ = usePrivacyMode; - } - - virtual bool use_privacy_mode() const { - return use_privacy_mode_; - } - - void set_service_certificate(const std::string& serviceCertificate) { - service_certificate_ = serviceCertificate; - } - - virtual const std::string& service_certificate() const { - return service_certificate_; - } - - virtual bool is_session_sharing_enabled() const { - return true; // This is unused by common cdm but we need a definition - // for the pure virtual methods. - } - - void set_is_session_sharing_enabled(bool shareKeys) { - return; // This is unused by common cdm but we need a definition - // for the pure virtual methods. - } - - virtual uint32_t session_sharing_id() const { - return 1; // This is unused by common cdm but we need a - // definition for the pure virtual methods. - } - - virtual void set_session_sharing_id(uint32_t id) { - return; // This is unused by common cdm but we need a - // definition for the pure virtual methods. - } - - private: - CORE_DISALLOW_COPY_AND_ASSIGN(WVClientPropertySet); - - std::string security_level_; - bool use_privacy_mode_; - std::string service_certificate_; -}; - -class WvContentDecryptionModule : public cdm::ContentDecryptionModule, - public IFileFactory, - public IClock { - public: - explicit WvContentDecryptionModule(cdm::Host* host); - - virtual ~WvContentDecryptionModule(); - - // cdm::ContentDecryptionModule implementation. + // cdm::ContentDecryptionModule_1 implementation. virtual cdm::Status GenerateKeyRequest(const char* type, int type_size, const uint8_t* init_data, int init_data_size) OVERRIDE; @@ -125,20 +67,46 @@ class WvContentDecryptionModule : public cdm::ContentDecryptionModule, virtual int64_t GetCurrentTimeInSeconds() OVERRIDE; + /* |parameters| is expected to be initialized with anything not related to + * subsample parsing. |iv| is initialized by the caller, but may be modified + * during decryption. |decrypted_block| may be NULL for L1 decrypts, since + * no data is passed back to the caller. */ cdm::Status DoSubsampleDecrypt(CdmDecryptionParameters& parameters, std::vector& iv, const cdm::InputBuffer& encrypted_buffer, cdm::DecryptedBlock* decrypted_block); + cdm::Status DoDecrypt(CdmDecryptionParameters& parameters, + std::vector& iv, + const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block); + + /* |parameters| is expected to be initialized with everything required by + * DoSubsampleDecrypt and DoDecrypt, plus |is_encrypted| and + * |subsample_flags|. Counters and |iv| will be updated to prepare for + * subsequent calls. */ + cdm::Status DecryptAndUpdateCounters(CdmDecryptionParameters& parameters, + std::vector& iv, + const cdm::InputBuffer& encrypted_buffer, + const size_t bytes, + cdm::DecryptedBlock* decrypted_block, + size_t& offset, + size_t& encrypted_offset, + uint32_t& block_ctr); + + void SetSizesAndAllocate(size_t output_size, + CdmDecryptionParameters& parameters, + cdm::DecryptedBlock* decrypted_block); + + cdm::Host_1* const host_; + HostEventListener host_event_listener_; CdmEngine cdm_engine_; - cdm::Host* const host_; - HostEventListener host_event_listener_; WVClientPropertySet property_set_; bool timer_enabled_; - CORE_DISALLOW_COPY_AND_ASSIGN(WvContentDecryptionModule); + CORE_DISALLOW_COPY_AND_ASSIGN(WvContentDecryptionModule_1); }; } // namespace wvcdm -#endif // WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_H_ +#endif // WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_1_H_ diff --git a/cdm/include/wv_content_decryption_module_4.h b/cdm/include/wv_content_decryption_module_4.h new file mode 100644 index 00000000..87e2a146 --- /dev/null +++ b/cdm/include/wv_content_decryption_module_4.h @@ -0,0 +1,155 @@ +// Copyright 2014 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_4_H_ +#define WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_4_H_ + +#include "content_decryption_module.h" + +#include "cdm_client_property_set.h" +#include "cdm_engine.h" +#include "cdm_host_clock.h" +#include "cdm_host_file.h" +#include "clock.h" +#include "wv_cdm_common.h" +#include "wv_cdm_event_listener.h" +#include "wv_cdm_types.h" +#include "wv_client_property_set.h" + +#if defined(UNIT_TEST) +# include "gtest/gtest_prod.h" +#endif + +namespace wvcdm { + +class WvContentDecryptionModule_4 : public cdm::ContentDecryptionModule_4, + public IFileFactory, + public IClock, + public WvCdmEventListener { + public: + explicit WvContentDecryptionModule_4(cdm::Host_4* host); + + virtual ~WvContentDecryptionModule_4(); + + virtual void CreateSession(uint32_t session_id, + const char* mime_type, uint32_t mime_type_size, + const uint8_t* init_data, uint32_t init_data_size, + cdm::SessionType session_type) OVERRIDE; + + virtual void LoadSession(uint32_t session_id, const char* web_session_id, + uint32_t web_session_id_length) OVERRIDE; + + virtual void UpdateSession(uint32_t session_id, const uint8_t* response, + uint32_t response_size) OVERRIDE; + + virtual bool IsKeyValid(const uint8_t* key_id, int key_id_size) OVERRIDE; + + virtual void ReleaseSession(uint32_t session_id) OVERRIDE; + + virtual void RemoveSession(uint32_t session_id, const char* web_session_id, + uint32_t web_session_id_length) OVERRIDE; + + virtual cdm::Status UsePrivacyMode() OVERRIDE; + + virtual cdm::Status SetServerCertificate( + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) OVERRIDE; + + virtual void TimerExpired(void* context) OVERRIDE; + + virtual cdm::Status Decrypt(const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_buffer) OVERRIDE; + + virtual cdm::Status DecryptDecodeAndRender( + const cdm::InputBuffer& encrypted_buffer, + cdm::StreamType stream_type) OVERRIDE; + + virtual void Destroy() OVERRIDE; + + // wvcdm::WvCdmEventListener implementation. + virtual void OnEvent(const CdmSessionId& session_id, + CdmEventType cdm_event) OVERRIDE; + + private: + class InternalSession { + public: + enum SessionType { + kDecrypt = 1, + kRelease = 2, + kProvision = 3, + }; + + InternalSession() : webid_(), session_type_(kDecrypt) {} + InternalSession(const std::string& webid, SessionType session_type) + : webid_(webid), session_type_(session_type) {} + + const std::string& webid() const { return webid_; } + bool is_decrypt() const { return session_type_ == kDecrypt; } + bool is_release() const { return session_type_ == kRelease; } + bool is_provision() const { return session_type_ == kProvision; } + + private: + std::string webid_; + SessionType session_type_; + }; + + void EnablePolicyTimer(); + void DisablePolicyTimer(); + + void CreateProvisionSession(uint32_t session_id); + void UpdateProvisionSession(uint32_t session_id, const uint8_t* response, + uint32_t response_size); + + bool CallOpenSession(uint32_t session_id); + + virtual File::Impl* NewFileImpl() OVERRIDE; + + virtual int64_t GetCurrentTimeInSeconds() OVERRIDE; + + /* |parameters| is expected to be initialized with anything not related to + * subsample parsing. |iv| is initialized by the caller, but may be modified + * during decryption. |decrypted_block| may be NULL for L1 decrypts, since + * no data is passed back to the caller. */ + cdm::Status DoSubsampleDecrypt(CdmDecryptionParameters& parameters, + std::vector& iv, + const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block); + cdm::Status DoDecrypt(CdmDecryptionParameters& parameters, + std::vector& iv, + const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block); + + /* |parameters| is expected to be initialized with everything required by + * DoSubsampleDecrypt and DoDecrypt, plus |is_encrypted| and + * |subsample_flags|. Counters and |iv| will be updated to prepare for + * subsequent calls. */ + cdm::Status DecryptAndUpdateCounters(CdmDecryptionParameters& parameters, + std::vector& iv, + const cdm::InputBuffer& encrypted_buffer, + const size_t bytes, + cdm::DecryptedBlock* decrypted_block, + size_t& offset, size_t& encrypted_offset, + uint32_t& block_ctr); + + void SetSizesAndAllocate(size_t output_size, + CdmDecryptionParameters& parameters, + cdm::DecryptedBlock* decrypted_block); + +#if defined(UNIT_TEST) + FRIEND_TEST(CdmApi4Test, UsePrivacyMode); + FRIEND_TEST(CdmApi4Test, UsePrivacyModeFailsWithOpenSessions); + FRIEND_TEST(CdmApi4Test, SetExplicitServerCertificate); + FRIEND_TEST(CdmApi4Test, SetServerCertificateFailsWithOpenSessions); +#endif + + cdm::Host_4* const host_; + WVClientPropertySet property_set_; + CdmEngine cdm_engine_; + std::map session_map_; + bool timer_enabled_; + + CORE_DISALLOW_COPY_AND_ASSIGN(WvContentDecryptionModule_4); +}; + +} // namespace wvcdm + +#endif // WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_4_H_ diff --git a/cdm/src/cdm_library_init.cpp b/cdm/src/cdm_library_init.cpp new file mode 100644 index 00000000..3ca26572 --- /dev/null +++ b/cdm/src/cdm_library_init.cpp @@ -0,0 +1,48 @@ +// Copyright 2014 Google Inc. All Rights Reserved. + +#include + +#include "wv_content_decryption_module_1.h" +#include "wv_content_decryption_module_4.h" + +#include "wv_cdm_version.h" + +void InitializeCdmModule() {} + +void DeinitializeCdmModule() {} + +void* CreateCdmInstance(int cdm_interface_version, const char* key_system, + uint32_t key_system_size, + GetCdmHostFunc get_cdm_host_func, void* user_data) { + void *host = NULL; + + switch (cdm_interface_version) { + case cdm::ContentDecryptionModule_1::kVersion: + host = get_cdm_host_func(cdm::Host_1::kVersion, user_data); + break; + + case cdm::ContentDecryptionModule_4::kVersion: + host = get_cdm_host_func(cdm::Host_4::kVersion, user_data); + break; + } + + if (!host) + return NULL; + + switch (cdm_interface_version) { + case cdm::ContentDecryptionModule_1::kVersion: + return new wvcdm::WvContentDecryptionModule_1( + static_cast(host)); + + case cdm::ContentDecryptionModule_4::kVersion: + return new wvcdm::WvContentDecryptionModule_4( + static_cast(host)); + } + + assert(false); // NOT REACHED + return NULL; +} + +const char* GetCdmVersion() { + return WV_CDM_VERSION; +} diff --git a/cdm/src/file_store.cpp b/cdm/src/file_store.cpp index 58a43f1a..49282862 100644 --- a/cdm/src/file_store.cpp +++ b/cdm/src/file_store.cpp @@ -1,73 +1,168 @@ // Copyright 2013 Google Inc. All Rights Reserved. -#include -#include #include "cdm_host_file.h" +#include +#include +#include + namespace wvcdm { IFileFactory* File::Impl::factory_ = NULL; // File::Impl() Section -// The file handler for cert.bin, aka DeviceCertificate is all we're -// setting up for now. +// The file handler for cert.bin, aka DeviceCertificate, and usage.bin, +// aka UsageTable, are all we're setting up for now. -bool File::Impl::Exists(const std::string& name) { +namespace { + +const char* const kDeviceCertificateKey = "DeviceCertificate"; +const char* const kUsageInfoKey = "UsageInfo"; +const char* const kUsageTableKey = "MockOemCryptoUsageTable"; +const char* const kGenerationNumberKey = "MockOemCryptoGenerationNumber"; + +const char* GetKeyForFileName(const std::string& name) { if (name == "cert.bin") { + return kDeviceCertificateKey; + } else if (name == "usage.bin") { + return kUsageInfoKey; + } else if (name == "UsageTable.dat") { + return kUsageTableKey; + } else if (name == "GenerationNumber.dat") { + return kGenerationNumberKey; + } else { + return ""; + } +} + +} // namespace + +// File1 implmentation. +bool File1Impl::Open(const std::string& name) { + if (name.empty()) + return false; + fname_ = name; + return true; +} + +ssize_t File1Impl::Read(char* buffer, size_t bytes) { + std::string key(GetKeyForFileName(fname_)); + if (key.length() > 0) { std::string value; - host_->GetPlatformString("DeviceCertificate", &value); - return !value.empty(); + host_1_->GetPlatformString(key, &value); + size_t bytes_to_copy = std::min(bytes, value.size()); + memcpy(buffer, value.data(), bytes_to_copy); + return value.size() ? bytes_to_copy : -1; + } else { + return -1; } - return false; } -bool File::Impl::Open(const std::string& name) { - if (name == "cert.bin") { - fname_ = name; +bool File1Impl::Remove(const std::string& name) { + std::string key(GetKeyForFileName(name)); + if (key.length() > 0) { + host_1_->SetPlatformString(key, ""); return true; + } else { + return false; } - return false; } -bool File::Impl::Close() { +ssize_t File1Impl::FileSize(const std::string& name) { + std::string key(GetKeyForFileName(name)); + if (key.length() > 0) { + std::string value; + host_1_->GetPlatformString(key, &value); + return value.empty() ? -1 : value.size(); + } else { + return -1; + } +} + +ssize_t File1Impl::Write(const char* buffer, size_t bytes) { + std::string key(GetKeyForFileName(fname_)); + if (key.length() > 0) { + std::string value(buffer, bytes); + host_1_->SetPlatformString(key, value); + return bytes; + } else { + return -1; + } +} + +bool File1Impl::Close() { fname_.clear(); return true; } -bool File::Impl::Remove(const std::string& name) { - if (name == "cert.bin") { - host_->SetPlatformString("DeviceCertificate", ""); - return true; +// File 4 Implementation. +bool File4Impl::Open(const std::string& name) { + return host_4_file_io_client_.Open(name); +} + +ssize_t File4Impl::Read(char* buffer, size_t bytes) { + if (host_4_file_io_client_.Read(buffer, bytes) && + host_4_file_io_client_.data_size() > 0) { + return std::min(host_4_file_io_client_.data_size(), bytes); + } else { + return -1; } - return false; +} + +ssize_t File4Impl::Write(const char* buffer, size_t bytes) { + if (host_4_file_io_client_.Write(buffer, bytes)) { + return bytes; + } else { + return -1; + } +} + +bool File4Impl::Close() { + return host_4_file_io_client_.Close(); +} + +bool File4Impl::Remove(const std::string& name) { + Host4FileIOClient file_io_client(host_4_); + return file_io_client.Open(name) && file_io_client.Write("", 0); +} + +ssize_t File4Impl::FileSize(const std::string& name) { + Host4FileIOClient file_io_client(host_4_); + if (file_io_client.Open(name) && file_io_client.ReadFileSize() && + file_io_client.data_size() > 0) { + return file_io_client.data_size(); + } else { + return -1; + } +} + +// Common file implementation. +bool File::Impl::Open(const std::string& name) { + return file_api_->Open(name); } ssize_t File::Impl::Read(char* buffer, size_t bytes) { - if (fname_ == "cert.bin") { - std::string value; - host_->GetPlatformString("DeviceCertificate", &value); - memcpy(buffer, value.data(), std::min(bytes, value.size())); - return value.size() ? value.size() : -1; - } - return -1; + return file_api_->Read(buffer, bytes); } ssize_t File::Impl::Write(const char* buffer, size_t bytes) { - if (fname_ == "cert.bin") { - std::string value(buffer, bytes); - host_->SetPlatformString("DeviceCertificate", value); - return bytes; - } - return -1; + return file_api_->Write(buffer, bytes); +} + +bool File::Impl::Close() { + return file_api_->Close(); +} + +bool File::Impl::Exists(const std::string& name) { + return FileSize(name) > 0; +} + +bool File::Impl::Remove(const std::string& name) { + return file_api_->Remove(name); } ssize_t File::Impl::FileSize(const std::string& name) { - if (name == "cert.bin") { - std::string value; - host_->GetPlatformString("DeviceCertificate", &value); - return value.size() ? value.size() : -1; - } - return -1; + return file_api_->FileSize(name); } File::File() : impl_(File::Impl::factory_->NewFileImpl()) {} @@ -81,9 +176,7 @@ bool File::Open(const std::string& name, int flags) { return impl_->Open(name); } -void File::Close() { - impl_->Close(); -} +void File::Close() { impl_->Close(); } ssize_t File::Read(char* buffer, size_t bytes) { return impl_->Read(buffer, bytes); @@ -93,13 +186,9 @@ ssize_t File::Write(const char* buffer, size_t bytes) { return impl_->Write(buffer, bytes); } -bool File::Exists(const std::string& path) { - return impl_->Exists(path); -} +bool File::Exists(const std::string& path) { return impl_->Exists(path); } -bool File::Remove(const std::string& path) { - return impl_->Remove(path); -} +bool File::Remove(const std::string& path) { return impl_->Remove(path); } bool File::Copy(const std::string& from, const std::string& to) { // Required for linkage only - no API implementation is needed by the CDM diff --git a/cdm/src/host_4_file_io_client.cpp b/cdm/src/host_4_file_io_client.cpp new file mode 100644 index 00000000..b2057e9f --- /dev/null +++ b/cdm/src/host_4_file_io_client.cpp @@ -0,0 +1,66 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "host_4_file_io_client.h" + +#include +#include + +namespace wvcdm { + +Host4FileIOClient::~Host4FileIOClient() { + if (file_io_ != NULL) Close(); +} + +bool Host4FileIOClient::Open(const std::string& name) { + if (host_ == NULL) return false; + if (file_io_ != NULL) return false; + file_io_ = host_->CreateFileIO(this); + file_io_->Open(name.data(), name.length()); + return status_ == kSuccess; +} + +bool Host4FileIOClient::Read(char* buffer, size_t buffer_size) { + if (file_io_ == NULL) return false; + buffer_ = buffer; + buffer_size_ = buffer_size; + file_io_->Read(); + return status_ == kSuccess; +} + +bool Host4FileIOClient::Write(const char* data, size_t data_size) { + if (file_io_ == NULL) return false; + file_io_->Write(reinterpret_cast(data), data_size); + return status_ == kSuccess; +} + +bool Host4FileIOClient::Close() { + if (file_io_ == NULL) return false; + file_io_->Close(); + file_io_ = NULL; + status_ = kSuccess; + data_size_ = 0; + return true; +} + +void Host4FileIOClient::OnOpenComplete(Status status) { + status_ = status; + data_size_ = 0; +} + +void Host4FileIOClient::OnReadComplete(Status status, const uint8_t* data, + uint32_t data_size) { + status_ = status; + data_size_ = data_size; + if (buffer_ != NULL) { + memcpy(buffer_, data, std::min(data_size, buffer_size_)); + buffer_ = NULL; + buffer_size_ = 0; + } +} + +void Host4FileIOClient::OnWriteComplete(Status status) { + status_ = status; + data_size_ = 0; +} + +} // namespace wvcdm diff --git a/cdm/src/wv_content_decryption_module.cpp b/cdm/src/wv_content_decryption_module_1.cpp similarity index 55% rename from cdm/src/wv_content_decryption_module.cpp rename to cdm/src/wv_content_decryption_module_1.cpp index e53386a6..94385c8f 100644 --- a/cdm/src/wv_content_decryption_module.cpp +++ b/cdm/src/wv_content_decryption_module_1.cpp @@ -1,20 +1,19 @@ // Copyright 2013 Google Inc. All Rights Reserved. -#include "wv_content_decryption_module.h" +#include "wv_content_decryption_module_1.h" +#include #include -#include - #include "initialization_data.h" #include "log.h" #include "OEMCryptoCENC.h" #include "properties.h" #include "wv_cdm_constants.h" #include "wv_cdm_types.h" -#include "wv_cdm_version.h" namespace { + enum { // individual error codes kAttachEventListenerError = 0x0001, @@ -24,31 +23,6 @@ enum { kGenerateKeyRequestErrorBase = 0x0200, kAddKeyErrorBase = 0x0300, }; -} // namespace - -void INITIALIZE_CDM_MODULE() {} - -void DeinitializeCdmModule() {} - -void* CreateCdmInstance(int cdm_interface_version, const char* key_system, - int key_system_size, GetCdmHostFunc get_cdm_host_func, - void* user_data) { - if (cdm_interface_version != cdm::kCdmInterfaceVersion) return NULL; - - cdm::Host* host = static_cast( - get_cdm_host_func(cdm::kHostInterfaceVersion, user_data)); - - if (!host) return NULL; - - return static_cast( - new wvcdm::WvContentDecryptionModule(host)); -} - -int GetCdmVersion() { return cdm::kCdmInterfaceVersion; } - -namespace { - -static const std::string kWvCdmVersionString(WV_CDM_VERSION); const int kCdmPolicyTimerDurationSeconds = 5; @@ -58,10 +32,8 @@ const uint32_t kCencIvSize = 8; const uint32_t kIvSize = 16; bool Ctr128Add(size_t block_count, uint8_t* counter) { - if (NULL == counter) - return false; - if (0 == block_count) - return true; + if (NULL == counter) return false; + if (0 == block_count) return true; uint8_t carry = 0; uint8_t n = kIvSize - 1; while (n >= kCencIvSize) { @@ -79,35 +51,33 @@ bool Ctr128Add(size_t block_count, uint8_t* counter) { return true; } -} // namespace +} // namespace namespace wvcdm { -// An empty iv string signals that the frame is unencrypted. -bool IsBufferEncrypted(const cdm::InputBuffer& input_buffer) { - return input_buffer.iv_size != 0; -} +// cdm::ContentDecryptionModule_1 implementation. -// cdm::ContentDecryptionModule implementation. - -WvContentDecryptionModule::WvContentDecryptionModule(cdm::Host* host) +WvContentDecryptionModule_1::WvContentDecryptionModule_1(cdm::Host_1* host) : host_(host), host_event_listener_(host, &cdm_engine_), + cdm_engine_(), + property_set_(), timer_enabled_(false) { HostClock::SetClockInterface(this); } -WvContentDecryptionModule::~WvContentDecryptionModule() { +WvContentDecryptionModule_1::~WvContentDecryptionModule_1() { DisablePolicyTimer(); } -cdm::Status WvContentDecryptionModule::GenerateKeyRequest( +cdm::Status WvContentDecryptionModule_1::GenerateKeyRequest( const char* type, int type_size, const uint8_t* init_data, int init_data_size) { - LOGI("WvContentDecryptionModule::GenerateKeyRequest()"); + LOGI("WvContentDecryptionModule_1::GenerateKeyRequest()"); CdmInitData init_data_internal(reinterpret_cast(init_data), init_data_size); - InitializationData initialization_data(ISO_BMFF_VIDEO_MIME_TYPE, + std::string type_string(type, type_size); + InitializationData initialization_data(type_string, init_data_internal); CdmKeyMessage key_request; CdmSessionId session_id; @@ -124,8 +94,8 @@ cdm::Status WvContentDecryptionModule::GenerateKeyRequest( property_set_.set_use_privacy_mode(privacy_mode == "True" ? 1 : 0); property_set_.set_service_certificate(service_certificate); - CdmResponseType result = - cdm_engine_.OpenSession("com.widevine.alpha", &property_set_, &session_id); + CdmResponseType result = cdm_engine_.OpenSession("com.widevine.alpha", + &property_set_, &session_id); if (result == NEED_PROVISIONING) { LOGI("Need to aquire a Device Certificate from the Provisioning Server"); @@ -150,7 +120,7 @@ cdm::Status WvContentDecryptionModule::GenerateKeyRequest( result = cdm_engine_.GenerateKeyRequest( session_id, key_set_id, initialization_data, kLicenseTypeStreaming, - app_parameters, &key_request, &server_url); + app_parameters, &key_request, &server_url, NULL); if (KEY_MESSAGE != result) { cdm_engine_.CloseSession(session_id); host_->SendKeyError("", 0, cdm::kClientError, @@ -164,18 +134,16 @@ cdm::Status WvContentDecryptionModule::GenerateKeyRequest( return cdm::kSuccess; } -cdm::Status WvContentDecryptionModule::AddKey(const char* session_id, - int session_id_size, - const uint8_t* key, int key_size, - const uint8_t* key_id, - int key_id_size) { - LOGI("WvContentDecryptionModule::AddKey()"); +cdm::Status WvContentDecryptionModule_1::AddKey( + const char* session_id, int session_id_size, const uint8_t* key, + int key_size, const uint8_t* key_id, int key_id_size) { + LOGI("WvContentDecryptionModule_1::AddKey()"); CdmSessionId session_id_internal(session_id, session_id_size); CdmKeyResponse key_data((const char*)key, key_size); CdmKeySetId key_set_id; - CdmResponseType response = cdm_engine_.AddKey(session_id_internal, - key_data, &key_set_id); + CdmResponseType response = + cdm_engine_.AddKey(session_id_internal, key_data, &key_set_id); if (response == KEY_ADDED) { EnablePolicyTimer(); @@ -187,66 +155,58 @@ cdm::Status WvContentDecryptionModule::AddKey(const char* session_id, return cdm::kSessionError; } -bool WvContentDecryptionModule::IsKeyValid(const uint8_t* key_id, - int key_id_size) { +bool WvContentDecryptionModule_1::IsKeyValid(const uint8_t* key_id, + int key_id_size) { KeyId key(reinterpret_cast(key_id), key_id_size); return cdm_engine_.IsKeyLoaded(key); } -cdm::Status WvContentDecryptionModule::CloseSession(const char* session_id, - int session_id_size) { - LOGI("WvContentDecryptionModule::CloseSession()"); +cdm::Status WvContentDecryptionModule_1::CloseSession(const char* session_id, + int session_id_size) { + LOGI("WvContentDecryptionModule_1::CloseSession()"); CdmSessionId session_id_internal(session_id, session_id_size); return cdm_engine_.CloseSession(session_id_internal) == NO_ERROR ? cdm::kSuccess : cdm::kSessionError; } -void WvContentDecryptionModule::TimerExpired(void* context) { - LOGI("WvContentDecryptionModule::TimerExpired()"); +void WvContentDecryptionModule_1::TimerExpired(void* context) { + LOGI("WvContentDecryptionModule_1::TimerExpired()"); if (timer_enabled_) { cdm_engine_.OnTimerEvent(); host_->SetTimer(kCdmPolicyTimerDurationSeconds * 1000, NULL); } } -cdm::Status WvContentDecryptionModule::Decrypt( +cdm::Status WvContentDecryptionModule_1::Decrypt( const cdm::InputBuffer& encrypted_buffer, cdm::DecryptedBlock* decrypted_block) { - LOGI("WvContentDecryptionModule::Decrypt()"); + LOGI("WvContentDecryptionModule_1::Decrypt()"); if (static_cast(encrypted_buffer.iv_size) != KEY_IV_SIZE) return cdm::kDecryptError; - std::vector < uint8_t > iv(KEY_IV_SIZE); + std::vector iv(KEY_IV_SIZE); memcpy(&iv[0], encrypted_buffer.iv, encrypted_buffer.iv_size); - KeyId key_id(reinterpret_cast(encrypted_buffer.key_id), encrypted_buffer.key_id_size); - CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id? - - if (!encrypted_buffer.subsamples || - encrypted_buffer.num_subsamples <= 0) - return cdm::kDecryptError; - - CdmDecryptionParameters parameters(&key_id, - encrypted_buffer.data, 0, &iv, 0, + CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0, NULL); parameters.is_secure = Properties::oem_crypto_use_secure_buffers(); - return DoSubsampleDecrypt(parameters, - iv, - encrypted_buffer, - decrypted_block); + if (encrypted_buffer.num_subsamples) + return DoSubsampleDecrypt(parameters, iv, encrypted_buffer, decrypted_block); + else + return DoDecrypt(parameters, iv, encrypted_buffer, decrypted_block); } // This is the Level 1 API. When the host application calls the CDM's // DecryptDecodeAndRenderFrame(), rather than the CDM's Decrypt(), // OEMCrypto_DecryptCTR() will be told to use direct rendering with no // cleartext in the return. -cdm::Status WvContentDecryptionModule::DecryptDecodeAndRenderFrame( +cdm::Status WvContentDecryptionModule_1::DecryptDecodeAndRenderFrame( const cdm::InputBuffer& encrypted_buffer) { - LOGI("WvContentDecryptionModule::DecryptDecodeAndRenderFrame()"); + LOGI("WvContentDecryptionModule_1::DecryptDecodeAndRenderFrame()"); if (static_cast(encrypted_buffer.iv_size) != KEY_IV_SIZE) return cdm::kDecryptError; @@ -255,24 +215,23 @@ cdm::Status WvContentDecryptionModule::DecryptDecodeAndRenderFrame( memcpy(&iv[0], encrypted_buffer.iv, encrypted_buffer.iv_size); KeyId key_id(reinterpret_cast(encrypted_buffer.key_id), encrypted_buffer.key_id_size); - CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id. - - if (!encrypted_buffer.subsamples || - encrypted_buffer.num_subsamples <= 0) - return cdm::kDecryptError; CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0, NULL); - return DoSubsampleDecrypt(parameters, iv, encrypted_buffer, NULL); + + if (encrypted_buffer.num_subsamples) + return DoSubsampleDecrypt(parameters, iv, encrypted_buffer, NULL); + else + return DoDecrypt(parameters, iv, encrypted_buffer, NULL); } // This is the Level 1 API. When the host application calls the CDM's // DecryptDecodeAndRenderSamples(), rather than the CDM's Decrypt(), // OEMCrypto_DecryptCTR() will be told to use direct rendering with no cleartext // in the return. -cdm::Status WvContentDecryptionModule::DecryptDecodeAndRenderSamples( +cdm::Status WvContentDecryptionModule_1::DecryptDecodeAndRenderSamples( const cdm::InputBuffer& encrypted_buffer) { - LOGI("WvContentDecryptionModule::DecryptDecodeAndRenderSamples()"); + LOGI("WvContentDecryptionModule_1::DecryptDecodeAndRenderSamples()"); if (static_cast(encrypted_buffer.iv_size) != KEY_IV_SIZE) return cdm::kDecryptError; @@ -281,92 +240,78 @@ cdm::Status WvContentDecryptionModule::DecryptDecodeAndRenderSamples( memcpy(&iv[0], encrypted_buffer.iv, encrypted_buffer.iv_size); KeyId key_id(reinterpret_cast(encrypted_buffer.key_id), encrypted_buffer.key_id_size); - CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id. - - if (!encrypted_buffer.subsamples || - encrypted_buffer.num_subsamples <= 0) - return cdm::kDecryptError; CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0, NULL); parameters.is_video = false; // override the default true value for audio. - return DoSubsampleDecrypt(parameters, iv, encrypted_buffer, NULL); + + if (encrypted_buffer.num_subsamples) + return DoSubsampleDecrypt(parameters, iv, encrypted_buffer, NULL); + else + return DoDecrypt(parameters, iv, encrypted_buffer, NULL); } -void WvContentDecryptionModule::Destroy() { delete this; } +void WvContentDecryptionModule_1::Destroy() { delete this; } // Provisioning related methods -cdm::Status WvContentDecryptionModule::GetProvisioningRequest( +cdm::Status WvContentDecryptionModule_1::GetProvisioningRequest( std::string* request, std::string* provisioning_server_url) { CdmCertificateType cert_type = kCertificateWidevine; std::string cert_authority; - if (cdm_engine_.GetProvisioningRequest(cert_type, cert_authority, - static_cast(request), - provisioning_server_url) == NO_ERROR) { + if (cdm_engine_.GetProvisioningRequest( + cert_type, cert_authority, + static_cast(request), + provisioning_server_url) == NO_ERROR) { return cdm::kSuccess; } return cdm::kSessionError; } -cdm::Status WvContentDecryptionModule::HandleProvisioningResponse( +cdm::Status WvContentDecryptionModule_1::HandleProvisioningResponse( std::string& response) { std::string cert, wrapped_key; if (cdm_engine_.HandleProvisioningResponse( - static_cast(response), &cert, &wrapped_key) == - NO_ERROR) { + static_cast(response), &cert, + &wrapped_key) == NO_ERROR) { return cdm::kSuccess; } return cdm::kSessionError; } -void WvContentDecryptionModule::EnablePolicyTimer() { - LOGI("WvContentDecryptionModule::EnablePolicyTimer()"); +void WvContentDecryptionModule_1::EnablePolicyTimer() { + LOGI("WvContentDecryptionModule_1::EnablePolicyTimer()"); if (!timer_enabled_) { timer_enabled_ = true; host_->SetTimer(kCdmPolicyTimerDurationSeconds * 1000, NULL); } } -void WvContentDecryptionModule::DisablePolicyTimer() { - LOGI("WvContentDecryptionModule::DisablePolicyTimer()"); +void WvContentDecryptionModule_1::DisablePolicyTimer() { + LOGI("WvContentDecryptionModule_1::DisablePolicyTimer()"); timer_enabled_ = false; } -cdm::Status WvContentDecryptionModule::DoSubsampleDecrypt( - CdmDecryptionParameters& parameters, - std::vector < uint8_t >& iv, +cdm::Status WvContentDecryptionModule_1::DoSubsampleDecrypt( + CdmDecryptionParameters& parameters, std::vector& iv, const cdm::InputBuffer& encrypted_buffer, cdm::DecryptedBlock* decrypted_block) { - - /* This routine assumes session_id and iv have already - been initialized by the caller and encrypted_buffer contains subsample - information. Also, parameters is expected to be pre-initialized with any - needed parameters not related to subsample parsing. - decrypted_block may be NULL. */ - - CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id. - CdmResponseType status = NO_ERROR; + if (!encrypted_buffer.subsamples) + return cdm::kDecryptError; size_t output_size = 0; for (int i = 0; i < encrypted_buffer.num_subsamples; ++i) { const cdm::SubsampleEntry& subsample = encrypted_buffer.subsamples[i]; output_size += subsample.cipher_bytes + subsample.clear_bytes; } - parameters.decrypt_buffer_length = output_size; + assert(output_size == + encrypted_buffer.data_size - encrypted_buffer.data_offset); - if (decrypted_block) { - cdm::Buffer* buffer = host_->Allocate(output_size); - decrypted_block->SetDecryptedBuffer(buffer); - parameters.decrypt_buffer = buffer->Data(); - } else { - parameters.decrypt_buffer = NULL; - } + SetSizesAndAllocate(output_size, parameters, decrypted_block); size_t offset = 0; size_t encrypted_offset = 0; uint32_t block_ctr = 0; - const cdm::SubsampleEntry *subsamples = encrypted_buffer.subsamples; - bool first = true; + const cdm::SubsampleEntry* subsamples = encrypted_buffer.subsamples; for (int i = 0; i < encrypted_buffer.num_subsamples; ++i) { const cdm::SubsampleEntry& subsample = subsamples[i]; @@ -374,53 +319,103 @@ cdm::Status WvContentDecryptionModule::DoSubsampleDecrypt( for (int is_encrypted = 0; is_encrypted < 2; ++is_encrypted) { size_t bytes = is_encrypted ? subsample.cipher_bytes : subsample.clear_bytes; - if (0 == bytes) - continue; - if (is_encrypted) { - uint32_t counter = encrypted_offset / kIvSize; - Ctr128Add(counter - block_ctr, &iv[0]); - block_ctr = counter; - } - - parameters.encrypt_buffer = - &encrypted_buffer.data[encrypted_buffer.data_offset + offset]; - parameters.decrypt_buffer_offset = offset; - parameters.encrypt_length = bytes; - parameters.block_offset = encrypted_offset % kIvSize; - - offset += bytes; - if (is_encrypted) - encrypted_offset += bytes; + if (bytes == 0) continue; parameters.is_encrypted = is_encrypted; parameters.subsample_flags = - first ? OEMCrypto_FirstSubsample : 0; - parameters.subsample_flags |= ( - offset == static_cast(encrypted_buffer.data_size) - ? OEMCrypto_LastSubsample : 0); + ((offset == 0) ? OEMCrypto_FirstSubsample : 0) | + ((offset + bytes == output_size) ? OEMCrypto_LastSubsample : 0); - first = false; - - status = cdm_engine_.Decrypt(session_id, parameters); - - switch (status) { - case wvcdm::NEED_KEY: - return cdm::kNoKey; - case wvcdm::NO_ERROR: - break; - default: - return cdm::kDecryptError; - } + cdm::Status status = + DecryptAndUpdateCounters(parameters, iv, encrypted_buffer, + bytes, decrypted_block, offset, + encrypted_offset, block_ctr); + if (status != cdm::kSuccess) + return status; } } return cdm::kSuccess; } -int64_t WvContentDecryptionModule::GetCurrentTimeInSeconds() { +cdm::Status WvContentDecryptionModule_1::DoDecrypt( + CdmDecryptionParameters& parameters, std::vector& iv, + const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block) { + assert(!encrypted_buffer.subsamples); + size_t output_size = + encrypted_buffer.data_size - encrypted_buffer.data_offset; + SetSizesAndAllocate(output_size, parameters, decrypted_block); + + size_t offset = 0; + size_t encrypted_offset = 0; + uint32_t block_ctr = 0; + + parameters.is_encrypted = true; + parameters.subsample_flags = + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample; + return DecryptAndUpdateCounters(parameters, iv, encrypted_buffer, + output_size, decrypted_block, offset, + encrypted_offset, block_ctr); +} + +void WvContentDecryptionModule_1::SetSizesAndAllocate( + size_t output_size, + CdmDecryptionParameters& parameters, + cdm::DecryptedBlock* decrypted_block) { + parameters.decrypt_buffer_length = output_size; + + if (decrypted_block) { + cdm::Buffer* buffer = host_->Allocate(output_size); + buffer->SetSize(output_size); + decrypted_block->SetDecryptedBuffer(buffer); + parameters.decrypt_buffer = buffer->Data(); + } else { + parameters.decrypt_buffer = NULL; + } +} + +cdm::Status WvContentDecryptionModule_1::DecryptAndUpdateCounters( + CdmDecryptionParameters& parameters, + std::vector& iv, + const cdm::InputBuffer& encrypted_buffer, + const size_t bytes, + cdm::DecryptedBlock* decrypted_block, + size_t& offset, + size_t& encrypted_offset, + uint32_t& block_ctr) { + if (parameters.is_encrypted) { + uint32_t counter = encrypted_offset / kIvSize; + Ctr128Add(counter - block_ctr, &iv[0]); + block_ctr = counter; + } + + parameters.encrypt_buffer = + &encrypted_buffer.data[encrypted_buffer.data_offset + offset]; + parameters.decrypt_buffer_offset = offset; + parameters.encrypt_length = bytes; + parameters.block_offset = encrypted_offset % kIvSize; + + offset += bytes; + if (parameters.is_encrypted) encrypted_offset += bytes; + + CdmSessionId session_id; // cdm_engine will locate via key_id. + CdmResponseType status = cdm_engine_.Decrypt(session_id, parameters); + + switch (status) { + case wvcdm::NEED_KEY: + return cdm::kNoKey; + case wvcdm::NO_ERROR: + return cdm::kSuccess; + default: + return cdm::kDecryptError; + } +} + +int64_t WvContentDecryptionModule_1::GetCurrentTimeInSeconds() { return host_->GetCurrentWallTimeInSeconds(); } -File::Impl* WvContentDecryptionModule::NewFileImpl() { +File::Impl* WvContentDecryptionModule_1::NewFileImpl() { return new File::Impl(host_); } diff --git a/cdm/src/wv_content_decryption_module_4.cpp b/cdm/src/wv_content_decryption_module_4.cpp new file mode 100644 index 00000000..afcc3a72 --- /dev/null +++ b/cdm/src/wv_content_decryption_module_4.cpp @@ -0,0 +1,597 @@ +// Copyright 2014 Google Inc. All Rights Reserved. + +#include "wv_content_decryption_module_4.h" + +#include + +#include "log.h" +#include "properties.h" +#include "wv_cdm_constants.h" + +namespace { +enum { + // individual error codes + kAttachEventListenerError = 0x0001, + kLicenseExpiredNotification = 0x0002, + kSessionNotFoundError = 0x0003, + kSessionAlreadyExistsError = 0x0004, + kInvalidParameter = 0x0005, + kNotImplemented = 0x0006, + + // error classes to be OR'd with cdm engine result values + kOpenSessionErrorBase = 0x0100, + kGenerateKeyRequestErrorBase = 0x0200, + kGenerateRenewalRequestErrorBase = 0x0300, + kUpdateSessionErrorBase = 0x0400, + kRestoreKeyErrorBase = 0x0500, + kOpenKeySetSessionErrorBase = 0x0600, + kGetProvisioningRequestErrorBase = 0x0700, + kHandleProvisioningResponseErrorBase = 0x0800, +}; + +const int kCdmPolicyTimerDurationSeconds = 5; + +// The iso spec only uses the lower 8 bytes of the iv as +// the counter. +const uint32_t kCencIvSize = 8; +const uint32_t kIvSize = 16; + +void Ctr128Add(size_t block_count, uint8_t* counter) { + assert(NULL != counter); + if (0 == block_count) return; + uint8_t carry = 0; + uint8_t n = kIvSize - 1; + while (n >= kCencIvSize) { + uint32_t temp = block_count & 0xff; + temp += counter[n]; + temp += carry; + counter[n] = temp & 0xff; + carry = (temp & 0x100) ? 1 : 0; + block_count = block_count >> 8; + n--; + if (!block_count && !carry) { + break; + } + } +} + +} // namespace + +namespace wvcdm { + +// cdm::ContentDecryptionModule_4 implementation. +WvContentDecryptionModule_4::WvContentDecryptionModule_4(cdm::Host_4* host) + : host_(host), timer_enabled_(false) { + HostClock::SetClockInterface(this); +} + +WvContentDecryptionModule_4::~WvContentDecryptionModule_4() { + DisablePolicyTimer(); +} + +void WvContentDecryptionModule_4::CreateSession(uint32_t session_id, + const char* mime_type, + uint32_t mime_type_size, + const uint8_t* init_data, + uint32_t init_data_size, + cdm::SessionType session_type) { + if (session_type == cdm::kProvisioning) { + // CreateProvisionSession() dispatches its own errors to the host. + CreateProvisionSession(session_id); + } else { + CdmLicenseType license_type; + switch (session_type) { + case cdm::kTemporary: + license_type = kLicenseTypeStreaming; + break; + case cdm::kPersistent: + license_type = kLicenseTypeOffline; + break; + default: + LOGE( + "WvContentDecryptionModule_4::CreateSession: Unsupported session " + "type ", + session_type); + host_->OnSessionError(session_id, cdm::kSessionError, + kInvalidParameter); + return; + } + + if (!CallOpenSession(session_id)) { + // CallOpenSession() dispatches its own errors to the host. + return; + } + + CdmSessionId internal_session_id = session_map_[session_id].webid(); + CdmKeySetId empty_key_set_id; + + std::string mime_type_string(mime_type, mime_type_size); + CdmInitData init_data_internal(reinterpret_cast(init_data), + init_data_size); + InitializationData initialization_data(mime_type_string, + init_data_internal); + + CdmAppParameterMap empty_app_parameters; + CdmKeyMessage key_request; + std::string server_url; + CdmKeySetId key_set_id; + + CdmResponseType result = cdm_engine_.GenerateKeyRequest( + internal_session_id, empty_key_set_id, initialization_data, + license_type, empty_app_parameters, &key_request, &server_url, + &key_set_id); + if (KEY_MESSAGE == result) { + if (session_type == cdm::kPersistent) { + host_->OnSessionCreated(session_id, key_set_id.c_str(), + key_set_id.length()); + } else { + host_->OnSessionCreated(session_id, internal_session_id.c_str(), + internal_session_id.length()); + } + host_->OnSessionMessage(session_id, key_request.c_str(), + key_request.length(), server_url.c_str(), + server_url.length()); + } else { + cdm_engine_.CloseSession(internal_session_id); + session_map_.erase(session_id); + host_->OnSessionError(session_id, cdm::kSessionError, + kGenerateKeyRequestErrorBase | result); + } + } +} + +void WvContentDecryptionModule_4::LoadSession(uint32_t session_id, + const char* web_session_id, + uint32_t web_session_id_length) { + if (!CallOpenSession(session_id)) { + // CallOpenSession() dispatches its own errors to the host. + return; + } + CdmSessionId internal_session_id = session_map_[session_id].webid(); + + CdmKeySetId key_set_id(web_session_id, web_session_id_length); + CdmResponseType result = + cdm_engine_.RestoreKey(internal_session_id, key_set_id); + if (result != KEY_ADDED) { + cdm_engine_.CloseSession(internal_session_id); + session_map_.erase(session_id); + host_->OnSessionError(session_id, cdm::kSessionError, + kRestoreKeyErrorBase | result); + return; + } + + host_->OnSessionCreated(session_id, web_session_id, web_session_id_length); +} + +void WvContentDecryptionModule_4::UpdateSession(uint32_t session_id, + const uint8_t* response, + uint32_t response_size) { + LOGI("WvContentDecryptionModule_4::UpdateSession()"); + if (session_map_.count(session_id) == 0) { + host_->OnSessionError(session_id, cdm::kSessionError, + kSessionNotFoundError); + return; + } + const InternalSession& session = session_map_[session_id]; + + if (session.is_provision()) { + // UpdateProvisionSession() dispatches its own errors to the host. + UpdateProvisionSession(session_id, response, response_size); + } else { + bool is_release = session.is_release(); + + CdmSessionId session_id_internal; + CdmKeySetId key_set_id; + if (!is_release) { + session_id_internal = session.webid(); + } else { + key_set_id = session.webid(); + } + CdmKeyResponse key_data((const char*)response, response_size); + + CdmResponseType status = + cdm_engine_.AddKey(session_id_internal, key_data, &key_set_id); + + if (status != KEY_ADDED) { + host_->OnSessionError(session_id, cdm::kSessionError, + status | kUpdateSessionErrorBase); + return; + } + if (!is_release) EnablePolicyTimer(); + host_->OnSessionUpdated(session_id); + } +} + +bool WvContentDecryptionModule_4::IsKeyValid(const uint8_t* key_id, + int key_id_size) { + KeyId cdm_key_id(reinterpret_cast(key_id), key_id_size); + return cdm_engine_.IsKeyLoaded(cdm_key_id); +} + +void WvContentDecryptionModule_4::ReleaseSession(uint32_t session_id) { + if (session_map_.count(session_id) == 0) { + host_->OnSessionError(session_id, cdm::kSessionError, + kSessionNotFoundError); + return; + } + if (session_map_[session_id].is_release()) { + cdm_engine_.CloseKeySetSession(session_map_[session_id].webid()); + } else if (session_map_[session_id].is_decrypt()) { + cdm_engine_.CloseSession(session_map_[session_id].webid()); + } + host_->OnSessionClosed(session_id); + session_map_.erase(session_id); +} + +void WvContentDecryptionModule_4::RemoveSession( + uint32_t session_id, const char* web_session_id, + uint32_t web_session_id_length) { + if (session_map_.count(session_id) != 0) { + host_->OnSessionError(session_id, cdm::kSessionError, + kSessionAlreadyExistsError); + return; + } + + CdmKeySetId key_set_id(web_session_id, web_session_id_length); + CdmResponseType result = cdm_engine_.OpenKeySetSession(key_set_id); + if (result != NO_ERROR) { + host_->OnSessionError(session_id, cdm::kSessionError, + kOpenKeySetSessionErrorBase | result); + return; + } + session_map_[session_id] = + InternalSession(key_set_id, InternalSession::kRelease); + + CdmSessionId empty_session_id; + InitializationData empty_initialization_data; + CdmAppParameterMap empty_app_parameters; + CdmKeyMessage key_request; + std::string server_url; + + result = cdm_engine_.GenerateKeyRequest( + empty_session_id, key_set_id, empty_initialization_data, + kLicenseTypeRelease, empty_app_parameters, &key_request, &server_url, + NULL); + if (KEY_MESSAGE == result) { + host_->OnSessionCreated(session_id, key_set_id.c_str(), + key_set_id.length()); + host_->OnSessionMessage(session_id, key_request.c_str(), + key_request.length(), server_url.c_str(), + server_url.length()); + } else { + cdm_engine_.CloseKeySetSession(key_set_id); + host_->OnSessionError(session_id, cdm::kSessionError, + kGenerateKeyRequestErrorBase | result); + } +} + +cdm::Status WvContentDecryptionModule_4::UsePrivacyMode() { + if (!session_map_.empty()) return cdm::kSessionError; + + property_set_.set_use_privacy_mode(true); + return cdm::kSuccess; +} + +cdm::Status WvContentDecryptionModule_4::SetServerCertificate( + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) { + if (!session_map_.empty()) return cdm::kSessionError; + + cdm::Status result = UsePrivacyMode(); + if (result != cdm::kSuccess) return result; + + std::string server_certificate( + reinterpret_cast(server_certificate_data), + server_certificate_data_size); + property_set_.set_service_certificate(server_certificate); + return cdm::kSuccess; +} + +void WvContentDecryptionModule_4::TimerExpired(void* context) { + LOGI("WvContentDecryptionModule_4::TimerExpired()"); + if (timer_enabled_) { + cdm_engine_.OnTimerEvent(); + host_->SetTimer(kCdmPolicyTimerDurationSeconds * 1000, NULL); + } +} + +cdm::Status WvContentDecryptionModule_4::Decrypt( + const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block) { + LOGI("WvContentDecryptionModule_4::Decrypt()"); + if (static_cast(encrypted_buffer.iv_size) != KEY_IV_SIZE) + return cdm::kDecryptError; + + std::vector iv(encrypted_buffer.iv, + encrypted_buffer.iv + encrypted_buffer.iv_size); + KeyId key_id(reinterpret_cast(encrypted_buffer.key_id), + encrypted_buffer.key_id_size); + + CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0, + NULL); + parameters.is_secure = Properties::oem_crypto_use_secure_buffers(); + + if (encrypted_buffer.num_subsamples) + return DoSubsampleDecrypt(parameters, iv, encrypted_buffer, + decrypted_block); + else + return DoDecrypt(parameters, iv, encrypted_buffer, decrypted_block); +} + +cdm::Status WvContentDecryptionModule_4::DecryptDecodeAndRender( + const cdm::InputBuffer& encrypted_buffer, cdm::StreamType stream_type) { + LOGI("WvContentDecryptionModule_4::DecryptDecodeAndRender()"); + + if (static_cast(encrypted_buffer.iv_size) != KEY_IV_SIZE) + return cdm::kDecryptError; + + std::vector iv(encrypted_buffer.iv, + encrypted_buffer.iv + encrypted_buffer.iv_size); + KeyId key_id(reinterpret_cast(encrypted_buffer.key_id), + encrypted_buffer.key_id_size); + + CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0, + NULL); + parameters.is_video = (stream_type == cdm::kStreamTypeVideo); + + if (encrypted_buffer.num_subsamples) + return DoSubsampleDecrypt(parameters, iv, encrypted_buffer, NULL); + else + return DoDecrypt(parameters, iv, encrypted_buffer, NULL); +} + +void WvContentDecryptionModule_4::Destroy() { delete this; } + +File::Impl* WvContentDecryptionModule_4::NewFileImpl() { + return new File::Impl(host_); +} + +cdm::Status WvContentDecryptionModule_4::DoSubsampleDecrypt( + CdmDecryptionParameters& parameters, std::vector& iv, + const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block) { + if (!encrypted_buffer.subsamples) return cdm::kDecryptError; + + size_t output_size = 0; + for (int i = 0; i < encrypted_buffer.num_subsamples; ++i) { + const cdm::SubsampleEntry& subsample = encrypted_buffer.subsamples[i]; + output_size += subsample.cipher_bytes + subsample.clear_bytes; + } + assert(output_size == + encrypted_buffer.data_size - encrypted_buffer.data_offset); + + SetSizesAndAllocate(output_size, parameters, decrypted_block); + + size_t offset = 0; + size_t encrypted_offset = 0; + uint32_t block_ctr = 0; + const cdm::SubsampleEntry* subsamples = encrypted_buffer.subsamples; + + for (int i = 0; i < encrypted_buffer.num_subsamples; ++i) { + const cdm::SubsampleEntry& subsample = subsamples[i]; + + for (int is_encrypted = 0; is_encrypted < 2; ++is_encrypted) { + size_t bytes = + is_encrypted ? subsample.cipher_bytes : subsample.clear_bytes; + if (bytes == 0) continue; + + parameters.is_encrypted = is_encrypted; + parameters.subsample_flags = + ((offset == 0) ? OEMCrypto_FirstSubsample : 0) | + ((offset + bytes == output_size) ? OEMCrypto_LastSubsample : 0); + + cdm::Status status = DecryptAndUpdateCounters( + parameters, iv, encrypted_buffer, bytes, decrypted_block, offset, + encrypted_offset, block_ctr); + if (status != cdm::kSuccess) return status; + } + } + return cdm::kSuccess; +} + +cdm::Status WvContentDecryptionModule_4::DoDecrypt( + CdmDecryptionParameters& parameters, std::vector& iv, + const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block) { + assert(!encrypted_buffer.subsamples); + size_t output_size = + encrypted_buffer.data_size - encrypted_buffer.data_offset; + SetSizesAndAllocate(output_size, parameters, decrypted_block); + + size_t offset = 0; + size_t encrypted_offset = 0; + uint32_t block_ctr = 0; + + parameters.is_encrypted = true; + parameters.subsample_flags = + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample; + return DecryptAndUpdateCounters(parameters, iv, encrypted_buffer, output_size, + decrypted_block, offset, encrypted_offset, + block_ctr); +} + +void WvContentDecryptionModule_4::SetSizesAndAllocate( + size_t output_size, CdmDecryptionParameters& parameters, + cdm::DecryptedBlock* decrypted_block) { + parameters.decrypt_buffer_length = output_size; + + if (decrypted_block) { + cdm::Buffer* buffer = host_->Allocate(output_size); + buffer->SetSize(output_size); + decrypted_block->SetDecryptedBuffer(buffer); + parameters.decrypt_buffer = buffer->Data(); + } else { + parameters.decrypt_buffer = NULL; + } +} + +cdm::Status WvContentDecryptionModule_4::DecryptAndUpdateCounters( + CdmDecryptionParameters& parameters, std::vector& iv, + const cdm::InputBuffer& encrypted_buffer, const size_t bytes, + cdm::DecryptedBlock* decrypted_block, size_t& offset, + size_t& encrypted_offset, uint32_t& block_ctr) { + if (parameters.is_encrypted) { + uint32_t counter = encrypted_offset / kIvSize; + Ctr128Add(counter - block_ctr, &iv[0]); + block_ctr = counter; + } + + parameters.encrypt_buffer = + &encrypted_buffer.data[encrypted_buffer.data_offset + offset]; + parameters.decrypt_buffer_offset = offset; + parameters.encrypt_length = bytes; + parameters.block_offset = encrypted_offset % kIvSize; + + offset += bytes; + if (parameters.is_encrypted) encrypted_offset += bytes; + + CdmSessionId session_id; // cdm_engine will locate via key_id. + CdmResponseType status = cdm_engine_.Decrypt(session_id, parameters); + + switch (status) { + case wvcdm::NEED_KEY: + return cdm::kNoKey; + case wvcdm::NO_ERROR: + return cdm::kSuccess; + default: + return cdm::kDecryptError; + } +} + +void WvContentDecryptionModule_4::EnablePolicyTimer() { + LOGI("WvContentDecryptionModule_4::EnablePolicyTimer()"); + if (!timer_enabled_) { + timer_enabled_ = true; + host_->SetTimer(kCdmPolicyTimerDurationSeconds * 1000, NULL); + } +} + +void WvContentDecryptionModule_4::DisablePolicyTimer() { + LOGI("WvContentDecryptionModule_4::DisablePolicyTimer()"); + timer_enabled_ = false; +} + +void WvContentDecryptionModule_4::CreateProvisionSession(uint32_t session_id) { + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority; + CdmProvisioningRequest request; + std::string default_url; + CdmResponseType status = cdm_engine_.GetProvisioningRequest( + cert_type, cert_authority, &request, &default_url); + if (status != NO_ERROR) { + host_->OnSessionError(session_id, cdm::kSessionError, + kGetProvisioningRequestErrorBase | status); + return; + } + std::string blank_web_id; + session_map_[session_id] = + InternalSession(blank_web_id, InternalSession::kProvision); + host_->OnSessionCreated(session_id, blank_web_id.c_str(), + blank_web_id.length()); + host_->OnSessionMessage(session_id, request.c_str(), request.length(), + default_url.c_str(), default_url.length()); +} + +void WvContentDecryptionModule_4::UpdateProvisionSession( + uint32_t session_id, const uint8_t* response, uint32_t response_size) { + CdmProvisioningResponse cdm_response(reinterpret_cast(response), + response_size); + std::string cert; + std::string wrapped_key; + CdmResponseType status = + cdm_engine_.HandleProvisioningResponse(cdm_response, &cert, &wrapped_key); + if (status != NO_ERROR) { + host_->OnSessionError(session_id, cdm::kSessionError, + kHandleProvisioningResponseErrorBase | status); + return; + } + host_->OnSessionUpdated(session_id); +} + +bool WvContentDecryptionModule_4::CallOpenSession(uint32_t session_id) { + CdmSessionId internal_session_id; + + if (session_map_.count(session_id) != 0) { + host_->OnSessionError(session_id, cdm::kSessionError, + kSessionAlreadyExistsError); + return false; + } + + CdmResponseType result = cdm_engine_.OpenSession( + "com.widevine.alpha", &property_set_, &internal_session_id); + if (result != NO_ERROR) { + if (result == NEED_PROVISIONING) { + host_->OnSessionError(session_id, cdm::kNeedsDeviceCertificate, + kOpenSessionErrorBase | result); + } else { + host_->OnSessionError(session_id, cdm::kSessionError, + kOpenSessionErrorBase | result); + } + return false; + } + + if (!cdm_engine_.AttachEventListener(internal_session_id, this)) { + cdm_engine_.CloseSession(internal_session_id); + host_->OnSessionError(session_id, cdm::kSessionError, + kAttachEventListenerError); + return false; + } + session_map_[session_id] = + InternalSession(internal_session_id, InternalSession::kDecrypt); + return true; +} + +int64_t WvContentDecryptionModule_4::GetCurrentTimeInSeconds() { + return host_->GetCurrentWallTimeInSeconds(); +} + +void WvContentDecryptionModule_4::OnEvent(const CdmSessionId& session_id, + CdmEventType cdm_event) { + bool found = false; + std::map::iterator session_pair_iterator; + uint32_t external_session_id = 0; + + for (session_pair_iterator = session_map_.begin(); + session_pair_iterator != session_map_.end(); ++session_pair_iterator) { + if (session_pair_iterator->second.webid() == session_id) { + found = true; + external_session_id = session_pair_iterator->first; + break; + } + } + + if (!found) { + LOGE("Unmapped Session Event on Session %s", session_id.c_str()); + host_->OnSessionError(0, cdm::kSessionError, kSessionNotFoundError); + return; + } + + switch (cdm_event) { + case LICENSE_RENEWAL_NEEDED_EVENT: { + wvcdm::CdmKeyMessage cdm_message; + std::string server_url; + CdmResponseType result = cdm_engine_.GenerateRenewalRequest( + session_id, &cdm_message, &server_url); + if (result == wvcdm::KEY_MESSAGE) { + LOGI("Renewal created"); + host_->OnSessionMessage(external_session_id, cdm_message.c_str(), + cdm_message.length(), server_url.c_str(), + server_url.length()); + + } else { + LOGD("Error on Generating a Renewal Request!"); + host_->OnSessionError(external_session_id, cdm::kSessionError, + kGenerateRenewalRequestErrorBase | result); + } + break; + } + case LICENSE_EXPIRED_EVENT: { + LOGD("License Expired Event"); + host_->OnSessionError(external_session_id, cdm::kSessionError, + kLicenseExpiredNotification); + break; + } + } +} + +} // namespace wvcdm diff --git a/cdm/test/cdm_api_1_test.cpp b/cdm/test/cdm_api_1_test.cpp new file mode 100644 index 00000000..2eeb1863 --- /dev/null +++ b/cdm/test/cdm_api_1_test.cpp @@ -0,0 +1,709 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// This source file provides a basic set of unit tests for the Content +// Decryption Module (CDM). It exercises much of the API that will be +// required by the host application to get the license and keys for +// rendering protected content. + +#include "cdm_test_config.h" +#include "test_host_1.h" +#include "test_util.h" + +#include + +#include + +#include "clock.h" +#include "config_test_env.h" +#include "content_decryption_module.h" +#include "device_cert.h" +#include "license_request.h" +#include "log.h" +#include "properties.h" +#include "scoped_ptr.h" +#include "string_conversions.h" +#include "url_request.h" + +static const int kTestPolicyRenewalDelaySeconds = 180; +static const int kDelayWaitToForRenewalMessageSeconds = 2; +static const int kHttpOk = 200; +static const int kHttpBadGateway = 502; +static const int kNumRetries = 5; +static const int kRetryBaseDelaySeconds = 3; + +namespace { + +// Default key system identifier. +const char kKeySystemWidevine[] = "com.widevine.alpha"; + +// Default mime type for key request generation. +const char kMimeType[] = "video/mp4"; + +// Key ID of key used to encrypt the test content. +// This is used to look up the content key. +const std::vector kTestKeyId = + wvcdm::a2b_hex("371ea35e1a985d75d198a7f41020dc23"); + +// Dummy encrypted data. +const std::vector kInputVector1 = wvcdm::a2b_hex( + "64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36" + "17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d" + "7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab" + "ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c" + "2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4" + "9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4" + "6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012" + "c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685"); +const std::vector kIv1 = + wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f"); + +// Expected output for kInputVector1. +const std::vector kOutputVector1 = wvcdm::a2b_hex( + "217ce9bde99bd91e9733a1a00b9b557ac3a433dc92633546156817fae26b6e1c" + "942ac20a89ff79f4c2f25fba99d6a44618a8c0420b27d54e3da17b77c9d43cca" + "595d259a1e4a8b6d7744cd98c5d3f921adc252eb7d8af6b916044b676a574747" + "8df21fdc42f166880d97a2225cd5c9ea5e7b752f4cf81bbdbe98e542ee10e1c6" + "ad868a6ac55c10d564fc23b8acff407daaf4ed2743520e02cda9680d9ea88e91" + "029359c4cf5906b6ab5bf60fbb3f1a1c7c59acfc7e4fb4ad8e623c04d503a3dd" + "4884604c8da8a53ce33db9ff8f1c5bb6bb97f37b39906bf41596555c1bcce9ed" + "08a899cd760ff0899a1170c2f224b9c52997a0785b7fe170805fd3e8b1127659"); + +// Dummy encrypted data. This is a combination of clear and encrypted data. +const std::vector kInputVector2 = wvcdm::a2b_hex( + // subsample 0 + "abcdef" + "53cc758763904ea5870458e6b23d36db1e6d7f7aaa2f3eeebb5393a7264991e7" + "ce4f57b198326e1a208a821799b2a29c90567ab57321b06e51fc20dc9bc5fc55" + "10720a8bb1f5e002c3e50ff70d2d806a9432cad237050d09581f5b0d59b00090" + "b3ad69b4087f5a155b17e13c44d33fa007475d207fc4ac2ef3b571ecb9" + // subsample 1 + "0123456789" + "f3c852" + "ce00dc4806f0c6856ae1732e20308096478e1d822d75c2bb768119565d3bd6e6" + "901e36164f4802355ee758fc46ef6cf5f852dd5256c7b1e5f96d29" + // subsample 2 + "deadbeefbaadf00d" + "3b20525d5e" + "78b8e5aa344d5c4e425e67ddf889ea7c4bb1d49af67eba67718b765e0a940402" + "8d306f4ce693ad6dc0a931d507fa14fff4d293d4170280b3e0fca2d628f722e8" +); +const std::vector kIv2 = + wvcdm::a2b_hex("6ba18dd40f49da7f64c368e4db43fc88"); + +// Expected output for kInputVector2. +const std::vector kOutputVector2 = wvcdm::a2b_hex( + // subsample 0 + "abcdef" + "52e65334501acadf78e2b26460def3ac973771ed7c64001a2e82917342a7eab3" + "047f5e85449692fae8f677be425a47bdea850df5a3ffff17043afb1f2b437ab2" + "b1d5e0784c4ed8f97fc24b8f565e85ed63fb7d1365980d9aea7b8b58f488f83c" + "1ce80b6096c60f3b113c988ff185b26e798da8fc6f327e4ff00e4b3fbf" + // subsample 1 + "0123456789" + "b1ed0a" + "a054bce40ccb0ebc70b181d1a12055f46ac55e29c7c2473a29d2a366d240ec48" + "7cede274f012813a877f99159e7062b6a37cfc9327a7bc2195814e" + // subsample 2 + "deadbeefbaadf00d" + "653b818d1d" + "4ab9a9128361d8ca6a9d2766df5c096ee29f4f5204febdf217a94a5b560cd692" + "cc36d3e071df789fdeac2fb7ec6dcd7af94bb1f85c22025b25e702e38212b927" +); + +// Dummy encrypted data. This will be decrypted with a data_offset +// instead of subsamples. +const std::vector kInputVector3 = wvcdm::a2b_hex( + "64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36" + "17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d" + "7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab" + "ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c" + "2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4" + "9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4" + "6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012" + "c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685"); +const std::vector kIv3 = + wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f"); + +// The data_offset for kInputVector3. +const uint32_t kInputOffset3 = 9; + +// Expected output for kInputVector3 offset by kInputOffset3. +const std::vector kOutputVector3 = wvcdm::a2b_hex( + "19ab304b49908e2395b32f26bf471adf4a4bc92f9e999cca8476d24a257931b4" + "c5fd177693ed55e31cd2b85dc196b2b722cd8854eb9334f3dab0b5bd26aa5e66" + "a9d1cfbba877c9456b11dc99a6bdc7015ca1544f7ce66171a8179eca19efe515" + "8c4c1d0612dff64100387065da108fdbfcc14738202ac3d27520eb48c020ddb7" + "714dca22e5e2241aff6932dba1587a97ac1a952827d411d8582dfecc2e9e1494" + "644046ca7044bc41c3c5e0a3a405d5551f3f5bdd6f36042e2f0f3693778b9277" + "6ed8d106647a7539df7d30288803cd9ca1c274bebe688151c72b451f571a441f" + "83d0ff77d8d57dcb395122e175f4944569917627d6c3dc"); + +void* GetCdmHost(int host_interface_version, void* user_data) { + if (host_interface_version != cdm::Host_1::kVersion) return NULL; + return user_data; +} + +} // namespace + +namespace wvcdm { + +class CdmApi1Test : public testing::Test { + public: + CdmApi1Test() : cdm_(NULL) {} + ~CdmApi1Test() {} + + protected: + virtual void SetUp() { + // Create the Host. + host_.reset(new TestHost_1()); + + // Set various parameters that the CDM will query. + host_->SetPlatformString("SecurityLevel", "L1"); + host_->SetPlatformString("PrivacyOn", "False"); + std::string cert(reinterpret_cast(kDeviceCert), + sizeof(kDeviceCert)); + host_->SetPlatformString("DeviceCertificate", cert); + + // Initialize the CDM module before creating a CDM instance. + InitializeCdmModule(); + + // Create the CDM. + cdm_ = + reinterpret_cast(::CreateCdmInstance( + cdm::ContentDecryptionModule_1::kVersion, kKeySystemWidevine, + strlen(kKeySystemWidevine), GetCdmHost, host_.get())); + + // Tell the Host about the CDM. + host_->SetCdmPtr(cdm_); + } + + cdm::Status GenerateKeyRequest(const std::string& init_data) { + cdm::Status status = cdm_->GenerateKeyRequest( + kMimeType, strlen(kMimeType), + (const uint8_t*)init_data.data(), init_data.length()); + return status; + } + + cdm::Status GenerateKeyRequestWithMimeType(const std::string& mime_type) { + cdm::Status status = cdm_->GenerateKeyRequest( + mime_type.c_str(), mime_type.length(), + (const uint8_t*)g_key_id.data(), g_key_id.length()); + return status; + } + + // posts a request and extracts the drm message from the response + std::string GetKeyRequestResponse(const TestHost_1::KeyMessage& key_msg) { + std::string url; + if (key_msg.default_url.empty()) { + url = g_license_server + g_client_auth; + } else { + // Note that the client auth string is not appended when the CDM tells + // us what URL to use. + url = key_msg.default_url; + } + + int status_code; + std::string response; + UrlRequest url_request(url); + + for (int retries = 0; retries < kNumRetries; ++retries) { + EXPECT_TRUE(url_request.is_connected()); + if (!url_request.is_connected()) { + return ""; + } + + url_request.PostRequest(key_msg.message); + int resp_bytes = url_request.GetResponse(&response); + + status_code = url_request.GetStatusCode(response); + // Sometimes, the server returns "HTTP 502 bad gateway". + // If we treat this as a non-fatal error, we reduce test flakiness. + if (status_code != kHttpBadGateway) { + // Move on with normal processing. + break; + } + + // Reconnect to the server and try again. Since the server's 502 + // response could indicate a temporary failure due to load, we use + // an exponential backoff. Each time we reconnect, we delay by + // exactly twice as long as the last time. This is a simplified + // version of the delay strategy recommended by Google in the + // internal document "Rate Limiting in Google Applications" under + // the heading "Settings for client exponential backoff". We do + // not bother to fuzz the delay, since unit tests are not running + // simultaneously in large numbers like real clients would be. + LOGE("Bad gateway, retrying."); + sleep(kRetryBaseDelaySeconds << retries); + url_request.Reconnect(); + } + + // Some license servers return 400 for invalid message, some + // return 500; treat anything other than 200 as an invalid message. + EXPECT_EQ(kHttpOk, status_code); + + if (status_code != kHttpOk) { + return ""; + } else { + std::string drm_msg; + LicenseRequest lic_request; + lic_request.GetDrmMessage(response, drm_msg); + LOGV("drm msg: %u bytes\n%s", drm_msg.size(), + HexEncode(reinterpret_cast(drm_msg.data()), + drm_msg.size()).c_str()); + return drm_msg; + } + } + + void ProcessKeyResponse() { + TestHost_1::KeyMessage key_msg = host_->GetLastKeyMessage(); + ASSERT_FALSE(key_msg.message.empty()); + EXPECT_TRUE(key_msg.default_url.empty()); + std::string drm_msg = GetKeyRequestResponse(key_msg); + EXPECT_EQ(cdm::kSuccess, AddKey(key_msg.session_id, drm_msg)); + } + + void ProcessKeyRenewalResponse() { + TestHost_1::KeyMessage key_msg = host_->GetLastKeyMessage(); + ASSERT_FALSE(key_msg.message.empty()); + EXPECT_FALSE(key_msg.default_url.empty()); + std::string drm_msg = GetKeyRequestResponse(key_msg); + EXPECT_EQ(cdm::kSuccess, AddKey(key_msg.session_id, drm_msg)); + } + + void CloseSession(const std::string& session_id) { + cdm::Status status = + cdm_->CloseSession(session_id.data(), session_id.length()); + EXPECT_EQ(cdm::kSuccess, status); + } + + cdm::Status AddKey(const std::string& session_id, + const std::string& drm_msg) { + cdm::Status status = + cdm_->AddKey(session_id.data(), session_id.size(), + (const uint8_t*)drm_msg.data(), drm_msg.size(), NULL, 0); + return status; + } + + cdm::InputBuffer BuildInputBuffer(const std::vector& encrypted, + const std::vector& iv) { + cdm::InputBuffer buf; + buf.data = &encrypted[0]; + buf.data_size = encrypted.size(); + buf.key_id = &kTestKeyId[0]; + buf.key_id_size = kTestKeyId.size(); + buf.iv = &iv[0]; + buf.iv_size = iv.size(); + buf.data_offset = 0; + buf.timestamp = 10; + return buf; + } + + cdm::InputBuffer BuildInputBuffer( + const std::vector& encrypted, + const std::vector& iv, + const std::vector& sub) { + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv); + buf.subsamples = &sub[0]; + buf.num_subsamples = sub.size(); + return buf; + } + + cdm::InputBuffer BuildInputBuffer( + const std::vector& encrypted, + const std::vector& iv, + const uint32_t offset) { + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv); + buf.data_offset = offset; + return buf; + } + + std::vector BuildMultipleSubsamples() { + std::vector sub; + sub.push_back(cdm::SubsampleEntry(3, 125)); + sub.push_back(cdm::SubsampleEntry(5, 62)); + sub.push_back(cdm::SubsampleEntry(8, 69)); + return sub; + } + + std::vector BuildSingleSubsample(size_t size) { + std::vector sub; + sub.push_back(cdm::SubsampleEntry(0, size)); + return sub; + } + + cdm::ContentDecryptionModule_1* cdm_; // owned by host_ + wvcdm::scoped_ptr host_; +}; + +namespace { + +class DummyCDM : public cdm::ContentDecryptionModule_1 { + public: + DummyCDM() : timer_fired_(false), last_context_(NULL) {} + + virtual cdm::Status GenerateKeyRequest(const char*, int, const uint8_t*, + int) OVERRIDE { + return cdm::kSessionError; + } + + virtual cdm::Status AddKey(const char*, int, const uint8_t*, int, + const uint8_t*, int) OVERRIDE { + return cdm::kSessionError; + } + + virtual bool IsKeyValid(const uint8_t*, int) OVERRIDE { return false; } + + virtual cdm::Status CloseSession(const char*, int) OVERRIDE { + return cdm::kSessionError; + } + + virtual void TimerExpired(void* context) OVERRIDE { + timer_fired_ = true; + last_context_ = context; + } + + virtual cdm::Status Decrypt(const cdm::InputBuffer&, + cdm::DecryptedBlock*) OVERRIDE { + return cdm::kSessionError; + } + + virtual cdm::Status DecryptDecodeAndRenderFrame( + const cdm::InputBuffer&) OVERRIDE { + return cdm::kSessionError; + } + + virtual cdm::Status DecryptDecodeAndRenderSamples( + const cdm::InputBuffer&) OVERRIDE { + return cdm::kSessionError; + } + + virtual void Destroy() OVERRIDE { delete this; } + + virtual cdm::Status GetProvisioningRequest(std::string*, + std::string*) OVERRIDE { + return cdm::kSessionError; + } + + virtual cdm::Status HandleProvisioningResponse(std::string&) OVERRIDE { + return cdm::kSessionError; + } + + bool TimerFired() const { return timer_fired_; } + + void* LastTimerContext() const { return last_context_; } + + void ResetTimerStatus() { + timer_fired_ = false; + last_context_ = NULL; + } + + private: + bool timer_fired_; + void* last_context_; +}; + +} // namespace + +TEST_F(CdmApi1Test, TestHostTimer) { + // Validate that the TestHost timers are processed in the correct order. + // To do this, we replace the cdm with a dummy that only tracks timers. + DummyCDM* cdm = new DummyCDM(); + + // The old CDM is destroyed by SetCdmPtr. + cdm_ = cdm; + host_->SetCdmPtr(cdm); + + const double kTimerDelaySeconds = 1.0; + const int64_t kTimerDelayMs = kTimerDelaySeconds * 1000; + void* kCtx1 = reinterpret_cast(0x1); + void* kCtx2 = reinterpret_cast(0x2); + + host_->SetTimer(kTimerDelayMs * 1, kCtx1); + host_->SetTimer(kTimerDelayMs * 2, kCtx2); + + host_->FastForwardTime(kTimerDelaySeconds); + EXPECT_TRUE(cdm->TimerFired()); + EXPECT_EQ(kCtx1, cdm->LastTimerContext()); + cdm->ResetTimerStatus(); + + host_->FastForwardTime(kTimerDelaySeconds); + EXPECT_TRUE(cdm->TimerFired()); + EXPECT_EQ(kCtx2, cdm->LastTimerContext()); + cdm->ResetTimerStatus(); + + host_->FastForwardTime(kTimerDelaySeconds); + EXPECT_FALSE(cdm->TimerFired()); +} + +TEST_F(CdmApi1Test, DeviceCertificateTest) { + if (Properties::use_certificates_as_identification()) { + // Clear any existing device cert. + host_->SetPlatformString("DeviceCertificate", ""); + + ASSERT_EQ(cdm::kNeedsDeviceCertificate, GenerateKeyRequest(g_key_id)); + + // The Host must handle the certificate provisioning request. + std::string server_url; + std::string request; + cdm::Status status = cdm_->GetProvisioningRequest(&request, &server_url); + ASSERT_EQ(cdm::kSuccess, status); + + UrlRequest url_request(server_url); + url_request.PostCertRequestInQueryString(request); + + std::string message; + bool ok = url_request.GetResponse(&message); + ASSERT_TRUE(ok); + + status = cdm_->HandleProvisioningResponse(message); + ASSERT_EQ(cdm::kSuccess, status); + + // Now we are provisioned, so GKR should succeed. + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + } else { + LOGI( + "Skipping CdmApi1Test::DeviceCertificateTest because this platform " + "does not support device certificates."); + } +} + +// Note that these tests, BaseMessageTest, NormalDecryption and TimeTest, +// are dependent on getting back a license from the license server where the +// url for the license server is defined in the conf_test_env.cpp. If these +// tests fail immediately, verify that the license server URL is correct +// and works in your test environment. + +TEST_F(CdmApi1Test, BaseMessageTest) { + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + ProcessKeyResponse(); +} + +TEST_F(CdmApi1Test, NormalDecryption) { + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + ProcessKeyResponse(); + + // Level 1 / Level 2 payload comes back in the cpu memory as cleartext. + std::vector encrypted = kInputVector1; + std::vector iv = kIv1; + std::vector expected = kOutputVector1; + std::vector sub = BuildSingleSubsample(encrypted.size()); + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub); + + TestDecryptedBlock output; + cdm::Status status = cdm_->Decrypt(buf, &output); + + EXPECT_EQ(cdm::kSuccess, status); + EXPECT_EQ( + 0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size)); +} + +TEST_F(CdmApi1Test, NormalSubSampleDecryptionWithSubsampleInfo) { + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + ProcessKeyResponse(); + + // Level 1 / Level 2 payload comes back in the cpu memory as cleartext. + std::vector encrypted = kInputVector2; + std::vector iv = kIv2; + std::vector expected = kOutputVector2; + std::vector sub = BuildMultipleSubsamples(); + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub); + + TestDecryptedBlock output; + cdm::Status status = cdm_->Decrypt(buf, &output); + + EXPECT_EQ(cdm::kSuccess, status); + EXPECT_EQ( + 0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size)); +} + +TEST_F(CdmApi1Test, NormalSubSampleDecryptionWithMissingSubsampleInfo) { + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + ProcessKeyResponse(); + + std::vector encrypted = kInputVector2; + std::vector iv = kIv2; + std::vector expected = kOutputVector2; + std::vector sub = BuildMultipleSubsamples(); + + // Don't add these subsamples yet! + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv); + buf.num_subsamples = sub.size(); + + TestDecryptedBlock output; + + cdm::Status status = cdm_->Decrypt(buf, &output); + EXPECT_EQ(cdm::kDecryptError, status); + + // Add the subsamples pointer and expect success. + buf.subsamples = &sub[0]; + status = cdm_->Decrypt(buf, &output); + EXPECT_EQ(cdm::kSuccess, status); + EXPECT_EQ( + 0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size)); +} + +TEST_F(CdmApi1Test, DecryptWithDataOffset) { + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + ProcessKeyResponse(); + + std::vector encrypted = kInputVector3; + std::vector iv = kIv3; + std::vector expected = kOutputVector3; + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, kInputOffset3); + + TestDecryptedBlock output; + cdm::Status status = cdm_->Decrypt(buf, &output); + cdm::Buffer *output_buf = output.DecryptedBuffer(); + + EXPECT_EQ(cdm::kSuccess, status); + EXPECT_EQ(buf.data_size - buf.data_offset, output_buf->Size()); + EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], + output_buf->Size())); +} + +TEST_F(CdmApi1Test, DecryptReturnsSizedBuffer) { + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + ProcessKeyResponse(); + + std::vector encrypted = kInputVector1; + std::vector iv = kIv1; + std::vector expected = kOutputVector1; + + std::vector sub = BuildSingleSubsample(encrypted.size()); + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub); + + TestDecryptedBlock output; + cdm::Status status = cdm_->Decrypt(buf, &output); + + EXPECT_EQ(cdm::kSuccess, status); + cdm::Buffer* buffer = output.DecryptedBuffer(); + EXPECT_NE((void*)NULL, buffer); + if (buffer) { + EXPECT_EQ(expected.size(), output.DecryptedBuffer()->Size()); + } +} + +TEST_F(CdmApi1Test, TimeTest) { + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + ProcessKeyResponse(); + + // We expect that by the time we've added a key, the CDM has set a timer. + // Otherwise, it couldn't correctly handle renewal. + EXPECT_NE(0, host_->NumTimers()); + host_->FastForwardTime(kTestPolicyRenewalDelaySeconds + + kDelayWaitToForRenewalMessageSeconds); + + // When the timer expired, we should have sent a renewal, so we can + // add this renewed key now, assuming things are working as expected. + ProcessKeyRenewalResponse(); +} + +TEST_F(CdmApi1Test, SecureDecryptionLevel1) { + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + ProcessKeyResponse(); + + // Level 1 passes encrypted payload straight through. By calling the + // CDM's DecryptDecodeAndRenderSamples, and/or DecryptDecodeAndRenderFrame, + // OEMCrypto_DecryptCTR will be told to use Direct Rendering. + std::vector encrypted = kInputVector1; + std::vector iv = kIv1; + std::vector sub = BuildSingleSubsample(encrypted.size()); + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub); + + cdm::Status status; + + status = cdm_->DecryptDecodeAndRenderSamples(buf); + EXPECT_EQ(cdm::kSuccess, status); + status = cdm_->DecryptDecodeAndRenderFrame(buf); + EXPECT_EQ(cdm::kSuccess, status); +} + +TEST_F(CdmApi1Test, SecureDecryptionLevel1WithSubsampleInfo) { + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + ProcessKeyResponse(); + + // Level 1 passes encrypted payload straight through. By calling the + // CDM's DecryptDecodeAndRenderSamples, and/or DecryptDecodeAndRenderFrame, + // OEMCrypto_DecryptCTR will be told to use Direct Rendering. + std::vector encrypted = kInputVector2; + std::vector iv = kIv2; + std::vector sub = BuildMultipleSubsamples(); + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub); + + cdm::Status status; + + status = cdm_->DecryptDecodeAndRenderSamples(buf); + EXPECT_EQ(cdm::kSuccess, status); + status = cdm_->DecryptDecodeAndRenderFrame(buf); + EXPECT_EQ(cdm::kSuccess, status); +} + +TEST_F(CdmApi1Test, SecureDecryptionLevel1WithMissingSubsampleInfo) { + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + ProcessKeyResponse(); + + std::vector encrypted = kInputVector2; + std::vector iv = kIv2; + std::vector sub = BuildMultipleSubsamples(); + + // Don't add these subsamples yet! + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv); + buf.num_subsamples = sub.size(); + + cdm::Status status; + + status = cdm_->DecryptDecodeAndRenderSamples(buf); + EXPECT_EQ(cdm::kDecryptError, status); + status = cdm_->DecryptDecodeAndRenderFrame(buf); + EXPECT_EQ(cdm::kDecryptError, status); + + // Add the subsamples pointer and expect success. + buf.subsamples = &sub[0]; + status = cdm_->DecryptDecodeAndRenderSamples(buf); + EXPECT_EQ(cdm::kSuccess, status); + status = cdm_->DecryptDecodeAndRenderFrame(buf); + EXPECT_EQ(cdm::kSuccess, status); +} + +TEST_F(CdmApi1Test, GenerateKeyRequestFailureSendsKeyError) { + // Pass a bogus key id and expect failure. + EXPECT_EQ(cdm::kSessionError, GenerateKeyRequest("")); + // Expect the CDM to pass a key error back to the host. + EXPECT_EQ(1, host_->KeyErrorsSize()); +} + +TEST_F(CdmApi1Test, AddKeyFailureSendsKeyError) { + EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); + + // Get the message and response. + TestHost_1::KeyMessage key_msg = host_->GetLastKeyMessage(); + EXPECT_TRUE(key_msg.default_url.empty()); + std::string drm_msg = GetKeyRequestResponse(key_msg); + + // Call AddKey with a bad session id and expect failure. + EXPECT_EQ(cdm::kSessionError, AddKey("BLAH", drm_msg)); + + // Expect the CDM to pass a key error back to the host. + EXPECT_EQ(1, host_->KeyErrorsSize()); + + // Call AddKey with a bad license and expect failure. + EXPECT_EQ(cdm::kSessionError, AddKey(key_msg.session_id, "BLAH")); + + // Expect the CDM to pass one more key error back to the host. + EXPECT_EQ(2, host_->KeyErrorsSize()); +} + +TEST_F(CdmApi1Test, MimeTypeMatters) { + cdm::Status status; + + status = GenerateKeyRequestWithMimeType("video/mp4"); + ASSERT_EQ(cdm::kSuccess, status); + + status = GenerateKeyRequestWithMimeType("video/webm"); + ASSERT_EQ(cdm::kSuccess, status); + + status = GenerateKeyRequestWithMimeType("video/blah"); + ASSERT_EQ(cdm::kSessionError, status); +} + +} // namespace wvcdm diff --git a/cdm/test/cdm_api_4_test.cpp b/cdm/test/cdm_api_4_test.cpp new file mode 100644 index 00000000..9793a981 --- /dev/null +++ b/cdm/test/cdm_api_4_test.cpp @@ -0,0 +1,922 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// This source file provides a basic set of unit tests for the Content +// Decryption Module (CDM). It exercises much of the API that will be +// required by the host application to get the license and keys for +// rendering protected content. + +#include "cdm_test_config.h" +#include "test_host_4.h" +#include "test_util.h" + +#include + +#include + +#include "cdm_client_property_set.h" +#include "clock.h" +#include "config_test_env.h" +#include "content_decryption_module.h" +#include "device_cert.h" +#include "license_request.h" +#include "log.h" +#include "properties.h" +#include "scoped_ptr.h" +#include "string_conversions.h" +#include "url_request.h" +#include "wv_content_decryption_module_4.h" + +static const int kTestPolicyRenewalDelaySeconds = 180; +static const int kDelayWaitToForRenewalMessageSeconds = 2; +static const int kHttpOk = 200; +static const int kSessionId1 = 1; +static const int kSessionId2 = 2; +static const int kSessionId3 = 3; + +namespace { + +// Default key system identifier. +const char kKeySystemWidevine[] = "com.widevine.alpha"; + +// Default mime type for session creation. +const char kMimeType[] = "video/mp4"; + +// Known filename for certificate manipulation. +const std::string kCertFilename = "cert.bin"; + +// Key ID of key used to encrypt the test content. +// This is used to look up the content key. +const std::vector kTestKeyId = + wvcdm::a2b_hex("371ea35e1a985d75d198a7f41020dc23"); + +// Dummy encrypted data. +const std::vector kInputVector1 = wvcdm::a2b_hex( + "64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36" + "17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d" + "7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab" + "ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c" + "2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4" + "9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4" + "6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012" + "c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685"); +const std::vector kIv1 = + wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f"); + +// Expected output for kInputVector1. +const std::vector kOutputVector1 = wvcdm::a2b_hex( + "217ce9bde99bd91e9733a1a00b9b557ac3a433dc92633546156817fae26b6e1c" + "942ac20a89ff79f4c2f25fba99d6a44618a8c0420b27d54e3da17b77c9d43cca" + "595d259a1e4a8b6d7744cd98c5d3f921adc252eb7d8af6b916044b676a574747" + "8df21fdc42f166880d97a2225cd5c9ea5e7b752f4cf81bbdbe98e542ee10e1c6" + "ad868a6ac55c10d564fc23b8acff407daaf4ed2743520e02cda9680d9ea88e91" + "029359c4cf5906b6ab5bf60fbb3f1a1c7c59acfc7e4fb4ad8e623c04d503a3dd" + "4884604c8da8a53ce33db9ff8f1c5bb6bb97f37b39906bf41596555c1bcce9ed" + "08a899cd760ff0899a1170c2f224b9c52997a0785b7fe170805fd3e8b1127659"); + +// Dummy encrypted data. This is a combination of clear and encrypted data. +const std::vector kInputVector2 = wvcdm::a2b_hex( + // subsample 0 + "abcdef" + "53cc758763904ea5870458e6b23d36db1e6d7f7aaa2f3eeebb5393a7264991e7" + "ce4f57b198326e1a208a821799b2a29c90567ab57321b06e51fc20dc9bc5fc55" + "10720a8bb1f5e002c3e50ff70d2d806a9432cad237050d09581f5b0d59b00090" + "b3ad69b4087f5a155b17e13c44d33fa007475d207fc4ac2ef3b571ecb9" + // subsample 1 + "0123456789" + "f3c852" + "ce00dc4806f0c6856ae1732e20308096478e1d822d75c2bb768119565d3bd6e6" + "901e36164f4802355ee758fc46ef6cf5f852dd5256c7b1e5f96d29" + // subsample 2 + "deadbeefbaadf00d" + "3b20525d5e" + "78b8e5aa344d5c4e425e67ddf889ea7c4bb1d49af67eba67718b765e0a940402" + "8d306f4ce693ad6dc0a931d507fa14fff4d293d4170280b3e0fca2d628f722e8" +); +const std::vector kIv2 = + wvcdm::a2b_hex("6ba18dd40f49da7f64c368e4db43fc88"); + +// Expected output for kInputVector2. +const std::vector kOutputVector2 = wvcdm::a2b_hex( + // subsample 0 + "abcdef" + "52e65334501acadf78e2b26460def3ac973771ed7c64001a2e82917342a7eab3" + "047f5e85449692fae8f677be425a47bdea850df5a3ffff17043afb1f2b437ab2" + "b1d5e0784c4ed8f97fc24b8f565e85ed63fb7d1365980d9aea7b8b58f488f83c" + "1ce80b6096c60f3b113c988ff185b26e798da8fc6f327e4ff00e4b3fbf" + // subsample 1 + "0123456789" + "b1ed0a" + "a054bce40ccb0ebc70b181d1a12055f46ac55e29c7c2473a29d2a366d240ec48" + "7cede274f012813a877f99159e7062b6a37cfc9327a7bc2195814e" + // subsample 2 + "deadbeefbaadf00d" + "653b818d1d" + "4ab9a9128361d8ca6a9d2766df5c096ee29f4f5204febdf217a94a5b560cd692" + "cc36d3e071df789fdeac2fb7ec6dcd7af94bb1f85c22025b25e702e38212b927" +); + +// Dummy encrypted data. This will be decrypted with a data_offset +// instead of subsamples. +const std::vector kInputVector3 = wvcdm::a2b_hex( + "64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36" + "17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d" + "7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab" + "ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c" + "2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4" + "9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4" + "6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012" + "c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685"); +const std::vector kIv3 = + wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f"); + +// The data_offset for kInputVector3. +const uint32_t kInputOffset3 = 9; + +// Expected output for kInputVector3 offset by kInputOffset3. +const std::vector kOutputVector3 = wvcdm::a2b_hex( + "19ab304b49908e2395b32f26bf471adf4a4bc92f9e999cca8476d24a257931b4" + "c5fd177693ed55e31cd2b85dc196b2b722cd8854eb9334f3dab0b5bd26aa5e66" + "a9d1cfbba877c9456b11dc99a6bdc7015ca1544f7ce66171a8179eca19efe515" + "8c4c1d0612dff64100387065da108fdbfcc14738202ac3d27520eb48c020ddb7" + "714dca22e5e2241aff6932dba1587a97ac1a952827d411d8582dfecc2e9e1494" + "644046ca7044bc41c3c5e0a3a405d5551f3f5bdd6f36042e2f0f3693778b9277" + "6ed8d106647a7539df7d30288803cd9ca1c274bebe688151c72b451f571a441f" + "83d0ff77d8d57dcb395122e175f4944569917627d6c3dc"); + +// Dummy data used as a server certificate. Not a real cert. +const std::vector kFakeServerCertficiate = wvcdm::a2b_hex( + "65393939a0e4a28fb07aa8237bdc9e2fd30fdf763061ed9ed91b368a2b78fc2d" + "15f0b6add5d56d05f7e5852aeae67de6127d6b61b39bb5d6e9657f352ba75e72" + "c436f334878568504f697ad01aa329efbadebc3b9aaf502ada9b8e6fcf066252" + "76690a0f50cd3852dd39f7c5444402f86831d8bb2cbbd7cba11ab1caa35445fe" + "35a332529845f2f4b5d5a0b1e2c0855fc2d644443eb967e1f9030fd0d9e6375b" + "a5100a997ff8a958606d59a00151d251eaf69f9e00465b5aa4c23ef33a11b05b" + "212c1ff830fcd095c681ef25a18db08d7bddfeee16763e9fec06daa8275de2b0" + "554a0bec821fb7bb6fbda081d8cecce6c51195e6b6a1c0fcbb2470bcff2a962e" + "57e767f83cc8b6c31d41d7c59526128f19cbf625fa4f5f382393a3bd2b76463a" + "fe97e4a6f30f631c83308aa5fdccc2d1765c113d474bf2496e03c030c18e5ca8" + "84cb98fa120804baa245682966926ccbf555450437de10e549afc088f8c36f63" + "8a943178bdd58e4ef1f7d501e2296bbe2df57ce8816d6ae9e7ab18b1e01dac9b" + "8c312298356cad58c6ed46b1cd3a895e496c66f5229da39e260a7c1f782653bd" + "ade5f6132fc4771bb8caca80eb063abb47144abc44aaba8d23fcc721199291b3" + "0b95bc7d310c3f90756916552151a6008feba73ddab87454822914732d6ee78a" + "3587f33c698b0ab90f01b38a71abd01660a5ef1473e4556aa8c17d34679065c5" + "689dd21026601e94146254346f4ccd979bc378046473b3de64b8654e18406e94" + "b673dc13fdcc70213365bd098f212ed7a973ef35da18e16b1c118f8d4eda0b39" + "ffb8084ca93a44923df8475e3feec6c0941a2a37d5df514e686dd182dc9ebbff" + "41d8d80aa39d059de3b7849d4e5946b09937c6e7a6f6392ea5e9370ba1867553" + "b716e31433c2e3ca3eff1e7c08a31b201fd18c363b8daeed59df7afb68ad5166" + "659914a2e137c9d2e5940aaa921694c8d84d5ea1c83486e473e3021cb825c0ed" + "f778c621598d35e44843cd53e32f90925e74bc8eb469889fb221a2c592b43d94" + "2079d6393ab76e47d30dbf837fa5ca07d7186710973c5bb17c04b3207a85a166" + "153053f6b0ec35c6b124efd43be274cdf8300234806a72231272d13b331cafd3" + "dab01002b5e1961a5f3221d4c589fac3e42c1d1de9f244090e31a08999d3434d" + "15a5159f09fa3307e0d9466ac40af63c0a62f8d2719e1df80bb24d4d7ab256f7" + "0906ee342a47a9bd6805d796cf928f0d5251701ba8e6888675b1b6fd03e77df0" + "8150495c778cb7942d8c060ace4b080ad22e44854988d5e2232f5dbcf2db559f" + "24bae8667c33cea77f1c6a58d9dd010363c233cff6d5d26f5f77230ee681456c" + "35a8f90d37c2fc0ab07a2a795431f829cca574fd37d8822e04fc500ba468f08a" + "556e53ba8992dd1ccbd44559a7b93bebc27cec81a834755cf110bce183481e42"); + +void* GetCdmHost(int host_interface_version, void* user_data) { + if (host_interface_version != cdm::Host_4::kVersion) return NULL; + return user_data; +} + +} // namespace + +namespace wvcdm { + +class CdmApi4Test : public testing::Test { + public: + CdmApi4Test() : cdm_(NULL) {} + ~CdmApi4Test() {} + + protected: + virtual void SetUp() { + // Create the Host. + host_.reset(new TestHost_4()); + + // Load the device cert that was already "saved" to the device. + std::string cert(reinterpret_cast(kDeviceCert), + sizeof(kDeviceCert)); + host_->file_store[kCertFilename] = cert; + + // Initialize the CDM module before creating a CDM instance. + InitializeCdmModule(); + + // Create the CDM. + cdm_ = + reinterpret_cast(::CreateCdmInstance( + cdm::ContentDecryptionModule_4::kVersion, kKeySystemWidevine, + strlen(kKeySystemWidevine), GetCdmHost, host_.get())); + + if (cdm_ == NULL) { + FAIL() << "Fatal CDM creation error!"; + } + + // Tell the Host about the CDM. + host_->SetCdmPtr(cdm_); + } + + void CreateSession(const uint32_t session_id, const std::string& init_data, + cdm::SessionType session_type) { + cdm_->CreateSession(session_id, kMimeType, strlen(kMimeType), + reinterpret_cast(init_data.data()), + init_data.length(), session_type); + } + + void CreateSessionWithMimeType(const uint32_t session_id, + const std::string& mime_type) { + cdm_->CreateSession(session_id, mime_type.c_str(), mime_type.length(), + reinterpret_cast(g_key_id.data()), + g_key_id.length(), cdm::kTemporary); + } + + void LoadSession(uint32_t session_id, const std::string& web_session_id) { + cdm_->LoadSession(session_id, web_session_id.data(), + web_session_id.length()); + } + + // posts a request and extracts the drm message from the response + std::string GetKeyRequestResponse( + const TestHost_4::SessionMessage& session_msg, + const std::string& default_url, bool is_provision) { + std::string url; + if (session_msg.default_url.empty()) { + url = default_url; + } else { + // Note that the client auth string is assumed to already be appended when + // the CDM tells us what URL to use. + url = session_msg.default_url; + } + + UrlRequest url_request(url); + EXPECT_TRUE(url_request.is_connected()); + if (!url_request.is_connected()) { + return ""; + } + + if (!is_provision) { + url_request.PostRequest(session_msg.message); + } else { + url_request.PostCertRequestInQueryString(session_msg.message); + } + std::string response; + int resp_bytes = url_request.GetResponse(&response); + + // Some license servers return 400 for invalid message, some + // return 500; treat anything other than 200 as an invalid message. + int status_code = url_request.GetStatusCode(response); + EXPECT_EQ(kHttpOk, status_code); + + if (status_code != kHttpOk) { + return ""; + } else { + std::string drm_msg; + LicenseRequest lic_request; + lic_request.GetDrmMessage(response, drm_msg); + LOGV("drm msg: %u bytes\n%s", drm_msg.size(), + HexEncode(reinterpret_cast(drm_msg.data()), + drm_msg.size()).c_str()); + return drm_msg; + } + } + + void ProcessKeyResponse(bool expect_url_in_message) { + ProcessKeyResponse(expect_url_in_message, g_license_server + g_client_auth); + } + + void ProcessKeyResponse(bool expect_url_in_message, + const std::string& default_url) { + ProcessServerResponse(expect_url_in_message, false, default_url); + } + + void ProcessProvisionResponse() { ProcessServerResponse(true, true, ""); } + + void ProcessServerResponse(bool expect_url_in_message, bool is_provision, + const std::string& default_url) { + TestHost_4::SessionMessage session_msg = host_->GetLastSessionMessage(); + ASSERT_FALSE(session_msg.message.empty()); + // Use EXPECT_bool instead of EXPECT_EQ to get better error printing. + if (expect_url_in_message) { + EXPECT_FALSE(session_msg.default_url.empty()); + } else { + EXPECT_TRUE(session_msg.default_url.empty()); + } + std::string drm_msg = + GetKeyRequestResponse(session_msg, default_url, is_provision); + UpdateSession(session_msg.session_id, (const uint8_t*)drm_msg.c_str(), + drm_msg.length()); + } + + void ReleaseSession(uint32_t session_id) { cdm_->ReleaseSession(session_id); } + + void RemoveSession(uint32_t session_id, const std::string& web_session_id) { + cdm_->RemoveSession(session_id, web_session_id.data(), + web_session_id.length()); + } + + void UpdateSession(uint32_t session_id, const uint8_t* response, + uint32_t response_size) { + cdm_->UpdateSession(session_id, response, response_size); + } + + cdm::Status UsePrivacyMode() { return cdm_->UsePrivacyMode(); } + + cdm::Status SetServerCertificate(const std::vector& cert) { + return cdm_->SetServerCertificate(&cert[0], cert.size()); + } + + bool SessionErrorPresent(uint32_t session_id) { + TestHost_4::SessionError serr = host_->GetLastSessionError(); + if (serr.session_id == session_id) { + return true; + } + return false; + } + + bool SessionErrorPresent(uint32_t session_id, cdm::Status error_code) { + TestHost_4::SessionError serr = host_->GetLastSessionError(); + if (serr.session_id == session_id && serr.error_code == error_code) { + return true; + } + return false; + } + + cdm::InputBuffer BuildInputBuffer(const std::vector& encrypted, + const std::vector& iv) { + cdm::InputBuffer buf; + buf.data = &encrypted[0]; + buf.data_size = encrypted.size(); + buf.key_id = &kTestKeyId[0]; + buf.key_id_size = kTestKeyId.size(); + buf.iv = &iv[0]; + buf.iv_size = iv.size(); + buf.data_offset = 0; + buf.timestamp = 10; + return buf; + } + + cdm::InputBuffer BuildInputBuffer( + const std::vector& encrypted, const std::vector& iv, + const std::vector& sub) { + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv); + buf.subsamples = &sub[0]; + buf.num_subsamples = sub.size(); + return buf; + } + + cdm::InputBuffer BuildInputBuffer(const std::vector& encrypted, + const std::vector& iv, + const uint32_t data_offset) { + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv); + buf.data_offset = data_offset; + return buf; + } + + std::vector BuildMultipleSubsamples() { + std::vector sub; + sub.push_back(cdm::SubsampleEntry(3, 125)); + sub.push_back(cdm::SubsampleEntry(5, 62)); + sub.push_back(cdm::SubsampleEntry(8, 69)); + return sub; + } + + std::vector BuildSingleSubsample(size_t size) { + std::vector sub; + sub.push_back(cdm::SubsampleEntry(0, size)); + return sub; + } + + void GetOfflineConfiguration(std::string* key_id, std::string* license_server, + std::string* client_auth) { + // This method compares three different places settings could be found: + // 1) A configuration object using the default settings. + // 2) The global variables prefixed with g_. + // 3) A configuration object configured for offline playback. + // The desire is to use 3 unless the user customized the settings on the + // command line, in which case we want to defer to them and use 2. The "if" + // statements check if 1 and 2 are the same. If so, we know the user + // did not customize the settings on the command line, and therefore we can + // use 3. Otherwise, we use 2. This will never return the values from 1; + // 1 is only used to check if 2 has been altered by the user. + ConfigTestEnv std_config(kLicenseServerId); + // TODO (juce): Switch this to kLicenseServerId once + // kContentProtectionServer is the default. + ConfigTestEnv config(wvcdm::kContentProtectionServer, false); + + if (g_key_id.compare(a2bs_hex(std_config.key_id())) == 0) + key_id->assign(wvcdm::a2bs_hex(config.key_id())); + else + key_id->assign(g_key_id); + + if (g_license_server.compare(std_config.license_server()) == 0) + license_server->assign(config.license_server()); + else + license_server->assign(g_license_server); + + if (g_client_auth.compare(std_config.client_auth()) == 0) + client_auth->assign(config.client_auth()); + else + client_auth->assign(g_client_auth); + } + + cdm::ContentDecryptionModule_4* cdm_; // owned by host_ + wvcdm::scoped_ptr host_; +}; + +namespace { + +class DummyCDM : public cdm::ContentDecryptionModule_4 { + public: + DummyCDM() : timer_fired_(false), last_context_(NULL) {} + + virtual void CreateSession(uint32_t session_id, + const char* mime_type, uint32_t mime_type_size, + const uint8_t* init_data, uint32_t init_data_size, + cdm::SessionType session_type) OVERRIDE {} + + virtual void LoadSession(uint32_t session_id, const char* web_session_id, + uint32_t web_session_id_length) OVERRIDE {} + + virtual void UpdateSession(uint32_t session_id, const uint8_t* response, + uint32_t response_size) OVERRIDE {} + + virtual bool IsKeyValid(const uint8_t*, int) OVERRIDE { return false; } + + virtual void ReleaseSession(uint32_t session_id) OVERRIDE {} + + virtual void RemoveSession(uint32_t session_id, const char* web_session_id, + uint32_t web_session_id_length) {} + + virtual cdm::Status UsePrivacyMode() OVERRIDE { return cdm::kSuccess; } + + virtual cdm::Status SetServerCertificate( + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) OVERRIDE { + return cdm::kSuccess; + } + + virtual void TimerExpired(void* context) OVERRIDE { + timer_fired_ = true; + last_context_ = context; + } + + virtual cdm::Status Decrypt(const cdm::InputBuffer&, + cdm::DecryptedBlock*) OVERRIDE { + return cdm::kSessionError; + } + + virtual cdm::Status DecryptDecodeAndRender( + const cdm::InputBuffer& encrypted_buffer, + cdm::StreamType stream_type) OVERRIDE { + return cdm::kDecryptError; + } + + virtual void Destroy() OVERRIDE { delete this; } + + bool TimerFired() const { return timer_fired_; } + + void* LastTimerContext() const { return last_context_; } + + void ResetTimerStatus() { + timer_fired_ = false; + last_context_ = NULL; + } + + private: + bool timer_fired_; + void* last_context_; +}; + +} // namespace + +TEST_F(CdmApi4Test, TestHostTimer) { + // Validate that the TestHost timers are processed in the correct order. + // To do this, we replace the cdm with a dummy that only tracks timers. + DummyCDM* cdm = new DummyCDM(); + + // The old CDM is destroyed by SetCdmPtr. + cdm_ = cdm; + host_->SetCdmPtr(cdm); + + const double kTimerDelaySeconds = 1.0; + const int64_t kTimerDelayMs = kTimerDelaySeconds * 1000; + void* kCtx1 = reinterpret_cast(0x1); + void* kCtx2 = reinterpret_cast(0x2); + + host_->SetTimer(kTimerDelayMs * 1, kCtx1); + host_->SetTimer(kTimerDelayMs * 2, kCtx2); + + host_->FastForwardTime(kTimerDelaySeconds); + EXPECT_TRUE(cdm->TimerFired()); + EXPECT_EQ(kCtx1, cdm->LastTimerContext()); + cdm->ResetTimerStatus(); + + host_->FastForwardTime(kTimerDelaySeconds); + EXPECT_TRUE(cdm->TimerFired()); + EXPECT_EQ(kCtx2, cdm->LastTimerContext()); + cdm->ResetTimerStatus(); + + host_->FastForwardTime(kTimerDelaySeconds); + EXPECT_FALSE(cdm->TimerFired()); +} + +TEST_F(CdmApi4Test, DeviceCertificateTest) { + if (Properties::use_certificates_as_identification()) { + // Clear any existing certificates + host_->file_store.erase(kCertFilename); + + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ASSERT_TRUE(SessionErrorPresent(kSessionId1, cdm::kNeedsDeviceCertificate)); + + // The Host must handle the certificate provisioning request. + CreateSession(kSessionId2, "", cdm::kProvisioning); + ASSERT_FALSE(SessionErrorPresent(kSessionId2)); + ProcessProvisionResponse(); + ASSERT_FALSE(SessionErrorPresent(kSessionId2)); + + CreateSession(kSessionId3, g_key_id, cdm::kTemporary); + ASSERT_FALSE(SessionErrorPresent(kSessionId3)); + } else { + LOGI( + "Skipping CdmApi4Test::DeviceCertificateTest because this platform " + "does not support device certificates."); + } +} + +// Note that these tests, BaseMessageTest, NormalDecryption, TimeTest, and +// others are dependent on getting back a license from the license server where +// the url for the license server is defined in the conf_test_env.cpp. If these +// tests fail immediately, verify that the license server URL is correct +// and works in your test environment. + +TEST_F(CdmApi4Test, BaseMessageTest) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ProcessKeyResponse(false); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, NormalDecryption) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ProcessKeyResponse(false); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + // Level 1 / Level 2 payload comes back in the cpu memory as cleartext. + std::vector encrypted = kInputVector1; + std::vector iv = kIv1; + std::vector expected = kOutputVector1; + std::vector sub = BuildSingleSubsample(encrypted.size()); + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub); + + TestDecryptedBlock output; + cdm::Status status = cdm_->Decrypt(buf, &output); + + EXPECT_EQ(cdm::kSuccess, status); + EXPECT_EQ( + 0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size)); + + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, NormalSubSampleDecryptionWithSubsampleInfo) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ProcessKeyResponse(false); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + // Level 1 / Level 2 payload comes back in the cpu memory as cleartext. + std::vector encrypted = kInputVector2; + std::vector iv = kIv2; + std::vector expected = kOutputVector2; + std::vector sub = BuildMultipleSubsamples(); + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub); + + TestDecryptedBlock output; + cdm::Status status = cdm_->Decrypt(buf, &output); + + EXPECT_EQ(cdm::kSuccess, status); + EXPECT_EQ( + 0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size)); + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, NormalSubSampleDecryptionWithMissingSubsampleInfo) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ProcessKeyResponse(false); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + std::vector encrypted = kInputVector2; + std::vector iv = kIv2; + std::vector expected = kOutputVector2; + std::vector sub = BuildMultipleSubsamples(); + + // Don't add these subsamples yet! + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv); + buf.num_subsamples = sub.size(); + + TestDecryptedBlock output; + + cdm::Status status = cdm_->Decrypt(buf, &output); + EXPECT_EQ(cdm::kDecryptError, status); + + // Add the subsamples pointer and expect success. + buf.subsamples = &sub[0]; + status = cdm_->Decrypt(buf, &output); + EXPECT_EQ(cdm::kSuccess, status); + EXPECT_EQ( + 0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size)); + + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, DecryptWithDataOffset) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ProcessKeyResponse(false); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + + std::vector encrypted = kInputVector3; + std::vector iv = kIv3; + std::vector expected = kOutputVector3; + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, kInputOffset3); + + TestDecryptedBlock output; + cdm::Status status = cdm_->Decrypt(buf, &output); + cdm::Buffer* output_buf = output.DecryptedBuffer(); + + EXPECT_EQ(cdm::kSuccess, status); + EXPECT_EQ(buf.data_size - buf.data_offset, output_buf->Size()); + EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], + output_buf->Size())); + + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, DecryptReturnsSizedBuffer) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ProcessKeyResponse(false); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + + std::vector encrypted = kInputVector1; + std::vector iv = kIv1; + std::vector expected = kOutputVector1; + + std::vector sub = BuildSingleSubsample(encrypted.size()); + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub); + + TestDecryptedBlock output; + cdm::Status status = cdm_->Decrypt(buf, &output); + + EXPECT_EQ(cdm::kSuccess, status); + cdm::Buffer* buffer = output.DecryptedBuffer(); + EXPECT_NE((void*)NULL, buffer); + if (buffer) { + EXPECT_EQ(expected.size(), output.DecryptedBuffer()->Size()); + } + + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, TimeTest) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ProcessKeyResponse(false); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + // We expect that by the time we've added a key, the CDM has set a timer. + // Otherwise, it couldn't correctly handle renewal. + EXPECT_NE(0, host_->NumTimers()); + host_->FastForwardTime(kTestPolicyRenewalDelaySeconds + + kDelayWaitToForRenewalMessageSeconds); + // When the timer expired, we should have sent a renewal, so we can + // add this renewed key now, assuming things are working as expected. + ProcessKeyResponse(true); + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, SecureDecryptionLevel1) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ProcessKeyResponse(false); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + // Level 1 passes encrypted payload straight through. By calling the CDM's + // DecryptDecodeAndRender, OEMCrypto_DecryptCTR will be told to use Direct + // Rendering. + std::vector encrypted = kInputVector1; + std::vector iv = kIv1; + std::vector sub = BuildSingleSubsample(encrypted.size()); + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub); + + cdm::Status status; + + status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeVideo); + EXPECT_EQ(cdm::kSuccess, status); + status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeAudio); + EXPECT_EQ(cdm::kSuccess, status); + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, SecureDecryptionLevel1WithSubsampleInfo) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ProcessKeyResponse(false); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + // Level 1 passes encrypted payload straight through. By calling the + // CDM's DecryptDecodeAndRender, OEMCrypto_DecryptCTR will be told + // to use Direct Rendering. + std::vector encrypted = kInputVector2; + std::vector iv = kIv2; + std::vector sub = BuildMultipleSubsamples(); + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub); + + cdm::Status status; + + status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeVideo); + EXPECT_EQ(cdm::kSuccess, status); + status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeAudio); + EXPECT_EQ(cdm::kSuccess, status); + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, SecureDecryptionLevel1WithMissingSubsampleInfo) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ProcessKeyResponse(false); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + std::vector encrypted = kInputVector2; + std::vector iv = kIv2; + std::vector sub = BuildMultipleSubsamples(); + + // Don't add these subsamples yet! + cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv); + buf.num_subsamples = sub.size(); + + cdm::Status status; + + status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeVideo); + EXPECT_EQ(cdm::kDecryptError, status); + status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeAudio); + EXPECT_EQ(cdm::kDecryptError, status); + + // Add the subsamples pointer and expect success. + buf.subsamples = &sub[0]; + status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeVideo); + EXPECT_EQ(cdm::kSuccess, status); + status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeAudio); + EXPECT_EQ(cdm::kSuccess, status); + + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, GenerateKeyRequestFailureSendsKeyError) { + // Pass a bogus key id and expect failure. + // Expect the CDM to pass a key error back to the host. + CreateSession(kSessionId1, g_wrong_key_id, cdm::kTemporary); + EXPECT_EQ(1, host_->SessionErrorsSize()); + EXPECT_EQ(0, host_->SessionMessagesSize()); + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, UpdateSessionFailureSendsKeyError) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + TestHost_4::SessionMessage session_msg = host_->GetLastSessionMessage(); + EXPECT_TRUE(session_msg.default_url.empty()); + std::string drm_msg = GetKeyRequestResponse( + session_msg, g_license_server + g_client_auth, false); + + // Call UpdateSession with a bad session id, and expect the CDM to pass a key + // error back to the host. + UpdateSession(kSessionId2, reinterpret_cast(drm_msg.c_str()), + drm_msg.length()); + EXPECT_EQ(1, host_->SessionErrorsSize()); + + // Call UpdateSession with a bad license, and expect the CDM to pass a key + // error back to the host. + static const std::string kBadLicense = "!kGoodLicense"; + UpdateSession(kSessionId1, + reinterpret_cast(kBadLicense.c_str()), + kBadLicense.length()); + EXPECT_EQ(2, host_->SessionErrorsSize()); + + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, IsKeyValidDetectsValidKey) { + EXPECT_FALSE(cdm_->IsKeyValid( + reinterpret_cast(kTestKeyId.data()), kTestKeyId.size())); + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ProcessKeyResponse(false); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + EXPECT_TRUE(cdm_->IsKeyValid( + reinterpret_cast(kTestKeyId.data()), kTestKeyId.size())); + ReleaseSession(kSessionId1); + EXPECT_FALSE(cdm_->IsKeyValid( + reinterpret_cast(kTestKeyId.data()), kTestKeyId.size())); +} + +TEST_F(CdmApi4Test, OfflineLicense) { + // override default settings unless configured through the command line + std::string key_id; + std::string license_server; + std::string client_auth; + GetOfflineConfiguration(&key_id, &license_server, &client_auth); + + CreateSession(kSessionId1, key_id, cdm::kPersistent); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + ProcessKeyResponse(false, license_server + client_auth); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, RestoreOfflineLicense) { + // override default settings unless configured through the command line + std::string key_id; + std::string license_server; + std::string client_auth; + GetOfflineConfiguration(&key_id, &license_server, &client_auth); + + CreateSession(kSessionId1, key_id, cdm::kPersistent); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + ProcessKeyResponse(false, license_server + client_auth); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + + std::string web_session_id = host_->session_map[kSessionId1]; + EXPECT_FALSE(web_session_id.empty()); + ReleaseSession(kSessionId1); + + LoadSession(kSessionId2, web_session_id); + ASSERT_FALSE(SessionErrorPresent(kSessionId2)); + ReleaseSession(kSessionId2); +} + +TEST_F(CdmApi4Test, ReleaseOfflineLicense) { + // override default settings unless configured through the command line + std::string key_id; + std::string license_server; + std::string client_auth; + GetOfflineConfiguration(&key_id, &license_server, &client_auth); + + CreateSession(kSessionId1, key_id, cdm::kPersistent); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + ProcessKeyResponse(false, license_server + client_auth); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + std::string web_session_id = host_->session_map[kSessionId1]; + EXPECT_FALSE(web_session_id.empty()); + ReleaseSession(kSessionId1); + + RemoveSession(kSessionId2, web_session_id); + ProcessKeyResponse(true, license_server + client_auth); + ASSERT_FALSE(SessionErrorPresent(kSessionId2)); +} + +TEST_F(CdmApi4Test, MimeTypeMatters) { + CreateSessionWithMimeType(kSessionId1, "video/mp4"); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + ReleaseSession(kSessionId1); + + CreateSessionWithMimeType(kSessionId1, "video/webm"); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + ReleaseSession(kSessionId1); + + CreateSessionWithMimeType(kSessionId1, "video/blah"); + ASSERT_TRUE(SessionErrorPresent(kSessionId1)); +} + +TEST_F(CdmApi4Test, UsePrivacyMode) { + ASSERT_EQ(cdm::kSuccess, UsePrivacyMode()); + + const CdmClientPropertySet& property_set = + reinterpret_cast(cdm_)->property_set_; + EXPECT_TRUE(property_set.use_privacy_mode()); +} + +TEST_F(CdmApi4Test, UsePrivacyModeFailsWithOpenSessions) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + + EXPECT_NE(cdm::kSuccess, UsePrivacyMode()); + const CdmClientPropertySet& property_set = + reinterpret_cast(cdm_)->property_set_; + EXPECT_FALSE(property_set.use_privacy_mode()); + + ReleaseSession(kSessionId1); +} + +TEST_F(CdmApi4Test, SetExplicitServerCertificate) { + ASSERT_EQ(cdm::kSuccess, SetServerCertificate(kFakeServerCertficiate)); + + const CdmClientPropertySet& property_set = + reinterpret_cast(cdm_)->property_set_; + EXPECT_TRUE(property_set.use_privacy_mode()); + const std::string& set_cert = property_set.service_certificate(); + ASSERT_EQ(kFakeServerCertficiate.size(), set_cert.size()); + EXPECT_EQ(0, + memcmp(&kFakeServerCertficiate[0], &set_cert[0], set_cert.size())); +} + +TEST_F(CdmApi4Test, SetServerCertificateFailsWithOpenSessions) { + CreateSession(kSessionId1, g_key_id, cdm::kTemporary); + ASSERT_FALSE(SessionErrorPresent(kSessionId1)); + + EXPECT_NE(cdm::kSuccess, SetServerCertificate(kFakeServerCertficiate)); + const CdmClientPropertySet& property_set = + reinterpret_cast(cdm_)->property_set_; + EXPECT_FALSE(property_set.use_privacy_mode()); + + ReleaseSession(kSessionId1); +} + +} // namespace wvcdm diff --git a/cdm/test/cdm_api_test.cpp b/cdm/test/cdm_api_test.cpp deleted file mode 100644 index 80c1dd7e..00000000 --- a/cdm/test/cdm_api_test.cpp +++ /dev/null @@ -1,1289 +0,0 @@ -// Copyright 2013 Google Inc. All Rights Reserved. -// -// This source file provides a basic set of unit tests for the Content -// Decryption Module (CDM). It exercises much of the API that will be -// required by the host application to get the license and keys for -// rendering protected content. - -// Review the TestHost class below to observe how the CDM interfaces with -// the host application. - -#include - -#include -#include -#include - -#include - -#include "clock.h" -#include "config_test_env.h" -#include "content_decryption_module.h" -#include "device_cert.h" -#include "license_request.h" -#include "log.h" -#include "scoped_ptr.h" -#include "string_conversions.h" -#include "url_request.h" -#include "wv_cdm_common.h" - -using wvcdm::scoped_ptr; - -static const int kTestPolicyRenewalDelaySeconds = 180; -static const int kDelayWaitToForRenewalMessageSeconds = 2; -static const int kHttpOk = 200; - -static double GetCurrentTime() { - struct timeval tv; - tv.tv_sec = tv.tv_usec = 0; - gettimeofday(&tv, NULL); - return tv.tv_sec + (tv.tv_usec / (1000.0 * 1000.0)); -} - -// These classes below are naive implementation of the abstract classes defined -// in the CDM interface (content_decryptiom_module.h), which are used for tests -// only. -class TestBuffer : public cdm::Buffer { - public: - static TestBuffer* Create(uint32_t capacity); - - virtual void Destroy() OVERRIDE; - - virtual int32_t Capacity() const OVERRIDE; - virtual uint8_t* Data() OVERRIDE; - virtual void SetSize(int32_t size) OVERRIDE; - virtual int32_t Size() const OVERRIDE; - - private: - // TestBuffer can only be created by calling Create(). - explicit TestBuffer(uint32_t capacity); - // TestBuffer can only be destroyed by calling Destroy(). - virtual ~TestBuffer(); - - uint8_t* buffer_; - int32_t capacity_; - int32_t size_; - - CORE_DISALLOW_COPY_AND_ASSIGN(TestBuffer); -}; - -class TestHost : public cdm::Host { - public: - // These structs are used to store the KeyMessages and KeyErrors passed to - // this class' objects. - struct KeyMessage { - std::string session_id; - std::string message; - std::string default_url; - }; - - struct KeyError { - KeyError() : error_code(cdm::kUnknownError), system_code(0) {} - std::string session_id; - cdm::MediaKeyError error_code; - uint32_t system_code; - }; - - TestHost(); - virtual ~TestHost(); - - // cdm::Host implementation. - virtual cdm::Buffer* Allocate(int32_t capacity) OVERRIDE; - - virtual void SetTimer(int64_t delay_ms, void* context) OVERRIDE; - - virtual double GetCurrentWallTimeInSeconds() OVERRIDE; - - virtual void SendKeyMessage(const char* session_id, int32_t session_id_length, - const char* message, int32_t message_length, - const char* default_url, - int32_t default_url_length) OVERRIDE; - - virtual void SendKeyError(const char* session_id, int32_t session_id_length, - cdm::MediaKeyError error_code, - uint32_t system_code) OVERRIDE; - - virtual void GetPlatformString(const std::string& name, - std::string* value) OVERRIDE; - - virtual void SetPlatformString(const std::string& name, - const std::string& value) OVERRIDE; - - // Methods only for this test. - void FastForwardTime(double seconds); - int KeyMessagesSize() const; - int KeyErrorsSize() const; - int NumTimers() const; - - // Returns Key{Message,Error} (replace Message with Error for KeyError). It - // returns the most recent message passed to SendKeyMessage(). Another call - // to this method without a new SendKeyMessage() call will return an empty - // KeyMessage struct. - KeyMessage GetLastKeyMessage(); - KeyError GetLastKeyError(); - - KeyMessage GetKeyMessage(int index) const; - KeyError GetKeyError(int index) const; - - void SetCdmPtr(cdm::ContentDecryptionModule* cdm); - - private: - struct Timer { - Timer(double expiry_time, void* context) - : expiry_time(expiry_time), context(context) {} - - bool operator<(const Timer& other) const { - // We want to reverse the order so that the smallest expiry times go to - // the top of the priority queue. - return expiry_time > other.expiry_time; - } - - double expiry_time; - void* context; - }; - - double current_time_; - std::priority_queue timers_; - - std::vector key_messages_; - std::vector key_errors_; - - bool has_new_key_message_; - bool has_new_key_error_; - - std::map platform_strings_; - - cdm::ContentDecryptionModule* cdm_; - - CORE_DISALLOW_COPY_AND_ASSIGN(TestHost); -}; - -TestBuffer* TestBuffer::Create(uint32_t capacity) { - return new TestBuffer(capacity); -} - -void TestBuffer::Destroy() { - if (buffer_) { - delete[] buffer_; - buffer_ = NULL; - } - delete this; -} - -int32_t TestBuffer::Capacity() const { return capacity_; } - -uint8_t* TestBuffer::Data() { return buffer_; } - -void TestBuffer::SetSize(int32_t size) { size_ = size; } - -int32_t TestBuffer::Size() const { return size_; } - -TestBuffer::TestBuffer(uint32_t capacity) - : buffer_(new uint8_t[capacity]), - capacity_(capacity) {} - -TestBuffer::~TestBuffer() {} - -TestHost::TestHost() - : current_time_(GetCurrentTime()), - has_new_key_message_(false), - has_new_key_error_(false), - cdm_(NULL) { -} - -TestHost::~TestHost() { - if (cdm_) - cdm_->Destroy(); -} - -cdm::Buffer* TestHost::Allocate(int32_t capacity) { - return TestBuffer::Create(capacity); -} - -void TestHost::SetTimer(int64_t delay_ms, void* context) { - double expiry_time = current_time_ + (delay_ms / 1000.0); - timers_.push(Timer(expiry_time, context)); -} - -double TestHost::GetCurrentWallTimeInSeconds() { - return current_time_; -} - -void TestHost::SendKeyMessage(const char* session_id, int32_t session_id_length, - const char* message, int32_t message_length, - const char* default_url, - int32_t default_url_length) { - KeyMessage key_message; - key_message.session_id.assign(session_id, session_id_length); - key_message.message.assign(message, message_length); - key_message.default_url.assign(default_url, default_url_length); - key_messages_.push_back(key_message); - has_new_key_message_ = true; -} - -void TestHost::SendKeyError(const char* session_id, int32_t session_id_length, - cdm::MediaKeyError error_code, - uint32_t system_code) { - KeyError key_error; - key_error.session_id.assign(session_id, session_id_length); - key_error.error_code = error_code; - key_error.system_code = system_code; - key_errors_.push_back(key_error); - has_new_key_error_ = true; -} - -void TestHost::FastForwardTime(double seconds) { - double goal_time = current_time_ + seconds; - while (current_time_ < goal_time) { - if (timers_.empty()) { - current_time_ = goal_time; - } else { - Timer t = timers_.top(); - timers_.pop(); - ASSERT_GE(t.expiry_time, current_time_); - current_time_ = t.expiry_time; - cdm_->TimerExpired(t.context); - } - } -} - -void TestHost::GetPlatformString(const std::string& name, - std::string* value) { - *value = platform_strings_[name]; -} - -void TestHost::SetPlatformString(const std::string& name, - const std::string& value) { - platform_strings_[name] = value; -} - -int TestHost::KeyMessagesSize() const { return key_messages_.size(); } - -int TestHost::KeyErrorsSize() const { return key_errors_.size(); } - -int TestHost::NumTimers() const { return timers_.size(); } - -TestHost::KeyMessage TestHost::GetLastKeyMessage() { - if (!has_new_key_message_) { - LOGD("No NEW"); - return KeyMessage(); - } - - if (key_messages_.empty()) { - LOGD("empty"); - return KeyMessage(); - } - - LOGD("not empty"); - has_new_key_message_ = false; - return key_messages_.back(); -} - -TestHost::KeyError TestHost::GetLastKeyError() { - if (!has_new_key_error_) return KeyError(); - - if (key_errors_.empty()) return KeyError(); - - has_new_key_error_ = false; - return key_errors_.back(); -} - -TestHost::KeyMessage TestHost::GetKeyMessage(int index) const { - return key_messages_[index]; -} - -TestHost::KeyError TestHost::GetKeyError(int index) const { - return key_errors_[index]; -} - -void TestHost::SetCdmPtr(cdm::ContentDecryptionModule* cdm) { - if (cdm_) { - cdm_->Destroy(); - } - cdm_ = cdm; -} - -class TestDecryptedBlock : public cdm::DecryptedBlock { - public: - TestDecryptedBlock(); - virtual ~TestDecryptedBlock(); - - virtual void SetDecryptedBuffer(cdm::Buffer* buffer) OVERRIDE; - virtual cdm::Buffer* DecryptedBuffer() OVERRIDE; - - virtual void SetTimestamp(int64_t timestamp) OVERRIDE; - virtual int64_t Timestamp() const OVERRIDE; - - private: - cdm::Buffer* buffer_; - int64_t timestamp_; - - CORE_DISALLOW_COPY_AND_ASSIGN(TestDecryptedBlock); -}; - -TestDecryptedBlock::TestDecryptedBlock() : buffer_(NULL), timestamp_(0) {} - -TestDecryptedBlock::~TestDecryptedBlock() { - if (buffer_) { - buffer_->Destroy(); - buffer_ = NULL; - } -} - -void TestDecryptedBlock::SetDecryptedBuffer(cdm::Buffer* buffer) { - if (buffer_) buffer_->Destroy(); - buffer_ = buffer; -} - -cdm::Buffer* TestDecryptedBlock::DecryptedBuffer() { return buffer_; } - -void TestDecryptedBlock::SetTimestamp(int64_t timestamp) { - timestamp_ = timestamp; -} - -int64_t TestDecryptedBlock::Timestamp() const { return timestamp_; } - -namespace { - -// Default license server, can be configured using --server command line option -// Default key id (pssh), can be configured using --keyid command line option -const char kKeySystemWidevine[] = "com.widevine.alpha"; -std::string g_client_auth; -wvcdm::KeyId g_key_id; -std::string g_license_server; -wvcdm::KeyId g_wrong_key_id; - -void* GetCdmHost(int host_interface_version, void* user_data) { - if (host_interface_version != cdm::kHostInterfaceVersion) - return NULL; - return user_data; -} - -} // namespace - -namespace wvcdm { - -class WvCdmApiTest : public testing::Test { - public: - WvCdmApiTest() : cdm_(NULL) {} - ~WvCdmApiTest() {} - - protected: - virtual void SetUp() { - // Create the Host. - host_.reset(new TestHost()); - - // Set various parameters that the CDM will query. - host_->SetPlatformString("SecurityLevel", "L1"); - host_->SetPlatformString("PrivacyOn", "False"); - std::string cert(kDeviceCert, sizeof(kDeviceCert)); - host_->SetPlatformString("DeviceCertificate", cert); - - // Initialize the CDM module before creating a CDM instance. - INITIALIZE_CDM_MODULE(); - - // Create the CDM. - cdm_ = reinterpret_cast(::CreateCdmInstance( - cdm::kCdmInterfaceVersion, kKeySystemWidevine, - strlen(kKeySystemWidevine), GetCdmHost, host_.get())); - - // Tell the Host about the CDM. - host_->SetCdmPtr(cdm_); - } - - cdm::Status GenerateKeyRequest(const std::string& init_data) { - cdm::Status status = cdm_->GenerateKeyRequest( - NULL, 0, (const uint8_t*)init_data.data(), init_data.length()); - return status; - } - - // posts a request and extracts the drm message from the response - std::string GetKeyRequestResponse(const TestHost::KeyMessage& key_msg) { - std::string url; - if (key_msg.default_url.empty()) { - url = g_license_server + g_client_auth; - } else { - // Note that the client auth string is not appended when the CDM tells - // us what URL to use. - url = key_msg.default_url; - } - - UrlRequest url_request(url); - EXPECT_TRUE(url_request.is_connected()); - if (!url_request.is_connected()) { - return ""; - } - - url_request.PostRequest(key_msg.message); - std::string response; - int resp_bytes = url_request.GetResponse(&response); - - // Some license servers return 400 for invalid message, some - // return 500; treat anything other than 200 as an invalid message. - int status_code = url_request.GetStatusCode(response); - EXPECT_EQ(kHttpOk, status_code); - - if (status_code != kHttpOk) { - return ""; - } else { - std::string drm_msg; - LicenseRequest lic_request; - lic_request.GetDrmMessage(response, drm_msg); - LOGV("drm msg: %u bytes\n%s", drm_msg.size(), - HexEncode(reinterpret_cast(drm_msg.data()), - drm_msg.size()).c_str()); - return drm_msg; - } - } - - void ProcessKeyResponse() { - TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); - EXPECT_TRUE(key_msg.default_url.empty()); - std::string drm_msg = GetKeyRequestResponse(key_msg); - EXPECT_EQ(cdm::kSuccess, AddKey(key_msg.session_id, drm_msg)); - } - - void ProcessKeyRenewalResponse() { - TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); - EXPECT_FALSE(key_msg.default_url.empty()); - std::string drm_msg = GetKeyRequestResponse(key_msg); - EXPECT_EQ(cdm::kSuccess, AddKey(key_msg.session_id, drm_msg)); - } - - void CloseSession(const std::string& session_id) { - cdm::Status status = - cdm_->CloseSession(session_id.data(), session_id.length()); - EXPECT_EQ(cdm::kSuccess, status); - } - - cdm::Status AddKey(const std::string& session_id, - const std::string& drm_msg) { - cdm::Status status = - cdm_->AddKey(session_id.data(), session_id.size(), - (const uint8_t*)drm_msg.data(), drm_msg.size(), NULL, 0); - return status; - } - - // Level 1 / Level 2 payload comes back in the cpu memory as cleartext. - void DecryptClearPayloadTest() { - typedef struct DecryptionData { - bool is_encrypted; - bool is_secure; - wvcdm::KeyId key_id; - std::vector encrypt_data; - std::vector iv; - size_t block_offset; - std::vector decrypt_data; - } DecryptionData; - - DecryptionData data; - data.is_encrypted = true; - data.is_secure = false; - - // Key ID of key used to encrypt the test content. - // This is used by the secure layer to look up the content key - data.key_id = wvcdm::a2bs_hex("371ea35e1a985d75d198a7f41020dc23"); - - // Dummy encrypted data. - data.encrypt_data = wvcdm::a2b_hex( - "64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36" - "17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d" - "7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab" - "ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c" - "2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4" - "9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4" - "6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012" - "c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685"); - data.iv = wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f"); - data.block_offset = 0; - - // Expected decrypted data. - data.decrypt_data = wvcdm::a2b_hex( - "217ce9bde99bd91e9733a1a00b9b557ac3a433dc92633546156817fae26b6e1c" - "942ac20a89ff79f4c2f25fba99d6a44618a8c0420b27d54e3da17b77c9d43cca" - "595d259a1e4a8b6d7744cd98c5d3f921adc252eb7d8af6b916044b676a574747" - "8df21fdc42f166880d97a2225cd5c9ea5e7b752f4cf81bbdbe98e542ee10e1c6" - "ad868a6ac55c10d564fc23b8acff407daaf4ed2743520e02cda9680d9ea88e91" - "029359c4cf5906b6ab5bf60fbb3f1a1c7c59acfc7e4fb4ad8e623c04d503a3dd" - "4884604c8da8a53ce33db9ff8f1c5bb6bb97f37b39906bf41596555c1bcce9ed" - "08a899cd760ff0899a1170c2f224b9c52997a0785b7fe170805fd3e8b1127659"); - - cdm::InputBuffer buf; - - buf.data = &data.encrypt_data[0]; - buf.data_size = data.encrypt_data.size(); - buf.key_id = (const uint8_t*)&data.key_id[0]; - buf.key_id_size = data.key_id.length(); - buf.iv = &data.iv[0]; - buf.iv_size = data.iv.size(); - buf.data_offset = 0; - cdm::SubsampleEntry sub(0, buf.data_size); - buf.subsamples = ⊂ - buf.num_subsamples = 1; - buf.timestamp = 10; - - TestDecryptedBlock output; - cdm::Status status = cdm_->Decrypt(buf, &output); - - EXPECT_EQ(cdm::kSuccess, status); - EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0], - buf.data_size)); - } - - // Level 1 / Level 2 payload comes back in the cpu memory as cleartext. - void DecryptClearSubsampleTest() { - typedef struct DecryptionData { - bool is_encrypted; - bool is_secure; - wvcdm::KeyId key_id; - std::vector encrypt_data; - std::vector iv; - size_t block_offset; - std::vector decrypt_data; - } DecryptionData; - - DecryptionData data; - data.is_encrypted = true; - data.is_secure = false; - - // Key ID of key used to encrypt the test content. - // This is used by the secure layer to look up the content key - data.key_id = wvcdm::a2bs_hex("371ea35e1a985d75d198a7f41020dc23"); - - // Dummy encrypted data. This is a combination of clear and - // encrypted data. - data.encrypt_data = wvcdm::a2b_hex( - // subsample 0 - "abcdef" - "53cc758763904ea5870458e6b23d36db1e6d7f7aaa2f3eeebb5393a7264991e7" - "ce4f57b198326e1a208a821799b2a29c90567ab57321b06e51fc20dc9bc5fc55" - "10720a8bb1f5e002c3e50ff70d2d806a9432cad237050d09581f5b0d59b00090" - "b3ad69b4087f5a155b17e13c44d33fa007475d207fc4ac2ef3b571ecb9" - // subsample 1 - "0123456789" - "f3c852" - "ce00dc4806f0c6856ae1732e20308096478e1d822d75c2bb768119565d3bd6e6" - "901e36164f4802355ee758fc46ef6cf5f852dd5256c7b1e5f96d29" - // subsample 2 - "deadbeefbaadf00d" - "3b20525d5e" - "78b8e5aa344d5c4e425e67ddf889ea7c4bb1d49af67eba67718b765e0a940402" - "8d306f4ce693ad6dc0a931d507fa14fff4d293d4170280b3e0fca2d628f722e8" - ); - data.iv = wvcdm::a2b_hex("6ba18dd40f49da7f64c368e4db43fc88"); - data.block_offset = 0; - - // Expected decrypted data. - data.decrypt_data = wvcdm::a2b_hex( - // subsample 0 - "abcdef" - "52e65334501acadf78e2b26460def3ac973771ed7c64001a2e82917342a7eab3" - "047f5e85449692fae8f677be425a47bdea850df5a3ffff17043afb1f2b437ab2" - "b1d5e0784c4ed8f97fc24b8f565e85ed63fb7d1365980d9aea7b8b58f488f83c" - "1ce80b6096c60f3b113c988ff185b26e798da8fc6f327e4ff00e4b3fbf" - // subsample 1 - "0123456789" - "b1ed0a" - "a054bce40ccb0ebc70b181d1a12055f46ac55e29c7c2473a29d2a366d240ec48" - "7cede274f012813a877f99159e7062b6a37cfc9327a7bc2195814e" - // subsample 2 - "deadbeefbaadf00d" - "653b818d1d" - "4ab9a9128361d8ca6a9d2766df5c096ee29f4f5204febdf217a94a5b560cd692" - "cc36d3e071df789fdeac2fb7ec6dcd7af94bb1f85c22025b25e702e38212b927" - ); - - cdm::InputBuffer buf; - - buf.data = &data.encrypt_data[0]; - buf.data_size = data.encrypt_data.size(); - buf.key_id = (const uint8_t*) &data.key_id[0]; - buf.key_id_size = data.key_id.length(); - buf.iv = &data.iv[0]; - buf.iv_size = data.iv.size(); - buf.data_offset = 0; - std::vector sub; - sub.push_back(cdm::SubsampleEntry(3, 125)); - sub.push_back(cdm::SubsampleEntry(5, 62)); - sub.push_back(cdm::SubsampleEntry(8, 69)); - buf.subsamples = &sub[0]; - buf.num_subsamples = sub.size(); - buf.timestamp = 10; - - TestDecryptedBlock output; - cdm::Status status = cdm_->Decrypt(buf, &output); - - EXPECT_EQ(cdm::kSuccess, status); - EXPECT_EQ( - 0, - memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0], - buf.data_size)); - } - - void DecryptClearSubsampleTestWithMissingSubsampleInfo() { - typedef struct DecryptionData { - bool is_encrypted; - bool is_secure; - wvcdm::KeyId key_id; - std::vector encrypt_data; - std::vector iv; - size_t block_offset; - std::vector decrypt_data; - } DecryptionData; - - DecryptionData data; - data.is_encrypted = true; - data.is_secure = false; - - // Key ID of key used to encrypt the test content. - // This is used by the secure layer to look up the content key - data.key_id = wvcdm::a2bs_hex("371ea35e1a985d75d198a7f41020dc23"); - - // Dummy encrypted data. This is a combination of clear and - // encrypted data. - data.encrypt_data = wvcdm::a2b_hex( - // subsample 0 - "abcdef" - "53cc758763904ea5870458e6b23d36db1e6d7f7aaa2f3eeebb5393a7264991e7" - "ce4f57b198326e1a208a821799b2a29c90567ab57321b06e51fc20dc9bc5fc55" - "10720a8bb1f5e002c3e50ff70d2d806a9432cad237050d09581f5b0d59b00090" - "b3ad69b4087f5a155b17e13c44d33fa007475d207fc4ac2ef3b571ecb9" - // subsample 1 - "0123456789" - "f3c852" - "ce00dc4806f0c6856ae1732e20308096478e1d822d75c2bb768119565d3bd6e6" - "901e36164f4802355ee758fc46ef6cf5f852dd5256c7b1e5f96d29" - // subsample 2 - "deadbeefbaadf00d" - "3b20525d5e" - "78b8e5aa344d5c4e425e67ddf889ea7c4bb1d49af67eba67718b765e0a940402" - "8d306f4ce693ad6dc0a931d507fa14fff4d293d4170280b3e0fca2d628f722e8" - ); - data.iv = wvcdm::a2b_hex("6ba18dd40f49da7f64c368e4db43fc88"); - data.block_offset = 0; - - // Expected decrypted data. - data.decrypt_data = wvcdm::a2b_hex( - // subsample 0 - "abcdef" - "52e65334501acadf78e2b26460def3ac973771ed7c64001a2e82917342a7eab3" - "047f5e85449692fae8f677be425a47bdea850df5a3ffff17043afb1f2b437ab2" - "b1d5e0784c4ed8f97fc24b8f565e85ed63fb7d1365980d9aea7b8b58f488f83c" - "1ce80b6096c60f3b113c988ff185b26e798da8fc6f327e4ff00e4b3fbf" - // subsample 1 - "0123456789" - "b1ed0a" - "a054bce40ccb0ebc70b181d1a12055f46ac55e29c7c2473a29d2a366d240ec48" - "7cede274f012813a877f99159e7062b6a37cfc9327a7bc2195814e" - // subsample 2 - "deadbeefbaadf00d" - "653b818d1d" - "4ab9a9128361d8ca6a9d2766df5c096ee29f4f5204febdf217a94a5b560cd692" - "cc36d3e071df789fdeac2fb7ec6dcd7af94bb1f85c22025b25e702e38212b927" - ); - - cdm::InputBuffer buf; - - buf.data = &data.encrypt_data[0]; - buf.data_size = data.encrypt_data.size(); - buf.key_id = (const uint8_t*)&data.key_id[0]; - buf.key_id_size = data.key_id.length(); - buf.iv = &data.iv[0]; - buf.iv_size = data.iv.size(); - buf.data_offset = 0; - std::vector sub; - sub.push_back(cdm::SubsampleEntry(3, 125)); - sub.push_back(cdm::SubsampleEntry(5, 62)); - sub.push_back(cdm::SubsampleEntry(8, 69)); - //buf.subsamples = &sub[0]; - //buf.num_subsamples = sub.size(); - buf.timestamp = 10; - - TestDecryptedBlock output; - - cdm::Status status = cdm_->Decrypt(buf, &output); - EXPECT_EQ(cdm::kDecryptError, status); - - buf.subsamples = &sub[0]; - status = cdm_->Decrypt(buf, &output); - EXPECT_EQ(cdm::kDecryptError, status); - - buf.num_subsamples = sub.size(); - status = cdm_->Decrypt(buf, &output); - EXPECT_EQ(cdm::kSuccess, status); - EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0], - buf.data_size)); - - buf.subsamples = NULL; - status = cdm_->Decrypt(buf, &output); - EXPECT_EQ(cdm::kDecryptError, status); - } - - // Level 1 passes encrypted payload straight through. By calling the - // CDM's DecryptDecodeAndRenderSamples, and/or DecryptDecodeAndRenderFrame, - // OEMCrypto_DecryptCTR will be told to use Direct Rendering. - void SecureDecryptLevel1Test() { - typedef struct DecryptionData { - bool is_encrypted; - bool is_secure; - wvcdm::KeyId key_id; - std::vector encrypt_data; - std::vector iv; - size_t block_offset; - std::vector decrypt_data; - } DecryptionData; - - DecryptionData data; - data.is_encrypted = true; - data.is_secure = false; - - // Key ID of key used to encrypt the test content. - // This is used by the secure layer to look up the content key - data.key_id = wvcdm::a2bs_hex("371ea35e1a985d75d198a7f41020dc23"); - - // Dummy encrypted data. - data.encrypt_data = wvcdm::a2b_hex( - "64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36" - "17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d" - "7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab" - "ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c" - "2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4" - "9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4" - "6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012" - "c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685"); - data.iv = wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f"); - data.block_offset = 0; - - // Expected decrypted data. - data.decrypt_data = wvcdm::a2b_hex( - "217ce9bde99bd91e9733a1a00b9b557ac3a433dc92633546156817fae26b6e1c" - "942ac20a89ff79f4c2f25fba99d6a44618a8c0420b27d54e3da17b77c9d43cca" - "595d259a1e4a8b6d7744cd98c5d3f921adc252eb7d8af6b916044b676a574747" - "8df21fdc42f166880d97a2225cd5c9ea5e7b752f4cf81bbdbe98e542ee10e1c6" - "ad868a6ac55c10d564fc23b8acff407daaf4ed2743520e02cda9680d9ea88e91" - "029359c4cf5906b6ab5bf60fbb3f1a1c7c59acfc7e4fb4ad8e623c04d503a3dd" - "4884604c8da8a53ce33db9ff8f1c5bb6bb97f37b39906bf41596555c1bcce9ed" - "08a899cd760ff0899a1170c2f224b9c52997a0785b7fe170805fd3e8b1127659"); - - cdm::InputBuffer buf; - - buf.data = &data.encrypt_data[0]; - buf.data_size = data.encrypt_data.size(); - buf.key_id = (const uint8_t*)&data.key_id[0]; - buf.key_id_size = data.key_id.length(); - buf.iv = &data.iv[0]; - buf.iv_size = data.iv.size(); - buf.data_offset = 0; - cdm::SubsampleEntry sub(0, buf.data_size); - buf.subsamples = ⊂ - buf.num_subsamples = 1; - buf.timestamp = 10; - - cdm::Status status; - - status = cdm_->DecryptDecodeAndRenderSamples(buf); - EXPECT_EQ(cdm::kSuccess, status); - status = cdm_->DecryptDecodeAndRenderFrame(buf); - EXPECT_EQ(cdm::kSuccess, status); - } - - // Level 1 passes encrypted payload straight through. By calling the - // CDM's DecryptDecodeAndRenderSamples, and/or DecryptDecodeAndRenderFrame, - // OEMCrypto_DecryptCTR will be told to use Direct Rendering. - void SecureDecryptLevel1MultipleSubsamplesTest() { - typedef struct DecryptionData { - bool is_encrypted; - bool is_secure; - wvcdm::KeyId key_id; - std::vector encrypt_data; - std::vector iv; - size_t block_offset; - std::vector decrypt_data; - } DecryptionData; - - DecryptionData data; - data.is_encrypted = true; - data.is_secure = false; - - // Key ID of key used to encrypt the test content. - // This is used by the secure layer to look up the content key - data.key_id = wvcdm::a2bs_hex("371ea35e1a985d75d198a7f41020dc23"); - - // Dummy encrypted data. This is a combination of clear and - // encrypted data. - data.encrypt_data = wvcdm::a2b_hex( - // subsample 0 - "abcdef" - "53cc758763904ea5870458e6b23d36db1e6d7f7aaa2f3eeebb5393a7264991e7" - "ce4f57b198326e1a208a821799b2a29c90567ab57321b06e51fc20dc9bc5fc55" - "10720a8bb1f5e002c3e50ff70d2d806a9432cad237050d09581f5b0d59b00090" - "b3ad69b4087f5a155b17e13c44d33fa007475d207fc4ac2ef3b571ecb9" - // subsample 1 - "0123456789" - "f3c852" - "ce00dc4806f0c6856ae1732e20308096478e1d822d75c2bb768119565d3bd6e6" - "901e36164f4802355ee758fc46ef6cf5f852dd5256c7b1e5f96d29" - // subsample 2 - "deadbeefbaadf00d" - "3b20525d5e" - "78b8e5aa344d5c4e425e67ddf889ea7c4bb1d49af67eba67718b765e0a940402" - "8d306f4ce693ad6dc0a931d507fa14fff4d293d4170280b3e0fca2d628f722e8" - ); - data.iv = wvcdm::a2b_hex("6ba18dd40f49da7f64c368e4db43fc88"); - data.block_offset = 0; - - // Expected decrypted data. - data.decrypt_data = wvcdm::a2b_hex( - // subsample 0 - "abcdef" - "52e65334501acadf78e2b26460def3ac973771ed7c64001a2e82917342a7eab3" - "047f5e85449692fae8f677be425a47bdea850df5a3ffff17043afb1f2b437ab2" - "b1d5e0784c4ed8f97fc24b8f565e85ed63fb7d1365980d9aea7b8b58f488f83c" - "1ce80b6096c60f3b113c988ff185b26e798da8fc6f327e4ff00e4b3fbf" - // subsample 1 - "0123456789" - "b1ed0a" - "a054bce40ccb0ebc70b181d1a12055f46ac55e29c7c2473a29d2a366d240ec48" - "7cede274f012813a877f99159e7062b6a37cfc9327a7bc2195814e" - // subsample 2 - "deadbeefbaadf00d" - "653b818d1d" - "4ab9a9128361d8ca6a9d2766df5c096ee29f4f5204febdf217a94a5b560cd692" - "cc36d3e071df789fdeac2fb7ec6dcd7af94bb1f85c22025b25e702e38212b927" - ); - - cdm::InputBuffer buf; - - buf.data = &data.encrypt_data[0]; - buf.data_size = data.encrypt_data.size(); - buf.key_id = (const uint8_t*)&data.key_id[0]; - buf.key_id_size = data.key_id.length(); - buf.iv = &data.iv[0]; - buf.iv_size = data.iv.size(); - buf.data_offset = 0; - std::vector sub; - sub.push_back(cdm::SubsampleEntry(3, 125)); - sub.push_back(cdm::SubsampleEntry(5, 62)); - sub.push_back(cdm::SubsampleEntry(8, 69)); - buf.subsamples = &sub[0]; - buf.num_subsamples = sub.size(); - buf.timestamp = 10; - - cdm::Status status; - - status = cdm_->DecryptDecodeAndRenderSamples(buf); - EXPECT_EQ(cdm::kSuccess, status); - status = cdm_->DecryptDecodeAndRenderFrame(buf); - EXPECT_EQ(cdm::kSuccess, status); - } - - void WithMissingSubsampleInfoTest() { - typedef struct DecryptionData { - bool is_encrypted; - bool is_secure; - wvcdm::KeyId key_id; - std::vector encrypt_data; - std::vector iv; - size_t block_offset; - std::vector decrypt_data; - } DecryptionData; - - DecryptionData data; - data.is_encrypted = true; - data.is_secure = false; - - // Key ID of key used to encrypt the test content. - // This is used by the secure layer to look up the content key - data.key_id = wvcdm::a2bs_hex("371ea35e1a985d75d198a7f41020dc23"); - - // Dummy encrypted data. - data.encrypt_data = wvcdm::a2b_hex( - // subsample 0 - "abcdef" - "53cc758763904ea5870458e6b23d36db1e6d7f7aaa2f3eeebb5393a7264991e7" - "ce4f57b198326e1a208a821799b2a29c90567ab57321b06e51fc20dc9bc5fc55" - "10720a8bb1f5e002c3e50ff70d2d806a9432cad237050d09581f5b0d59b00090" - "b3ad69b4087f5a155b17e13c44d33fa007475d207fc4ac2ef3b571ecb9" - // subsample 1 - "0123456789" - "f3c852" - "ce00dc4806f0c6856ae1732e20308096478e1d822d75c2bb768119565d3bd6e6" - "901e36164f4802355ee758fc46ef6cf5f852dd5256c7b1e5f96d29" - // subsample 2 - "deadbeefbaadf00d" - "3b20525d5e" - "78b8e5aa344d5c4e425e67ddf889ea7c4bb1d49af67eba67718b765e0a940402" - "8d306f4ce693ad6dc0a931d507fa14fff4d293d4170280b3e0fca2d628f722e8" - ); - data.iv = wvcdm::a2b_hex("6ba18dd40f49da7f64c368e4db43fc88"); - data.block_offset = 0; - - // Expected decrypted data. - data.decrypt_data = wvcdm::a2b_hex( - // subsample 0 - "abcdef" - "52e65334501acadf78e2b26460def3ac973771ed7c64001a2e82917342a7eab3" - "047f5e85449692fae8f677be425a47bdea850df5a3ffff17043afb1f2b437ab2" - "b1d5e0784c4ed8f97fc24b8f565e85ed63fb7d1365980d9aea7b8b58f488f83c" - "1ce80b6096c60f3b113c988ff185b26e798da8fc6f327e4ff00e4b3fbf" - // subsample 1 - "0123456789" - "b1ed0a" - "a054bce40ccb0ebc70b181d1a12055f46ac55e29c7c2473a29d2a366d240ec48" - "7cede274f012813a877f99159e7062b6a37cfc9327a7bc2195814e" - // subsample 2 - "deadbeefbaadf00d" - "653b818d1d" - "4ab9a9128361d8ca6a9d2766df5c096ee29f4f5204febdf217a94a5b560cd692" - "cc36d3e071df789fdeac2fb7ec6dcd7af94bb1f85c22025b25e702e38212b927" - ); - - cdm::InputBuffer buf; - - buf.data = &data.encrypt_data[0]; - buf.data_size = data.encrypt_data.size(); - buf.key_id = (const uint8_t*)&data.key_id[0]; - buf.key_id_size = data.key_id.length(); - buf.iv = &data.iv[0]; - buf.iv_size = data.iv.size(); - buf.data_offset = 0; - std::vector sub; - sub.push_back(cdm::SubsampleEntry(3, 125)); - sub.push_back(cdm::SubsampleEntry(5, 62)); - sub.push_back(cdm::SubsampleEntry(8, 69)); - buf.timestamp = 10; - - cdm::Status status; - - status = cdm_->DecryptDecodeAndRenderSamples(buf); - EXPECT_EQ(cdm::kDecryptError, status); - status = cdm_->DecryptDecodeAndRenderFrame(buf); - EXPECT_EQ(cdm::kDecryptError, status); - - buf.subsamples = &sub[0]; - status = cdm_->DecryptDecodeAndRenderSamples(buf); - EXPECT_EQ(cdm::kDecryptError, status); - status = cdm_->DecryptDecodeAndRenderFrame(buf); - EXPECT_EQ(cdm::kDecryptError, status); - - buf.num_subsamples = sub.size(); - status = cdm_->DecryptDecodeAndRenderSamples(buf); - EXPECT_EQ(cdm::kSuccess, status); - status = cdm_->DecryptDecodeAndRenderFrame(buf); - EXPECT_EQ(cdm::kSuccess, status); - - buf.subsamples = NULL; - status = cdm_->DecryptDecodeAndRenderSamples(buf); - EXPECT_EQ(cdm::kDecryptError, status); - status = cdm_->DecryptDecodeAndRenderFrame(buf); - EXPECT_EQ(cdm::kDecryptError, status); - } - - cdm::ContentDecryptionModule* cdm_; // owned by host_ - scoped_ptr host_; -}; - - -class DummyCDM : public cdm::ContentDecryptionModule { - public: - DummyCDM() - : timer_fired_(false), - last_context_(NULL) {} - - virtual cdm::Status GenerateKeyRequest(const char*, int, const uint8_t*, int) - OVERRIDE { - return cdm::kSessionError; - } - - virtual cdm::Status AddKey(const char*, int, const uint8_t*, int, - const uint8_t*, int) OVERRIDE { - return cdm::kSessionError; - } - - virtual bool IsKeyValid(const uint8_t*, int) OVERRIDE { - return false; - } - - virtual cdm::Status CloseSession(const char*, int) OVERRIDE { - return cdm::kSessionError; - } - - virtual void TimerExpired(void* context) OVERRIDE { - timer_fired_ = true; - last_context_ = context; - } - - virtual cdm::Status Decrypt(const cdm::InputBuffer&, cdm::DecryptedBlock*) - OVERRIDE { - return cdm::kSessionError; - } - - virtual cdm::Status DecryptDecodeAndRenderFrame(const cdm::InputBuffer&) - OVERRIDE { - return cdm::kSessionError; - } - - virtual cdm::Status DecryptDecodeAndRenderSamples(const cdm::InputBuffer&) - OVERRIDE { - return cdm::kSessionError; - } - - virtual void Destroy() OVERRIDE { - delete this; - } - - virtual cdm::Status GetProvisioningRequest(std::string*, std::string*) - OVERRIDE { - return cdm::kSessionError; - } - - virtual cdm::Status HandleProvisioningResponse(std::string&) OVERRIDE { - return cdm::kSessionError; - } - - bool TimerFired() const { - return timer_fired_; - } - - void* LastTimerContext() const { - return last_context_; - } - - void ResetTimerStatus() { - timer_fired_ = false; - last_context_ = NULL; - } - - private: - bool timer_fired_; - void* last_context_; -}; - -TEST_F(WvCdmApiTest, TestHostTimer) { - // Validate that the TestHost timers are processed in the correct order. - // To do this, we replace the cdm with a dummy that only tracks timers. - DummyCDM* cdm = new DummyCDM(); - - // The old CDM is destroyed by SetCdmPtr. - cdm_ = cdm; - host_->SetCdmPtr(cdm); - - const double kTimerDelaySeconds = 1.0; - const int64_t kTimerDelayMs = kTimerDelaySeconds * 1000; - void* kCtx1 = reinterpret_cast(0x1); - void* kCtx2 = reinterpret_cast(0x2); - - host_->SetTimer(kTimerDelayMs * 1, kCtx1); - host_->SetTimer(kTimerDelayMs * 2, kCtx2); - - host_->FastForwardTime(kTimerDelaySeconds); - EXPECT_TRUE(cdm->TimerFired()); - EXPECT_EQ(kCtx1, cdm->LastTimerContext()); - cdm->ResetTimerStatus(); - - host_->FastForwardTime(kTimerDelaySeconds); - EXPECT_TRUE(cdm->TimerFired()); - EXPECT_EQ(kCtx2, cdm->LastTimerContext()); - cdm->ResetTimerStatus(); - - host_->FastForwardTime(kTimerDelaySeconds); - EXPECT_FALSE(cdm->TimerFired()); -} - -// Note that these tests, BaseMessageTest, NormalDecryption and TimeTest, -// are dependent on getting back a license from the license server where the -// url for the license server is defined in the conf_test_env.cpp. If these -// tests fail immediately, verify that the license server URL is correct -// and works in your test environment. - -TEST_F(WvCdmApiTest, DeviceCertificateTest) { - // Clear any existing device cert. - host_->SetPlatformString("DeviceCertificate", ""); - - ASSERT_EQ(cdm::kNeedsDeviceCertificate, GenerateKeyRequest(g_key_id)); - - // The Host must handle the certificate provisioning request. - std::string server_url; - std::string request; - cdm::Status status = cdm_->GetProvisioningRequest(&request, &server_url); - ASSERT_EQ(cdm::kSuccess, status); - - UrlRequest url_request(server_url); - url_request.PostCertRequestInQueryString(request); - - std::string message; - bool ok = url_request.GetResponse(&message); - ASSERT_TRUE(ok); - - status = cdm_->HandleProvisioningResponse(message); - ASSERT_EQ(cdm::kSuccess, status); - - // Now we are provisioned, so GKR should succeed. - EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); -} - -TEST_F(WvCdmApiTest, BaseMessageTest) { - EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); - ProcessKeyResponse(); -} - -TEST_F(WvCdmApiTest, NormalDecryption) { - EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); - ProcessKeyResponse(); - DecryptClearPayloadTest(); -} - -TEST_F(WvCdmApiTest, NormalSubSampleDecryptionWithSubsampleInfo) { - EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); - ProcessKeyResponse(); - DecryptClearSubsampleTest(); -} - -TEST_F(WvCdmApiTest, NormalSubSampleDecryptionWithMissingSubsampleInfo) { - EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); - ProcessKeyResponse(); - DecryptClearSubsampleTestWithMissingSubsampleInfo(); -} - -TEST_F(WvCdmApiTest, TimeTest) { - EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); - ProcessKeyResponse(); - - // We expect that by the time we've added a key, the CDM has set a timer. - // Otherwise, it couldn't correctly handle renewal. - EXPECT_NE(0, host_->NumTimers()); - host_->FastForwardTime(kTestPolicyRenewalDelaySeconds + - kDelayWaitToForRenewalMessageSeconds); - - // When the timer expired, we should have sent a renewal, so we can - // add this renewed key now, assuming things are working as expected. - ProcessKeyRenewalResponse(); -} - -TEST_F(WvCdmApiTest, SecureDecryptionLevel1) { - EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); - ProcessKeyResponse(); - SecureDecryptLevel1Test(); -} - -TEST_F(WvCdmApiTest, SecureDecryptionLevel1WithSubsampleInfo) { - EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); - ProcessKeyResponse(); - SecureDecryptLevel1MultipleSubsamplesTest(); -} - -TEST_F(WvCdmApiTest, SecureDecryptionLevel1WithMissingSubsampleInfo) { - EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); - ProcessKeyResponse(); - WithMissingSubsampleInfoTest(); -} - -TEST_F(WvCdmApiTest, GenerateKeyRequestFailureSendsKeyError) { - // Pass a bogus key id and expect failure. - EXPECT_EQ(cdm::kSessionError, GenerateKeyRequest("")); - // Expect the CDM to pass a key error back to the host. - EXPECT_EQ(1, host_->KeyErrorsSize()); -} - -TEST_F(WvCdmApiTest, AddKeyFailureSendsKeyError) { - EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id)); - - // Get the message and response. - TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); - EXPECT_TRUE(key_msg.default_url.empty()); - std::string drm_msg = GetKeyRequestResponse(key_msg); - - // Call AddKey with a bad session id and expect failure. - EXPECT_EQ(cdm::kSessionError, AddKey("BLAH", drm_msg)); - - // Expect the CDM to pass a key error back to the host. - EXPECT_EQ(1, host_->KeyErrorsSize()); - - // Call AddKey with a bad license and expect failure. - EXPECT_EQ(cdm::kSessionError, AddKey(key_msg.session_id, "BLAH")); - - // Expect the CDM to pass one more key error back to the host. - EXPECT_EQ(2, host_->KeyErrorsSize()); -} - -} // namespace wvcdm - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - wvcdm::InitLogging(argc, argv); - - wvcdm::ConfigTestEnv config(wvcdm::kContentProtectionServer); - g_client_auth.assign(config.client_auth()); - g_wrong_key_id.assign(config.wrong_key_id()); - - // The following variables are configurable through command line options. - g_license_server.assign(config.license_server()); - g_key_id.assign(config.key_id()); - std::string license_server(g_license_server); - - int show_usage = 0; - static const struct option long_options[] = { - {"keyid", required_argument, NULL, 'k'}, - {"server", required_argument, NULL, 's'}, - {NULL, 0, NULL, '\0'}}; - - int option_index = 0; - int opt = 0; - while ((opt = getopt_long(argc, argv, "k:s:v", long_options, - &option_index)) != -1) { - switch (opt) { - case 'k': { - g_key_id.clear(); - g_key_id.assign(optarg); - break; - } - case 's': { - g_license_server.clear(); - g_license_server.assign(optarg); - break; - } - case 'v': { - // This option has already been consumed by wvcdm::InitLogging() above. - // We only tell getopt about it so that it is not an error. We ignore - // the option here when seen. - // TODO: Stop passing argv to InitLogging, and instead set the log - // level here through the logging API. We should keep all command-line - // parsing at the application level, rather than split between various - // apps and various platform-specific logging implementations. - break; - } - case '?': { - show_usage = 1; - break; - } - } - } - - if (show_usage) { - std::cout << std::endl; - std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl; - - std::cout << std::setw(30) << std::left << " --server="; - std::cout - << "configure the license server url, please include http[s] in the url" - << std::endl; - std::cout << std::setw(30) << std::left << " "; - std::cout << "default: " << license_server << std::endl; - - std::cout << std::setw(30) << std::left << " --keyid="; - std::cout << "configure the key id or pssh, in hex format" << std::endl; - std::cout << std::setw(30) << std::left << " default keyid:"; - std::cout << g_key_id << std::endl; - return 0; - } - - std::cout << std::endl; - std::cout << "Server: " << g_license_server << std::endl; - std::cout << "KeyID: " << g_key_id << std::endl << std::endl; - - g_key_id = wvcdm::a2bs_hex(g_key_id); - config.set_license_server(g_license_server); - config.set_key_id(g_key_id); - - return RUN_ALL_TESTS(); -} diff --git a/cdm/test/cdm_test_config.h b/cdm/test/cdm_test_config.h new file mode 100644 index 00000000..108cbd28 --- /dev/null +++ b/cdm/test/cdm_test_config.h @@ -0,0 +1,18 @@ +// Copyright 2014 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_TEST_CDM_TEST_CONFIG_H_ +#define WVCDM_CDM_TEST_CDM_TEST_CONFIG_H_ + +#include + +#include "config_test_env.h" + +extern std::string g_client_auth; +extern std::string g_key_id; +extern std::string g_license_server; +extern std::string g_wrong_key_id; + +static const wvcdm::LicenseServerId kLicenseServerId = + wvcdm::kContentProtectionServer; + +#endif // WVCDM_CDM_TEST_CDM_TEST_CONFIG_H_ diff --git a/cdm/test/cdm_test_main.cpp b/cdm/test/cdm_test_main.cpp new file mode 100644 index 00000000..49190924 --- /dev/null +++ b/cdm/test/cdm_test_main.cpp @@ -0,0 +1,96 @@ +// Copyright 2014 Google Inc. All Rights Reserved. + +#include "cdm_test_config.h" + +#include +#include + +#include "log.h" +#include "string_conversions.h" + +// Default license server, can be configured using --server command line option +// Default key id (pssh), can be configured using --keyid command line option +std::string g_client_auth; +std::string g_key_id; +std::string g_license_server; +std::string g_wrong_key_id; + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + wvcdm::InitLogging(argc, argv); + + wvcdm::ConfigTestEnv config(kLicenseServerId); + g_client_auth.assign(config.client_auth()); + g_wrong_key_id.assign(config.wrong_key_id()); + + // The following variables are configurable through command line options. + g_license_server.assign(config.license_server()); + g_key_id.assign(config.key_id()); + std::string license_server(g_license_server); + + int show_usage = 0; + static const struct option long_options[] = { + {"keyid", required_argument, NULL, 'k'}, + {"server", required_argument, NULL, 's'}, + {NULL, 0, NULL, '\0'}}; + + int option_index = 0; + int opt = 0; + while ((opt = getopt_long(argc, argv, "k:s:v", long_options, + &option_index)) != -1) { + switch (opt) { + case 'k': { + g_key_id.clear(); + g_key_id.assign(optarg); + break; + } + case 's': { + g_license_server.clear(); + g_license_server.assign(optarg); + break; + } + case 'v': { + // This option has already been consumed by wvcdm::InitLogging() above. + // We only tell getopt about it so that it is not an error. We ignore + // the option here when seen. + // TODO: Stop passing argv to InitLogging, and instead set the log + // level here through the logging API. We should keep all command-line + // parsing at the application level, rather than split between various + // apps and various platform-specific logging implementations. + break; + } + case '?': { + show_usage = 1; + break; + } + } + } + + if (show_usage) { + std::cout << std::endl; + std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl; + + std::cout << std::setw(30) << std::left << " --server="; + std::cout + << "configure the license server url, please include http[s] in the url" + << std::endl; + std::cout << std::setw(30) << std::left << " "; + std::cout << "default: " << license_server << std::endl; + + std::cout << std::setw(30) << std::left << " --keyid="; + std::cout << "configure the key id or pssh, in hex format" << std::endl; + std::cout << std::setw(30) << std::left << " default keyid:"; + std::cout << g_key_id << std::endl; + return 0; + } + + std::cout << std::endl; + std::cout << "Server: " << g_license_server << std::endl; + std::cout << "KeyID: " << g_key_id << std::endl << std::endl; + + g_key_id = wvcdm::a2bs_hex(g_key_id); + config.set_license_server(g_license_server); + config.set_key_id(g_key_id); + + return RUN_ALL_TESTS(); +} diff --git a/cdm/test/device_cert.h b/cdm/test/device_cert.h index 3507c354..079a2ca8 100644 --- a/cdm/test/device_cert.h +++ b/cdm/test/device_cert.h @@ -1,6 +1,9 @@ // Copyright 2014 Google Inc. All Rights Reserved. -const char kDeviceCert[] = { +#ifndef WVCDM_CDM_TEST_DEVICE_CERT_H_ +#define WVCDM_CDM_TEST_DEVICE_CERT_H_ + +const uint8_t kDeviceCert[] = { 0x0A, 0x9A, 0x14, 0x08, 0x01, 0x10, 0x01, 0x1A, 0x93, 0x14, 0x0A, 0xED, 0x09, 0x0A, 0xAE, 0x02, 0x08, 0x02, 0x12, 0x10, 0x19, 0x77, 0x50, 0x4D, @@ -330,3 +333,5 @@ const char kDeviceCert[] = { 0xA6, 0x95, 0xCC, 0x54, 0x82, 0xB5, 0x8B, 0xF3, 0xE6, 0xBD, 0xA1, 0xD8, 0xE8, 0x19, 0xB8, }; + +#endif // WVCDM_CDM_TEST_DEVICE_CERT_H_ diff --git a/cdm/test/gtest.gyp b/cdm/test/gtest.gyp deleted file mode 100644 index 9cf72694..00000000 --- a/cdm/test/gtest.gyp +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2013 Google Inc. All Rights Reserved. -{ - 'targets': [ - { - 'target_name': 'gtest', - 'type': 'static_library', - 'include_dirs': [ - '../../third_party/gmock/gtest', - '../../third_party/gmock/gtest/include', - ], - 'sources': [ - '../../third_party/gmock/gtest/src/gtest-all.cc', - ], - }, - ], -} diff --git a/cdm/test/test_host_1.cpp b/cdm/test/test_host_1.cpp new file mode 100644 index 00000000..37739705 --- /dev/null +++ b/cdm/test/test_host_1.cpp @@ -0,0 +1,134 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +// Review the TestHost_1 class below to observe how the CDM interfaces with +// the host application. + +#include "test_host_1.h" + +#include +#include + +#include "test_util.h" + +static double GetCurrentTime() { + struct timeval tv; + tv.tv_sec = tv.tv_usec = 0; + gettimeofday(&tv, NULL); + return tv.tv_sec + (tv.tv_usec / (1000.0 * 1000.0)); +} + +TestHost_1::TestHost_1() + : current_time_(GetCurrentTime()), + has_new_key_message_(false), + has_new_key_error_(false), + cdm_(NULL) { +} + +TestHost_1::~TestHost_1() { + if (cdm_) + cdm_->Destroy(); +} + +cdm::Buffer* TestHost_1::Allocate(int32_t capacity) { + return TestBuffer::Create(capacity); +} + +void TestHost_1::SetTimer(int64_t delay_ms, void* context) { + double expiry_time = current_time_ + (delay_ms / 1000.0); + timers_.push(Timer(expiry_time, context)); +} + +double TestHost_1::GetCurrentWallTimeInSeconds() { + return current_time_; +} + +void TestHost_1::SendKeyMessage(const char* session_id, + int32_t session_id_length, const char* message, + int32_t message_length, const char* default_url, + int32_t default_url_length) { + KeyMessage key_message; + key_message.session_id.assign(session_id, session_id_length); + key_message.message.assign(message, message_length); + key_message.default_url.assign(default_url, default_url_length); + key_messages_.push_back(key_message); + has_new_key_message_ = true; +} + +void TestHost_1::SendKeyError(const char* session_id, int32_t session_id_length, + cdm::MediaKeyError error_code, + uint32_t system_code) { + KeyError key_error; + key_error.session_id.assign(session_id, session_id_length); + key_error.error_code = error_code; + key_error.system_code = system_code; + key_errors_.push_back(key_error); + has_new_key_error_ = true; +} + +void TestHost_1::FastForwardTime(double seconds) { + double goal_time = current_time_ + seconds; + while (current_time_ < goal_time) { + if (timers_.empty()) { + current_time_ = goal_time; + } else { + Timer t = timers_.top(); + timers_.pop(); + ASSERT_GE(t.expiry_time, current_time_); + current_time_ = t.expiry_time; + cdm_->TimerExpired(t.context); + } + } +} + +void TestHost_1::GetPlatformString(const std::string& name, + std::string* value) { + *value = platform_strings_[name]; +} + +void TestHost_1::SetPlatformString(const std::string& name, + const std::string& value) { + platform_strings_[name] = value; +} + +int TestHost_1::KeyMessagesSize() const { return key_messages_.size(); } + +int TestHost_1::KeyErrorsSize() const { return key_errors_.size(); } + +int TestHost_1::NumTimers() const { return timers_.size(); } + +TestHost_1::KeyMessage TestHost_1::GetLastKeyMessage() { + if (!has_new_key_message_) { + return KeyMessage(); + } + + if (key_messages_.empty()) { + return KeyMessage(); + } + + has_new_key_message_ = false; + return key_messages_.back(); +} + +TestHost_1::KeyError TestHost_1::GetLastKeyError() { + if (!has_new_key_error_) return KeyError(); + + if (key_errors_.empty()) return KeyError(); + + has_new_key_error_ = false; + return key_errors_.back(); +} + +TestHost_1::KeyMessage TestHost_1::GetKeyMessage(int index) const { + return key_messages_[index]; +} + +TestHost_1::KeyError TestHost_1::GetKeyError(int index) const { + return key_errors_[index]; +} + +void TestHost_1::SetCdmPtr(cdm::ContentDecryptionModule_1* cdm) { + if (cdm_) { + cdm_->Destroy(); + } + cdm_ = cdm; +} diff --git a/cdm/test/test_host_1.h b/cdm/test/test_host_1.h new file mode 100644 index 00000000..60a4b1d7 --- /dev/null +++ b/cdm/test/test_host_1.h @@ -0,0 +1,105 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_TEST_TEST_HOST_1_H_ +#define WVCDM_CDM_TEST_TEST_HOST_1_H_ + +#include "content_decryption_module.h" + +#include +#include + +#include "wv_cdm_common.h" +#include "wv_cdm_types.h" + +class TestHost_1 : public cdm::Host_1 { + public: + // These structs are used to store the KeyMessages and KeyErrors passed to + // this class' objects. + struct KeyMessage { + std::string session_id; + std::string message; + std::string default_url; + }; + + struct KeyError { + KeyError() : error_code(cdm::kUnknownError), system_code(0) {} + std::string session_id; + cdm::MediaKeyError error_code; + uint32_t system_code; + }; + + TestHost_1(); + virtual ~TestHost_1(); + + // cdm::Host implementation. + virtual cdm::Buffer* Allocate(int32_t capacity) OVERRIDE; + + virtual void SetTimer(int64_t delay_ms, void* context) OVERRIDE; + + virtual double GetCurrentWallTimeInSeconds() OVERRIDE; + + virtual void SendKeyMessage(const char* session_id, int32_t session_id_length, + const char* message, int32_t message_length, + const char* default_url, + int32_t default_url_length) OVERRIDE; + + virtual void SendKeyError(const char* session_id, int32_t session_id_length, + cdm::MediaKeyError error_code, + uint32_t system_code) OVERRIDE; + + virtual void GetPlatformString(const std::string& name, + std::string* value) OVERRIDE; + + virtual void SetPlatformString(const std::string& name, + const std::string& value) OVERRIDE; + + // Methods only for this test. + void FastForwardTime(double seconds); + int KeyMessagesSize() const; + int KeyErrorsSize() const; + int NumTimers() const; + + // Returns Key{Message,Error} (replace Message with Error for KeyError). It + // returns the most recent message passed to SendKeyMessage(). Another call + // to this method without a new SendKeyMessage() call will return an empty + // KeyMessage struct. + KeyMessage GetLastKeyMessage(); + KeyError GetLastKeyError(); + + KeyMessage GetKeyMessage(int index) const; + KeyError GetKeyError(int index) const; + + void SetCdmPtr(cdm::ContentDecryptionModule_1* cdm); + + private: + struct Timer { + Timer(double expiry_time, void* context) + : expiry_time(expiry_time), context(context) {} + + bool operator<(const Timer& other) const { + // We want to reverse the order so that the smallest expiry times go to + // the top of the priority queue. + return expiry_time > other.expiry_time; + } + + double expiry_time; + void* context; + }; + + double current_time_; + std::priority_queue timers_; + + std::vector key_messages_; + std::vector key_errors_; + + bool has_new_key_message_; + bool has_new_key_error_; + + std::map platform_strings_; + + cdm::ContentDecryptionModule_1* cdm_; + + CORE_DISALLOW_COPY_AND_ASSIGN(TestHost_1); +}; + +#endif // WVCDM_CDM_TEST_TEST_HOST_1_H_ diff --git a/cdm/test/test_host_4.cpp b/cdm/test/test_host_4.cpp new file mode 100644 index 00000000..7716b1b1 --- /dev/null +++ b/cdm/test/test_host_4.cpp @@ -0,0 +1,137 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +// Review the TestHost_4 class below to observe how the CDM interfaces with +// the host application. + +#include "test_host_4.h" + +#include +#include + +#include "test_host_4_file_io.h" +#include "test_util.h" + +static double GetCurrentTime() { + struct timeval tv; + tv.tv_sec = tv.tv_usec = 0; + gettimeofday(&tv, NULL); + return tv.tv_sec + (tv.tv_usec / (1000.0 * 1000.0)); +} + +TestHost_4::TestHost_4() + : current_time_(GetCurrentTime()), + has_new_session_message_(false), + has_new_session_error_(false), + cdm_(NULL) {} + +TestHost_4::~TestHost_4() { + if (cdm_) cdm_->Destroy(); +} + +cdm::Buffer* TestHost_4::Allocate(uint32_t capacity) { + return TestBuffer::Create(capacity); +} + +void TestHost_4::SetTimer(int64_t delay_ms, void* context) { + double expiry_time = current_time_ + (delay_ms / 1000.0); + timers_.push(Timer(expiry_time, context)); +} + +double TestHost_4::GetCurrentWallTimeInSeconds() { return current_time_; } + +void TestHost_4::FastForwardTime(double seconds) { + double goal_time = current_time_ + seconds; + while (current_time_ < goal_time) { + if (timers_.empty()) { + current_time_ = goal_time; + } else { + Timer t = timers_.top(); + timers_.pop(); + ASSERT_GE(t.expiry_time, current_time_); + current_time_ = t.expiry_time; + cdm_->TimerExpired(t.context); + } + } +} + +int TestHost_4::SessionMessagesSize() const { return session_messages_.size(); } + +int TestHost_4::SessionErrorsSize() const { return session_errors_.size(); } + +int TestHost_4::NumTimers() const { return timers_.size(); } + +TestHost_4::SessionMessage TestHost_4::GetLastSessionMessage() { + if (!has_new_session_message_) { + return SessionMessage(); + } + + if (session_messages_.empty()) { + return SessionMessage(); + } + + has_new_session_message_ = false; + return session_messages_.back(); +} + +TestHost_4::SessionError TestHost_4::GetLastSessionError() { + if (!has_new_session_error_) return SessionError(); + + if (session_errors_.empty()) return SessionError(); + + has_new_session_error_ = false; + return session_errors_.back(); +} + +TestHost_4::SessionMessage TestHost_4::GetSessionMessage(int index) const { + return session_messages_[index]; +} + +TestHost_4::SessionError TestHost_4::GetSessionError(int index) const { + return session_errors_[index]; +} + +void TestHost_4::SetCdmPtr(cdm::ContentDecryptionModule_4* cdm) { + if (cdm_) { + cdm_->Destroy(); + } + cdm_ = cdm; +} + +void TestHost_4::OnSessionCreated(uint32_t session_id, + const char* web_session_id, + uint32_t web_session_id_length) { + std::string webid(web_session_id, web_session_id_length); + session_map[session_id] = webid; // keep a parallel map with cdm. +} + +void TestHost_4::OnSessionMessage(uint32_t session_id, const char* message, + uint32_t message_length, + const char* destination_url, + uint32_t destination_url_length) { + SessionMessage session_message; + session_message.session_id = session_id; + session_message.message.assign(message, message_length); + session_message.default_url.assign(destination_url, destination_url_length); + session_messages_.push_back(session_message); + has_new_session_message_ = true; +} + +void TestHost_4::OnSessionUpdated(uint32_t session_id) {} + +void TestHost_4::OnSessionClosed(uint32_t session_id) { + session_map.erase(session_id); +} + +void TestHost_4::OnSessionError(uint32_t session_id, cdm::Status error_code, + uint32_t system_code) { + SessionError session_error; + session_error.session_id = session_id; + session_error.error_code = error_code; + session_error.system_code = system_code; + session_errors_.push_back(session_error); + has_new_session_error_ = true; +} + +cdm::FileIO* TestHost_4::CreateFileIO(cdm::FileIOClient* client) { + return new TestHost_4_FileIO(this, client); +} diff --git a/cdm/test/test_host_4.h b/cdm/test/test_host_4.h new file mode 100644 index 00000000..f9c61637 --- /dev/null +++ b/cdm/test/test_host_4.h @@ -0,0 +1,108 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_TEST_TEST_HOST_4_H_ +#define WVCDM_CDM_TEST_TEST_HOST_4_H_ + +#include "content_decryption_module.h" +#include "gmock/gmock.h" +#include + +#include "wv_cdm_common.h" +#include "wv_cdm_types.h" + +class TestHost_4 : public cdm::Host_4 { + public: + // These structs are used to store the SessionMessages and SessionErrors + // passed to this class' objects. + + struct SessionMessage { + uint32_t session_id; + std::string message; + std::string default_url; + }; + + struct SessionError { + uint32_t session_id; + cdm::Status error_code; + uint32_t system_code; + }; + + TestHost_4(); + virtual ~TestHost_4(); + + // cdm::Host implementation. + virtual cdm::Buffer* Allocate(uint32_t capacity) OVERRIDE; + + virtual void SetTimer(int64_t delay_ms, void* context) OVERRIDE; + + virtual double GetCurrentWallTimeInSeconds() OVERRIDE; + + virtual void OnSessionCreated(uint32_t session_id, const char* web_session_id, + uint32_t web_session_id_length) OVERRIDE; + + virtual void OnSessionMessage(uint32_t session_id, const char* message, + uint32_t message_length, + const char* destination_url, + uint32_t destination_url_length) OVERRIDE; + + virtual void OnSessionUpdated(uint32_t session_id) OVERRIDE; + + virtual void OnSessionClosed(uint32_t session_id) OVERRIDE; + + virtual void OnSessionError(uint32_t session_id, cdm::Status error_code, + uint32_t system_code) OVERRIDE; + + virtual cdm::FileIO* CreateFileIO(cdm::FileIOClient* client) OVERRIDE; + + // Methods only for this test. + void FastForwardTime(double seconds); + int SessionMessagesSize() const; + int SessionErrorsSize() const; + int NumTimers() const; + + // Returns Key{Message,Error} (replace Message with Error for SessionError). + // It returns the most recent message passed to SendSessionMessage(). Another + // call to this method without a new SendSessionMessage() call will return an + // empty SessionMessage struct. + SessionMessage GetLastSessionMessage(); + SessionError GetLastSessionError(); + + SessionMessage GetSessionMessage(int index) const; + SessionError GetSessionError(int index) const; + + void SetCdmPtr(cdm::ContentDecryptionModule_4* cdm); + std::map session_map; + + // Accessed by all FileIO objects. + std::map file_store; + + private: + struct Timer { + Timer(double expiry_time, void* context) + : expiry_time(expiry_time), context(context) {} + + bool operator<(const Timer& other) const { + // We want to reverse the order so that the smallest expiry times go to + // the top of the priority queue. + return expiry_time > other.expiry_time; + } + + double expiry_time; + void* context; + }; + + double current_time_; + std::priority_queue timers_; + + std::vector session_messages_; + std::vector session_errors_; + + bool has_new_session_message_; + bool has_new_session_error_; + + cdm::ContentDecryptionModule_4* cdm_; + + CORE_DISALLOW_COPY_AND_ASSIGN(TestHost_4); +}; + +#endif // WVCDM_CDM_TEST_TEST_HOST_4_H_ diff --git a/cdm/test/test_host_4_file_io.cpp b/cdm/test/test_host_4_file_io.cpp new file mode 100644 index 00000000..155a30f0 --- /dev/null +++ b/cdm/test/test_host_4_file_io.cpp @@ -0,0 +1,31 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +// Review the TestHost_4 class below to observe how the CDM interfaces with +// the host application. + +#include "test_host_4_file_io.h" + +#include + +void TestHost_4_FileIO::Open(const char* file_name, uint32_t file_name_size) { + ASSERT_EQ(0, file_name_.size()); + file_name_.assign(file_name, file_name_size); + client_->OnOpenComplete(cdm::FileIOClient::kSuccess); +} + +void TestHost_4_FileIO::Read() { + ASSERT_NE(0, file_name_.size()); + const std::string& data = host_->file_store[file_name_]; + client_->OnReadComplete(cdm::FileIOClient::kSuccess, + reinterpret_cast(data.data()), + data.size()); +} + +void TestHost_4_FileIO::Write(const uint8_t* data, uint32_t data_size) { + ASSERT_NE(0, file_name_.size()); + host_->file_store[file_name_].assign(reinterpret_cast(data), + data_size); + client_->OnWriteComplete(cdm::FileIOClient::kSuccess); +} + +void TestHost_4_FileIO::Close() { delete this; } diff --git a/cdm/test/test_host_4_file_io.h b/cdm/test/test_host_4_file_io.h new file mode 100644 index 00000000..017e416e --- /dev/null +++ b/cdm/test/test_host_4_file_io.h @@ -0,0 +1,34 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_TEST_TEST_HOST_4_FILE_IO_H_ +#define WVCDM_CDM_TEST_TEST_HOST_4_FILE_IO_H_ + +#include + +#include "content_decryption_module.h" +#include "test_host_4.h" +#include "wv_cdm_common.h" +#include "wv_cdm_types.h" + +class TestHost_4_FileIO : public cdm::FileIO { + public: + TestHost_4_FileIO(TestHost_4* host, cdm::FileIOClient* client) + : host_(host), client_(client) {} + virtual ~TestHost_4_FileIO() {} + + // cdm::FileIO implementation. + virtual void Open(const char* file_name, uint32_t file_name_size) OVERRIDE; + virtual void Read() OVERRIDE; + virtual void Write(const uint8_t* data, uint32_t data_size) OVERRIDE; + virtual void Close() OVERRIDE; + + private: + TestHost_4* host_; + cdm::FileIOClient* client_; + + std::string file_name_; + + CORE_DISALLOW_COPY_AND_ASSIGN(TestHost_4_FileIO); +}; + +#endif // WVCDM_CDM_TEST_TEST_HOST_4_FILE_IO_H_ diff --git a/cdm/test/test_util.cpp b/cdm/test/test_util.cpp new file mode 100644 index 00000000..a32e3a82 --- /dev/null +++ b/cdm/test/test_util.cpp @@ -0,0 +1,52 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "test_util.h" + +TestBuffer* TestBuffer::Create(uint32_t capacity) { + return new TestBuffer(capacity); +} + +void TestBuffer::Destroy() { + delete this; +} + +int32_t TestBuffer::Capacity() const { return capacity_; } + +uint8_t* TestBuffer::Data() { return buffer_; } + +void TestBuffer::SetSize(int32_t size) { size_ = size; } + +int32_t TestBuffer::Size() const { return size_; } + +TestBuffer::TestBuffer(uint32_t capacity) + : buffer_(new uint8_t[capacity]), + capacity_(capacity) {} + +TestBuffer::~TestBuffer() { + if (buffer_) { + delete[] buffer_; + buffer_ = NULL; + } +} + +TestDecryptedBlock::TestDecryptedBlock() : buffer_(NULL), timestamp_(0) {} + +TestDecryptedBlock::~TestDecryptedBlock() { + if (buffer_) { + buffer_->Destroy(); + buffer_ = NULL; + } +} + +void TestDecryptedBlock::SetDecryptedBuffer(cdm::Buffer* buffer) { + if (buffer_) buffer_->Destroy(); + buffer_ = buffer; +} + +cdm::Buffer* TestDecryptedBlock::DecryptedBuffer() { return buffer_; } + +void TestDecryptedBlock::SetTimestamp(int64_t timestamp) { + timestamp_ = timestamp; +} + +int64_t TestDecryptedBlock::Timestamp() const { return timestamp_; } diff --git a/cdm/test/test_util.h b/cdm/test/test_util.h new file mode 100644 index 00000000..2607e6bd --- /dev/null +++ b/cdm/test/test_util.h @@ -0,0 +1,58 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_TEST_TEST_UTIL_H_ +#define WVCDM_CDM_TEST_TEST_UTIL_H_ + +#include "content_decryption_module.h" + +#include "wv_cdm_common.h" +#include "wv_cdm_types.h" + +// These classes below are naive implementation of the abstract classes defined +// in the CDM interface (content_decryptiom_module.h), which are used for tests +// only. + +class TestBuffer : public cdm::Buffer { + public: + static TestBuffer* Create(uint32_t capacity); + + virtual void Destroy() OVERRIDE; + + virtual int32_t Capacity() const OVERRIDE; + virtual uint8_t* Data() OVERRIDE; + virtual void SetSize(int32_t size) OVERRIDE; + virtual int32_t Size() const OVERRIDE; + + private: + // TestBuffer can only be created by calling Create(). + explicit TestBuffer(uint32_t capacity); + + // TestBuffer can only be destroyed by calling Destroy(). + virtual ~TestBuffer(); + + uint8_t* buffer_; + int32_t capacity_; + int32_t size_; + + CORE_DISALLOW_COPY_AND_ASSIGN(TestBuffer); +}; + +class TestDecryptedBlock : public cdm::DecryptedBlock { + public: + TestDecryptedBlock(); + virtual ~TestDecryptedBlock(); + + virtual void SetDecryptedBuffer(cdm::Buffer* buffer) OVERRIDE; + virtual cdm::Buffer* DecryptedBuffer() OVERRIDE; + + virtual void SetTimestamp(int64_t timestamp) OVERRIDE; + virtual int64_t Timestamp() const OVERRIDE; + + private: + cdm::Buffer* buffer_; + int64_t timestamp_; + + CORE_DISALLOW_COPY_AND_ASSIGN(TestDecryptedBlock); +}; + +#endif // WVCDM_CDM_TEST_TEST_UTIL_H_ diff --git a/core/include/buffer_reader.h b/core/include/buffer_reader.h index a692517d..f098a977 100644 --- a/core/include/buffer_reader.h +++ b/core/include/buffer_reader.h @@ -24,13 +24,13 @@ namespace wvcdm { class BufferReader { public: BufferReader(const uint8_t* buf, size_t size) - : buf_(buf), size_(size), pos_(0) {} + : buf_(buf), size_(size), pos_(0) {} bool HasBytes(int count) { return (pos() + count <= size()); } // Read a value from the stream, performing endian correction, // and advance the stream pointer. - bool Read1(uint8_t* v) WARN_UNUSED_RESULT; + bool Read1(uint8_t* v) WARN_UNUSED_RESULT; bool Read2(uint16_t* v) WARN_UNUSED_RESULT; bool Read2s(int16_t* v) WARN_UNUSED_RESULT; bool Read4(uint32_t* v) WARN_UNUSED_RESULT; @@ -58,7 +58,8 @@ class BufferReader { size_t size_; size_t pos_; - template bool Read(T* t) WARN_UNUSED_RESULT; + template + bool Read(T* t) WARN_UNUSED_RESULT; CORE_DISALLOW_COPY_AND_ASSIGN(BufferReader); }; diff --git a/core/include/cdm_engine.h b/core/include/cdm_engine.h index 79894ed3..a42adb87 100644 --- a/core/include/cdm_engine.h +++ b/core/include/cdm_engine.h @@ -3,7 +3,6 @@ #ifndef WVCDM_CORE_CDM_ENGINE_H_ #define WVCDM_CORE_CDM_ENGINE_H_ -#include "cdm_session.h" #include "certificate_provisioning.h" #include "initialization_data.h" #include "oemcrypto_adapter.h" @@ -13,6 +12,7 @@ namespace wvcdm { class CdmClientPropertySet; +class CdmSession; class CryptoEngine; class WvCdmEventListener; @@ -34,12 +34,33 @@ class CdmEngine { virtual CdmResponseType CloseKeySetSession(const CdmKeySetId& key_set_id); // License related methods - // Construct a valid license request + + // Construct a valid license request. The arguments are used as follows: + // session_id: The Session ID of the session the request is being generated + // for. This is ignored for license release requests. + // key_set_id: The Key Set ID of the key set the request is being generated + // for. This is ignored except for license release requests. + // init_data: The initialization data from the media file, which is used to + // build the key request. This is ignored for release and renewal + // requests. + // license_type: The type of license being requested. Never ignored. + // 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. + // 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. 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, - std::string* server_url); + std::string* server_url, CdmKeySetId* key_set_id_out); // Accept license response and extract key info. virtual CdmResponseType AddKey(const CdmSessionId& session_id, diff --git a/core/include/cdm_session.h b/core/include/cdm_session.h index 27808924..789b72a9 100644 --- a/core/include/cdm_session.h +++ b/core/include/cdm_session.h @@ -36,7 +36,7 @@ class CdmSession { virtual CdmResponseType GenerateKeyRequest( const InitializationData& init_data, const CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, - std::string* server_url); + std::string* server_url, CdmKeySetId* key_set_id); // AddKey() - Accept license response and extract key info. virtual CdmResponseType AddKey(const CdmKeyResponse& key_response, @@ -81,7 +81,8 @@ class CdmSession { virtual void OnKeyReleaseEvent(const CdmKeySetId& key_set_id); virtual SecurityLevel GetRequestedSecurityLevel() { - return requested_security_level_; } + return requested_security_level_; + } virtual CdmSecurityLevel GetSecurityLevel() { return security_level_; } virtual CdmResponseType UpdateUsageInformation(); diff --git a/core/include/certificate_provisioning.h b/core/include/certificate_provisioning.h index 50267953..7a1ad185 100644 --- a/core/include/certificate_provisioning.h +++ b/core/include/certificate_provisioning.h @@ -31,8 +31,7 @@ class CertificateProvisioning { CdmProvisioningRequest* request); bool ParseJsonResponse(const CdmProvisioningResponse& json_str, const std::string& start_substr, - const std::string& end_substr, - std::string* result); + const std::string& end_substr, std::string* result); CryptoSession crypto_session_; CdmCertificateType cert_type_; diff --git a/core/include/clock.h b/core/include/clock.h index 461b2bfc..d3c0ba27 100644 --- a/core/include/clock.h +++ b/core/include/clock.h @@ -11,7 +11,6 @@ namespace wvcdm { // Provides time related information. The implementation is platform dependent. class Clock { - public: Clock() {} virtual ~Clock() {} diff --git a/core/include/crypto_key.h b/core/include/crypto_key.h index 765f7275..d87c239a 100644 --- a/core/include/crypto_key.h +++ b/core/include/crypto_key.h @@ -8,7 +8,7 @@ namespace wvcdm { class CryptoKey { -public: + public: CryptoKey() {}; ~CryptoKey() {}; @@ -27,7 +27,7 @@ public: bool HasKeyControl() const { return key_control_.size() >= 16; } -private: + private: std::string key_id_; std::string key_data_iv_; std::string key_data_; @@ -35,6 +35,6 @@ private: std::string key_control_iv_; }; -}; // namespace wvcdm +}; // namespace wvcdm #endif // WVCDM_CORE_CRYPTO_KEY_H_ diff --git a/core/include/crypto_session.h b/core/include/crypto_session.h index d19b20d3..e223f293 100644 --- a/core/include/crypto_session.h +++ b/core/include/crypto_session.h @@ -18,6 +18,17 @@ typedef std::map CryptoKeyMap; class CryptoSession { public: + // This enum should be kept in sync with the values specified for + // HDCP capabilities in OEMCryptoCENC.h. (See comments for + // OEMCrypto_GetHDCPCapability) + typedef enum { + kOemCryptoHdcpNotSupported = 0, + kOemCryptoHdcpVersion1 = 1, + kOemCryptoHdcpVersion2 = 2, + kOemCryptoHdcpVersion2_1 = 3, + kOemCryptoHdcpVersion2_2 = 4, + kOemCryptoNoHdcpDeviceAttached = 0xff, + } OemCryptoHdcpVersion; CryptoSession(); virtual ~CryptoSession(); @@ -37,7 +48,7 @@ class CryptoSession { virtual CryptoSessionId oec_session_id() { return oec_session_id_; } // Key request/response - virtual void GenerateRequestId(std::string& req_id_str); + virtual bool GenerateRequestId(std::string* req_id_str); virtual bool PrepareRequest(const std::string& key_deriv_message, bool is_provisioning, std::string* signature); virtual bool PrepareRenewalRequest(const std::string& message, @@ -66,6 +77,7 @@ class CryptoSession { // Media data path virtual CdmResponseType Decrypt(const CdmDecryptionParameters& parameters); + virtual bool UsageInformationSupport(bool* has_support); virtual CdmResponseType UpdateUsageInformation(); virtual CdmResponseType GenerateUsageReport( const std::string& provider_session_token, std::string* usage_report); @@ -73,6 +85,8 @@ class CryptoSession { const std::string& message, const std::string& signature, const std::string& provider_session_token); + virtual bool GetHdcpCapabilities(OemCryptoHdcpVersion* current, + OemCryptoHdcpVersion* max); virtual bool GetRandom(size_t data_length, uint8_t* random_data); private: diff --git a/core/include/device_files.h b/core/include/device_files.h index 50c3b31e..7482890c 100644 --- a/core/include/device_files.h +++ b/core/include/device_files.h @@ -27,7 +27,7 @@ class DeviceFiles { virtual bool Init(CdmSecurityLevel security_level); virtual bool Reset(CdmSecurityLevel security_level) { - return Init(security_level); + return Init(security_level); } virtual bool StoreCertificate(const std::string& certificate, @@ -44,8 +44,7 @@ class DeviceFiles { const CdmKeyResponse& key_renewal_response, const std::string& release_server_url); virtual bool RetrieveLicense(const std::string& key_set_id, - LicenseState* state, - CdmInitData* pssh_data, + LicenseState* state, CdmInitData* pssh_data, CdmKeyMessage* key_request, CdmKeyResponse* key_response, CdmKeyMessage* key_renewal_request, @@ -55,6 +54,7 @@ class DeviceFiles { virtual bool DeleteAllFiles(); virtual bool DeleteAllLicenses(); virtual bool LicenseExists(const std::string& key_set_id); + virtual bool ReserveLicenseId(const std::string& key_set_id); virtual bool StoreUsageInfo(const std::string& provider_session_token, const CdmKeyMessage& key_request, @@ -65,8 +65,9 @@ class DeviceFiles { std::vector >* usage_info); private: - bool StoreFile(const char* name, const std::string& serialized_file); - bool RetrieveFile(const char* name, std::string* serialized_file); + bool StoreFileWithHash(const char* name, const std::string& serialized_file); + bool StoreFileRaw(const char* name, const std::string& serialized_file); + bool RetrieveHashedFile(const char* name, std::string* serialized_file); // Certificate and offline licenses are now stored in security // level specific directories. In an earlier version they were @@ -77,6 +78,7 @@ class DeviceFiles { static std::string GetCertificateFileName(); static std::string GetLicenseFileNameExtension(); static std::string GetUsageInfoFileName(); + static std::string GetBlankFileData(); void SetTestFile(File* file); #if defined(UNIT_TEST) FRIEND_TEST(DeviceFilesSecurityLevelTest, SecurityLevel); @@ -84,6 +86,7 @@ class DeviceFiles { FRIEND_TEST(DeviceFilesStoreTest, StoreLicense); FRIEND_TEST(DeviceFilesTest, DeleteLicense); FRIEND_TEST(DeviceFilesTest, ReadCertificate); + FRIEND_TEST(DeviceFilesTest, ReserveLicenseIds); FRIEND_TEST(DeviceFilesTest, RetrieveLicenses); FRIEND_TEST(DeviceFilesTest, SecurityLevelPathBackwardCompatibility); FRIEND_TEST(DeviceFilesTest, StoreLicenses); diff --git a/core/include/file_store.h b/core/include/file_store.h index 65f4f3d4..c205ee46 100644 --- a/core/include/file_store.h +++ b/core/include/file_store.h @@ -44,7 +44,7 @@ class File { virtual ssize_t FileSize(const std::string& file_path); private: - Impl *impl_; + Impl* impl_; CORE_DISALLOW_COPY_AND_ASSIGN(File); }; diff --git a/core/include/initialization_data.h b/core/include/initialization_data.h index c5355a2d..af33b6b4 100644 --- a/core/include/initialization_data.h +++ b/core/include/initialization_data.h @@ -13,7 +13,7 @@ class WvCdmEngineTest; class InitializationData { public: - InitializationData(const std::string& type, + InitializationData(const std::string& type = std::string(), const CdmInitData& data = CdmInitData()); bool is_supported() const { return is_cenc_ || is_webm_; } diff --git a/core/include/license.h b/core/include/license.h index 54fc8286..ee77a239 100644 --- a/core/include/license.h +++ b/core/include/license.h @@ -6,6 +6,7 @@ #include #include "initialization_data.h" +#include "scoped_ptr.h" #include "wv_cdm_types.h" namespace video_widevine_server { @@ -16,39 +17,44 @@ class SignedMessage; namespace wvcdm { +class Clock; class CryptoSession; class PolicyEngine; class CdmLicense { public: - - CdmLicense() : session_(NULL), initialized_(false) {} - virtual ~CdmLicense() {} + CdmLicense(); + virtual ~CdmLicense(); virtual bool Init(const std::string& token, CryptoSession* session, - PolicyEngine* policy_engine); + PolicyEngine* policy_engine); virtual bool PrepareKeyRequest(const InitializationData& init_data, - const CdmLicenseType license_type, - const CdmAppParameterMap& app_parameters, - const CdmSessionId& session_id, - CdmKeyMessage* signed_request, - std::string* server_url); - virtual bool PrepareKeyUpdateRequest(bool is_renewal, CdmKeyMessage* signed_request, - std::string* server_url); - virtual CdmResponseType HandleKeyResponse(const CdmKeyResponse& license_response); + const CdmLicenseType license_type, + const CdmAppParameterMap& app_parameters, + const CdmSessionId& session_id, + CdmKeyMessage* signed_request, + std::string* server_url); + virtual bool PrepareKeyUpdateRequest(bool is_renewal, + CdmKeyMessage* signed_request, + std::string* server_url); + virtual CdmResponseType HandleKeyResponse( + const CdmKeyResponse& license_response); virtual CdmResponseType HandleKeyUpdateResponse( bool is_renewal, const CdmKeyResponse& license_response); - virtual bool RestoreOfflineLicense(const CdmKeyMessage& license_request, - const CdmKeyResponse& license_response, - const CdmKeyResponse& license_renewal_response); + virtual bool RestoreOfflineLicense( + const CdmKeyMessage& license_request, + const CdmKeyResponse& license_response, + const CdmKeyResponse& license_renewal_response); virtual bool RestoreUsageLicense(const CdmKeyMessage& license_request, - const CdmKeyResponse& license_response); + const CdmKeyResponse& license_response); virtual bool HasInitData() { return !stored_init_data_.empty(); } virtual bool IsKeyLoaded(const KeyId& key_id); - virtual std::string provider_session_token() { return provider_session_token_; } + virtual std::string provider_session_token() { + return provider_session_token_; + } private: bool PrepareServiceCertificateRequest(CdmKeyMessage* signed_request, @@ -59,9 +65,9 @@ class CdmLicense { CdmResponseType HandleKeyErrorResponse( const video_widevine_server::sdk::SignedMessage& signed_message); - template bool PrepareContentId(const CdmLicenseType license_type, - const std::string& request_id, - T* content_id); + template + bool PrepareContentId(const CdmLicenseType license_type, + const std::string& request_id, T* content_id); CryptoSession* session_; PolicyEngine* policy_engine_; @@ -76,6 +82,14 @@ class CdmLicense { // Used for certificate based licensing CdmKeyMessage key_request_; + scoped_ptr clock_; + + // For testing + CdmLicense(Clock* clock); // CdmLicense takes ownership of the clock. +#if defined(UNIT_TEST) + friend class CdmLicenseTest; +#endif + CORE_DISALLOW_COPY_AND_ASSIGN(CdmLicense); }; diff --git a/core/include/lock.h b/core/include/lock.h index 1f502975..5cbc6e31 100644 --- a/core/include/lock.h +++ b/core/include/lock.h @@ -29,7 +29,7 @@ class Lock { private: class Impl; - Impl *impl_; + Impl* impl_; CORE_DISALLOW_COPY_AND_ASSIGN(Lock); }; @@ -38,20 +38,14 @@ class Lock { // is constructed and release when AutoLock goes out of scope. class AutoLock { public: - explicit AutoLock(Lock& lock) : lock_(&lock) { - lock_->Acquire(); - } + explicit AutoLock(Lock& lock) : lock_(&lock) { lock_->Acquire(); } - explicit AutoLock(Lock* lock) : lock_(lock) { - lock_->Acquire(); - } + explicit AutoLock(Lock* lock) : lock_(lock) { lock_->Acquire(); } - ~AutoLock() { - lock_->Release(); - } + ~AutoLock() { lock_->Release(); } private: - Lock *lock_; + Lock* lock_; CORE_DISALLOW_COPY_AND_ASSIGN(AutoLock); }; diff --git a/core/include/oemcrypto_adapter.h b/core/include/oemcrypto_adapter.h index aac9ecd5..6d96eb0a 100644 --- a/core/include/oemcrypto_adapter.h +++ b/core/include/oemcrypto_adapter.h @@ -7,10 +7,7 @@ namespace wvcdm { -enum SecurityLevel { - kLevelDefault, - kLevel3 -}; +enum SecurityLevel { kLevelDefault, kLevel3 }; /* This attempts to open a session at the desired security level. If one level is not available, the other will be used instead. */ diff --git a/core/include/privacy_crypto.h b/core/include/privacy_crypto.h index 309aeecc..68511e12 100644 --- a/core/include/privacy_crypto.h +++ b/core/include/privacy_crypto.h @@ -52,10 +52,9 @@ class RsaPublicKey { // Encrypt a message using RSA-OAEP. Caller retains ownership of all // parameters. Returns true if successful, false otherwise. - bool Encrypt(const std::string& plaintext, - std::string* ciphertext); + bool Encrypt(const std::string& plaintext, std::string* ciphertext); - // Verify RSSASSA-PSS signature. Caller retains ownership of all parameters. + // Verify RSASSA-PSS signature. Caller retains ownership of all parameters. // Returns true if validation succeeds, false otherwise. bool VerifySignature(const std::string& message, const std::string& signature); diff --git a/core/include/properties.h b/core/include/properties.h index cb0709cb..5bb98a22 100644 --- a/core/include/properties.h +++ b/core/include/properties.h @@ -11,6 +11,10 @@ #include "scoped_ptr.h" #include "wv_cdm_types.h" +#if defined(UNIT_TEST) +# include "gtest/gtest_prod.h" +#endif + namespace wvcdm { typedef std::map @@ -93,6 +97,7 @@ class Properties { FRIEND_TEST(CdmSessionTest, ReInitFail); FRIEND_TEST(CdmSessionTest, InitFailCryptoError); FRIEND_TEST(CdmSessionTest, InitNeedsProvisioning); + FRIEND_TEST(CdmLicenseTest, PrepareKeyRequestValidation); #endif private: diff --git a/core/include/scoped_ptr.h b/core/include/scoped_ptr.h index 7c50f74f..515b0ec4 100644 --- a/core/include/scoped_ptr.h +++ b/core/include/scoped_ptr.h @@ -53,6 +53,7 @@ class scoped_ptr { ptr_ = p; } } + private: T* ptr_; diff --git a/core/src/buffer_reader.cpp b/core/src/buffer_reader.cpp index f81b9a21..83771881 100644 --- a/core/src/buffer_reader.cpp +++ b/core/src/buffer_reader.cpp @@ -17,7 +17,8 @@ bool BufferReader::Read1(uint8_t* v) { } // Internal implementation of multi-byte reads -template bool BufferReader::Read(T* v) { +template +bool BufferReader::Read(T* v) { if (!HasBytes(sizeof(T))) { LOGW("BufferReader::Read : Failure during parse: Not enough bytes (%u)", sizeof(T)); diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp index e334718b..8752e4df 100644 --- a/core/src/cdm_engine.cpp +++ b/core/src/cdm_engine.cpp @@ -19,8 +19,8 @@ #include "wv_cdm_event_listener.h" namespace { - const uint32_t kUpdateUsageInformationPeriod = 60; // seconds - const size_t kUsageReportsPerRequest = 1; +const uint32_t kUpdateUsageInformationPeriod = 60; // seconds +const size_t kUsageReportsPerRequest = 1; } // unnamed namespace namespace wvcdm { @@ -47,10 +47,9 @@ CdmEngine::~CdmEngine() { sessions_.clear(); } -CdmResponseType CdmEngine::OpenSession( - const CdmKeySystem& key_system, - const CdmClientPropertySet* property_set, - CdmSessionId* session_id) { +CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, + const CdmClientPropertySet* property_set, + CdmSessionId* session_id) { LOGI("CdmEngine::OpenSession"); if (!ValidateKeySystem(key_system)) { @@ -95,8 +94,7 @@ CdmResponseType CdmEngine::OpenKeySetSession(const CdmKeySetId& key_set_id) { CdmSessionId session_id; CdmResponseType sts = OpenSession(KEY_SYSTEM, NULL, &session_id); - if (sts != NO_ERROR) - return sts; + if (sts != NO_ERROR) return sts; release_key_sets_[key_set_id] = session_id; return NO_ERROR; @@ -123,7 +121,7 @@ CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) { CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter == release_key_sets_.end()) { LOGE("CdmEngine::CloseKeySetSession: key set id not found = %s", - key_set_id.c_str()); + key_set_id.c_str()); return KEY_ERROR; } @@ -133,13 +131,10 @@ CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_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, - std::string* server_url) { + const CdmSessionId& session_id, const CdmKeySetId& key_set_id, + const InitializationData& init_data, const CdmLicenseType license_type, + CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, + std::string* server_url, CdmKeySetId* key_set_id_out) { LOGI("CdmEngine::GenerateKeyRequest"); CdmSessionId id = session_id; @@ -153,14 +148,14 @@ CdmResponseType CdmEngine::GenerateKeyRequest( if (!session_id.empty()) { LOGE("CdmEngine::GenerateKeyRequest: invalid session ID = %s", - session_id.c_str()); + session_id.c_str()); return UNKNOWN_ERROR; } CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter == release_key_sets_.end()) { LOGE("CdmEngine::GenerateKeyRequest: key set ID not found = %s", - key_set_id.c_str()); + key_set_id.c_str()); return UNKNOWN_ERROR; } @@ -170,7 +165,7 @@ CdmResponseType CdmEngine::GenerateKeyRequest( CdmSessionMap::iterator iter = sessions_.find(id); if (iter == sessions_.end()) { LOGE("CdmEngine::GenerateKeyRequest: session_id not found = %s", - id.c_str()); + id.c_str()); return KEY_ERROR; } @@ -184,23 +179,27 @@ CdmResponseType CdmEngine::GenerateKeyRequest( if (license_type == kLicenseTypeRelease) { sts = iter->second->RestoreOfflineSession(key_set_id, kLicenseTypeRelease); if (sts != KEY_ADDED) { - LOGE("CdmEngine::GenerateKeyRequest: key release restoration failed," - "sts = %d", (int)sts); + LOGE( + "CdmEngine::GenerateKeyRequest: key release restoration failed," + "sts = %d", + (int)sts); return sts; } } - sts = iter->second->GenerateKeyRequest(init_data, license_type, - app_parameters, key_request, - server_url); + sts = iter->second->GenerateKeyRequest( + init_data, license_type, app_parameters, key_request, server_url, + key_set_id_out); if (KEY_MESSAGE != sts) { if (sts == NEED_PROVISIONING) { cert_provisioning_requested_security_level_ = iter->second->GetRequestedSecurityLevel(); } - LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, " - "sts = %d", (int)sts); + LOGE( + "CdmEngine::GenerateKeyRequest: key request generation failed, " + "sts = %d", + (int)sts); return sts; } @@ -211,10 +210,9 @@ CdmResponseType CdmEngine::GenerateKeyRequest( return KEY_MESSAGE; } -CdmResponseType CdmEngine::AddKey( - const CdmSessionId& session_id, - const CdmKeyResponse& key_data, - CdmKeySetId* key_set_id) { +CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id, + const CdmKeyResponse& key_data, + CdmKeySetId* key_set_id) { LOGI("CdmEngine::AddKey"); CdmSessionId id = session_id; @@ -262,9 +260,8 @@ CdmResponseType CdmEngine::AddKey( return KEY_ADDED; } -CdmResponseType CdmEngine::RestoreKey( - const CdmSessionId& session_id, - const CdmKeySetId& key_set_id) { +CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id, + const CdmKeySetId& key_set_id) { LOGI("CdmEngine::RestoreKey"); if (key_set_id.empty()) { @@ -275,7 +272,7 @@ CdmResponseType CdmEngine::RestoreKey( CdmSessionMap::iterator iter = sessions_.find(session_id); if (iter == sessions_.end()) { LOGE("CdmEngine::RestoreKey: session_id not found = %s ", - session_id.c_str()); + session_id.c_str()); return UNKNOWN_ERROR; } @@ -285,6 +282,9 @@ CdmResponseType CdmEngine::RestoreKey( cert_provisioning_requested_security_level_ = iter->second->GetRequestedSecurityLevel(); } + if (sts != KEY_ADDED) { + LOGE("CdmEngine::RestoreKey: restore offline session failed = %d", sts); + } return sts; } @@ -303,8 +303,7 @@ CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) { } CdmResponseType CdmEngine::GenerateRenewalRequest( - const CdmSessionId& session_id, - CdmKeyMessage* key_request, + const CdmSessionId& session_id, CdmKeyMessage* key_request, std::string* server_url) { LOGI("CdmEngine::GenerateRenewalRequest"); @@ -322,8 +321,8 @@ CdmResponseType CdmEngine::GenerateRenewalRequest( key_request->clear(); - CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request, - server_url); + CdmResponseType sts = + iter->second->GenerateRenewalRequest(key_request, server_url); if (KEY_MESSAGE != sts) { LOGE("CdmEngine::GenerateRenewalRequest: key request gen. failed, sts=%d", @@ -334,9 +333,8 @@ CdmResponseType CdmEngine::GenerateRenewalRequest( return KEY_MESSAGE; } -CdmResponseType CdmEngine::RenewKey( - const CdmSessionId& session_id, - const CdmKeyResponse& key_data) { +CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id, + const CdmKeyResponse& key_data) { LOGI("CdmEngine::RenewKey"); CdmSessionMap::iterator iter = sessions_.find(session_id); @@ -416,9 +414,8 @@ CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id, return iter->second->QueryStatus(key_info); } -CdmResponseType CdmEngine::QueryKeyStatus( - const CdmSessionId& session_id, - CdmQueryMap* key_info) { +CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id, + CdmQueryMap* key_info) { LOGI("CdmEngine::QueryKeyStatus"); CdmSessionMap::iterator iter = sessions_.find(session_id); if (iter == sessions_.end()) { @@ -429,9 +426,8 @@ CdmResponseType CdmEngine::QueryKeyStatus( return iter->second->QueryKeyStatus(key_info); } -CdmResponseType CdmEngine::QueryKeyControlInfo( - const CdmSessionId& session_id, - CdmQueryMap* key_info) { +CdmResponseType CdmEngine::QueryKeyControlInfo(const CdmSessionId& session_id, + CdmQueryMap* key_info) { LOGI("CdmEngine::QueryKeyControlInfo"); CdmSessionMap::iterator iter = sessions_.find(session_id); if (iter == sessions_.end()) { @@ -450,20 +446,15 @@ CdmResponseType CdmEngine::QueryKeyControlInfo( * Returns NO_ERROR for success and UNKNOWN_ERROR if fails. */ CdmResponseType CdmEngine::GetProvisioningRequest( - CdmCertificateType cert_type, - const std::string& cert_authority, - CdmProvisioningRequest* request, - std::string* default_url) { + CdmCertificateType cert_type, const std::string& cert_authority, + CdmProvisioningRequest* request, std::string* default_url) { if (!request || !default_url) { LOGE("CdmEngine::GetProvisioningRequest: invalid input parameters"); return UNKNOWN_ERROR; } return cert_provisioning_.GetProvisioningRequest( - cert_provisioning_requested_security_level_, - cert_type, - cert_authority, - request, - default_url); + cert_provisioning_requested_security_level_, cert_type, cert_authority, + request, default_url); } /* @@ -474,21 +465,22 @@ CdmResponseType CdmEngine::GetProvisioningRequest( * Returns NO_ERROR for success and UNKNOWN_ERROR if fails. */ CdmResponseType CdmEngine::HandleProvisioningResponse( - CdmProvisioningResponse& response, - std::string* cert, + CdmProvisioningResponse& response, std::string* cert, std::string* wrapped_key) { if (response.empty()) { LOGE("CdmEngine::HandleProvisioningResponse: Empty provisioning response."); return UNKNOWN_ERROR; } if (NULL == cert) { - LOGE("CdmEngine::HandleProvisioningResponse: invalid certificate " - "destination"); + LOGE( + "CdmEngine::HandleProvisioningResponse: invalid certificate " + "destination"); return UNKNOWN_ERROR; } if (NULL == wrapped_key) { - LOGE("CdmEngine::HandleProvisioningResponse: invalid wrapped key " - "destination"); + LOGE( + "CdmEngine::HandleProvisioningResponse: invalid wrapped key " + "destination"); return UNKNOWN_ERROR; } return cert_provisioning_.HandleProvisioningResponse(response, cert, @@ -540,15 +532,16 @@ CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) { uint32_t index = rand() % license_info.size(); status = usage_session_->RestoreUsageSession(license_info[index].first, - license_info[index].second); + license_info[index].second); if (KEY_ADDED != status) { - LOGE("CdmEngine::GetUsageInfo: restore usage session (%d) error %ld", - index, status); + LOGE("CdmEngine::GetUsageInfo: restore usage session (%d) error %ld", index, + status); usage_info->clear(); return status; } - status = usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url); + status = + usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url); if (KEY_MESSAGE != status) { LOGE("CdmEngine::GetUsageInfo: generate release request error: %ld", @@ -573,9 +566,8 @@ CdmResponseType CdmEngine::ReleaseUsageInfo( return status; } -CdmResponseType CdmEngine::Decrypt( - const CdmSessionId& session_id, - const CdmDecryptionParameters& parameters) { +CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id, + const CdmDecryptionParameters& parameters) { if (parameters.key_id == NULL) { LOGE("CdmEngine::Decrypt: no key_id"); return KEY_ERROR; @@ -596,7 +588,7 @@ CdmResponseType CdmEngine::Decrypt( !Properties::Properties::oem_crypto_use_fifo()) { LOGE("CdmEngine::Decrypt: no dest decrypt buffer"); return KEY_ERROR; - } // else we must be level 1 direct and we don't need to return a buffer. + } // else we must be level 1 direct and we don't need to return a buffer. } CdmSessionMap::iterator iter; @@ -612,8 +604,7 @@ CdmResponseType CdmEngine::Decrypt( } if (iter == sessions_.end()) { LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d", - session_id.c_str(), - session_id.size()); + session_id.c_str(), session_id.size()); return KEY_ERROR; } @@ -630,9 +621,8 @@ bool CdmEngine::IsKeyLoaded(const KeyId& key_id) { return false; } -bool CdmEngine::FindSessionForKey( - const KeyId& key_id, - CdmSessionId* session_id) { +bool CdmEngine::FindSessionForKey(const KeyId& key_id, + CdmSessionId* session_id) { if (NULL == session_id) { LOGE("CdmEngine::FindSessionForKey: session id not provided"); return false; @@ -660,10 +650,8 @@ bool CdmEngine::FindSessionForKey( return false; } -bool CdmEngine::AttachEventListener( - const CdmSessionId& session_id, - WvCdmEventListener* listener) { - +bool CdmEngine::AttachEventListener(const CdmSessionId& session_id, + WvCdmEventListener* listener) { CdmSessionMap::iterator iter = sessions_.find(session_id); if (iter == sessions_.end()) { return false; @@ -672,10 +660,8 @@ bool CdmEngine::AttachEventListener( return iter->second->AttachEventListener(listener); } -bool CdmEngine::DetachEventListener( - const CdmSessionId& session_id, - WvCdmEventListener* listener) { - +bool CdmEngine::DetachEventListener(const CdmSessionId& session_id, + WvCdmEventListener* listener) { CdmSessionMap::iterator iter = sessions_.find(session_id); if (iter == sessions_.end()) { return false; @@ -700,7 +686,7 @@ void CdmEngine::OnTimerEvent() { } for (CdmSessionMap::iterator iter = sessions_.begin(); - iter != sessions_.end(); ++iter) { + iter != sessions_.end(); ++iter) { iter->second->OnTimerEvent(); if (update_usage_information && iter->second->is_usage_update_needed()) { @@ -718,7 +704,6 @@ void CdmEngine::OnTimerEvent() { } void CdmEngine::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) { - for (CdmSessionMap::iterator iter = sessions_.begin(); iter != sessions_.end(); ++iter) { iter->second->OnKeyReleaseEvent(key_set_id); diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp index 3c4a3052..5a2a6ec6 100644 --- a/core/src/cdm_session.cpp +++ b/core/src/cdm_session.cpp @@ -29,22 +29,18 @@ CdmSession::CdmSession(const CdmClientPropertySet* cdm_client_property_set) { new DeviceFiles(), cdm_client_property_set); } -CdmSession::CdmSession( - CdmLicense* license_parser, - CryptoSession* crypto_session, - PolicyEngine* policy_engine, - DeviceFiles* file_handle, - const CdmClientPropertySet* cdm_client_property_set) { +CdmSession::CdmSession(CdmLicense* license_parser, + CryptoSession* crypto_session, + PolicyEngine* policy_engine, DeviceFiles* file_handle, + const CdmClientPropertySet* cdm_client_property_set) { Create(license_parser, crypto_session, policy_engine, file_handle, cdm_client_property_set); } -void CdmSession::Create( - CdmLicense* license_parser, - CryptoSession* crypto_session, - PolicyEngine* policy_engine, - DeviceFiles* file_handle, - const CdmClientPropertySet* cdm_client_property_set) { +void CdmSession::Create(CdmLicense* license_parser, + CryptoSession* crypto_session, + PolicyEngine* policy_engine, DeviceFiles* file_handle, + const CdmClientPropertySet* cdm_client_property_set) { // Just return on failures. Failures will be signaled in Init. if (NULL == license_parser) { LOGE("CdmSession::Create: License parser not provided"); @@ -76,7 +72,7 @@ void CdmSession::Create( requested_security_level_ = kLevelDefault; if (NULL != cdm_client_property_set) { if (QUERY_VALUE_SECURITY_LEVEL_L3.compare( - cdm_client_property_set->security_level()) == 0) { + cdm_client_property_set->security_level()) == 0) { requested_security_level_ = kLevel3; } Properties::AddSessionPropertySet(session_id_, cdm_client_property_set); @@ -125,17 +121,14 @@ CdmResponseType CdmSession::RestoreOfflineSession( key_set_id_ = key_set_id; // Retrieve license information from persistent store - if (!file_handle_->Reset(security_level_)) - return UNKNOWN_ERROR; + if (!file_handle_->Reset(security_level_)) return UNKNOWN_ERROR; DeviceFiles::LicenseState license_state; - if (!file_handle_->RetrieveLicense(key_set_id, &license_state, - &offline_init_data_, - &key_request_, &key_response_, - &offline_key_renewal_request_, - &offline_key_renewal_response_, - &offline_release_server_url_)) { + if (!file_handle_->RetrieveLicense( + key_set_id, &license_state, &offline_init_data_, &key_request_, + &key_response_, &offline_key_renewal_request_, + &offline_key_renewal_response_, &offline_release_server_url_)) { LOGE("CdmSession::Init failed to retrieve license. key set id = %s", key_set_id.c_str()); return UNKNOWN_ERROR; @@ -158,9 +151,7 @@ CdmResponseType CdmSession::RestoreOfflineSession( } CdmResponseType CdmSession::RestoreUsageSession( - const CdmKeyMessage& key_request, - const CdmKeyResponse& key_response) { - + const CdmKeyMessage& key_request, const CdmKeyResponse& key_response) { key_request_ = key_request; key_response_ = key_response; @@ -177,7 +168,7 @@ CdmResponseType CdmSession::RestoreUsageSession( CdmResponseType CdmSession::GenerateKeyRequest( const InitializationData& init_data, const CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, - std::string* server_url) { + std::string* server_url, CdmKeySetId* key_set_id) { if (crypto_session_.get() == NULL) { LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session"); return UNKNOWN_ERROR; @@ -189,9 +180,15 @@ CdmResponseType CdmSession::GenerateKeyRequest( } switch (license_type) { - case kLicenseTypeStreaming: is_offline_ = false; break; - case kLicenseTypeOffline: is_offline_ = true; break; - case kLicenseTypeRelease: is_release_ = true; break; + case kLicenseTypeStreaming: + is_offline_ = false; + break; + case kLicenseTypeOffline: + is_offline_ = true; + break; + case kLicenseTypeRelease: + is_release_ = true; + break; default: LOGE("CdmSession::GenerateKeyRequest: unrecognized license type: %ld", license_type); @@ -212,6 +209,10 @@ CdmResponseType CdmSession::GenerateKeyRequest( LOGW("CdmSession::GenerateKeyRequest: init data absent"); return KEY_ERROR; } + if (is_offline_ && !GenerateKeySetId(&key_set_id_)) { + LOGE("CdmSession::GenerateKeyRequest: Unable to generate key set ID"); + return UNKNOWN_ERROR; + } if (!license_parser_->PrepareKeyRequest(init_data, license_type, app_parameters, session_id_, @@ -225,6 +226,7 @@ CdmResponseType CdmSession::GenerateKeyRequest( offline_release_server_url_ = *server_url; } + if (key_set_id) *key_set_id = key_set_id_; return KEY_MESSAGE; } } @@ -260,7 +262,7 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response, if (sts != NO_ERROR) return sts; } - *key_set_id = key_set_id_; + if (key_set_id) *key_set_id = key_set_id_; return KEY_ADDED; } } @@ -332,7 +334,7 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { } if (!is_usage_update_needed_) { is_usage_update_needed_ = - !license_parser_->provider_session_token().empty(); + !license_parser_->provider_session_token().empty(); } } @@ -384,10 +386,9 @@ CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request, // ReleaseKey() - Accept release response and release license. CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) { - CdmResponseType sts = license_parser_->HandleKeyUpdateResponse(false, - key_response); - if (KEY_ADDED != sts) - return sts; + CdmResponseType sts = + license_parser_->HandleKeyUpdateResponse(false, key_response); + if (KEY_ADDED != sts) return sts; if (is_offline_ || !license_parser_->provider_session_token().empty()) { DeleteLicense(); @@ -413,8 +414,7 @@ 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; + if (!file_handle_->Reset(security_level_)) return false; while (key_set_id->empty()) { if (!crypto_session_->GetRandom(random_data.size(), &random_data[0])) @@ -427,13 +427,14 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) { key_set_id->clear(); } } + file_handle_->ReserveLicenseId(*key_set_id); return true; } CdmResponseType CdmSession::StoreLicense() { if (is_offline_) { - if (!GenerateKeySetId(&key_set_id_)) { - LOGE("CdmSession::StoreLicense: Unable to generate key set Id"); + if (key_set_id_.empty()) { + LOGE("CdmSession::StoreLicense: No key set ID"); return UNKNOWN_ERROR; } @@ -472,13 +473,12 @@ CdmResponseType CdmSession::StoreLicense() { } bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) { - if (!file_handle_->Reset(security_level_)) - return false; + 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_, offline_release_server_url_); + key_set_id_, state, offline_init_data_, key_request_, key_response_, + offline_key_renewal_request_, offline_key_renewal_response_, + offline_release_server_url_); } bool CdmSession::DeleteLicense() { @@ -494,7 +494,7 @@ bool CdmSession::DeleteLicense() { return file_handle_->DeleteLicense(key_set_id_); else return file_handle_->DeleteUsageInfo( - license_parser_->provider_session_token()); + license_parser_->provider_session_token()); } bool CdmSession::AttachEventListener(WvCdmEventListener* listener) { diff --git a/core/src/certificate_provisioning.cpp b/core/src/certificate_provisioning.cpp index 6469761f..864ea23d 100644 --- a/core/src/certificate_provisioning.cpp +++ b/core/src/certificate_provisioning.cpp @@ -14,9 +14,9 @@ namespace { // The provisioning server supplies the certificate that is needed // to communicate with the License Server. const std::string kProvisioningServerUrl = - "https://www.googleapis.com/" - "certificateprovisioning/v1/devicecertificates/create" - "?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE"; + "https://www.googleapis.com/" + "certificateprovisioning/v1/devicecertificates/create" + "?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE"; } namespace wvcdm { @@ -41,9 +41,7 @@ using video_widevine_server::sdk::SignedProvisioningMessage; * base64 encoded message */ void CertificateProvisioning::ComposeJsonRequestAsQueryString( - const std::string& message, - CdmProvisioningRequest* request) { - + const std::string& message, CdmProvisioningRequest* request) { // Performs base64 encoding for message std::vector message_vector(message.begin(), message.end()); std::string message_b64 = Base64SafeEncodeNoPad(message_vector); @@ -58,10 +56,8 @@ void CertificateProvisioning::ComposeJsonRequestAsQueryString( * Returns NO_ERROR for success and UNKNOWN_ERROR if fails. */ CdmResponseType CertificateProvisioning::GetProvisioningRequest( - SecurityLevel requested_security_level, - CdmCertificateType cert_type, - const std::string& cert_authority, - CdmProvisioningRequest* request, + SecurityLevel requested_security_level, CdmCertificateType cert_type, + const std::string& cert_authority, CdmProvisioningRequest* request, std::string* default_url) { if (!default_url) { LOGE("GetProvisioningRequest: pointer for returning URL is NULL"); @@ -123,7 +119,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( // Derives signing and encryption keys and constructs signature. std::string request_signature; if (!crypto_session_.PrepareRequest(serialized_message, true, - &request_signature)) { + &request_signature)) { LOGE("GetProvisioningRequest: fails to prepare request"); return UNKNOWN_ERROR; } @@ -152,10 +148,8 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( * Returns true for success and false if fails. */ bool CertificateProvisioning::ParseJsonResponse( - const CdmProvisioningResponse& json_str, - const std::string& start_substr, - const std::string& end_substr, - std::string* result) { + const CdmProvisioningResponse& json_str, const std::string& start_substr, + const std::string& end_substr, std::string* result) { std::string b64_string; size_t start = json_str.find(start_substr); if (start == json_str.npos) { @@ -186,10 +180,8 @@ bool CertificateProvisioning::ParseJsonResponse( * Returns NO_ERROR for success and UNKNOWN_ERROR if fails. */ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( - CdmProvisioningResponse& response, - std::string* cert, + CdmProvisioningResponse& response, std::string* cert, std::string* wrapped_key) { - // Extracts signed response from JSON string, decodes base64 signed response const std::string kMessageStart = "\"signedResponse\": \""; const std::string kMessageEnd = "\""; @@ -220,8 +212,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( error = true; } - if (error) - return UNKNOWN_ERROR; + if (error) return UNKNOWN_ERROR; const std::string& signed_message = signed_response.message(); ProvisioningResponse provisioning_response; @@ -241,12 +232,9 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( const std::string& rsa_key_iv = provisioning_response.device_rsa_key_iv(); const std::string& signature = signed_response.signature(); std::string wrapped_rsa_key; - if (!crypto_session_.RewrapDeviceRSAKey(signed_message, - signature, - nonce, - enc_rsa_key, - rsa_key_iv, - &wrapped_rsa_key)){ + if (!crypto_session_.RewrapDeviceRSAKey(signed_message, signature, nonce, + enc_rsa_key, rsa_key_iv, + &wrapped_rsa_key)) { LOGE("HandleProvisioningResponse: RewrapDeviceRSAKey fails"); return UNKNOWN_ERROR; } diff --git a/core/src/crypto_session.cpp b/core/src/crypto_session.cpp index 75e69b4e..f9b282dc 100644 --- a/core/src/crypto_session.cpp +++ b/core/src/crypto_session.cpp @@ -262,13 +262,19 @@ void CryptoSession::Close() { } } -void CryptoSession::GenerateRequestId(std::string& req_id_str) { +bool CryptoSession::GenerateRequestId(std::string* req_id_str) { LOGV("CryptoSession::GenerateRequestId: Lock"); AutoLock auto_lock(crypto_lock_); - req_id_str = HexEncode(reinterpret_cast(&request_id_base_), - sizeof(request_id_base_)) + - HexEncode(reinterpret_cast(&request_id_index_), - sizeof(request_id_index_)); + if (!req_id_str) { + LOGE("CryptoSession::GenerateRequestId: No output destination provided."); + return false; + } + + *req_id_str = HexEncode(reinterpret_cast(&request_id_base_), + sizeof(request_id_base_)) + + HexEncode(reinterpret_cast(&request_id_index_), + sizeof(request_id_index_)); + return true; } bool CryptoSession::PrepareRequest(const std::string& message, @@ -406,8 +412,10 @@ CdmResponseType CryptoSession::LoadKeys( if (OEMCrypto_SUCCESS == sts) { return KEY_ADDED; } else if (OEMCrypto_ERROR_TOO_MANY_KEYS == sts) { + LOGE("LoadKeys: OEMCrypto_LoadKeys error=%d", sts); return INSUFFICIENT_CRYPTO_RESOURCES; } else { + LOGE("LoadKeys: OEMCrypto_LoadKeys error=%d", sts); return KEY_ERROR; } } @@ -570,8 +578,8 @@ bool CryptoSession::GenerateRsaSignature(const std::string& message, OEMCryptoResult sts = OEMCrypto_GenerateRSASignature( oec_session_id_, reinterpret_cast(message.data()), message.size(), - reinterpret_cast(const_cast(signature->data())), - &length, kSign_RSASSA_PSS); + reinterpret_cast(const_cast(signature->data())), &length, + kSign_RSASSA_PSS); if (OEMCrypto_SUCCESS != sts) { if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { @@ -656,9 +664,26 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { } } +bool CryptoSession::UsageInformationSupport(bool* has_support) { + LOGV("UsageInformationSupport: id=%ld", (uint32_t)oec_session_id_); + if (!initialized_) return false; + + *has_support = OEMCrypto_SupportsUsageTable( + kSecurityLevelL3 == GetSecurityLevel() ? kLevel3 : kLevelDefault); + return true; +} + CdmResponseType CryptoSession::UpdateUsageInformation() { - return (OEMCrypto_UpdateUsageTable() == OEMCrypto_SUCCESS) ? NO_ERROR - : UNKNOWN_ERROR; + LOGV("UpdateUsageInformation: id=%ld", (uint32_t)oec_session_id_); + AutoLock auto_lock(crypto_lock_); + if (!initialized_) return UNKNOWN_ERROR; + + OEMCryptoResult status = OEMCrypto_UpdateUsageTable(); + if (status != OEMCrypto_SUCCESS) { + LOGE("CryptoSession::UsageUsageInformation: error=%ld", status); + return UNKNOWN_ERROR; + } + return NO_ERROR; } CdmResponseType CryptoSession::GenerateUsageReport( @@ -816,6 +841,23 @@ bool CryptoSession::RewrapDeviceRSAKey(const std::string& message, return true; } +bool CryptoSession::GetHdcpCapabilities(OemCryptoHdcpVersion* current_version, + OemCryptoHdcpVersion* max_version) { + LOGV("GetHdcpCapabilities: id=%ld", (uint32_t)oec_session_id_); + if (!initialized_) return UNKNOWN_ERROR; + OEMCrypto_HDCP_Capability current, max; + OEMCryptoResult status = OEMCrypto_GetHDCPCapability(¤t, &max); + + if (OEMCrypto_SUCCESS != status) { + LOGW("OEMCrypto_GetHDCPCapability fails with %d", status); + return false; + } + *current_version = static_cast(current); + *max_version = static_cast(max); + + return true; +} + bool CryptoSession::GetRandom(size_t data_length, uint8_t* random_data) { if (random_data == NULL) { LOGE("CryptoSession::GetRandom: random data destination not provided"); diff --git a/core/src/device_files.cpp b/core/src/device_files.cpp index 29d817d2..082d1e37 100644 --- a/core/src/device_files.cpp +++ b/core/src/device_files.cpp @@ -3,11 +3,11 @@ #include "device_files.h" #if defined(__APPLE__) -# include -# define SHA256 CC_SHA256 -# define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH +#include +#define SHA256 CC_SHA256 +#define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH #else -# include +#include #endif #include @@ -39,13 +39,15 @@ const char* kSecurityLevelPathCompatibilityExclusionList[] = {"ay64.dat"}; size_t kSecurityLevelPathCompatibilityExclusionListSize = sizeof(kSecurityLevelPathCompatibilityExclusionList) / sizeof(*kSecurityLevelPathCompatibilityExclusionList); +// Some platforms cannot store a truly blank file, so we use a W for Widevine. +const char kBlankFileData[] = "W"; bool Hash(const std::string& data, std::string* hash) { if (!hash) return false; hash->resize(SHA256_DIGEST_LENGTH); const unsigned char* input = - reinterpret_cast(data.data()); + reinterpret_cast(data.data()); unsigned char* output = reinterpret_cast(&(*hash)[0]); SHA256(input, data.size(), output); return true; @@ -56,9 +58,10 @@ bool Hash(const std::string& data, std::string* hash) { namespace wvcdm { DeviceFiles::DeviceFiles() - : file_(NULL), security_level_(kSecurityLevelUninitialized), - initialized_(false), test_file_(false) { -} + : file_(NULL), + security_level_(kSecurityLevelUninitialized), + initialized_(false), + test_file_(false) {} DeviceFiles::~DeviceFiles() { if (test_file_) file_.release(); @@ -100,7 +103,7 @@ bool DeviceFiles::StoreCertificate(const std::string& certificate, std::string serialized_file; file.SerializeToString(&serialized_file); - return StoreFile(kCertificateFileName, serialized_file); + return StoreFileWithHash(kCertificateFileName, serialized_file); } bool DeviceFiles::RetrieveCertificate(std::string* certificate, @@ -115,8 +118,7 @@ bool DeviceFiles::RetrieveCertificate(std::string* certificate, } std::string serialized_file; - if (!RetrieveFile(kCertificateFileName, &serialized_file)) - return false; + if (!RetrieveHashedFile(kCertificateFileName, &serialized_file)) return false; video_widevine_client::sdk::File file; if (!file.ParseFromString(serialized_file)) { @@ -189,7 +191,7 @@ bool DeviceFiles::StoreLicense(const std::string& key_set_id, file.SerializeToString(&serialized_file); std::string file_name = key_set_id + kLicenseFileNameExt; - return StoreFile(file_name.c_str(), serialized_file); + return StoreFileWithHash(file_name.c_str(), serialized_file); } bool DeviceFiles::RetrieveLicense(const std::string& key_set_id, @@ -206,7 +208,7 @@ bool DeviceFiles::RetrieveLicense(const std::string& key_set_id, std::string serialized_file; std::string file_name = key_set_id + kLicenseFileNameExt; - if (!RetrieveFile(file_name.c_str(), &serialized_file)) return false; + if (!RetrieveHashedFile(file_name.c_str(), &serialized_file)) return false; video_widevine_client::sdk::File file; if (!file.ParseFromString(serialized_file)) { @@ -310,7 +312,7 @@ bool DeviceFiles::LicenseExists(const std::string& key_set_id) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { - LOGW("DeviceFiles::StoreFile: Unable to get base path"); + LOGW("DeviceFiles::LicenseExists: Unable to get base path"); return false; } path.append(key_set_id); @@ -319,6 +321,16 @@ bool DeviceFiles::LicenseExists(const std::string& key_set_id) { return file_->Exists(path); } +bool DeviceFiles::ReserveLicenseId(const std::string& key_set_id) { + if (!initialized_) { + LOGW("DeviceFiles::ReserveLicenseId: not initialized"); + return false; + } + + std::string file_name = key_set_id + kLicenseFileNameExt; + return StoreFileRaw(file_name.c_str(), kBlankFileData); +} + bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token, const CdmKeyMessage& key_request, const CdmKeyResponse& key_response) { @@ -329,7 +341,7 @@ bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token, std::string serialized_file; video_widevine_client::sdk::File file; - if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) { + if (!RetrieveHashedFile(kUsageInfoFileName, &serialized_file)) { file.set_type(video_widevine_client::sdk::File::USAGE_INFO); file.set_version(video_widevine_client::sdk::File::VERSION_1); } else { @@ -344,13 +356,11 @@ bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token, provider_session->set_token(provider_session_token.data(), provider_session_token.size()); - provider_session->set_license_request(key_request.data(), - key_request.size()); - provider_session->set_license(key_response.data(), - key_response.size()); + provider_session->set_license_request(key_request.data(), key_request.size()); + provider_session->set_license(key_response.data(), key_response.size()); file.SerializeToString(&serialized_file); - return StoreFile(kUsageInfoFileName, serialized_file); + return StoreFileWithHash(kUsageInfoFileName, serialized_file); } bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) { @@ -360,7 +370,7 @@ bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) { } std::string serialized_file; - if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) return false; + if (!RetrieveHashedFile(kUsageInfoFileName, &serialized_file)) return false; video_widevine_client::sdk::File file; if (!file.ParseFromString(serialized_file)) { @@ -372,15 +382,18 @@ bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) { int index = 0; bool found = false; for (; index < usage_info->sessions_size(); ++index) { - if (usage_info->sessions(index).token().compare(provider_session_token) == 0) { + if (usage_info->sessions(index).token().compare(provider_session_token) == + 0) { found = true; break; } } if (!found) { - LOGW("DeviceFiles::DeleteUsageInfo: Unable to find provider session " - "token: %s", b2a_hex(provider_session_token).c_str()); + LOGW( + "DeviceFiles::DeleteUsageInfo: Unable to find provider session " + "token: %s", + b2a_hex(provider_session_token).c_str()); return false; } @@ -392,7 +405,7 @@ bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) { sessions->RemoveLast(); file.SerializeToString(&serialized_file); - return StoreFile(kUsageInfoFileName, serialized_file); + return StoreFileWithHash(kUsageInfoFileName, serialized_file); } bool DeviceFiles::DeleteUsageInfo() { @@ -411,21 +424,22 @@ bool DeviceFiles::DeleteUsageInfo() { return file_->Remove(path); } -bool DeviceFiles::RetrieveUsageInfo(std::vector< - std::pair >* usage_info) { +bool DeviceFiles::RetrieveUsageInfo( + std::vector >* usage_info) { if (!initialized_) { LOGW("DeviceFiles::RetrieveUsageInfo: not initialized"); return false; } if (NULL == usage_info) { - LOGW("DeviceFiles::RetrieveUsageInfo: license destination not " + LOGW( + "DeviceFiles::RetrieveUsageInfo: license destination not " "provided"); return false; } std::string serialized_file; - if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) { + if (!RetrieveHashedFile(kUsageInfoFileName, &serialized_file)) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { return false; @@ -457,22 +471,22 @@ bool DeviceFiles::RetrieveUsageInfo(std::vector< return true; } -bool DeviceFiles::StoreFile(const char* name, - const std::string& serialized_file) { +bool DeviceFiles::StoreFileWithHash(const char* name, + const std::string& serialized_file) { if (!file_.get()) { - LOGW("DeviceFiles::StoreFile: Invalid file handle"); + LOGW("DeviceFiles::StoreFileWithHash: Invalid file handle"); return false; } if (!name) { - LOGW("DeviceFiles::StoreFile: Unspecified file name parameter"); + LOGW("DeviceFiles::StoreFileWithHash: Unspecified file name parameter"); return false; } // calculate SHA hash std::string hash; if (!Hash(serialized_file, &hash)) { - LOGW("DeviceFiles::StoreFile: Hash computation failed"); + LOGW("DeviceFiles::StoreFileWithHash: Hash computation failed"); return false; } @@ -484,9 +498,24 @@ bool DeviceFiles::StoreFile(const char* name, std::string serialized_hash_file; hash_file.SerializeToString(&serialized_hash_file); + return StoreFileRaw(name, serialized_hash_file); +} + +bool DeviceFiles::StoreFileRaw(const char* name, + const std::string& serialized_file) { + if (!file_.get()) { + LOGW("DeviceFiles::StoreFileRaw: Invalid file handle"); + return false; + } + + if (!name) { + LOGW("DeviceFiles::StoreFileRaw: Unspecified file name parameter"); + return false; + } + std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { - LOGW("DeviceFiles::StoreFile: Unable to get base path"); + LOGW("DeviceFiles::StoreFileRaw: Unable to get base path"); return false; } @@ -497,59 +526,62 @@ bool DeviceFiles::StoreFile(const char* name, path += name; if (!file_->Open(path, File::kCreate | File::kTruncate | File::kBinary)) { - LOGW("DeviceFiles::StoreFile: File open failed: %s", path.c_str()); + LOGW("DeviceFiles::StoreFileRaw: File open failed: %s", path.c_str()); return false; } - ssize_t bytes = file_->Write(serialized_hash_file.data(), - serialized_hash_file.size()); + ssize_t bytes = file_->Write(serialized_file.data(), serialized_file.size()); file_->Close(); - if (bytes != static_cast(serialized_hash_file.size())) { - LOGW("DeviceFiles::StoreFile: write failed: (actual: %d, expected: %d)", - bytes, - serialized_hash_file.size()); + if (bytes != static_cast(serialized_file.size())) { + LOGW( + "DeviceFiles::StoreFileRaw: write failed: (actual: %d, " + "expected: %d)", + bytes, serialized_file.size()); return false; } - LOGV("DeviceFiles::StoreFile: success: %s (%db)", - path.c_str(), - serialized_hash_file.size()); + LOGV("DeviceFiles::StoreFileRaw: success: %s (%db)", path.c_str(), + serialized_file.size()); return true; } -bool DeviceFiles::RetrieveFile(const char* name, std::string* serialized_file) { +bool DeviceFiles::RetrieveHashedFile(const char* name, + std::string* serialized_file) { if (!file_.get()) { - LOGW("DeviceFiles::RetrieveFile: Invalid file handle"); + LOGW("DeviceFiles::RetrieveHashedFile: Invalid file handle"); return false; } if (!name) { - LOGW("DeviceFiles::RetrieveFile: Unspecified file name parameter"); + LOGW("DeviceFiles::RetrieveHashedFile: Unspecified file name parameter"); return false; } if (!serialized_file) { - LOGW("DeviceFiles::RetrieveFile: Unspecified serialized_file parameter"); + LOGW( + "DeviceFiles::RetrieveHashedFile: Unspecified serialized_file " + "parameter"); return false; } std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { - LOGW("DeviceFiles::StoreFile: Unable to get base path"); + LOGW("DeviceFiles::StoreFileWithHash: Unable to get base path"); return false; } path += name; if (!file_->Exists(path)) { - LOGW("DeviceFiles::RetrieveFile: %s does not exist", path.c_str()); + LOGW("DeviceFiles::RetrieveHashedFile: %s does not exist", path.c_str()); return false; } ssize_t bytes = file_->FileSize(path); if (bytes <= 0) { - LOGW("DeviceFiles::RetrieveFile: File size invalid: %s", path.c_str()); + LOGW("DeviceFiles::RetrieveHashedFile: File size invalid: %s", + path.c_str()); return false; } @@ -563,27 +595,27 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* serialized_file) { file_->Close(); if (bytes != static_cast(serialized_hash_file.size())) { - LOGW("DeviceFiles::RetrieveFile: read failed"); + LOGW("DeviceFiles::RetrieveHashedFile: read failed"); return false; } - LOGV("DeviceFiles::RetrieveFile: success: %s (%db)", path.c_str(), + LOGV("DeviceFiles::RetrieveHashedFile: success: %s (%db)", path.c_str(), serialized_hash_file.size()); HashedFile hash_file; if (!hash_file.ParseFromString(serialized_hash_file)) { - LOGW("DeviceFiles::RetrieveFile: Unable to parse hash file"); + LOGW("DeviceFiles::RetrieveHashedFile: Unable to parse hash file"); return false; } std::string hash; if (!Hash(hash_file.file(), &hash)) { - LOGW("DeviceFiles::RetrieveFile: Hash computation failed"); + LOGW("DeviceFiles::RetrieveHashedFile: Hash computation failed"); return false; } if (hash.compare(hash_file.hash())) { - LOGW("DeviceFiles::RetrieveFile: Hash mismatch"); + LOGW("DeviceFiles::RetrieveHashedFile: Hash mismatch"); return false; } @@ -663,9 +695,9 @@ std::string DeviceFiles::GetLicenseFileNameExtension() { return kLicenseFileNameExt; } -std::string DeviceFiles::GetUsageInfoFileName() { - return kUsageInfoFileName; -} +std::string DeviceFiles::GetUsageInfoFileName() { return kUsageInfoFileName; } + +std::string DeviceFiles::GetBlankFileData() { return kBlankFileData; } void DeviceFiles::SetTestFile(File* file) { file_.reset(file); diff --git a/core/src/initialization_data.cpp b/core/src/initialization_data.cpp index c12fe8b3..9606e3bd 100644 --- a/core/src/initialization_data.cpp +++ b/core/src/initialization_data.cpp @@ -14,12 +14,10 @@ namespace wvcdm { InitializationData::InitializationData(const std::string& type, const CdmInitData& data) : type_(type), is_cenc_(false), is_webm_(false) { - if (type == ISO_BMFF_VIDEO_MIME_TYPE || - type == ISO_BMFF_AUDIO_MIME_TYPE || + 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 || + } else if (type == WEBM_VIDEO_MIME_TYPE || type == WEBM_AUDIO_MIME_TYPE || type == WEBM_INIT_DATA_FORMAT) { is_webm_ = true; } @@ -35,10 +33,10 @@ InitializationData::InitializationData(const std::string& type, // Parse a blob of multiple concatenated PSSH atoms to extract the first // Widevine PSSH. -bool InitializationData::ExtractWidevinePssh( - const CdmInitData& init_data, CdmInitData* output) { - BufferReader reader( - reinterpret_cast(init_data.data()), init_data.length()); +bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, + CdmInitData* output) { + BufferReader reader(reinterpret_cast(init_data.data()), + init_data.length()); // TODO(kqyang): Extracted from an actual init_data; // Need to find out where it comes from. @@ -90,12 +88,10 @@ bool InitializationData::ExtractWidevinePssh( return false; } - if (memcmp(&system_id[0], kWidevineSystemId, - sizeof(kWidevineSystemId))) { + if (memcmp(&system_id[0], kWidevineSystemId, sizeof(kWidevineSystemId))) { // skip the remaining contents of the atom, // after size field, atom name, flags and system id - if (!reader.SkipBytes( - size - 4 - 4 - 4 - sizeof(kWidevineSystemId))) { + if (!reader.SkipBytes(size - 4 - 4 - 4 - sizeof(kWidevineSystemId))) { LOGW("CdmEngine::ExtractWidevinePssh: Unable to rest of PSSH atom"); return false; } diff --git a/core/src/license.cpp b/core/src/license.cpp index cf580c23..0a60555a 100644 --- a/core/src/license.cpp +++ b/core/src/license.cpp @@ -23,64 +23,48 @@ std::string kDeviceNameKey = "device_name"; std::string kProductNameKey = "product_name"; std::string kBuildInfoKey = "build_info"; std::string kDeviceIdKey = "device_id"; -std::string kOemCryptoApiVersion = "oemcrypto_api_version"; 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, - 0xf2, 0xc3, 0x5e, 0x9b, 0xf2, 0xe7, 0x4d, 0x23, - 0xb1, 0x10, 0xdb, 0x87, 0x65, 0xdf, 0xdc, 0xfb, - 0x9f, 0x35, 0xa0, 0x57, 0x03, 0x53, 0x4c, 0xf6, - 0x6d, 0x35, 0x7d, 0xa6, 0x78, 0xdb, 0xb3, 0x36, - 0xd2, 0x3f, 0x9c, 0x40, 0xa9, 0x95, 0x26, 0x72, - 0x7f, 0xb8, 0xbe, 0x66, 0xdf, 0xc5, 0x21, 0x98, - 0x78, 0x15, 0x16, 0x68, 0x5d, 0x2f, 0x46, 0x0e, - 0x43, 0xcb, 0x8a, 0x84, 0x39, 0xab, 0xfb, 0xb0, - 0x35, 0x80, 0x22, 0xbe, 0x34, 0x23, 0x8b, 0xab, - 0x53, 0x5b, 0x72, 0xec, 0x4b, 0xb5, 0x48, 0x69, - 0x53, 0x3e, 0x47, 0x5f, 0xfd, 0x09, 0xfd, 0xa7, - 0x76, 0x13, 0x8f, 0x0f, 0x92, 0xd6, 0x4c, 0xdf, - 0xae, 0x76, 0xa9, 0xba, 0xd9, 0x22, 0x10, 0xa9, - 0x9d, 0x71, 0x45, 0xd6, 0xd7, 0xe1, 0x19, 0x25, - 0x85, 0x9c, 0x53, 0x9a, 0x97, 0xeb, 0x84, 0xd7, - 0xcc, 0xa8, 0x88, 0x82, 0x20, 0x70, 0x26, 0x20, - 0xfd, 0x7e, 0x40, 0x50, 0x27, 0xe2, 0x25, 0x93, - 0x6f, 0xbc, 0x3e, 0x72, 0xa0, 0xfa, 0xc1, 0xbd, - 0x29, 0xb4, 0x4d, 0x82, 0x5c, 0xc1, 0xb4, 0xcb, - 0x9c, 0x72, 0x7e, 0xb0, 0xe9, 0x8a, 0x17, 0x3e, - 0x19, 0x63, 0xfc, 0xfd, 0x82, 0x48, 0x2b, 0xb7, - 0xb2, 0x33, 0xb9, 0x7d, 0xec, 0x4b, 0xba, 0x89, - 0x1f, 0x27, 0xb8, 0x9b, 0x88, 0x48, 0x84, 0xaa, - 0x18, 0x92, 0x0e, 0x65, 0xf5, 0xc8, 0x6c, 0x11, - 0xff, 0x6b, 0x36, 0xe4, 0x74, 0x34, 0xca, 0x8c, - 0x33, 0xb1, 0xf9, 0xb8, 0x8e, 0xb4, 0xe6, 0x12, - 0xe0, 0x02, 0x98, 0x79, 0x52, 0x5e, 0x45, 0x33, - 0xff, 0x11, 0xdc, 0xeb, 0xc3, 0x53, 0xba, 0x7c, - 0x60, 0x1a, 0x11, 0x3d, 0x00, 0xfb, 0xd2, 0xb7, - 0xaa, 0x30, 0xfa, 0x4f, 0x5e, 0x48, 0x77, 0x5b, - 0x17, 0xdc, 0x75, 0xef, 0x6f, 0xd2, 0x19, 0x6d, - 0xdc, 0xbe, 0x7f, 0xb0, 0x78, 0x8f, 0xdc, 0x82, - 0x60, 0x4c, 0xbf, 0xe4, 0x29, 0x06, 0x5e, 0x69, - 0x8c, 0x39, 0x13, 0xad, 0x14, 0x25, 0xed, 0x19, - 0xb2, 0xf2, 0x9f, 0x01, 0x82, 0x0d, 0x56, 0x44, - 0x88, 0xc8, 0x35, 0xec, 0x1f, 0x11, 0xb3, 0x24, - 0xe0, 0x59, 0x0d, 0x37, 0xe4, 0x47, 0x3c, 0xea, - 0x4b, 0x7f, 0x97, 0x31, 0x1c, 0x81, 0x7c, 0x94, - 0x8a, 0x4c, 0x7d, 0x68, 0x15, 0x84, 0xff, 0xa5, - 0x08, 0xfd, 0x18, 0xe7, 0xe7, 0x2b, 0xe4, 0x47, - 0x27, 0x12, 0x11, 0xb8, 0x23, 0xec, 0x58, 0x93, - 0x3c, 0xac, 0x12, 0xd2, 0x88, 0x6d, 0x41, 0x3d, - 0xc5, 0xfe, 0x1c, 0xdc, 0xb9, 0xf8, 0xd4, 0x51, - 0x3e, 0x07, 0xe5, 0x03, 0x6f, 0xa7, 0x12, 0xe8, - 0x12, 0xf7, 0xb5, 0xce, 0xa6, 0x96, 0x55, 0x3f, - 0x78, 0xb4, 0x64, 0x82, 0x50, 0xd2, 0x33, 0x5f, - 0x91, 0x02, 0x03, 0x01, 0x00, 0x01}; + 0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01, 0x81, 0x00, 0xb4, 0xfe, 0x39, + 0xc3, 0x65, 0x90, 0x03, 0xdb, 0x3c, 0x11, 0x97, 0x09, 0xe8, 0x68, 0xcd, + 0xf2, 0xc3, 0x5e, 0x9b, 0xf2, 0xe7, 0x4d, 0x23, 0xb1, 0x10, 0xdb, 0x87, + 0x65, 0xdf, 0xdc, 0xfb, 0x9f, 0x35, 0xa0, 0x57, 0x03, 0x53, 0x4c, 0xf6, + 0x6d, 0x35, 0x7d, 0xa6, 0x78, 0xdb, 0xb3, 0x36, 0xd2, 0x3f, 0x9c, 0x40, + 0xa9, 0x95, 0x26, 0x72, 0x7f, 0xb8, 0xbe, 0x66, 0xdf, 0xc5, 0x21, 0x98, + 0x78, 0x15, 0x16, 0x68, 0x5d, 0x2f, 0x46, 0x0e, 0x43, 0xcb, 0x8a, 0x84, + 0x39, 0xab, 0xfb, 0xb0, 0x35, 0x80, 0x22, 0xbe, 0x34, 0x23, 0x8b, 0xab, + 0x53, 0x5b, 0x72, 0xec, 0x4b, 0xb5, 0x48, 0x69, 0x53, 0x3e, 0x47, 0x5f, + 0xfd, 0x09, 0xfd, 0xa7, 0x76, 0x13, 0x8f, 0x0f, 0x92, 0xd6, 0x4c, 0xdf, + 0xae, 0x76, 0xa9, 0xba, 0xd9, 0x22, 0x10, 0xa9, 0x9d, 0x71, 0x45, 0xd6, + 0xd7, 0xe1, 0x19, 0x25, 0x85, 0x9c, 0x53, 0x9a, 0x97, 0xeb, 0x84, 0xd7, + 0xcc, 0xa8, 0x88, 0x82, 0x20, 0x70, 0x26, 0x20, 0xfd, 0x7e, 0x40, 0x50, + 0x27, 0xe2, 0x25, 0x93, 0x6f, 0xbc, 0x3e, 0x72, 0xa0, 0xfa, 0xc1, 0xbd, + 0x29, 0xb4, 0x4d, 0x82, 0x5c, 0xc1, 0xb4, 0xcb, 0x9c, 0x72, 0x7e, 0xb0, + 0xe9, 0x8a, 0x17, 0x3e, 0x19, 0x63, 0xfc, 0xfd, 0x82, 0x48, 0x2b, 0xb7, + 0xb2, 0x33, 0xb9, 0x7d, 0xec, 0x4b, 0xba, 0x89, 0x1f, 0x27, 0xb8, 0x9b, + 0x88, 0x48, 0x84, 0xaa, 0x18, 0x92, 0x0e, 0x65, 0xf5, 0xc8, 0x6c, 0x11, + 0xff, 0x6b, 0x36, 0xe4, 0x74, 0x34, 0xca, 0x8c, 0x33, 0xb1, 0xf9, 0xb8, + 0x8e, 0xb4, 0xe6, 0x12, 0xe0, 0x02, 0x98, 0x79, 0x52, 0x5e, 0x45, 0x33, + 0xff, 0x11, 0xdc, 0xeb, 0xc3, 0x53, 0xba, 0x7c, 0x60, 0x1a, 0x11, 0x3d, + 0x00, 0xfb, 0xd2, 0xb7, 0xaa, 0x30, 0xfa, 0x4f, 0x5e, 0x48, 0x77, 0x5b, + 0x17, 0xdc, 0x75, 0xef, 0x6f, 0xd2, 0x19, 0x6d, 0xdc, 0xbe, 0x7f, 0xb0, + 0x78, 0x8f, 0xdc, 0x82, 0x60, 0x4c, 0xbf, 0xe4, 0x29, 0x06, 0x5e, 0x69, + 0x8c, 0x39, 0x13, 0xad, 0x14, 0x25, 0xed, 0x19, 0xb2, 0xf2, 0x9f, 0x01, + 0x82, 0x0d, 0x56, 0x44, 0x88, 0xc8, 0x35, 0xec, 0x1f, 0x11, 0xb3, 0x24, + 0xe0, 0x59, 0x0d, 0x37, 0xe4, 0x47, 0x3c, 0xea, 0x4b, 0x7f, 0x97, 0x31, + 0x1c, 0x81, 0x7c, 0x94, 0x8a, 0x4c, 0x7d, 0x68, 0x15, 0x84, 0xff, 0xa5, + 0x08, 0xfd, 0x18, 0xe7, 0xe7, 0x2b, 0xe4, 0x47, 0x27, 0x12, 0x11, 0xb8, + 0x23, 0xec, 0x58, 0x93, 0x3c, 0xac, 0x12, 0xd2, 0x88, 0x6d, 0x41, 0x3d, + 0xc5, 0xfe, 0x1c, 0xdc, 0xb9, 0xf8, 0xd4, 0x51, 0x3e, 0x07, 0xe5, 0x03, + 0x6f, 0xa7, 0x12, 0xe8, 0x12, 0xf7, 0xb5, 0xce, 0xa6, 0x96, 0x55, 0x3f, + 0x78, 0xb4, 0x64, 0x82, 0x50, 0xd2, 0x33, 0x5f, 0x91, 0x02, 0x03, 0x01, + 0x00, 0x01}; } namespace wvcdm { // Protobuf generated classes. using video_widevine_server::sdk::ClientIdentification; +using video_widevine_server::sdk::ClientIdentification_ClientCapabilities; using video_widevine_server::sdk::ClientIdentification_NameValue; using video_widevine_server::sdk::DeviceCertificate; using video_widevine_server::sdk::EncryptedClientIdentification; @@ -141,6 +125,19 @@ static std::vector ExtractContentKeys(const License& license) { return key_array; } +CdmLicense::CdmLicense() + : session_(NULL), initialized_(false), clock_(new Clock()) {} + +CdmLicense::CdmLicense(Clock* clock) : session_(NULL), initialized_(false) { + if (NULL == clock) { + LOGE("CdmLicense::CdmLicense: clock parameter not provided"); + return; + } + clock_.reset(clock); +} + +CdmLicense::~CdmLicense() {} + bool CdmLicense::Init(const std::string& token, CryptoSession* session, PolicyEngine* policy_engine) { if (token.size() == 0) { @@ -207,7 +204,7 @@ bool CdmLicense::PrepareKeyRequest(const InitializationData& init_data, } std::string request_id; - session_->GenerateRequestId(request_id); + session_->GenerateRequestId(&request_id); LicenseRequest license_request; ClientIdentification* client_id = license_request.mutable_client_id(); @@ -261,11 +258,54 @@ bool CdmLicense::PrepareKeyRequest(const InitializationData& init_data, client_info->set_name(kDeviceIdKey); client_info->set_value(value); } + + ClientIdentification_ClientCapabilities* client_capabilities = + client_id->mutable_client_capabilities(); + bool supports_usage_information; + if (session_->UsageInformationSupport(&supports_usage_information)) { + client_capabilities->set_session_token(supports_usage_information); + } + + CryptoSession::OemCryptoHdcpVersion current_version, max_version; + if (session_->GetHdcpCapabilities(¤t_version, &max_version)) { + switch (max_version) { + case CryptoSession::kOemCryptoHdcpNotSupported: + client_capabilities->set_max_hdcp_version( + video_widevine_server::sdk:: + ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_NONE); + break; + case CryptoSession::kOemCryptoHdcpVersion1: + client_capabilities->set_max_hdcp_version( + video_widevine_server::sdk:: + ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V1); + break; + case CryptoSession::kOemCryptoHdcpVersion2: + client_capabilities->set_max_hdcp_version( + video_widevine_server::sdk:: + ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2); + break; + case CryptoSession::kOemCryptoHdcpVersion2_1: + client_capabilities->set_max_hdcp_version( + video_widevine_server::sdk:: + ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_1); + break; + case CryptoSession::kOemCryptoHdcpVersion2_2: + client_capabilities->set_max_hdcp_version( + video_widevine_server::sdk:: + ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_2); + break; + case CryptoSession::kOemCryptoNoHdcpDeviceAttached: + default: + LOGW( + "CdmLicense::PrepareKeyRequest: unexpected HDCP max capability " + "version %d", + max_version); + } + } + uint32_t version = 0; if (session_->GetApiVersion(&version)) { - client_info = client_id->add_client_info(); - client_info->set_name(kOemCryptoApiVersion); - client_info->set_value(UintToString(version)); + client_capabilities->set_oem_crypto_api_version(version); } if (privacy_mode_enabled) { @@ -362,8 +402,7 @@ bool CdmLicense::PrepareKeyRequest(const InitializationData& init_data, license_request.set_type(LicenseRequest::NEW); - Clock clock; - license_request.set_request_time(clock.GetCurrentTime()); + license_request.set_request_time(clock_->GetCurrentTime()); // Get/set the nonce. This value will be reflected in the Key Control Block // of the license response. @@ -430,8 +469,7 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, else license_request.set_type(LicenseRequest::RELEASE); - Clock clock; - license_request.set_request_time(clock.GetCurrentTime()); + license_request.set_request_time(clock_->GetCurrentTime()); LicenseRequest_ContentIdentification_ExistingLicense* current_license = license_request.mutable_content_id()->mutable_license(); @@ -515,8 +553,9 @@ CdmResponseType CdmLicense::HandleKeyResponse( case SignedMessage::ERROR_RESPONSE: return HandleKeyErrorResponse(signed_response); default: - LOGE("CdmLicense::HandleKeyResponse: unrecognized signed message type: %d" - , signed_response.type()); + LOGE( + "CdmLicense::HandleKeyResponse: unrecognized signed message type: %d", + signed_response.type()); return KEY_ERROR; } @@ -579,18 +618,14 @@ CdmResponseType CdmLicense::HandleKeyResponse( policy_engine_->SetLicense(license); - CdmResponseType resp = session_->LoadKeys(signed_response.msg(), - signed_response.signature(), - mac_key_iv, - mac_key, - key_array, - provider_session_token_); + CdmResponseType resp = session_->LoadKeys( + signed_response.msg(), signed_response.signature(), mac_key_iv, mac_key, + key_array, provider_session_token_); if (KEY_ADDED == resp) { loaded_keys_.clear(); for (std::vector::iterator it = key_array.begin(); - it != key_array.end(); - ++it) { + it != key_array.end(); ++it) { loaded_keys_.insert(it->key_id()); } } @@ -642,10 +677,9 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( if (!license.id().has_provider_session_token()) return KEY_ADDED; provider_session_token_ = license.id().provider_session_token(); - CdmResponseType status = - session_->ReleaseUsageInformation(signed_response.msg(), - signed_response.signature(), - provider_session_token_); + CdmResponseType status = session_->ReleaseUsageInformation( + signed_response.msg(), signed_response.signature(), + provider_session_token_); return (NO_ERROR == status) ? KEY_ADDED : status; } @@ -668,7 +702,6 @@ bool CdmLicense::RestoreOfflineLicense( const CdmKeyMessage& license_request, const CdmKeyResponse& license_response, const CdmKeyResponse& license_renewal_response) { - if (license_request.empty() || license_response.empty()) { LOGE( "CdmLicense::RestoreOfflineLicense: key_request or response empty: " @@ -712,7 +745,6 @@ bool CdmLicense::RestoreOfflineLicense( bool CdmLicense::RestoreUsageLicense(const CdmKeyMessage& license_request, const CdmKeyResponse& license_response) { - if (license_request.empty() || license_response.empty()) { LOGE( "CdmLicense::RestoreUsageLicense: key_request or response empty: %u %u", @@ -738,14 +770,16 @@ bool CdmLicense::RestoreUsageLicense(const CdmKeyMessage& license_request, SignedMessage signed_response; if (!signed_response.ParseFromString(license_response)) { - LOGE("CdmLicense::RestoreUsageLicense: unable to parse signed license" + LOGE( + "CdmLicense::RestoreUsageLicense: unable to parse signed license" " response"); return false; } if (SignedMessage::LICENSE != signed_response.type()) { - LOGE("CdmLicense::RestoreUsageLicense: unrecognized signed message type: %d" - , signed_response.type()); + LOGE( + "CdmLicense::RestoreUsageLicense: unrecognized signed message type: %d", + signed_response.type()); return false; } @@ -809,7 +843,6 @@ bool CdmLicense::PrepareServiceCertificateRequest(CdmKeyMessage* signed_request, CdmResponseType CdmLicense::HandleServiceCertificateResponse( const video_widevine_server::sdk::SignedMessage& signed_response) { - SignedDeviceCertificate signed_service_certificate; if (!signed_service_certificate.ParseFromString(signed_response.msg())) { LOGE( @@ -862,7 +895,6 @@ CdmResponseType CdmLicense::HandleServiceCertificateResponse( CdmResponseType CdmLicense::HandleKeyErrorResponse( const SignedMessage& signed_message) { - LicenseError license_error; if (!license_error.ParseFromString(signed_message.msg())) { LOGE("CdmLicense::HandleKeyErrorResponse: Unable to parse license error"); @@ -882,7 +914,7 @@ CdmResponseType CdmLicense::HandleKeyErrorResponse( } } -template +template bool CdmLicense::PrepareContentId(const CdmLicenseType license_type, const std::string& request_id, T* content_id) { @@ -891,8 +923,7 @@ bool CdmLicense::PrepareContentId(const CdmLicenseType license_type, content_id->set_license_type(video_widevine_server::sdk::OFFLINE); break; case kLicenseTypeStreaming: - content_id->set_license_type( - video_widevine_server::sdk::STREAMING); + content_id->set_license_type(video_widevine_server::sdk::STREAMING); break; default: LOGD("CdmLicense::PrepareKeyRequest: Unknown license type = %d", diff --git a/core/src/license_protocol.proto b/core/src/license_protocol.proto index 6dec3bfd..163549a7 100644 --- a/core/src/license_protocol.proto +++ b/core/src/license_protocol.proto @@ -287,12 +287,6 @@ message SignedMessage { // This message is used to pass optional data on initial license issuance. message SessionInit { - enum ReplayControl { - NO_SESSION_USAGE = 0; - NONCE_REQUIRED_AND_NEW_SESSION_USAGE = 1; - NONCE_REQUIRED_OR_EXISTING_SESSION_USAGE = 2; - } - optional bytes session_id = 1; optional bytes purchase_id = 2; // master_signing_key should be 128 bits in length. @@ -312,8 +306,6 @@ message SessionInit { // LicenseIdentfication::provider_session_token, and sent back in all // license renewal and release requests for the session thereafter. optional bytes provider_session_token = 7; - // Replay control indicator which will be encoded into V9+ KeyControl blocks. - optional ReplayControl replay_control = 8 [default = NO_SESSION_USAGE]; } // This message is used by the server to preserve and restore session state. @@ -415,6 +407,7 @@ message ClientIdentification { optional bool session_token = 2 [default = false]; optional bool video_resolution_constraints = 3 [default = false]; optional HdcpVersion max_hdcp_version = 4 [default = HDCP_NONE]; + optional uint32 oem_crypto_api_version = 5; } // Type of factory-provisioned device root of trust. Optional. diff --git a/core/src/oemcrypto_adapter_static.cpp b/core/src/oemcrypto_adapter_static.cpp index 77dabd6e..e88d7521 100644 --- a/core/src/oemcrypto_adapter_static.cpp +++ b/core/src/oemcrypto_adapter_static.cpp @@ -43,4 +43,8 @@ const char* OEMCrypto_SecurityLevel(SecurityLevel level) { return ::OEMCrypto_SecurityLevel(); } +bool OEMCrypto_SupportsUsageTable(SecurityLevel level) { + return ::OEMCrypto_SupportsUsageTable(); +} + }; // namespace wvcdm diff --git a/core/src/oemcrypto_adapter_static_v8.cpp b/core/src/oemcrypto_adapter_static_v8.cpp index e631d05b..0a151b39 100644 --- a/core/src/oemcrypto_adapter_static_v8.cpp +++ b/core/src/oemcrypto_adapter_static_v8.cpp @@ -15,25 +15,15 @@ #include "OEMCryptoCENC.h" #include "oemcrypto_adapter.h" -extern "C" -OEMCryptoResult OEMCrypto_LoadKeys_V8(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); - -extern "C" -OEMCryptoResult OEMCrypto_GenerateRSASignature_V8(OEMCrypto_SESSION session, - const uint8_t* message, - size_t message_length, - uint8_t* signature, - size_t *signature_length); - +extern "C" OEMCryptoResult OEMCrypto_LoadKeys_V8( + 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); +extern "C" OEMCryptoResult OEMCrypto_GenerateRSASignature_V8( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + uint8_t* signature, size_t* signature_length); namespace wvcdm { @@ -70,12 +60,16 @@ const char* OEMCrypto_SecurityLevel(SecurityLevel level) { return ::OEMCrypto_SecurityLevel(); } +bool OEMCrypto_SupportsUsageTable(SecurityLevel level) { + return ::OEMCrypto_SupportsUsageTable(); +} + 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_key_iv, const uint8_t* enc_mac_key, size_t num_keys, - const OEMCrypto_KeyObject* key_array, - const uint8_t* pst, size_t pst_length) { + const OEMCrypto_KeyObject* key_array, const uint8_t* pst, + size_t pst_length) { return OEMCrypto_LoadKeys_V8(session, message, message_length, signature, signature_length, enc_mac_key_iv, enc_mac_key, num_keys, key_array); @@ -83,45 +77,40 @@ extern "C" OEMCryptoResult OEMCrypto_LoadKeys( extern "C" OEMCryptoResult OEMCrypto_GenerateRSASignature( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - uint8_t* signature, size_t* signature_length, RSA_Padding_Scheme padding_scheme) { + uint8_t* signature, size_t* signature_length, + RSA_Padding_Scheme padding_scheme) { return OEMCrypto_GenerateRSASignature_V8(session, message, message_length, - signature, signature_length); + signature, signature_length); } -extern "C" -OEMCryptoResult OEMCrypto_GetHDCPCapability(OEMCrypto_HDCP_Capability *current, - OEMCrypto_HDCP_Capability *maximum) { +extern "C" OEMCryptoResult OEMCrypto_GetHDCPCapability( + OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* maximum) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } -extern "C" bool OEMCrypto_SupportsUsageTable() { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; -} +extern "C" bool OEMCrypto_SupportsUsageTable() { return false; } extern "C" OEMCryptoResult OEMCrypto_UpdateUsageTable() { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } -extern "C" OEMCryptoResult OEMCrypto_DeactivateUsageEntry(const uint8_t *pst, +extern "C" OEMCryptoResult OEMCrypto_DeactivateUsageEntry(const uint8_t* pst, size_t pst_length) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } extern "C" OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session, - const uint8_t *pst, + const uint8_t* pst, size_t pst_length, - OEMCrypto_PST_Report *buffer, - size_t *buffer_length) { + OEMCrypto_PST_Report* buffer, + size_t* buffer_length) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } -extern "C" OEMCryptoResult OEMCrypto_DeleteUsageEntry(OEMCrypto_SESSION session, - const uint8_t* pst, - size_t pst_length, - const uint8_t *message, - size_t message_length, - const uint8_t *signature, - size_t signature_length) { +extern "C" OEMCryptoResult OEMCrypto_DeleteUsageEntry( + OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length, + const uint8_t* message, size_t message_length, const uint8_t* signature, + size_t signature_length) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } }; // namespace wvcdm diff --git a/core/src/policy_engine.cpp b/core/src/policy_engine.cpp index 9fdf97a8..c85772d4 100644 --- a/core/src/policy_engine.cpp +++ b/core/src/policy_engine.cpp @@ -16,17 +16,12 @@ namespace wvcdm { -PolicyEngine::PolicyEngine() { - Init(new Clock()); -} +PolicyEngine::PolicyEngine() { Init(new Clock()); } -PolicyEngine::PolicyEngine(Clock* clock) { - Init(clock); -} +PolicyEngine::PolicyEngine(Clock* clock) { Init(clock); } PolicyEngine::~PolicyEngine() { - if (clock_) - delete clock_; + if (clock_) delete clock_; } void PolicyEngine::Init(Clock* clock) { @@ -45,7 +40,7 @@ void PolicyEngine::OnTimerEvent(bool* event_occurred, CdmEventType* event) { // License expiration trumps all. if ((IsLicenseDurationExpired(current_time) || - IsPlaybackDurationExpired(current_time)) && + IsPlaybackDurationExpired(current_time)) && license_state_ != kLicenseStateExpired) { license_state_ = kLicenseStateExpired; can_decrypt_ = false; @@ -59,8 +54,7 @@ void PolicyEngine::OnTimerEvent(bool* event_occurred, CdmEventType* event) { // Test to determine if renewal should be attempted. switch (license_state_) { case kLicenseStateCanPlay: { - if (IsRenewalDelayExpired(current_time)) - renewal_needed = true; + if (IsRenewalDelayExpired(current_time)) renewal_needed = true; break; } @@ -70,8 +64,7 @@ void PolicyEngine::OnTimerEvent(bool* event_occurred, CdmEventType* event) { } case kLicenseStateWaitingLicenseUpdate: { - if (IsRenewalRetryIntervalExpired(current_time)) - renewal_needed = true; + if (IsRenewalRetryIntervalExpired(current_time)) renewal_needed = true; break; } @@ -112,8 +105,7 @@ void PolicyEngine::SetLicense( void PolicyEngine::UpdateLicense( const video_widevine_server::sdk::License& license) { - if (!license.has_policy()) - return; + if (!license.has_policy()) return; if (kLicenseStateExpired == license_state_) { LOGD("PolicyEngine::UpdateLicense: updating an expired license"); @@ -147,8 +139,7 @@ void PolicyEngine::UpdateLicense( policy_max_duration_seconds_ = policy_.rental_duration_seconds(); if ((policy_.license_duration_seconds() > 0) && - ((policy_.license_duration_seconds() < - policy_max_duration_seconds_) || + ((policy_.license_duration_seconds() < policy_max_duration_seconds_) || policy_max_duration_seconds_ == 0)) { policy_max_duration_seconds_ = policy_.license_duration_seconds(); } @@ -197,18 +188,18 @@ CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) { std::stringstream ss; int64_t current_time = clock_->GetCurrentTime(); - if (license_state_ == kLicenseStateInitial) - return UNKNOWN_ERROR; + if (license_state_ == kLicenseStateInitial) return UNKNOWN_ERROR; (*key_info)[QUERY_KEY_LICENSE_TYPE] = - license_id_.type() == video_widevine_server::sdk::STREAMING ? - QUERY_VALUE_STREAMING : QUERY_VALUE_OFFLINE; - (*key_info)[QUERY_KEY_PLAY_ALLOWED] = policy_.can_play() ? - QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; - (*key_info)[QUERY_KEY_PERSIST_ALLOWED] = policy_.can_persist() ? - QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; - (*key_info)[QUERY_KEY_RENEW_ALLOWED] = policy_.can_renew() ? - QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; + license_id_.type() == video_widevine_server::sdk::STREAMING + ? QUERY_VALUE_STREAMING + : QUERY_VALUE_OFFLINE; + (*key_info)[QUERY_KEY_PLAY_ALLOWED] = + policy_.can_play() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; + (*key_info)[QUERY_KEY_PERSIST_ALLOWED] = + policy_.can_persist() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; + (*key_info)[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(); ss.str(""); @@ -229,15 +220,14 @@ void PolicyEngine::UpdateRenewalRequest(int64_t current_time) { // will always return false if the value is 0. bool PolicyEngine::IsLicenseDurationExpired(int64_t current_time) { return policy_max_duration_seconds_ && - license_start_time_ + policy_max_duration_seconds_ <= - current_time; + license_start_time_ + policy_max_duration_seconds_ <= current_time; } int64_t PolicyEngine::GetLicenseDurationRemaining(int64_t current_time) { if (0 == policy_max_duration_seconds_) return LLONG_MAX; - int64_t remaining_time = policy_max_duration_seconds_ - + license_start_time_ - current_time; + int64_t remaining_time = + policy_max_duration_seconds_ + license_start_time_ - current_time; if (remaining_time < 0) remaining_time = 0; @@ -247,43 +237,38 @@ int64_t PolicyEngine::GetLicenseDurationRemaining(int64_t current_time) { } bool PolicyEngine::IsPlaybackDurationExpired(int64_t current_time) { - return (policy_.playback_duration_seconds() > 0) && - playback_start_time_ && - playback_start_time_ + policy_.playback_duration_seconds() <= - current_time; + return (policy_.playback_duration_seconds() > 0) && playback_start_time_ && + playback_start_time_ + policy_.playback_duration_seconds() <= + current_time; } int64_t PolicyEngine::GetPlaybackDurationRemaining(int64_t current_time) { if (0 == policy_.playback_duration_seconds()) return LLONG_MAX; if (0 == playback_start_time_) return policy_.playback_duration_seconds(); - int64_t remaining_time = policy_.playback_duration_seconds() - + playback_start_time_ - current_time; + int64_t remaining_time = + policy_.playback_duration_seconds() + playback_start_time_ - current_time; if (remaining_time < 0) remaining_time = 0; return remaining_time; } bool PolicyEngine::IsRenewalDelayExpired(int64_t current_time) { - return policy_.can_renew() && - (policy_.renewal_delay_seconds() > 0) && - license_start_time_ + policy_.renewal_delay_seconds() <= - current_time; + return policy_.can_renew() && (policy_.renewal_delay_seconds() > 0) && + license_start_time_ + policy_.renewal_delay_seconds() <= current_time; } -bool PolicyEngine::IsRenewalRecoveryDurationExpired( - int64_t current_time) { -// NOTE: Renewal Recovery Duration is currently not used. +bool PolicyEngine::IsRenewalRecoveryDurationExpired(int64_t current_time) { + // NOTE: Renewal Recovery Duration is currently not used. return (policy_.renewal_recovery_duration_seconds() > 0) && - license_start_time_ + policy_.renewal_recovery_duration_seconds() <= - current_time; + license_start_time_ + policy_.renewal_recovery_duration_seconds() <= + current_time; } -bool PolicyEngine::IsRenewalRetryIntervalExpired( - int64_t current_time) { +bool PolicyEngine::IsRenewalRetryIntervalExpired(int64_t current_time) { return policy_.can_renew() && - (policy_.renewal_retry_interval_seconds() > 0) && - next_renewal_time_ <= current_time; + (policy_.renewal_retry_interval_seconds() > 0) && + next_renewal_time_ <= current_time; } } // wvcdm diff --git a/core/src/privacy_crypto_dummy.cpp b/core/src/privacy_crypto_dummy.cpp index d59d8ddf..f9cb04ad 100644 --- a/core/src/privacy_crypto_dummy.cpp +++ b/core/src/privacy_crypto_dummy.cpp @@ -13,9 +13,7 @@ AesCbcKey::AesCbcKey() {} AesCbcKey::~AesCbcKey() {} -bool AesCbcKey::Init(const std::string& key) { - return false; -} +bool AesCbcKey::Init(const std::string& key) { return false; } bool AesCbcKey::Encrypt(const std::string& in, std::string* out, std::string* iv) { @@ -26,9 +24,7 @@ RsaPublicKey::RsaPublicKey() {} RsaPublicKey::~RsaPublicKey() {} -bool RsaPublicKey::Init(const std::string& serialized_key) { - return false; -} +bool RsaPublicKey::Init(const std::string& serialized_key) { return false; } bool RsaPublicKey::Encrypt(const std::string& clear_message, std::string* encrypted_message) { diff --git a/core/src/privacy_crypto.cpp b/core/src/privacy_crypto_openssl.cpp similarity index 100% rename from core/src/privacy_crypto.cpp rename to core/src/privacy_crypto_openssl.cpp diff --git a/core/src/string_conversions.cpp b/core/src/string_conversions.cpp index 8fc04124..1111dc64 100644 --- a/core/src/string_conversions.cpp +++ b/core/src/string_conversions.cpp @@ -43,7 +43,8 @@ std::vector a2b_hex(const std::string& byte) { unsigned char lsb = 0; // least significant 4 bits if (!CharToDigit(byte[i * 2], &msb) || !CharToDigit(byte[i * 2 + 1], &lsb)) { - LOGE("Invalid hex value %c%c at index %d", byte[i*2], byte[i*2+1], i); + LOGE("Invalid hex value %c%c at index %d", + byte[i * 2], byte[i * 2 + 1], i); return array; } array.push_back((msb << 4) | lsb); @@ -53,8 +54,8 @@ std::vector a2b_hex(const std::string& byte) { // converts an ascii hex string(2 bytes per digit) into a decimal byte string // dump the string with the label. -std::vector a2b_hex(const std::string& label, const std::string& byte) { - +std::vector a2b_hex(const std::string& label, + const std::string& byte) { std::cout << std::endl << "[[DUMP: " << label << " ]= \"" << byte << "\"]" << std::endl << std::endl; @@ -71,7 +72,7 @@ std::string b2a_hex(const std::vector& byte) { } std::string b2a_hex(const std::string& byte) { - return HexEncode(reinterpret_cast(byte.data()), + return HexEncode(reinterpret_cast(byte.data()), byte.length()); } @@ -90,9 +91,8 @@ std::string Base64SafeEncode(const std::vector& bin_input) { int in_size = bin_input.size(); std::string b64_output(modp_b64w_encode_len(in_size), 0); - int out_size = modp_b64w_encode(&b64_output[0], - reinterpret_cast(&bin_input[0]), - in_size); + int out_size = modp_b64w_encode( + &b64_output[0], reinterpret_cast(&bin_input[0]), in_size); if (out_size == -1) { LOGE("Base64SafeEncode failed"); return std::string(); @@ -119,8 +119,7 @@ std::vector Base64SafeDecode(const std::string& b64_input) { int in_size = b64_input.size(); std::vector bin_output(modp_b64w_decode_len(in_size), 0); int out_size = modp_b64w_decode(reinterpret_cast(&bin_output[0]), - b64_input.data(), - in_size); + b64_input.data(), in_size); if (out_size == -1) { LOGE("Base64SafeDecode failed"); return std::vector(0); diff --git a/core/test/base64_test.cpp b/core/test/base64_test.cpp index d24ee777..d8b2b2b5 100644 --- a/core/test/base64_test.cpp +++ b/core/test/base64_test.cpp @@ -38,28 +38,28 @@ const std::string kTwoBytesOverB64Data("SGVsbG8gRnJpZW5kISE="); 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), -}; + 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)}; -} // unnamed namespace +} // unnamed namespace namespace wvcdm { -class Base64EncodeDecodeTest : public ::testing::TestWithParam< - std::pair > {}; +class Base64EncodeDecodeTest + : public ::testing::TestWithParam< + std::pair > {}; TEST_P(Base64EncodeDecodeTest, EncodeDecodeTest) { - std::pair values = GetParam(); + std::pair values = GetParam(); std::vector decoded_vector = Base64SafeDecode(values.second->data()); std::string decoded_string(decoded_vector.begin(), decoded_vector.end()); EXPECT_STREQ(values.first->data(), decoded_string.data()); diff --git a/core/test/cdm_engine_test.cpp b/core/test/cdm_engine_test.cpp index 7df771b3..79803d58 100644 --- a/core/test/cdm_engine_test.cpp +++ b/core/test/cdm_engine_test.cpp @@ -1,4 +1,8 @@ // Copyright 2013 Google Inc. All Rights Reserved. +// These tests are for the cdm engine, and code below it in the stack. 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 @@ -14,6 +18,7 @@ #include "properties.h" #include "scoped_ptr.h" #include "string_conversions.h" +#include "test_printers.h" #include "url_request.h" #include "wv_cdm_constants.h" #include "wv_cdm_types.h" @@ -31,60 +36,6 @@ wvcdm::CdmKeySystem g_key_system; std::string g_license_server; wvcdm::KeyId g_wrong_key_id; -// This is an RSA certificate message from the provisioning server. -// The client sends this certificate to a license server for device -// authentication by the license server. -// This certificate is used to test the CDM engine's provisioning -// response handling. -static wvcdm::CdmProvisioningResponse kValidJsonProvisioningResponse = - "{\"signedResponse\": {" - "\"message\": \"CrAJYTyIdLPiA2jBzMskbE_gFQj69wv23VlJ2e3MBKtK4nJwKyNYGyyluqKo" - "TP751tvoADf86iLrf73mEzF58eSlaOjCpJRf2R3dojbNeSTy3JICmCc8vKtMjZRX9QWTvJbq_cg" - "yMB8FQC8enuYhOaw1yJDYyCFHgik34NrUVUfmvaKKdSKQimqAZmjXi6P0znAn-XdPtz2xJVRxZp" - "NH3QCD1bGcH_O1ercBW2JwF9KNalKFsxQrBhIwvyx-q-Ah4vf4r3M2HzY6JTHvcYGGc7dJNA3Xe" - "WfCrYIvg0SGCP_z7Y2wICIA36VMwR3gnwNZlKkx6WGCCgsaU6IbLm4HpRBZfajuiOlasoYN4z1R" - "lQ14Z32fdaFy8xOqLl-ZukxjWa7wv9zOSveH6JcHap1FS3R-RZ7E5WhfjxSTS0nWWZgmAjS2PkP" - "9g4GPNsnpsrVymI39j6R6jPoc3__2EGN6qAvmp4pFKR7lQyslgNn2vYLuE0Ps5mIXVkxNiZOO3T" - "jxgZyHaHOm1KmAZKI0EfddMATJCTt-UeLG3haqS_pYaBWcQ_xzWhoEHWU7_6ZaWrWemV8CVCg6s" - "OB1SRI5MrkRBBSV0r8UKddLJGthZVjuTG75KK72KE9yhe86mCadvfVYe5keJ5GOC-t1EiFzBo4c" - "4oqwkOCkkmYX_BEuZ3pOWztFp1_Br2Tl_fziw4O2vNIPCXB9yEewV6PkYPziTue3x4vRqD_mYjm" - "1ia8fxISQnEC0vrqvrFFs9fLAHPlsvaRFnhv_XKpRwFoBdfqWTakb3k6uRz0Oh2SJ8euzFIyQNB" - "efesMWk45DSrQjnlwlKXwZSiDKjAss0W2WwIb9F_x5LdB1Aa-CBudLVdxf62ggYaNZ57qx3YeHA" - "jkqMGIF7Fq09D4OxM0jRsnrmXbJWKleUpJi7nHJgQGZk2ifN95gjuTNcRaGfYXMOsDoWdkrNAq0" - "LScsPB06xEUR0DcO9vWx0zAEK7gsxxHziR7ZaYiIIkPysRR92r2NoLFPOUXf8j8ait-51jZmPKn" - "bD6adieLy6ujSl907QsUgyGvokLs1OCsYHZr-X6vnyMjdk4G3QfmWwRepD_CMyXGvtLbTNCto7E" - "L_M2yPZveAwYWwNlBtWK21gwIU2dgY298z7_S6jaQBc29f25sREjvN793ttYsPaeyom08qHYDnb" - "jae3XX-2qqde6AGXlv__jO8WDZ5od6DWu2ThqV10ijVGFfGniRsSruzq0iq8zuAqTOGhmA9Dw7b" - "rNlI95P4LpJA5pbjmNdnX7CQa2oHUuojmwlXRYuOA28PNEf-sc7ZPmMyFzedJi4EpkqzeQspEdH" - "yNMf23iEjK6GOff7dgAaxg9vYHyprhkEml4BdmFVYwCYQy8o6KRcA0NgJb8c3tg4d3aRXWp6L-F" - "sVhwqvq6FLOunSTNRIqhr2mOjRpU5w4mx-9GJRtk4XEcKT9YgUHGOUjGwfhQ5gBQDyZZVTddIUb" - "MOThsSg7zr38oUCfgXeZaai3X2foKo1Bt94Q_q18dw5xNAN5e7rSwfilltHL23zbZduuhWkvp8S" - "dag_NbO2C4IRMkzbjQBmiO9ixjXRhdqHlRRWcfR0wbQvEhD47egRVfnhKZ0W9G2-FGhyGuwJCq4" - "CCAISEAfZ_94TqpXBImeAUzYhNr0Y48SbiwUijgIwggEKAoIBAQDRigR9nFm4mfBUh1Y3SGyOcF" - "E-yK2NtfDiQe9l70KtkOeH4sB6MMB8g1QKPbUE8SBjPvXVJC_2DAWKjALzk4Aw-K-VmYe_Ag9CH" - "JiS-XcfUYEGgK4jVMxadEq3LufEEREKUZnzjgQlR39dzgjFqIrC1bwfy3_99RsjPt6QpWPg36PI" - "O4UKlmwBDTFzSOJB-4IV8Opy5Zv84BqPuyO9P5e3bXj_shRfy_XAGG2HGP_PpOCZWEfxuce0Iyu" - "vpTPLQpTOgNw-VvUBGCWMZFoERopmqp_pQwWZ2a-EwlT_vvYY4SkuNjflBskR70xz4QzEo9665g" - "k6I-HbHrTv29KEiAllAgMBAAEomSASgAIkKz1CSdFJVKcpO56jW0vsjKp92_cdqXBSEY3nuhzug" - "_LFluMJx_IqATUcCOY-w6w0yKn2ezfZGE0MDIaCngEgQFI_DRoaSOBNNeirF59uYM0sK3P2eGS9" - "G6F0l-OUXJdSO0b_LO8AbAK9LA3j7UHaajupJI1mdc4VtJfPRTsml2vIeKhDWXWaSvmeHgfF_tp" - "-OV7oPuk6Ub26xpCp2He2rEAblCYEl25Zlz97K4DhyTOV5_xuSdSt-KbTLY9cWM5i9ncND1RzCc" - "4qOixKarnMM5DdpZhs3B5xVj3yBAM1mVxPD2sZnqHSEN2EK7BMlHEnnyxhX0MGE36TQZR7P-I-G" - "rUFCq8CCAESEDAxMjM0NTY3ODlBQkNERUYYspIEIo4CMIIBCgKCAQEApwA2YGXcvVRaKkC04RWU" - "WBFPlFjd3qcfPCzgiAkpYVdnXlZ-7iePWTSaKqqdtE76p2rUyXpTwU6f4zT3PbfJEEdPKNo_zjF" - "7_QYQ6_e-kvmv-z5o2u4aZEzzKfJznjnY9m_YsoCCcY61pPLCPs0KyrYEzZoTi1RzVCVUjL6Yem" - "et2rNOs_qCqEpnmFZXVHHNEn_towHAaoskA5aIvpdmKrxTyYMGUVqIZRMY5Drta_FhW0zIHvTCr" - "gheLV_4En-i_LshGDDa_kD7AcouNw7O3XaHgkYLOnePwHIHLH-dHoZb7Scp3wOXYu9E01s925xe" - "G3s5tAttBGu7uyxfz7N6BQIDAQABKNKF2MwEEoADe9NAqNAxHpU13bMgz8LPySZJU8hY1RLwcfT" - "UM47Xb3m-F-s2cfI7w08668f79kD45uRRzkVc8GbRIlVyzVC0WgIvtxEkYRKfgF_J7snUe2J2NN" - "1FrkK7H3oYhcfPyYZH_SPZJr5HPoBFQTmS5A4l24U1dzQ6Z7_q-oS6uT0DiagTnzWhEg6AEnIkT" - "sJtK3cZuKGYq3NDefZ7nslPuLXxdXl6SAEOtrk-RvCY6EBqYOuPUXgxXOEPbyM289R6aHQyPPYw" - "qs9Pt9_E4BuMqCsbf5H5mLms9FA-wRx6mK2IaOboT4tf9_YObp3hVeL3WyxzXncETzJdE1GPGlO" - "t_x5S_MylgJKbiWQYSdmqs3fzYExunw3wvI4tPHT_O8A_xKjyTEAvE5cBuCkfjwT716qUOzFUzF" - "gZYLHnFiQLZekZUbUUlWY_CwU9Cv0UtxqQ6Oa835_Ug8_n1BwX6BPbmbcWe2Y19laSnDWg4JBNl" - "F2CyP9N75jPtW9rVfjUSqKEPOwaIgwzNDkyMjM3NDcAAAA=\"," - "\"signature\": \"r-LpoZcbbr2KtoPaFnuWTVBh4Gup1k8vn0ClW2qm32A=\"}}"; - const std::string kCencMimeType = "video/mp4"; const std::string kWebmMimeType = "video/webm"; @@ -95,14 +46,39 @@ namespace wvcdm { class WvCdmEngineTest : public testing::Test { public: virtual void SetUp() { - cdm_engine_.OpenSession(g_key_system, NULL, &session_id_); + CdmResponseType status = cdm_engine_.OpenSession(g_key_system, NULL, &session_id_); + if (status == NEED_PROVISIONING) { + Provision(); + status = cdm_engine_.OpenSession(g_key_system, NULL, &session_id_); + } + ASSERT_EQ(NO_ERROR, status); + ASSERT_NE("", session_id_) << "Could not open CDM session."; } - virtual void TearDown() { - cdm_engine_.CloseSession(session_id_); - } + virtual void TearDown() { cdm_engine_.CloseSession(session_id_); } 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); + 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)); + } + void GenerateKeyRequest(const std::string& key_id, const std::string& init_data_type_string) { CdmAppParameterMap app_parameters; @@ -112,25 +88,30 @@ class WvCdmEngineTest : public testing::Test { InitializationData init_data(init_data_type_string, key_id); EXPECT_EQ(KEY_MESSAGE, - cdm_engine_.GenerateKeyRequest(session_id_, - key_set_id, - init_data, - kLicenseTypeStreaming, - app_parameters, - &key_msg_, - &server_url)); + cdm_engine_.GenerateKeyRequest( + session_id_, key_set_id, init_data, kLicenseTypeStreaming, + app_parameters, &key_msg_, &server_url, NULL)); } void GenerateRenewalRequest() { - EXPECT_EQ(KEY_MESSAGE, - cdm_engine_.GenerateRenewalRequest(session_id_, - &key_msg_, - &server_url_)); + EXPECT_EQ(KEY_MESSAGE, cdm_engine_.GenerateRenewalRequest( + session_id_, &key_msg_, &server_url_)); + } + + std::string GetKeyRequestResponse(const std::string& server_url, + const std::string& client_auth) { + return GetKeyRequestResponse(server_url, client_auth, true); + } + + std::string FailToGetKeyRequestResponse(const std::string& server_url, + const std::string& client_auth) { + return GetKeyRequestResponse(server_url, client_auth, false); } // posts a request and extracts the drm message from the response std::string GetKeyRequestResponse(const std::string& server_url, - const std::string& client_auth) { + const std::string& client_auth, + bool expect_success) { // Use secure connection and chunk transfer coding. UrlRequest url_request(server_url + client_auth); if (!url_request.is_connected()) { @@ -144,7 +125,7 @@ class WvCdmEngineTest : public testing::Test { EXPECT_TRUE(ok); int status_code = url_request.GetStatusCode(response); - EXPECT_EQ(kHttpOk, status_code); + if (expect_success) EXPECT_EQ(kHttpOk, status_code); if (status_code != kHttpOk) { return ""; @@ -153,24 +134,23 @@ class WvCdmEngineTest : public testing::Test { LicenseRequest lic_request; lic_request.GetDrmMessage(response, drm_msg); LOGV("drm msg: %u bytes\r\n%s", drm_msg.size(), - HexEncode(reinterpret_cast(drm_msg.data()), - drm_msg.size()).c_str()); + HexEncode(reinterpret_cast(drm_msg.data()), + drm_msg.size()).c_str()); return drm_msg; } } void VerifyNewKeyResponse(const std::string& server_url, - const std::string& client_auth){ - std::string resp = GetKeyRequestResponse(server_url, - client_auth); + const std::string& client_auth) { + std::string resp = GetKeyRequestResponse(server_url, client_auth); CdmKeySetId key_set_id; - EXPECT_EQ(wvcdm::KEY_ADDED, cdm_engine_.AddKey(session_id_, resp, &key_set_id)); + EXPECT_EQ(wvcdm::KEY_ADDED, + cdm_engine_.AddKey(session_id_, resp, &key_set_id)); } void VerifyRenewalKeyResponse(const std::string& server_url, const std::string& client_auth) { - std::string resp = GetKeyRequestResponse(server_url, - client_auth); + std::string resp = GetKeyRequestResponse(server_url, client_auth); EXPECT_EQ(wvcdm::KEY_ADDED, cdm_engine_.RenewKey(session_id_, resp)); } @@ -180,18 +160,9 @@ class WvCdmEngineTest : public testing::Test { std::string server_url_; }; -TEST(WvCdmProvisioningTest, ProvisioningTest) { - CdmEngine cdm_engine; - CdmProvisioningRequest prov_request; - std::string provisioning_server_url; - CdmCertificateType cert_type = kCertificateWidevine; - std::string cert_authority; - std::string cert, wrapped_key; - - cdm_engine.GetProvisioningRequest(cert_type, cert_authority, - &prov_request, &provisioning_server_url); - cdm_engine.HandleProvisioningResponse(kValidJsonProvisioningResponse, - &cert, &wrapped_key); +// Test that provisioning works, even if device is already provisioned. +TEST_F(WvCdmEngineTest, ProvisioningTest) { + Provision(); } TEST_F(WvCdmEngineTest, BaseIsoBmffMessageTest) { @@ -211,7 +182,7 @@ TEST_F(WvCdmEngineTest, WrongMessageTest) { // We should receive a response with no license, i.e. the extracted license // response message should be empty. - ASSERT_EQ("", GetKeyRequestResponse(g_license_server, g_client_auth)); + ASSERT_EQ("", FailToGetKeyRequestResponse(g_license_server, g_client_auth)); } TEST_F(WvCdmEngineTest, NormalDecryptionIsoBmff) { @@ -236,11 +207,11 @@ TEST_F(WvCdmEngineTest, LicenseRenewal) { } // namespace wvcdm -int main(int argc, char **argv) { +int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); wvcdm::InitLogging(argc, argv); - wvcdm::ConfigTestEnv config(wvcdm::kGooglePlayServer); + wvcdm::ConfigTestEnv config(wvcdm::kContentProtectionServer); g_client_auth.assign(config.client_auth()); g_key_system.assign(config.key_system()); g_wrong_key_id.assign(config.wrong_key_id()); @@ -252,14 +223,14 @@ int main(int argc, char **argv) { int show_usage = 0; static const struct option long_options[] = { - { "keyid", required_argument, NULL, 'k' }, - { "server", required_argument, NULL, 's' }, - { NULL, 0, NULL, '\0' } - }; + {"keyid", required_argument, NULL, 'k'}, + {"server", required_argument, NULL, 's'}, + {NULL, 0, NULL, '\0'}}; int option_index = 0; int opt = 0; - while ((opt = getopt_long(argc, argv, "k:s:v", long_options, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "k:s:v", long_options, + &option_index)) != -1) { switch (opt) { case 'k': { g_key_id_pssh.clear(); @@ -292,11 +263,15 @@ int main(int argc, char **argv) { if (show_usage) { std::cout << std::endl; std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl; - std::cout << " enclose multiple arguments in '' when using adb shell" << std::endl; - std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" << std::endl << std::endl; + std::cout << " enclose multiple arguments in '' when using adb shell" + << std::endl; + std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" + << std::endl << std::endl; std::cout << std::setw(30) << std::left << " --server="; - std::cout << "configure the license server url, please include http[s] in the url" << std::endl; + std::cout + << "configure the license server url, please include http[s] in the url" + << std::endl; std::cout << std::setw(30) << std::left << " "; std::cout << "default: " << license_server << std::endl; diff --git a/core/test/cdm_session_unittest.cpp b/core/test/cdm_session_unittest.cpp index 51d41206..a32cbd5b 100644 --- a/core/test/cdm_session_unittest.cpp +++ b/core/test/cdm_session_unittest.cpp @@ -6,6 +6,7 @@ #include "gtest/gtest.h" #include "properties.h" #include "string_conversions.h" +#include "test_printers.h" namespace { const std::string kToken = wvcdm::a2bs_hex( @@ -111,7 +112,7 @@ class MockCryptoSession : public CryptoSession { class MockPolicyEngine : public PolicyEngine { public: - // Leaving a place holder for when PolicyEngine methods need to be mocked + // Leaving a place holder for when PolicyEngine methods need to be mocked }; class MockCdmLicense : public CdmLicense { @@ -132,10 +133,7 @@ class CdmSessionTest : public ::testing::Test { if (cdm_session_) delete cdm_session_; } - void CreateSession() { - cdm_session_ = new CdmSession(license_parser_, crypto_session_, - policy_engine_, file_handle_, NULL); - } + void CreateSession() { CreateSession(NULL); } void CreateSession(const CdmClientPropertySet* cdm_client_property_set) { cdm_session_ = diff --git a/core/test/config_test_env.cpp b/core/test/config_test_env.cpp index d289fa2f..fef158dd 100644 --- a/core/test/config_test_env.cpp +++ b/core/test/config_test_env.cpp @@ -6,25 +6,24 @@ namespace { const std::string kWidevineKeySystem = "com.widevine.alpha"; // Content Protection license server data -const std::string kCpLicenseServer = - "http://wv-ref-eme-player.appspot.com/proxy"; +const std::string kCpLicenseServer = "http://widevine-proxy.appspot.com/proxy"; const std::string kCpClientAuth = ""; const std::string kCpKeyId = - "00000042" // blob size - "70737368" // "pssh" - "00000000" // flags - "edef8ba979d64acea3c827dcd51d21ed" // Widevine system id - "00000022" // pssh data size + "00000042" // blob size + "70737368" // "pssh" + "00000000" // flags + "edef8ba979d64acea3c827dcd51d21ed" // Widevine system id + "00000022" // pssh data size // pssh data: "08011a0d7769646576696e655f746573" "74220f73747265616d696e675f636c69" "7031"; const std::string kCpOfflineKeyId = - "00000040" // blob size - "70737368" // "pssh" - "00000000" // flags - "edef8ba979d64acea3c827dcd51d21ed" // Widevine system id - "00000020" // pssh data size + "00000040" // blob size + "70737368" // "pssh" + "00000000" // flags + "edef8ba979d64acea3c827dcd51d21ed" // Widevine system id + "00000020" // pssh data size // pssh data: "08011a0d7769646576696e655f746573" "74220d6f66666c696e655f636c697031"; @@ -40,18 +39,17 @@ const std::string kGpLicenseServer = const std::string kGpClientAuth = "?source=YOUTUBE&video_id=EGHC6OHNbOo&oauth=ya.gtsqawidevine"; const std::string kGpKeyId = - "00000034" // blob size - "70737368" // "pssh" - "00000000" // flags + "00000034" // blob size + "70737368" // "pssh" + "00000000" // flags "edef8ba979d64acea3c827dcd51d21ed" // Widevine system id - "00000014" // pssh data size + "00000014" // pssh data size // pssh data: "08011210e02562e04cd55351b14b3d74" "8d36ed8e"; const std::string kGpOfflineKeyId = kGpKeyId; -const std::string kGpClientOfflineQueryParameters = - "&offline=true"; +const std::string kGpClientOfflineQueryParameters = "&offline=true"; const std::string kGpClientOfflineRenewalQueryParameters = "&offline=true&renewal=true"; const std::string kGpClientOfflineReleaseQueryParameters = @@ -59,11 +57,11 @@ const std::string kGpClientOfflineReleaseQueryParameters = // An invalid key id, expected to fail const std::string kWrongKeyId = - "00000034" // blob size - "70737368" // "pssh" - "00000000" // flags + "00000034" // blob size + "70737368" // "pssh" + "00000000" // flags "edef8ba979d64acea3c827dcd51d21ed" // Widevine system id - "00000014" // pssh data size + "00000014" // pssh data size // pssh data: "0901121094889920e8d6520098577df8" "f2dd5546"; @@ -75,24 +73,21 @@ const std::string kProductionProvisioningServerUrl = "?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE"; const wvcdm::ConfigTestEnv::LicenseServerConfiguration license_servers[] = { - { wvcdm::kGooglePlayServer, kGpLicenseServer, - kGpClientAuth, kGpKeyId, kGpOfflineKeyId }, - { wvcdm::kContentProtectionServer, kCpLicenseServer, - kCpClientAuth, kCpKeyId, kCpOfflineKeyId }, + {wvcdm::kGooglePlayServer, kGpLicenseServer, kGpClientAuth, kGpKeyId, + kGpOfflineKeyId}, + {wvcdm::kContentProtectionServer, kCpLicenseServer, kCpClientAuth, kCpKeyId, + kCpOfflineKeyId}, }; } // namespace namespace wvcdm { -ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id) { - Init(server_id); -} +ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id) { Init(server_id); } ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming) { Init(server_id); - if (!streaming) - key_id_ = license_servers[server_id].offline_key_id; + if (!streaming) key_id_ = license_servers[server_id].offline_key_id; } ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming, @@ -119,7 +114,7 @@ void ConfigTestEnv::Init(LicenseServerId server_id) { key_system_ = kWidevineKeySystem; license_server_ = license_servers[server_id].url; provisioning_server_url_ = kProductionProvisioningServerUrl; - wrong_key_id_= kWrongKeyId; + wrong_key_id_ = kWrongKeyId; } } // namespace wvcdm diff --git a/core/test/device_files_unittest.cpp b/core/test/device_files_unittest.cpp index 0d89a2db..a83cf81c 100644 --- a/core/test/device_files_unittest.cpp +++ b/core/test/device_files_unittest.cpp @@ -16,6 +16,7 @@ using ::testing::AllOf; using ::testing::Eq; using ::testing::Gt; using ::testing::HasSubstr; +using ::testing::InSequence; using ::testing::NotNull; using ::testing::Return; using ::testing::ReturnArg; @@ -1786,6 +1787,36 @@ TEST_F(DeviceFilesTest, DeleteLicense) { EXPECT_FALSE(device_files.LicenseExists(license_test_data[0].key_set_id)); } +TEST_F(DeviceFilesTest, ReserveLicenseIds) { + 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_ + + license_test_data[i].key_set_id + + DeviceFiles::GetLicenseFileNameExtension(); + InSequence calls; + EXPECT_CALL(file, Open(StrEq(license_path), + AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) + .WillOnce(Return(true)); + EXPECT_CALL(file, Write(StrEq(DeviceFiles::GetBlankFileData()), + DeviceFiles::GetBlankFileData().size())) + .WillOnce(ReturnArg<1>()); + EXPECT_CALL(file, Close()); + } + EXPECT_CALL(file, Read(_, _)).Times(0); + + DeviceFiles device_files; + 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)); + } +} + TEST_P(DeviceFilesUsageInfoTest, Read) { MockFile file; std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName(); @@ -1824,8 +1855,8 @@ TEST_P(DeviceFilesUsageInfoTest, Read) { for (size_t i = 0; i < license_info.size(); ++i) { bool found = false; for (size_t j = 0; j <= static_cast(index); ++j) { - if ((license_info[i].first.compare( - kUsageInfoTestData[j].license_request) == 0) && + if ((license_info[i] + .first.compare(kUsageInfoTestData[j].license_request) == 0) && (license_info[i].second.compare(kUsageInfoTestData[j].license) == 0)) { found = true; diff --git a/core/test/file_store_unittest.cpp b/core/test/file_store_unittest.cpp index 4f5cf13d..8697d21e 100644 --- a/core/test/file_store_unittest.cpp +++ b/core/test/file_store_unittest.cpp @@ -257,8 +257,7 @@ TEST_F(FileTest, ListFiles) { EXPECT_EQ(3u, files.size()); for (size_t i = 0; i < files.size(); ++i) { - EXPECT_TRUE(files[i] == kTestDirName || - files[i] == kTestFileName || + EXPECT_TRUE(files[i] == kTestDirName || files[i] == kTestFileName || files[i] == kTestFileName2); } } diff --git a/core/test/http_socket.cpp b/core/test/http_socket.cpp index 392fc863..a34e5bcf 100644 --- a/core/test/http_socket.cpp +++ b/core/test/http_socket.cpp @@ -41,8 +41,7 @@ SSL_CTX* InitSslContext() { SSL_load_error_strings(); method = SSLv3_client_method(); ctx = SSL_CTX_new(method); - if (!ctx) - LOGE("failed to create SSL context"); + if (!ctx) LOGE("failed to create SSL context"); return ctx; } @@ -77,8 +76,8 @@ bool SocketWait(int fd, bool for_read, int timeout_in_ms) { tv.tv_sec = timeout_in_ms / 1000; tv.tv_usec = (timeout_in_ms % 1000) * 1000; - fd_set *read_fds = NULL; - fd_set *write_fds = NULL; + fd_set* read_fds = NULL; + fd_set* write_fds = NULL; if (for_read) { read_fds = &fds; } else { @@ -104,12 +103,9 @@ namespace wvcdm { // Parses the URL and extracts all relevant information. // static -bool HttpSocket::ParseUrl(const std::string& url, - std::string* scheme, - bool* secure_connect, - std::string* domain_name, - int* port, - std::string* path) { +bool HttpSocket::ParseUrl(const std::string& url, std::string* scheme, + bool* secure_connect, std::string* domain_name, + int* port, std::string* path) { size_t offset = 0; if (!Tokenize(url, "://", offset, scheme, &offset)) { @@ -143,8 +139,7 @@ bool HttpSocket::ParseUrl(const std::string& url, // The domain name may optionally contain a port which overrides the default. std::string domain_name_without_port; size_t port_offset; - if (Tokenize(*domain_name, ":", 0, &domain_name_without_port, - &port_offset)) { + if (Tokenize(*domain_name, ":", 0, &domain_name_without_port, &port_offset)) { *port = atoi(domain_name->c_str() + port_offset); if (*port <= 0 || *port >= 65536) { LOGE("Invalid URL, port not valid: %s", url.c_str()); @@ -157,17 +152,13 @@ bool HttpSocket::ParseUrl(const std::string& url, } HttpSocket::HttpSocket(const std::string& url) - : socket_fd_(-1), - ssl_(NULL), - ssl_ctx_(NULL) { + : socket_fd_(-1), ssl_(NULL), ssl_ctx_(NULL) { valid_url_ = ParseUrl(url, &scheme_, &secure_connect_, &domain_name_, &port_, &resource_path_); SSL_library_init(); } -HttpSocket::~HttpSocket() { - CloseSocket(); -} +HttpSocket::~HttpSocket() { CloseSocket(); } void HttpSocket::CloseSocket() { if (socket_fd_ != -1) { @@ -223,8 +214,8 @@ bool HttpSocket::Connect(int timeout_in_ms) { } // set the port - struct sockaddr_in* addr_ipv4 = reinterpret_cast( - addr_info->ai_addr); + struct sockaddr_in* addr_ipv4 = + reinterpret_cast(addr_info->ai_addr); addr_ipv4->sin_port = htons(port_); // connect to the server @@ -276,8 +267,7 @@ bool HttpSocket::Connect(int timeout_in_ms) { ret = SSL_connect(ssl_); if (ret != 1) { int ssl_err = SSL_get_error(ssl_, ret); - if (ssl_err != SSL_ERROR_WANT_READ && - ssl_err != SSL_ERROR_WANT_WRITE) { + if (ssl_err != SSL_ERROR_WANT_READ && ssl_err != SSL_ERROR_WANT_WRITE) { char buf[256]; LOGE("SSL_connect error: %s", ERR_error_string(ERR_get_error(), buf)); CloseSocket(); diff --git a/core/test/http_socket.h b/core/test/http_socket.h index 0b628e26..1977f5c4 100644 --- a/core/test/http_socket.h +++ b/core/test/http_socket.h @@ -32,12 +32,9 @@ class HttpSocket { int Write(const char* data, int len, int timeout_in_ms); private: - static bool ParseUrl(const std::string& url, - std::string* scheme, - bool* secure_connect, - std::string* domain_name, - int* port, - std::string* path); + static bool ParseUrl(const std::string& url, std::string* scheme, + bool* secure_connect, std::string* domain_name, + int* port, std::string* path); FRIEND_TEST(HttpSocketTest, ParseUrlTest); std::string scheme_; diff --git a/core/test/http_socket_test.cpp b/core/test/http_socket_test.cpp index 35624025..ed8e660e 100644 --- a/core/test/http_socket_test.cpp +++ b/core/test/http_socket_test.cpp @@ -103,63 +103,63 @@ struct ParseUrlTests { }; ParseUrlTests parse_url_tests[] = { - { - "https://code.google.com/p/googletest/wiki/Primer", // url - "https", // scheme - true, // secure_connect - "code.google.com", // domain_name - 443, // port - "/p/googletest/wiki/Primer", // path - }, - { - "http://code.google.com/p/googletest/wiki/Primer/", // url - "http", // scheme - false, // secure_connect - "code.google.com", // domain_name - 80, // port - "/p/googletest/wiki/Primer/", // path - }, - { - "http://code.google.com/", // url - "http", // scheme - false, // secure_connect - "code.google.com", // domain_name - 80, // port - "/", // path - }, - { - "http://code.google.com", // url - "http", // scheme - false, // secure_connect - "code.google.com", // domain_name - 80, // port - "/", // path - }, - { - "http://10.11.12.13:8888/drm", // url - "http", // scheme - false, // secure_connect - "10.11.12.13", // domain_name - 8888, // port - "/drm", // path - }, - { - "http://10.11.12.13:8888", // url - "http", // scheme - false, // secure_connect - "10.11.12.13", // domain_name - 8888, // port - "/", // path - }, - { - "https://10.11.12.13:8888", // url - "https", // scheme - true, // secure_connect - "10.11.12.13", // domain_name - 8888, // port - "/", // path - }, - { NULL } // list terminator + { + "https://code.google.com/p/googletest/wiki/Primer", // url + "https", // scheme + true, // secure_connect + "code.google.com", // domain_name + 443, // port + "/p/googletest/wiki/Primer", // path + }, + { + "http://code.google.com/p/googletest/wiki/Primer/", // url + "http", // scheme + false, // secure_connect + "code.google.com", // domain_name + 80, // port + "/p/googletest/wiki/Primer/", // path + }, + { + "http://code.google.com/", // url + "http", // scheme + false, // secure_connect + "code.google.com", // domain_name + 80, // port + "/", // path + }, + { + "http://code.google.com", // url + "http", // scheme + false, // secure_connect + "code.google.com", // domain_name + 80, // port + "/", // path + }, + { + "http://10.11.12.13:8888/drm", // url + "http", // scheme + false, // secure_connect + "10.11.12.13", // domain_name + 8888, // port + "/drm", // path + }, + { + "http://10.11.12.13:8888", // url + "http", // scheme + false, // secure_connect + "10.11.12.13", // domain_name + 8888, // port + "/", // path + }, + { + "https://10.11.12.13:8888", // url + "https", // scheme + true, // secure_connect + "10.11.12.13", // domain_name + 8888, // port + "/", // path + }, + {NULL} // list terminator }; TEST_F(HttpSocketTest, ParseUrlTest) { diff --git a/core/test/license_request.cpp b/core/test/license_request.cpp index f6bbd3de..991c730d 100644 --- a/core/test/license_request.cpp +++ b/core/test/license_request.cpp @@ -9,13 +9,13 @@ static const std::string kTwoBlankLines("\r\n\r\n"); size_t LicenseRequest::FindHeaderEndPosition( const std::string& response) const { - return(response.find(kTwoBlankLines)); + return response.find(kTwoBlankLines); } // This routine parses the license server's response message and // extracts the drm message from the response header. void LicenseRequest::GetDrmMessage(const std::string& response, - std::string& drm_msg) { + std::string& drm_msg) { if (response.empty()) { drm_msg.clear(); return; @@ -27,23 +27,25 @@ void LicenseRequest::GetDrmMessage(const std::string& response, // the drm message length as below instead of using Content-Length size_t header_end_pos = FindHeaderEndPosition(response); if (header_end_pos != std::string::npos) { - header_end_pos += kTwoBlankLines.size(); // points to response body + header_end_pos += kTwoBlankLines.size(); // points to response body drm_msg.clear(); - size_t drm_msg_pos = response.find(kTwoBlankLines, header_end_pos); - if (drm_msg_pos != std::string::npos) { - drm_msg_pos += kTwoBlankLines.size(); // points to drm message - } else { - // For backward compatibility, no blank line after error code - drm_msg_pos = response.find("\r\n", header_end_pos); + // Messages from Google Play server add a GLS wrapper. These start + // with "GLS/1.0 ". + if (response.find("GLS/1.", header_end_pos) == header_end_pos) { + // For GLS messages, we should skip past the next blank line, and use + // what's left of the message. + size_t drm_msg_pos = response.find(kTwoBlankLines, header_end_pos); if (drm_msg_pos != std::string::npos) { - drm_msg_pos += 2; // points to drm message + drm_msg_pos += kTwoBlankLines.size(); // points to drm message + drm_msg = response.substr(drm_msg_pos); + } else { + LOGE("Message had GLS marker, but did not have extra blank line."); + drm_msg = response.substr(header_end_pos); } - } - - if (drm_msg_pos != std::string::npos) { - drm_msg = response.substr(drm_msg_pos); } else { + // For servers that do not use the GLS wrapper, we should just strip of + // the headers, and use the rest of the message. drm_msg = response.substr(header_end_pos); } } else { @@ -62,11 +64,10 @@ void LicenseRequest::GetHeartbeatUrl(const std::string& response, size_t header_end_pos = FindHeaderEndPosition(response); if (header_end_pos != std::string::npos) { - header_end_pos += kTwoBlankLines.size(); // points to response body + header_end_pos += kTwoBlankLines.size(); // points to response body heartbeat_url.clear(); - size_t heartbeat_url_pos = response.find("Heartbeat-Url: ", - header_end_pos); + 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)); @@ -78,4 +79,4 @@ void LicenseRequest::GetHeartbeatUrl(const std::string& response, } } -} // namespace wvcdm +} // namespace wvcdm diff --git a/core/test/license_unittest.cpp b/core/test/license_unittest.cpp index 55326873..f613fb7b 100644 --- a/core/test/license_unittest.cpp +++ b/core/test/license_unittest.cpp @@ -1,150 +1,282 @@ // Copyright 2012 Google Inc. All Rights Reserved. +#include "clock.h" #include "crypto_session.h" #include "license.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "initialization_data.h" #include "policy_engine.h" +#include "properties.h" #include "string_conversions.h" +#include "wv_cdm_constants.h" namespace { -// The test data is based on key box Eureka-Dev-G1-0001520 -// This unit test should run on oemcrypto mock with the same key box -static const char* kInitData = "0801121093789920E8D6520098577DF8F2DD5546"; -static const char* kSignedRequest = - "080112790A4C0800124800000002000001241F344DB9DFF087F01D917910F39B" - "60DC7797CD97789EE82516DC07A478CB70B8C08C299293150AA8E01D2DC9808C" - "98DAA16E40A0E55DFE3618C7584DD3C7BE4212250A230A140801121093789920" - "E8D6520098577DF8F2DD554610011A09393837363534333231180120001A20FA" - "E2DDCD7F1ACA4B728EC957FEE802F8A5541557ACA784EE0D05BFCC0E65FEA1"; -static const char* kValidResponse = - "080212D9020A190A093938373635343332311208C434AB9240A9EF2420012800" - "120E0801180120809A9E0128809A9E011A461210B72EEBF582B04BDB15C2E0E3" - "20B21C351A30E51FC1D27F70DB8E0DDF8C051BD6E251A44599DBCE4E1BE663FD" - "3AFAB191A7DD5736841FB04CE558E7F17BD9812A2DBA20011A6E0A1093789920" - "E8D6520098577DF8F2DD55461210367E8714B6F10087AFDE542EDC5C91541A20" - "ED51D4E84D81C8CBD8E2046EE079F8A2016268A2F192B902FDA241FEEB10C014" - "200242240A109209D46191B8752147C9F6A1CE2BEE6E12107910F39B60DC7797" - "CD97789EE82516DC1A6E0A107B1328EB61B554E293F75B1E3E94CC3B1210676F" - "69BBDA35EE972B77BC1328A087391A20D2B9FA92B164F5F6362CAD9200A11661" - "B8F71E9CE671A3A252D34586526B68FA200242240A109D7B13420FD6217666CC" - "CD43860FAA3A1210DBCE4E1BE663FD3AFAB191A7DD57368420E9FDCE86051A20" - "C6279E32FD2CB9067229E87AFF4B2DE14A077CDF8F061DAEE2CC2D1BCDEF62D0"; -static const char* kInvalidResponse = - "0802128D020A190A093938373635343332311208BA68C949396C438C20012800" - "120E0801180120809A9E0128809A9E011A4612105021EB9AEDC1F73E96DE7DCC" - "6D7D72401A300A82E118C0BF0DB230FCADE3F49A9777DDD392322240FEF32C97" - "F85428E2F6CCFA638B5481464ADBCF199CEC2FCF3AFB20011A480A1093789920" - "E8D6520098577DF8F2DD55461210EE52C59B99050A36E10569AFB34D1DA41A20" - "C61FCB8019AC9ADE99FF8FCA99ED35E2331B6488A35102F9379AA42C87A22DC7" - "20021A480A107B1328EB61B554E293F75B1E3E94CC3B12101BBF5286B859E349" - "2E4A47A24C06AC1B1A2061F21836A04E558BEE0244EF41C165F60CF23C580275" - "3175D48BAF1C6CA5759F200220A2BCCA86051A203FD4671075D9DEC6486A9317" - "70669993306831EDD57D77F34EFEB467470BA364"; - -const std::string kCencMimeType = "video/mp4"; -const std::string kWebmMimeType = "video/webm"; - -} +const uint32_t kAesBlockSize = 16; +const std::string kAesKey = wvcdm::a2bs_hex("000102030405060708090a0b0c0d0e0f"); +const std::string kAesIv = wvcdm::a2bs_hex("000102030405060708090a0b0c0d0e0f"); +const std::string kCencInitDataHdr = wvcdm::a2bs_hex( + "00000042" // blob size + "70737368" // "pssh" + "00000000" // flags + "edef8ba979d64acea3c827dcd51d21ed" // Widevine system id + "00000022"); // pssh data size +const std::string kCencPssh = wvcdm::a2bs_hex( + "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); +const std::string kCdmSessionId = "sid2"; +const std::string kCryptoSessionId = "id2"; +const std::string kCryptoRequestId = wvcdm::a2bs_hex( + "4341444542353737444337393044394330313030303030303030303030303030"); +const uint32_t kNonce = 0x49e81305; +const int64_t kLicenseStartTime = 1413517500; // ~ 01/01/2013 +const std::string kToken = wvcdm::a2bs_hex( + "0AAE02080212107E0A892DEEB021E7AF696B938BB1D5B1188B85AD9D05228E023082010A02" + "82010100DBEDF2BFB0EC98213766E65049B9AB176FA4B1FBFBB2A0C96C87D9F2B895E0ED77" + "93BDA057E6BC3E0CA2348BC6831E03609445CA4D418CB98EAC98FFC87AB2364CE76BA26BEE" + "CDB0C45BD2A6FE9FD38CC5A1C26303AEEB7E9C3CAFAB0D10E46C07E50BEDAB42BF21F40BD2" + "E055DB0B455191D6B4CEEB11B3F1AFA42B5C0CE4C96B75A5283C0E3AE049AA7CF86D1C4EF6" + "6A9088B53BCF320ABC9B98A22C219DC109014EFEA72DA5FF2ED5D655DE7AE06EAC6C6B4191" + "523B2CD2DC1EBFF5F08B11CFE056F2826C1323F12704EC7EBBC1AF935129E5543804492AF9" + "23B848F4AF47B4BFB131C39DDDC99DBAEEE0F30AD2ADBBC63E60793D0876E37391008BB4DB" + "F7020301000128DD22128002A9E571776EA9D22A1BD14248DA88E12FD859989F613360B8D2" + "DA40AF31CC071C7A138466F0EB745E3FD664C0E1A5E4F01098B8D56C34A0158DF9916D192F" + "841ADCA17FD630E1C0CBE652CAC6A52B6A1581BE4029CE6FAE0E04D2D2C7861187AF8299D8" + "3E008DB9A2789672CA1DED903773D7E82B234CE2C799EB73CF80600C08F17EEDDDF369D2B8" + "4A08292F22D1F18FE89521905E713BA674F2217881DBD7711B8C48D5FDCE6FAB51F935E293" + "CB29191AB012B115FD2F5F23164B063D0A929C3E254BF0F4FA60051EB6B3498ED99FF77C19" + "68E8CD83A35CEB054D05433FD0EA6AAE43C87DDA377591D1DCC1831EE130BFFF6D139A5ADA" + "738B0F257CCE2649E71AB4050AAE020801121017DCBC27D11341D497135442A188DAA6188F" + "89809105228E023082010A0282010100D21ADD7549D2748B3494526A9C3FB86C79376BBE8C" + "8856F601B8D10461F77ACC7331B10DEBF365120056CDB5662D25907B74F12382F0F4A0CA47" + "5EEA9562815C6228F6F698ADA27879D8890F2A2D96A746DDEF5316301C003519C2A2250354" + "674169FDDA41CE14D3C52BEA7A20384515012D5952B38AA19E15E8563CC7AAA81C2122880A" + "A370A64FEA23C53FB83AC3DB5753214730A349E07F64BF32BE7EAD30D02612AF110BB44FB0" + "8E1D308173B327EF64D40C41639542B2D1A73C98A6607EC6C683B513A58470514106EF87AE" + "1E7B9C695B93A104DF7437BFC4167789748A43ED208F2C1FA710793C688885EAE732A8BFDF" + "5B423B23D75B88FC0ADC8FBDB5020301000128DD2212800372D2FB88098BA3B85B6B4354E0" + "3767DBE2D7724663FB0A62ABF7704EA910E01F221349EE16D0152C769384050CE78520668C" + "06CCFD3D789AF3EB69FF163615CD609169FDBE2E15A029D34AD2605625BC81844C9D1E2CE0" + "519039F3799ADAEF86641E20B033DC16DF2E5B9A1A2A417B8BB3B7A4D9AD1A99367448587D" + "A13DDE05A3ED9D62FA42078973B4AA40263D7BFA23F1072E94CDF323FA45F78408823E55C4" + "F4C5C723819CF44CE6D98E50C04EC24D93B1AAB8877B9108B9CA391308E1A3645EBB0E7CAC" + "BB40B5451560ED799421873BFB5ABB917FA60DB9C77CB8606AF7E3142626F5EA40E5CB8AA0" + "89D8E7D6A9361935C426A4450EA8BC2E57290D3BF0A0962991D2A91B752FC80C3E7E4E5503" + "3D71C94B325307A68815F026448F56A2741CEBEFC18E8C142F5F62BFAA67A291517DDE982D" + "8CD5A9DF6E3D3A99B806F6D60991358C5BE77117D4F3168F3348E9A048539F892F4D783152" + "C7A8095224AA56B78C5CF7BD1AB1B179C0C0D11E3C3BAC84C141A00191321E3ACC17242E68" + "3C"); +const std::string kLicenseRequestSignature = wvcdm::a2bs_hex( + "4A560ACFED04787BE0D29D7396234FA2E11D6DD0B22F87FD77AEAEDAA6C8FE54AD9859AE4E" + "C9F12BCB947892D906DAEC1AD78CABD6F9D479CCF91AF5587DB6FC29CBEBF9C338BAF17790" + "90980B1F3333BC901CDBF877490C7B85DB2BF9BC559C98450C6F1E8B2E192959F59CC53BD4" + "85F2CC87D87C324750D5A8E28B821A3C55ABF27305AE4C58474D16E4FEE499D87A7D10AC84" + "8D24103EB15C63C227A0D57A9D90F5A409D2D55147EE10A35AE291D2D725C7F161FF827221" + "9AE18B91516E0CDD0B581590DDDEA2A2527E2C9ABA273629B586A9D22D451A827E332CFC3E" + "9BEDB6CF3D8713F9E11675DF1F5DB9038DBBECAB9D1683F8722CAF6E18EC8C04AEE5"); +} // namespace namespace wvcdm { -class LicenseTest : public ::testing::Test { +// Protobuf generated classes +using video_widevine_server::sdk::LicenseRequest_ContentIdentification; +using video_widevine_server::sdk::ClientIdentification; +using video_widevine_server::sdk::LicenseRequest; +using video_widevine_server::sdk::SignedMessage; + +// gmock methods +using ::testing::_; +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; + +class MockCryptoSession : public CryptoSession { + public: + MOCK_METHOD0(IsOpen, bool()); + MOCK_METHOD1(GenerateRequestId, bool(std::string*)); + MOCK_METHOD1(UsageInformationSupport, bool(bool*)); + MOCK_METHOD2(GetHdcpCapabilities, + bool(OemCryptoHdcpVersion*, OemCryptoHdcpVersion*)); + MOCK_METHOD1(GetApiVersion, bool(uint32_t*)); + MOCK_METHOD1(GenerateNonce, bool(uint32_t*)); + MOCK_METHOD3(PrepareRequest, bool(const std::string&, bool, std::string*)); +}; + +class MockPolicyEngine : public PolicyEngine { + public: +}; + +class MockClock : public Clock { + public: + MOCK_METHOD0(GetCurrentTime, int64_t()); +}; + +class MockInitializationData : public InitializationData { + public: + MockInitializationData(const std::string& type, const CdmInitData& data) + : InitializationData(type, data) {} + MOCK_METHOD0(is_supported, bool()); + MOCK_METHOD0(is_cenc, bool()); +}; + +class CdmLicenseTest : public ::testing::Test { protected: virtual void SetUp() { - session_ = new CryptoSession(); - EXPECT_TRUE(session_ != NULL); - - std::string token; - EXPECT_TRUE(session_->GetToken(&token)); - - EXPECT_TRUE(session_->Open()); - EXPECT_TRUE(license_.Init(token, session_, &policy_engine_)); + clock_ = new MockClock(); + crypto_session_ = new MockCryptoSession(); + init_data_ = new MockInitializationData(CENC_INIT_DATA_FORMAT, + kCencInitDataHdr + kCencPssh); + policy_engine_ = new MockPolicyEngine(); } virtual void TearDown() { - session_->Close(); - delete session_; + if (cdm_license_) delete cdm_license_; + if (policy_engine_) delete policy_engine_; + if (init_data_) delete init_data_; + if (crypto_session_) delete crypto_session_; + if (clock_) delete clock_; } - CryptoSession* session_; - CdmLicense license_; - PolicyEngine policy_engine_; + void CreateCdmLicense() { + cdm_license_ = new CdmLicense(clock_); + clock_ = NULL; + } + + CdmLicense* cdm_license_; + MockClock* clock_; + MockCryptoSession* crypto_session_; + MockInitializationData* init_data_; + MockPolicyEngine* policy_engine_; }; -TEST(LicenseTestSession, InitNullSession) { - CdmLicense license; - EXPECT_FALSE(license.Init("Dummy", NULL, NULL)); +TEST_F(CdmLicenseTest, InitSuccess) { + EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); + + CreateCdmLicense(); + ASSERT_TRUE(cdm_license_->Init(kToken, crypto_session_, policy_engine_)); } -// TODO(rfrias): Fix or remove test. -TEST_F(LicenseTest, DISABLED_PrepareIsoBmffKeyRequest) { - std::string signed_request; +TEST_F(CdmLicenseTest, InitFail_EmptyToken) { + CreateCdmLicense(); + EXPECT_FALSE(cdm_license_->Init("", crypto_session_, policy_engine_)); +} + +TEST_F(CdmLicenseTest, InitFail_CryptoSessionNull) { + CreateCdmLicense(); + EXPECT_FALSE(cdm_license_->Init(kToken, NULL, policy_engine_)); +} + +TEST_F(CdmLicenseTest, InitFail_PolicyEngineNull) { + EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); + + CreateCdmLicense(); + EXPECT_FALSE(cdm_license_->Init(kToken, crypto_session_, NULL)); +} + +TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { + bool usage_information_support = true; + CryptoSession::OemCryptoHdcpVersion current_hdcp_version = + CryptoSession::kOemCryptoNoHdcpDeviceAttached; + CryptoSession::OemCryptoHdcpVersion max_hdcp_version = + CryptoSession::kOemCryptoHdcpVersion2_1; + uint32_t crypto_session_api_version = 9; + + EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); + EXPECT_CALL(*crypto_session_, GenerateRequestId(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kCryptoRequestId), Return(true))); + EXPECT_CALL(*crypto_session_, UsageInformationSupport(NotNull())).WillOnce( + DoAll(SetArgPointee<0>(usage_information_support), Return(true))); + EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(current_hdcp_version), + SetArgPointee<1>(max_hdcp_version), Return(true))); + EXPECT_CALL(*crypto_session_, GetApiVersion(NotNull())).WillOnce( + DoAll(SetArgPointee<0>(crypto_session_api_version), Return(true))); + EXPECT_CALL(*clock_, GetCurrentTime()).WillOnce(Return(kLicenseStartTime)); + EXPECT_CALL(*crypto_session_, GenerateNonce(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kNonce), Return(true))); + EXPECT_CALL(*crypto_session_, PrepareRequest(_, Eq(false), NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kLicenseRequestSignature), Return(true))); + + CreateCdmLicense(); + EXPECT_TRUE(cdm_license_->Init(kToken, crypto_session_, policy_engine_)); + CdmAppParameterMap app_parameters; + CdmKeyMessage signed_request; + Properties::set_use_certificates_as_identification(true); std::string server_url; - CdmSessionId session_id; - InitializationData init_data(kCencMimeType, a2bs_hex(kInitData)); - license_.PrepareKeyRequest(init_data, - kLicenseTypeStreaming, - app_parameters, - session_id, - &signed_request, - &server_url); - EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest)); -} + EXPECT_TRUE(cdm_license_->PrepareKeyRequest( + *init_data_, kLicenseTypeStreaming, app_parameters, kCdmSessionId, + &signed_request, &server_url)); -// TODO(rfrias): Fix or remove test. -TEST_F(LicenseTest, DISABLED_PrepareWebmKeyRequest) { - std::string signed_request; - CdmAppParameterMap app_parameters; - std::string server_url; - CdmSessionId session_id; - InitializationData init_data(kWebmMimeType, a2bs_hex(kInitData)); - license_.PrepareKeyRequest(init_data, - kLicenseTypeStreaming, - app_parameters, - session_id, - &signed_request, - &server_url); - EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest)); -} + EXPECT_TRUE(!signed_request.empty()); -// TODO(rfrias): Fix or remove test. -TEST_F(LicenseTest, DISABLED_HandleKeyResponseValid) { - std::string signed_request; - CdmAppParameterMap app_parameters; - CdmSessionId session_id; - std::string server_url; - InitializationData init_data(kCencMimeType, a2bs_hex(kInitData)); - license_.PrepareKeyRequest(init_data, - kLicenseTypeStreaming, - app_parameters, - session_id, - &signed_request, - &server_url); - EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest)); - EXPECT_TRUE(license_.HandleKeyResponse(a2bs_hex(kValidResponse))); -} + // Verify signed message + SignedMessage signed_message; + EXPECT_TRUE(signed_message.ParseFromString(signed_request)); + EXPECT_EQ(SignedMessage::LICENSE_REQUEST, signed_message.type()); + EXPECT_TRUE(signed_message.has_signature()); + EXPECT_TRUE(std::equal(signed_message.signature().begin(), + signed_message.signature().end(), + kLicenseRequestSignature.begin())); + EXPECT_TRUE(!signed_message.msg().empty()); -// TODO(rfrias): Fix or remove test. -TEST_F(LicenseTest, DISABLED_HandleKeyResponseInvalid) { - std::string signed_request; - CdmAppParameterMap app_parameters; - CdmSessionId session_id; - std::string server_url; - InitializationData init_data(kCencMimeType, a2bs_hex(kInitData)); - license_.PrepareKeyRequest(init_data, - kLicenseTypeStreaming, - app_parameters, - session_id, - &signed_request, - &server_url); - EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest)); - EXPECT_FALSE(license_.HandleKeyResponse(a2bs_hex(kInvalidResponse))); -} + // Verify license request + LicenseRequest license_request; + EXPECT_TRUE(license_request.ParseFromString(signed_message.msg())); -// TODO(kqyang): add unit test cases for PrepareKeyRenewalRequest -// and HandleRenewalKeyResponse + // Verify Client Identification + const ClientIdentification& client_id = license_request.client_id(); + EXPECT_EQ(video_widevine_server::sdk:: + ClientIdentification_TokenType_DEVICE_CERTIFICATE, + client_id.type()); + EXPECT_TRUE(std::equal(client_id.token().begin(), client_id.token().end(), + kToken.begin())); + EXPECT_LT(0, client_id.client_info_size()); + for (int i = 0; i < client_id.client_info_size(); ++i) { + const ::video_widevine_server::sdk::ClientIdentification_NameValue& + name_value = client_id.client_info(i); + EXPECT_TRUE(!name_value.name().empty()); + EXPECT_TRUE(!name_value.value().empty()); + } + + EXPECT_FALSE(client_id.has_provider_client_token()); + EXPECT_FALSE(client_id.has_license_counter()); + + const ::video_widevine_server::sdk::ClientIdentification_ClientCapabilities& + client_capabilities = client_id.client_capabilities(); + EXPECT_FALSE(client_capabilities.has_client_token()); + EXPECT_TRUE(client_capabilities.has_session_token()); + EXPECT_FALSE(client_capabilities.video_resolution_constraints()); + EXPECT_EQ(video_widevine_server::sdk:: + ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_1, + client_capabilities.max_hdcp_version()); + EXPECT_EQ(crypto_session_api_version, + client_capabilities.oem_crypto_api_version()); + + // Verify Content Identification + const LicenseRequest_ContentIdentification& content_id = + license_request.content_id(); + EXPECT_TRUE(content_id.has_cenc_id()); + EXPECT_FALSE(content_id.has_webm_id()); + EXPECT_FALSE(content_id.has_license()); + + const ::video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC& + cenc_id = content_id.cenc_id(); + EXPECT_TRUE(std::equal(cenc_id.pssh(0).begin(), cenc_id.pssh(0).end(), + kCencPssh.begin())); + EXPECT_EQ(video_widevine_server::sdk::STREAMING, cenc_id.license_type()); + EXPECT_TRUE(std::equal(cenc_id.request_id().begin(), + cenc_id.request_id().end(), kCryptoRequestId.begin())); + + // Verify other license request fields + EXPECT_EQ(::video_widevine_server::sdk::LicenseRequest_RequestType_NEW, + license_request.type()); + EXPECT_EQ(kLicenseStartTime, license_request.request_time()); + EXPECT_EQ(video_widevine_server::sdk::VERSION_2_1, + license_request.protocol_version()); + EXPECT_EQ(kNonce, license_request.key_control_nonce()); +} } diff --git a/core/test/policy_engine_unittest.cpp b/core/test/policy_engine_unittest.cpp index a093c8e0..8f0b497d 100644 --- a/core/test/policy_engine_unittest.cpp +++ b/core/test/policy_engine_unittest.cpp @@ -10,6 +10,7 @@ #include "gtest/gtest.h" #include "license.h" #include "policy_engine.h" +#include "test_printers.h" #include "wv_cdm_constants.h" namespace { diff --git a/core/test/test_printers.cpp b/core/test/test_printers.cpp new file mode 100644 index 00000000..05f08aa1 --- /dev/null +++ b/core/test/test_printers.cpp @@ -0,0 +1,93 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// This file adds some print methods so that when unit tests fail, the +// will print the name of an enumeration instead of the numeric value. + +#include "test_printers.h" + +namespace wvcdm { + +void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { + switch(value) { + case NO_ERROR: *os << "NO_ERROR"; + break; + case UNKNOWN_ERROR: *os << "UNKNOWN_ERROR"; + break; + case KEY_ADDED: *os << "KEY_ADDED"; + break; + case KEY_ERROR: *os << "KEY_ERROR"; + break; + case KEY_MESSAGE: *os << "KEY_MESSAGE"; + break; + case NEED_KEY: *os << "NEED_KEY"; + break; + case KEY_CANCELED: *os << "KEY_CANCELED"; + break; + case NEED_PROVISIONING: *os << "NEED_PROVISIONING"; + break; + case DEVICE_REVOKED: *os << "DEVICE_REVOKED"; + break; + case INSUFFICIENT_CRYPTO_RESOURCES: *os << "INSUFFICIENT_CRYPTO_RESOURCES"; + break; + default: + *os << "Unknown CdmResponseType"; + break; + } +} + +void PrintTo(const enum CdmEventType& value, ::std::ostream* os) { + switch(value) { + case LICENSE_EXPIRED_EVENT: *os << "LICENSE_EXPIRED_EVENT"; + break; + case LICENSE_RENEWAL_NEEDED_EVENT: *os << "LICENSE_RENEWAL_NEEDED_EVENT"; + break; + default: + *os << "Unknown CdmEventType"; + break; + } +}; + +void PrintTo(const enum CdmLicenseType& value, ::std::ostream* os) { + switch(value) { + case kLicenseTypeOffline: *os << "kLicenseTypeOffline"; + break; + case kLicenseTypeStreaming: *os << "kLicenseTypeStreaming"; + break; + case kLicenseTypeRelease: *os << "kLicenseTypeRelease"; + break; + default: + *os << "Unknown CdmLicenseType"; + break; + } +}; + +void PrintTo(const enum CdmSecurityLevel& value, ::std::ostream* os) { + switch(value) { + case kSecurityLevelUninitialized: *os << "kSecurityLevelUninitialized"; + break; + case kSecurityLevelL1: *os << "kSecurityLevelL1"; + break; + case kSecurityLevelL2: *os << "kSecurityLevelL2"; + break; + case kSecurityLevelL3: *os << "kSecurityLevelL3"; + break; + case kSecurityLevelUnknown: *os << "kSecurityLevelUnknown"; + break; + default: + *os << "Unknown CdmSecurityLevel"; + break; + } +}; + +void PrintTo(const enum CdmCertificateType& value, ::std::ostream* os) { + switch(value) { + case kCertificateWidevine: *os << "kCertificateWidevine"; + break; + case kCertificateX509: *os << "kCertificateX509"; + break; + default: + *os << "Unknown CdmCertificateType"; + break; + } +}; + +}; // namespace wvcdm diff --git a/core/test/test_printers.h b/core/test/test_printers.h new file mode 100644 index 00000000..a50c4cc8 --- /dev/null +++ b/core/test/test_printers.h @@ -0,0 +1,19 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// This file adds some print methods so that when unit tests fail, the +// will print the name of an enumeration instead of the numeric value. + +#ifndef CDM_TEST_PRINTERS_H_ +#define CDM_TEST_PRINTERS_H_ + +#include +#include "wv_cdm_types.h" + +namespace wvcdm { +void PrintTo(const enum CdmResponseType& value, ::std::ostream* os); +void PrintTo(const enum CdmEventType& value, ::std::ostream* os); +void PrintTo(const enum CdmLicenseType& value, ::std::ostream* os); +void PrintTo(const enum CdmSecurityLevel& value, ::std::ostream* os); +void PrintTo(const enum CdmCertificateType& value, ::std::ostream* os); +}; // namespace wvcdm + +#endif // CDM_TEST_PRINTERS_H_ diff --git a/core/test/timer_unittest.cpp b/core/test/timer_unittest.cpp index edc63879..9a2940d1 100644 --- a/core/test/timer_unittest.cpp +++ b/core/test/timer_unittest.cpp @@ -10,9 +10,7 @@ class TestTimerHandler : public TimerHandler { TestTimerHandler() : timer_events_(0) {}; virtual ~TestTimerHandler() {}; - virtual void OnTimerEvent() { - timer_events_++; - } + virtual void OnTimerEvent() { timer_events_++; } uint32_t timer_events() { return timer_events_; } void ResetTimerEvents() { timer_events_ = 0; } @@ -41,14 +39,13 @@ TEST(TimerTest, TimerCheck) { EXPECT_TRUE(timer.IsRunning()); sleep(duration); - EXPECT_LE(duration-1, handler.timer_events()); - EXPECT_LE(handler.timer_events(), duration+1); + EXPECT_LE(duration - 1, handler.timer_events()); + EXPECT_LE(handler.timer_events(), duration + 1); timer.Stop(); EXPECT_FALSE(timer.IsRunning()); sleep(duration); - EXPECT_LE(duration-1, handler.timer_events()); - EXPECT_LE(handler.timer_events(), duration+1); + EXPECT_LE(duration - 1, handler.timer_events()); + EXPECT_LE(handler.timer_events(), duration + 1); } - } diff --git a/core/test/url_request.cpp b/core/test/url_request.cpp index e15fb357..ca173ac3 100644 --- a/core/test/url_request.cpp +++ b/core/test/url_request.cpp @@ -12,9 +12,9 @@ namespace { const int kReadBufferSize = 1024; -const int kConnectTimeoutMs = 5000; -const int kWriteTimeoutMs = 3000; -const int kReadTimeoutMs = 3000; +const int kConnectTimeoutMs = 15000; +const int kWriteTimeoutMs = 12000; +const int kReadTimeoutMs = 12000; // Concatenate all chunks into one blob and returns the response with // header information. @@ -69,12 +69,17 @@ void ConcatenateChunkedResponse(const std::string http_response, } // namespace - namespace wvcdm { UrlRequest::UrlRequest(const std::string& url) - : is_connected_(false), - socket_(url) { + : is_connected_(false), socket_(url) { + Reconnect(); +} + +UrlRequest::~UrlRequest() {} + +bool UrlRequest::Reconnect() { + socket_.CloseSocket(); if (socket_.Connect(kConnectTimeoutMs)) { is_connected_ = true; } else { @@ -83,8 +88,6 @@ UrlRequest::UrlRequest(const std::string& url) } } -UrlRequest::~UrlRequest() {} - bool UrlRequest::GetResponse(std::string* message) { std::string response; diff --git a/core/test/url_request.h b/core/test/url_request.h index 498bb624..f178c0a2 100644 --- a/core/test/url_request.h +++ b/core/test/url_request.h @@ -17,6 +17,7 @@ class UrlRequest { ~UrlRequest(); bool is_connected() const { return is_connected_; } + bool Reconnect(); bool PostRequest(const std::string& data); bool PostCertRequestInQueryString(const std::string& data); diff --git a/linux/src/log.cpp b/linux/src/log.cpp index 719e7e51..323542e1 100644 --- a/linux/src/log.cpp +++ b/linux/src/log.cpp @@ -9,16 +9,35 @@ #include "log.h" +namespace { + +FILE* output_file = stdout; + +} // namespace + namespace wvcdm { LogPriority g_cutoff = LOG_WARN; void InitLogging(int argc, const char* const* argv) { + // default to stdout if no arguments set it otherwise. + output_file = stdout; + for (int i = 1; i < argc; i++) { - if (strncmp(argv[i], "-v", 2) == 0) { + if (!strcmp(argv[i], "-v")) { g_cutoff = LOG_VERBOSE; + } else if (!strcmp(argv[i], "-")) { + // default above is stdout, so keep that. + } else { + // open a file for output. + output_file = fopen(argv[i], "a"); + if (!output_file) { + output_file = stdout; + printf("Could not open log file \"%s\"\n", argv[i]); + } } } + const char *verbose_env = getenv("VERBOSE_LOG"); if (verbose_env && ! strncmp(verbose_env, "yes", 3) ) { g_cutoff = LOG_VERBOSE; @@ -28,20 +47,21 @@ void InitLogging(int argc, const char* const* argv) { void Log(const char* file, int line, LogPriority level, const char* fmt, ...) { const char* severities[] = { "ERROR", "WARN", "INFO", "DEBUG", "VERBOSE" }; if (level >= sizeof(severities) / sizeof(*severities)) { - printf("[FATAL:%s(%d)] Invalid log priority level: %d\n", file, line, - level); + fprintf(output_file, "[FATAL:%s(%d)] Invalid log priority level: %d\n", + file, line, level); return; } if (level > g_cutoff) return; - printf("[%s:%s(%d)] ", severities[level], file, line); + fprintf(output_file, "[%s:%s(%d)] ", severities[level], file, line); va_list ap; va_start(ap, fmt); - vprintf(fmt, ap); + vfprintf(output_file, fmt, ap); va_end(ap); - putc('\n', stdout); + putc('\n', output_file); + fflush(output_file); } }; // namespace wvcdm diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index ba095d4d..9998c978 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -562,7 +562,7 @@ OEMCryptoResult OEMCrypto_GenerateSignature( * as a successful comparison). * * 2. The enc_mac_keys pointer must be either null, or point inside the - * message. If the pointer enc_mac_keys is not null, [e]the API shall verify + * message. If the pointer enc_mac_keys is not null, the API shall verify * that the two pointers enc_mac_keys_iv and enc_mac_keys point to locations in * the message. I.e. (message <= p && p < message+message_length)for p in * each of enc_mac_keys_iv, enc_mac_keys. If not, return @@ -603,8 +603,8 @@ OEMCryptoResult OEMCrypto_GenerateSignature( * license from being loaded more than once, and will be used for online * streaming. * - * - If Replay_Control is 2 = “Require existing Session Usage table entry or - * Nonce”, then OEMCrypto will check the Session Usage table for an existing + * - If Replay_Control is 2 = "Require existing Session Usage table entry or + * Nonce", then OEMCrypto will check the Session Usage table for an existing * entry with the same pst. * * --- If the pst is not in the table yet, a new entry will be created in the @@ -867,8 +867,8 @@ OEMCryptoResult OEMCrypto_SelectKey(const OEMCrypto_SESSION session, * secure buffer. * * If the session has an entry in the Usage Table, then OEMCrypto will update the - * 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.[f] + * 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. * * * Verification: @@ -877,7 +877,7 @@ OEMCryptoResult OEMCrypto_SelectKey(const OEMCrypto_SESSION session, * * 1. If the current key’s control block has a nonzero Duration field, then the * API shall verify that the duration is greater than the session’s elapsed time - * clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED[g]. + * clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED. * * 2. If the current key’s control block has the Data_Path_Type bit set, then the * API shall verify that the output buffer is secure or direct. If not, return @@ -893,8 +893,8 @@ OEMCryptoResult OEMCrypto_SelectKey(const OEMCrypto_SESSION session, * current version is not at least as high as that in the control block, then * return OEMCrypto_ERROR_INSUFFICIENT_HDCP. * - * 1. If the current session has an entry in the Usage Table, and the status of - * that entry is “inactive”, then return OEMCrypto_ERROR_INVALID_SESSION.[h] + * 5. If the current session has an entry in the Usage Table, and the status of + * that entry is "inactive", then return OEMCrypto_ERROR_INVALID_SESSION. * * If the flag is_encrypted is false, then no verification is performed. This * call shall copy clear data even when there are no keys loaded, or there is no @@ -1160,11 +1160,11 @@ OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, * GenerateDerivedKeys. * * After decrypting enc_rsa_key, If the first four bytes of the buffer are the - * string “SIGN”, then the actual RSA key begins on the 9th byte of the buffer. - * The second four bytes of the buffer is the 32 bit field “allowed_schemes”, + * string "SIGN", then the actual RSA key begins on the 9th byte of the buffer. + * The second four bytes of the buffer is the 32 bit field "allowed_schemes", * of type RSA_Padding_Scheme, which is used in OEMCrypto_GenerateRSASignature. The * value of allowed_schemes must also be wrapped with RSA key. We recommend - * storing the magic string “SIGN” with the key to distinguish keys that have a + * storing the magic string "SIGN" with the key to distinguish keys that have a * value for allowed_schemes from those that should use the default * allowed_schemes. Devices that do not support the alternative signing * algorithms may refuse to load these keys and return an error of @@ -1173,7 +1173,7 @@ OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, * authentication when acting as a ChromeCast receiver. This is not needed for * devices that wish to send data to a ChromeCast. * - * If the first four bytes of the buffer enc_rsa_key are not the string “SIGN”, + * If the first four bytes of the buffer enc_rsa_key are not the string "SIGN", * then the default value of allowed_schemes = 1 will be used. * * Verification: @@ -1262,7 +1262,7 @@ OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey(OEMCrypto_SESSION session, * the one verified and wrapped by OEMCrypto_RewrapDeviceRSAKey. The RSA private * key should be stored in secure memory. * - * If the bit field “allowed_schemes” was wrapped with this RSA key, its value + * If the bit field "allowed_schemes" was wrapped with this RSA key, its value * will be loaded and stored with the RSA key. If there was not bit field * wrapped with the RSA key, the key will use a default value of 1 = RSASSA-PSS * with SHA1. @@ -1550,8 +1550,8 @@ bool OEMCrypto_SupportsUsageTable(); * This function encrypts a generic buffer of data using the current key. * * If the session has an entry in the Usage Table, then OEMCrypto will update the - * 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. + * 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. * * Verification: * The following checks should be performed. If any check fails, an error is @@ -1562,7 +1562,7 @@ bool OEMCrypto_SupportsUsageTable(); * API shall verify that the duration is greater than the session’s elapsed time * clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED. * 3. If the current session has an entry in the Usage Table, and the status of - * that entry is “inactive”, then return OEMCrypto_ERROR_INVALID_SESSION. + * that entry is "inactive", then return OEMCrypto_ERROR_INVALID_SESSION. * * Parameters: * session (in) - crypto session identifier. @@ -1602,8 +1602,8 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt(OEMCrypto_SESSION session, * This function decrypts a generic buffer of data using the current key. * * If the session has an entry in the Usage Table, then OEMCrypto will update the - * 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. + * 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. * * Verification: * The following checks should be performed. If any check fails, an error is @@ -1616,7 +1616,7 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt(OEMCrypto_SESSION session, * API shall verify that the duration is greater than the session’s elapsed time * clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED. * 4. If the current session has an entry in the Usage Table, and the status of - * that entry is “inactive”, then return OEMCrypto_ERROR_INVALID_SESSION. + * that entry is "inactive", then return OEMCrypto_ERROR_INVALID_SESSION. * * Parameters: * session (in) - crypto session identifier. @@ -1657,8 +1657,8 @@ OEMCryptoResult OEMCrypto_Generic_Decrypt(OEMCrypto_SESSION session, * This function signs a generic buffer of data using the current key. * * If the session has an entry in the Usage Table, then OEMCrypto will update the - * 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. + * 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. * * Verification: * The following checks should be performed. If any check fails, an error is @@ -1668,7 +1668,7 @@ OEMCryptoResult OEMCrypto_Generic_Decrypt(OEMCrypto_SESSION session, * API shall verify that the duration is greater than the session’s elapsed time * clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED. * 3. If the current session has an entry in the Usage Table, and the status of - * that entry is “inactive”, then return OEMCrypto_ERROR_INVALID_SESSION. + * that entry is "inactive", then return OEMCrypto_ERROR_INVALID_SESSION. * * Parameters: * session (in) - crypto session identifier. @@ -1714,8 +1714,8 @@ OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session, * current key. * * If the session has an entry in the Usage Table, then OEMCrypto will update the - * 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. + * 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. * * Verification: * The following checks should be performed. If any check fails, an error is @@ -1730,7 +1730,7 @@ OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session, * API shall verify that the duration is greater than the session’s elapsed time * clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED. * 5. If the current session has an entry in the Usage Table, and the status of - * that entry is “inactive”, then return OEMCrypto_ERROR_INVALID_SESSION. + * that entry is "inactive", then return OEMCrypto_ERROR_INVALID_SESSION. * * Parameters: * session (in) - crypto session identifier. @@ -1797,8 +1797,8 @@ OEMCryptoResult OEMCrypto_UpdateUsageTable(); * * Description: * Find the entry in the Usage Table with a matching PST. Mark the status of - * that entry as “inactive”. If it corresponds to an open session, the status of - * that session will also be marked as “inactive”. Then OEMCrypto will increment + * that entry as "inactive". If it corresponds to an open session, the status of + * that session will also be marked as "inactive". Then OEMCrypto will increment * Usage Table’s generation number, sign, encrypt, and save the Usage Table. * * If no entry in the Usage Table has a matching PST, return the error @@ -1934,7 +1934,7 @@ OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session, * the computed signature matches the signature passed in. The signature will be * computed using HMAC-SHA256 and the mac_key_server. If they do not match, * return OEMCrypto_ERROR_SIGNATURE_FAILURE. - * 3. If the session is not associated with an entry in the Usage Table, return + * 3. If the session is not associated with an entry in the Usage Table, return * OEMCrypto_ERROR_UNKNOWN_FAILURE. * 4. If the pst passed in as a parameter does not match that in the Usage Table, * return OEMCrypto_ERROR_UNKNOWN_FAILURE. diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index 11d2aacc..4bdb2d85 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -26,6 +26,7 @@ #include "OEMCryptoCENC.h" #include "oemcrypto_key_mock.h" +#include "log.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" @@ -899,6 +900,7 @@ 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), @@ -1546,13 +1548,14 @@ class OEMCryptoClientTest : public ::testing::Test { protected: OEMCryptoClientTest() {} - void SetUp() { + virtual void SetUp() { ::testing::Test::SetUp(); wvcdm::Properties::Init(); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); + wvcdm::g_cutoff = wvcdm::LOG_INFO; } - void TearDown() { + virtual void TearDown() { OEMCrypto_Terminate(); ::testing::Test::TearDown(); } @@ -1766,36 +1769,88 @@ TEST_F(OEMCryptoClientTest, PreventNonceFlood) { s.open(); int error_counter = 0; uint32_t nonce; - // More than 20 nonces should generate an error. + time_t test_start = time(NULL); + // More than 20 nonces per second should generate an error. // To allow for some slop, we actually test for more than 40. for (int i = 0; i < 60; i++) { s.GenerateNonce(&nonce, &error_counter); } - ASSERT_LE(20, error_counter); + time_t test_end = time(NULL); + // Either there should be a two second delay, or there should have been + // at least 20 errors. + if (20 > error_counter) { + EXPECT_LE(2, test_end - test_start); + } else { + EXPECT_LE(20, error_counter); + } error_counter = 0; sleep(2); // After a pause, we should be able to regenerate nonces. s.GenerateNonce(&nonce, &error_counter); - ASSERT_EQ(0, error_counter); + EXPECT_EQ(0, error_counter); } // Prevent a nonce flood even if each nonce is in a different session. TEST_F(OEMCryptoClientTest, PreventNonceFlood2) { int error_counter = 0; uint32_t nonce; - // More than 20 nonces should generate an error. + time_t test_start = time(NULL); + // More than 20 nonces per second should generate an error. // To allow for some slop, we actually test for more than 40. for (int i = 0; i < 60; i++) { Session s; s.open(); + EXPECT_TRUE(s.isOpen()); s.GenerateNonce(&nonce, &error_counter); } - ASSERT_LE(20, error_counter); + time_t test_end = time(NULL); + // Either there should be a two second delay, or there should have been + // at least 20 errors. + if (20 > error_counter) { + EXPECT_LE(2, test_end - test_start); + } else { + EXPECT_LE(20, error_counter); + } error_counter = 0; sleep(2); // After a pause, we should be able to regenerate nonces. Session s; s.open(); s.GenerateNonce(&nonce, &error_counter); - ASSERT_EQ(0, error_counter); + EXPECT_EQ(0, error_counter); +} + +// Prevent a nonce flood even if some nonces are in a different session. This +// is different from the test above because there are several session open at +// the same time. We want to make sure you can't get a flood of nonces by +// opening a flood of sessions. +TEST_F(OEMCryptoClientTest, PreventNonceFlood3) { + int error_counter = 0; + uint32_t nonce; + time_t test_start = time(NULL); + // More than 20 nonces per second should generate an error. + // To allow for some slop, we actually test for more than 40. + Session s[6]; + for (int i = 0; i < 6; i++) { + s[i].open(); + EXPECT_TRUE(s[i].isOpen()); + for (int j = 0; j < 10; j++) { + s[i].GenerateNonce(&nonce, &error_counter); + } + } + time_t test_end = time(NULL); + // Either there should be a two second delay, or there should have been + // at least 20 errors. + if (20 > error_counter) { + EXPECT_LE(2, test_end - test_start); + } else { + EXPECT_LE(20, error_counter); + } + error_counter = 0; + sleep(2); // After a pause, we should be able to regenerate nonces. + Session s7; + s7.open(); + EXPECT_TRUE(s7.isOpen()); + s7.GenerateNonce(&nonce, &error_counter); + EXPECT_EQ(0, error_counter); } TEST_F(OEMCryptoClientTest, GenerateDerivedKeys) { @@ -1815,18 +1870,43 @@ TEST_F(OEMCryptoClientTest, GenerateDerivedKeys) { class DISABLED_TestKeybox : public OEMCryptoClientTest { protected: - void SetUp() { + virtual void SetUp() { OEMCryptoClientTest::SetUp(); InstallKeybox(kDefaultKeybox, true); OEMCrypto_DeleteUsageTable(); } + virtual void TearDown() { + InstallKeybox(kDefaultKeybox, true); + OEMCryptoClientTest::TearDown(); + } + + bool IsDefaultKeyboxInstalled() { + uint8_t key_data[256]; + size_t key_data_len = sizeof(key_data); + OEMCryptoResult sts = OEMCrypto_GetKeyData(key_data, &key_data_len); + + uint32_t* data = reinterpret_cast(key_data); + uint32_t system_id = htonl(data[1]); + return sts == OEMCrypto_SUCCESS && system_id == 0x1019; + } + void InstallKeybox(const wvoec_mock::WidevineKeybox& keybox, bool good) { OEMCryptoResult sts; uint8_t wrapped[sizeof(wvoec_mock::WidevineKeybox)]; size_t length = sizeof(wvoec_mock::WidevineKeybox); sts = OEMCrypto_WrapKeybox(reinterpret_cast(&keybox), sizeof(keybox), wrapped, &length, NULL, 0); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED && + &keybox == &kDefaultKeybox && + IsDefaultKeyboxInstalled()) { + // Allow implementations which don't support installation to continue + // so long as: + // 1) we wanted to install the default keybox AND + // 2) the default keybox is already installed. + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); sts = OEMCrypto_InstallKeybox(wrapped, sizeof(keybox)); if (good) { @@ -1884,7 +1964,6 @@ TEST_F(DISABLED_TestKeybox, BadCRCKeybox) { InstallKeybox(keybox, false); sts = OEMCrypto_IsKeyboxValid(); ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); - InstallKeybox(kDefaultKeybox, true); } TEST_F(DISABLED_TestKeybox, BadMagicKeybox) { @@ -1894,7 +1973,6 @@ TEST_F(DISABLED_TestKeybox, BadMagicKeybox) { InstallKeybox(keybox, false); sts = OEMCrypto_IsKeyboxValid(); ASSERT_EQ(OEMCrypto_ERROR_BAD_MAGIC, sts); - InstallKeybox(kDefaultKeybox, true); } TEST_F(DISABLED_TestKeybox, BadDataKeybox) { @@ -1904,11 +1982,9 @@ TEST_F(DISABLED_TestKeybox, BadDataKeybox) { InstallKeybox(keybox, false); sts = OEMCrypto_IsKeyboxValid(); ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); - InstallKeybox(kDefaultKeybox, true); } TEST_F(DISABLED_TestKeybox, GenerateSignature) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); @@ -1942,7 +2018,6 @@ TEST_F(DISABLED_TestKeybox, GenerateSignature) { } TEST_F(DISABLED_TestKeybox, LoadKeyNoNonce) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -1952,7 +2027,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyNoNonce) { } TEST_F(DISABLED_TestKeybox, LoadKeyWithNonce) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -1962,7 +2036,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithNonce) { } TEST_F(DISABLED_TestKeybox, LoadKeyWithNoMAC) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2000,7 +2073,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithNoMAC) { of all the pointers. It should reject a message if the pointer does not point into the message buffer */ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange1) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2019,7 +2091,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange1) { } TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange2) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2038,7 +2109,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange2) { } TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange3) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2057,7 +2127,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange3) { } TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange4) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2077,7 +2146,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange4) { } TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange5) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2095,7 +2163,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange5) { } TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange6) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); @@ -2116,7 +2183,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange6) { } TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange7) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); @@ -2137,7 +2203,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange7) { } TEST_F(DISABLED_TestKeybox, LoadKeyWithBadNonce) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2152,7 +2217,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadNonce) { } TEST_F(DISABLED_TestKeybox, LoadKeyWithRepeatNonce) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2176,7 +2240,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithRepeatNonce) { } TEST_F(DISABLED_TestKeybox, LoadKeyWithBadVerification) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); @@ -2193,7 +2256,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadVerification) { } TEST_F(DISABLED_TestKeybox, LoadKeysBadSignature) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2208,7 +2270,6 @@ TEST_F(DISABLED_TestKeybox, LoadKeysBadSignature) { } TEST_F(DISABLED_TestKeybox, LoadKeysWithNoDerivedKeys) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); // s.GenerateDerivedKeys(); @@ -2226,7 +2287,6 @@ class DISABLED_DecryptWithHDCP : public DISABLED_TestKeybox, public: void DecryptWithHDCP(OEMCrypto_HDCP_Capability version) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); OEMCrypto_HDCP_Capability current, maximum; sts = OEMCrypto_GetHDCPCapability(¤t, &maximum); ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -2286,7 +2346,7 @@ TEST_P(DISABLED_RefreshKeyTest, RefreshWithNonce) { s.RefreshTestKeys(num_keys_, wvoec_mock::kControlNonceEnabled, nonce, true); } -TEST_P(DISABLED_RefreshKeyTest, RefresNoNonce) { +TEST_P(DISABLED_RefreshKeyTest, RefreshNoNonce) { Session s; s.open(); s.GenerateDerivedKeys(); @@ -2339,7 +2399,6 @@ INSTANTIATE_TEST_CASE_P(TestRefreshEachKeys, DISABLED_RefreshKeyTest, // TEST_F(DISABLED_TestKeybox, Decrypt) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); @@ -2352,7 +2411,6 @@ TEST_F(DISABLED_TestKeybox, Decrypt) { TEST_F(DISABLED_TestKeybox, DecryptZeroDuration) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); @@ -2365,7 +2423,6 @@ TEST_F(DISABLED_TestKeybox, DecryptZeroDuration) { TEST_F(DISABLED_TestKeybox, DecryptWithOffset) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); @@ -2460,7 +2517,6 @@ vector EncryptCTR(const vector& key, TEST_F(DISABLED_TestKeybox, DecryptWithNearWrap) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); @@ -2511,7 +2567,6 @@ TEST_F(DISABLED_TestKeybox, DecryptWithNearWrap) { TEST_F(DISABLED_TestKeybox, DecryptUnencrypted) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); @@ -2558,7 +2613,6 @@ TEST_F(DISABLED_TestKeybox, DecryptUnencrypted) { TEST_F(DISABLED_TestKeybox, DecryptUnencryptedNoKey) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); @@ -2597,7 +2651,6 @@ TEST_F(DISABLED_TestKeybox, DecryptUnencryptedNoKey) { TEST_F(DISABLED_TestKeybox, DecryptSecureToClear) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2611,7 +2664,6 @@ TEST_F(DISABLED_TestKeybox, DecryptSecureToClear) { TEST_F(DISABLED_TestKeybox, KeyDuration) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2657,7 +2709,6 @@ TEST_F(DISABLED_TestKeybox, ValidateRSATestKeys) { } TEST_F(DISABLED_TestKeybox, CertificateProvision) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2674,7 +2725,6 @@ TEST_F(DISABLED_TestKeybox, CertificateProvision) { } TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRange1) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2702,7 +2752,6 @@ TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRange1) { } TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRange2) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2732,7 +2781,6 @@ TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRange2) { } TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRange3) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2763,7 +2811,6 @@ TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRange3) { } TEST_F(DISABLED_TestKeybox, CertificateProvisionBadSignature) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2792,7 +2839,6 @@ TEST_F(DISABLED_TestKeybox, CertificateProvisionBadSignature) { } TEST_F(DISABLED_TestKeybox, CertificateProvisionBadNonce) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2821,7 +2867,6 @@ TEST_F(DISABLED_TestKeybox, CertificateProvisionBadNonce) { } TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRSAKey) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -2851,7 +2896,6 @@ TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRSAKey) { TEST_F(DISABLED_TestKeybox, LoadWrappedRSAKey) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); std::vector wrapped_rsa_key; CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); @@ -2864,7 +2908,6 @@ TEST_F(DISABLED_TestKeybox, LoadWrappedRSAKey) { TEST_F(DISABLED_TestKeybox, RSASignature) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); std::vector wrapped_rsa_key; CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); @@ -2902,7 +2945,6 @@ TEST_F(DISABLED_TestKeybox, RSASignature) { } TEST_F(DISABLED_TestKeybox, LoadRSASessionKey) { - InstallKeybox(kDefaultKeybox, true); std::vector wrapped_rsa_key; CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); Session s; @@ -2912,7 +2954,6 @@ TEST_F(DISABLED_TestKeybox, LoadRSASessionKey) { TEST_F(DISABLED_TestKeybox, CertificateDecrypt) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); std::vector wrapped_rsa_key; CreateWrappedRSAKey(&wrapped_rsa_key, kSign_RSASSA_PSS, true); Session s; @@ -3006,7 +3047,6 @@ class DISABLED_AlternateRSAAlgorithms : public DISABLED_TestKeybox { } void LoadWithAllowedSchemes(uint32_t schemes, bool force) { - InstallKeybox(kDefaultKeybox, true); CreateWrappedRSAKey(&wrapped_rsa_key_, schemes, force); key_loaded_ = (wrapped_rsa_key_.size() > 0); } @@ -3080,6 +3120,7 @@ class DISABLED_AlternateRSAKey : public DISABLED_TestKeybox { void BuildRSAKey() { vector field_n = encode(0x02, wvcdm::a2b_hex( + "00" "df271fd25f8644496b0c81be4bd50297" "ef099b002a6fd67727eb449cea566ed6" "a3981a71312a141cabc9815c1209e320" @@ -3099,6 +3140,7 @@ class DISABLED_AlternateRSAKey : public DISABLED_TestKeybox { vector field_e = encode(0x02, wvcdm::a2b_hex("010001")); vector field_d = encode(0x02, wvcdm::a2b_hex( + "00" "5bd910257830dce17520b03441a51a8c" "ab94020ac6ecc252c808f3743c95b7c8" "3b8c8af1a5014346ebc4242cdfb5d718" @@ -3117,6 +3159,7 @@ class DISABLED_AlternateRSAKey : public DISABLED_TestKeybox { "d4c6591c807defd71ab06866bb5e7745")); vector field_p = encode(0x02, wvcdm::a2b_hex( + "00" "f44f5e4246391f482b2f5296e3602eb3" "4aa136427710f7c0416d403fd69d4b29" "130cfebef34e885abdb1a8a0a5f0e9b5" @@ -3127,6 +3170,7 @@ class DISABLED_AlternateRSAKey : public DISABLED_TestKeybox { "bd9efc123d9c54b6705590d006cfcf3f")); vector field_q = encode(0x02, wvcdm::a2b_hex( + "00" "e9d49841e0e0a6ad0d517857133e36dc" "72c1bdd90f9174b52e26570f373640f1" "c185e7ea8e2ed7f1e4ebb951f70a5802" @@ -3137,6 +3181,7 @@ class DISABLED_AlternateRSAKey : public DISABLED_TestKeybox { "9eec1cf85e80aba079b2e6106b97bced")); vector field_exp1 = encode(0x02, wvcdm::a2b_hex( + "00" "ed102acdb26871534d1c414ecad9a4d7" "32fe95b10eea370da62f05de2c393b1a" "633303ea741b6b3269c97f704b352702" @@ -3147,6 +3192,7 @@ class DISABLED_AlternateRSAKey : public DISABLED_TestKeybox { "0dcbbc9b528f64a01706e05b0b91106f")); vector field_exp2 = encode(0x02, wvcdm::a2b_hex( + "00" "6827924a85e88b55ba00f8219128bd37" "24c6b7d1dfe5629ef197925fecaff5ed" "b9cdf3a7befd8ea2e8dd3707138b3ff8" @@ -3157,6 +3203,7 @@ class DISABLED_AlternateRSAKey : public DISABLED_TestKeybox { "eec82d7f5458ec19e71b90eeef7dff61")); vector field_invq = encode(0x02, wvcdm::a2b_hex( + "00" "57b73888d183a99a6307422277551a3d" "9e18adf06a91e8b55ceffef9077c8496" "948ecb3b16b78155cb2a3a57c119d379" @@ -3295,7 +3342,6 @@ class DISABLED_AlternateRSAKey : public DISABLED_TestKeybox { void LoadWithAllowedSchemes(uint32_t schemes, bool force) { BuildRSAKey(); - InstallKeybox(kDefaultKeybox, true); CreateWrappedRSAKey(&wrapped_rsa_key_, schemes, force, &encoded_key_[0], encoded_key_.size()); key_loaded_ = (wrapped_rsa_key_.size() > 0); @@ -4072,7 +4118,6 @@ class DISABLED_GenericDRMTest : public DISABLED_TestKeybox { void BadEncrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, size_t buffer_length) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4094,7 +4139,6 @@ class DISABLED_GenericDRMTest : public DISABLED_TestKeybox { void BadDecrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, size_t buffer_length) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4115,7 +4159,6 @@ class DISABLED_GenericDRMTest : public DISABLED_TestKeybox { void BadSign(unsigned int key_index, OEMCrypto_Algorithm algorithm) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4139,7 +4182,6 @@ class DISABLED_GenericDRMTest : public DISABLED_TestKeybox { void BadVerify(unsigned int key_index, OEMCrypto_Algorithm algorithm, size_t signature_size, bool alter_data) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4162,7 +4204,6 @@ class DISABLED_GenericDRMTest : public DISABLED_TestKeybox { }; TEST_F(DISABLED_GenericDRMTest, GenericKeyLoad) { - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4173,7 +4214,6 @@ TEST_F(DISABLED_GenericDRMTest, GenericKeyLoad) { TEST_F(DISABLED_GenericDRMTest, GenericKeyEncrypt) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4204,7 +4244,6 @@ TEST_F(DISABLED_GenericDRMTest, GenericKeyBadEncrypt) { TEST_F(DISABLED_GenericDRMTest, GenericKeyDecrypt) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4226,7 +4265,6 @@ TEST_F(DISABLED_GenericDRMTest, GenericKeyDecrypt) { TEST_F(DISABLED_GenericDRMTest, GenericSecureToClear) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4258,7 +4296,6 @@ TEST_F(DISABLED_GenericDRMTest, GenericKeyBadDecrypt) { TEST_F(DISABLED_GenericDRMTest, GenericKeySign) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4295,7 +4332,6 @@ TEST_F(DISABLED_GenericDRMTest, GenericKeyBadSign) { TEST_F(DISABLED_GenericDRMTest, GenericKeyVerify) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4327,7 +4363,6 @@ TEST_F(DISABLED_GenericDRMTest, GenericKeyBadVerify) { TEST_F(DISABLED_GenericDRMTest, KeyDurationEncrypt) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4364,7 +4399,6 @@ TEST_F(DISABLED_GenericDRMTest, KeyDurationEncrypt) { TEST_F(DISABLED_GenericDRMTest, KeyDurationDecrypt) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4399,7 +4433,6 @@ TEST_F(DISABLED_GenericDRMTest, KeyDurationDecrypt) { TEST_F(DISABLED_GenericDRMTest, KeyDurationSign) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); @@ -4438,7 +4471,6 @@ TEST_F(DISABLED_GenericDRMTest, KeyDurationSign) { TEST_F(DISABLED_GenericDRMTest, KeyDurationVerify) { OEMCryptoResult sts; - InstallKeybox(kDefaultKeybox, true); Session s; s.open(); s.GenerateDerivedKeys(); diff --git a/platforms/global_config.gypi b/platforms/global_config.gypi index 66a82678..87bb1c34 100644 --- a/platforms/global_config.gypi +++ b/platforms/global_config.gypi @@ -14,7 +14,7 @@ 'oemcrypto_v8%': 'false', 'certificate_provision%': 'false', 'force_use_of_secure_buffers%': 'false', - 'disable_privacy_crypto%': 'false', + 'privacy_crypto_impl%': 'openssl', # There are three protobuf configurations: # diff --git a/cdm/test/gmock.gyp b/third_party/gmock.gyp similarity index 54% rename from cdm/test/gmock.gyp rename to third_party/gmock.gyp index 23f89c86..e8df5aa1 100644 --- a/cdm/test/gmock.gyp +++ b/third_party/gmock.gyp @@ -3,14 +3,15 @@ 'target_defaults': { 'type': 'static_library', 'include_dirs': [ - '../../third_party/gmock', - '../../third_party/gmock/include', - '../../third_party/gmock/gtest/include', + 'gmock', + 'gmock/include', + 'gmock/gtest', + 'gmock/gtest/include', ], 'direct_dependent_settings': { 'include_dirs': [ - '../../third_party/gmock/include', - '../../third_party/gmock/gtest/include', + 'gmock/include', + 'gmock/gtest/include', ], }, }, @@ -18,13 +19,19 @@ { 'target_name': 'gmock', 'sources': [ - '../../third_party/gmock/src/gmock-all.cc', + 'gmock/src/gmock-all.cc', ], }, { 'target_name': 'gmock_main', 'sources': [ - '../../third_party/gmock/src/gmock_main.cc', + 'gmock/src/gmock_main.cc', + ], + }, + { + 'target_name': 'gtest', + 'sources': [ + 'gmock/gtest/src/gtest-all.cc', ], }, ], diff --git a/third_party/gmock/README.android b/third_party/gmock/README.android index f77a880c..e7053cc7 100644 --- a/third_party/gmock/README.android +++ b/third_party/gmock/README.android @@ -14,6 +14,13 @@ needs a different format of build script, and it needs to build against the OS' modified version of gTest, rather than the packaged version. Local Modifications: +--Tue Jun 3, 2014 (joeyparrish)-- + +Removed DOS newlines for the sake of the git-to-piper bridge. Piper converts +newlines automatically to unix style. If this change is not made in git, the +bridge detects differences and attempts to sync to piper, even when nothing +has changed in git. + --Mon Feb 4, 2014 (juce)-- Upgraded to gMock 1.7.0. This is required for compatibility with the upgrade @@ -59,4 +66,4 @@ Excluded the following files from being copied to Android repo: make/ msvc/ fused-src/ - gtest/ \ No newline at end of file + gtest/