From 702aadf853ec0633e3c70e701f83cdcb93ff3a3e Mon Sep 17 00:00:00 2001 From: "John \"Juce\" Bruce" Date: Mon, 31 Mar 2014 14:12:11 -0700 Subject: [PATCH] Add Support for WebM Back Adds support for WebM to the CDM. Decryption remains untouched, however the initialization data is passed differently for WebM. The previous version of this change broke playback for certain apps that were being allowed to pass invalid MIME types before this change was made. This version maintains backwards-compatiblity for these apps for now by rewriting their MIME types as "video/mp4". Merge of https://widevine-internal-review.googlesource.com/9225/ and https://widevine-internal-review.googlesource.com/9611/ from the Widevine cdm repo. Bug: 10638562 Change-Id: Ib37e838d08363f07b34b3a2e79a3f80a1f43e9ad --- libwvdrmengine/cdm/core/include/cdm_engine.h | 1 + libwvdrmengine/cdm/core/include/cdm_session.h | 5 +- libwvdrmengine/cdm/core/include/license.h | 11 +- .../cdm/core/include/wv_cdm_constants.h | 2 + libwvdrmengine/cdm/core/src/cdm_engine.cpp | 3 +- libwvdrmengine/cdm/core/src/cdm_session.cpp | 29 ++-- libwvdrmengine/cdm/core/src/license.cpp | 90 +++++++++---- .../cdm/core/test/cdm_engine_test.cpp | 35 +++-- .../cdm/core/test/license_unittest.cpp | 27 +++- .../include/wv_content_decryption_module.h | 1 + .../cdm/src/wv_content_decryption_module.cpp | 3 +- .../cdm/test/request_license_test.cpp | 20 +-- libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp | 68 +++++++--- .../mediadrm/test/WVDrmPlugin_test.cpp | 127 ++++++++++++++++-- libwvdrmengine/src/WVDrmFactory.cpp | 6 +- .../test/unit/WVDrmFactory_test.cpp | 6 +- 16 files changed, 329 insertions(+), 105 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index aa4900f7..03f8d244 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -37,6 +37,7 @@ class CdmEngine : public TimerHandler { // Construct a valid license request virtual CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id, const CdmKeySetId& key_set_id, + const std::string& mime_type, const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index 6cc5920d..ac57f844 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -36,7 +36,8 @@ class CdmSession { bool VerifySession(const CdmKeySystem& key_system, const CdmInitData& init_data); - CdmResponseType GenerateKeyRequest(const CdmInitData& init_data, + CdmResponseType GenerateKeyRequest(const std::string& mime_type, + const CdmInitData& init_data, const CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, @@ -109,7 +110,7 @@ class CdmSession { CdmLicenseType license_type_; // license type offline related information - CdmInitData offline_pssh_data_; + CdmInitData offline_init_data_; CdmKeyMessage offline_key_request_; CdmKeyResponse offline_key_response_; CdmKeyMessage offline_key_renewal_request_; diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index 6e4534df..f888c9a0 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -27,7 +27,8 @@ class CdmLicense { bool Init(const std::string& token, CryptoSession* session, PolicyEngine* policy_engine); - bool PrepareKeyRequest(const CdmInitData& pssh_data, + bool PrepareKeyRequest(const std::string& mime_type, + const CdmInitData& init_data, const CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, const CdmSessionId& session_id, @@ -42,7 +43,7 @@ class CdmLicense { bool RestoreOfflineLicense(CdmKeyMessage& license_request, CdmKeyResponse& license_response, CdmKeyResponse& license_renewal_response); - bool HasInitData() { return !init_data_.empty(); } + bool HasInitData() { return !stored_init_data_.empty(); } bool IsKeyLoaded(const KeyId& key_id); private: @@ -54,12 +55,16 @@ 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); + CryptoSession* session_; PolicyEngine* policy_engine_; std::string server_url_; std::string token_; std::string service_certificate_; - std::string init_data_; + std::string stored_init_data_; bool initialized_; std::set loaded_keys_; diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h index 1f5291d9..58cbdf38 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h @@ -56,6 +56,8 @@ static const std::string QUERY_VALUE_SECURITY_LEVEL_L2 = "L2"; static const std::string QUERY_VALUE_SECURITY_LEVEL_L3 = "L3"; static const std::string QUERY_VALUE_SECURITY_LEVEL_Unknown = "Unknown"; +static const std::string ISO_BMFF_MIME_TYPE = "video/mp4"; +static const std::string WEBM_MIME_TYPE = "video/webm"; } // namespace wvcdm #endif // CDM_BASE_WV_CDM_CONSTANTS_H_ diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index ff1efe1f..de70bb89 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -124,6 +124,7 @@ CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) { CdmResponseType CdmEngine::GenerateKeyRequest( const CdmSessionId& session_id, const CdmKeySetId& key_set_id, + const std::string& mime_type, const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, @@ -179,7 +180,7 @@ CdmResponseType CdmEngine::GenerateKeyRequest( } } - sts = iter->second->GenerateKeyRequest(init_data, license_type, + sts = iter->second->GenerateKeyRequest(mime_type, init_data, license_type, app_parameters, key_request, server_url); diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index c3982a91..b88358c0 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -79,7 +79,7 @@ CdmResponseType CdmSession::RestoreOfflineSession( DeviceFiles::LicenseState license_state; - if (!handle.RetrieveLicense(key_set_id, &license_state, &offline_pssh_data_, + if (!handle.RetrieveLicense(key_set_id, &license_state, &offline_init_data_, &offline_key_request_, &offline_key_response_, &offline_key_renewal_request_, &offline_key_renewal_response_, @@ -119,9 +119,9 @@ bool CdmSession::VerifySession(const CdmKeySystem& key_system, } CdmResponseType CdmSession::GenerateKeyRequest( - const CdmInitData& init_data, const CdmLicenseType license_type, - const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, - std::string* server_url) { + const std::string& mime_type, const CdmInitData& init_data, + const CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, + CdmKeyMessage* key_request, std::string* server_url) { if (reinitialize_session_) { CdmResponseType sts = Init(); if (sts != NO_ERROR) { @@ -149,14 +149,18 @@ CdmResponseType CdmSession::GenerateKeyRequest( ? UNKNOWN_ERROR : GenerateRenewalRequest(key_request, server_url); } else { + if (mime_type != ISO_BMFF_MIME_TYPE && mime_type != WEBM_MIME_TYPE) { + LOGW("CdmSession::GenerateKeyRequest: unknown MIME type"); + return KEY_ERROR; + } if (init_data.empty() && !license_parser_.HasInitData()) { LOGW("CdmSession::GenerateKeyRequest: init data absent"); return KEY_ERROR; } - CdmInitData pssh_data = init_data; - if (Properties::extract_pssh_data()) { - if (!CdmEngine::ExtractWidevinePssh(init_data, &pssh_data)) { + CdmInitData extracted_init_data = init_data; + if (Properties::extract_pssh_data() && mime_type == ISO_BMFF_MIME_TYPE) { + if (!CdmEngine::ExtractWidevinePssh(init_data, &extracted_init_data)) { return KEY_ERROR; } } @@ -172,14 +176,15 @@ CdmResponseType CdmSession::GenerateKeyRequest( } } - if (!license_parser_.PrepareKeyRequest(pssh_data, license_type, - app_parameters, session_id_, - key_request, server_url)) { + if (!license_parser_.PrepareKeyRequest(mime_type, extracted_init_data, + license_type, app_parameters, + session_id_, key_request, + server_url)) { return KEY_ERROR; } if (license_type_ == kLicenseTypeOffline) { - offline_pssh_data_ = pssh_data; + offline_init_data_ = extracted_init_data; offline_key_request_ = *key_request; offline_release_server_url_ = *server_url; } @@ -410,7 +415,7 @@ bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) { return false; return handle.StoreLicense( - key_set_id_, state, offline_pssh_data_, offline_key_request_, + key_set_id_, state, offline_init_data_, offline_key_request_, offline_key_response_, offline_key_renewal_request_, offline_key_renewal_response_, offline_release_server_url_); } diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index a0d99c75..3085a070 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -84,6 +84,7 @@ using video_widevine_server::sdk::EncryptedClientIdentification; using video_widevine_server::sdk::LicenseRequest; using video_widevine_server::sdk::LicenseRequest_ContentIdentification; using video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC; +using video_widevine_server::sdk::LicenseRequest_ContentIdentification_WebM; using video_widevine_server::sdk:: LicenseRequest_ContentIdentification_ExistingLicense; using video_widevine_server::sdk::License; @@ -157,7 +158,8 @@ bool CdmLicense::Init(const std::string& token, CryptoSession* session, return true; } -bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, +bool CdmLicense::PrepareKeyRequest(const std::string& mime_type, + const CdmInitData& init_data, const CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, const CdmSessionId& session_id, @@ -167,7 +169,12 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, LOGE("CdmLicense::PrepareKeyRequest: not initialized"); return false; } - if (init_data.empty() && init_data_.empty()) { + if (mime_type != ISO_BMFF_MIME_TYPE && mime_type != WEBM_MIME_TYPE) { + LOGE("CdmLicense::PrepareKeyRequest: unsupported MIME type %s", + mime_type.c_str()); + return false; + } + if (init_data.empty() && stored_init_data_.empty()) { LOGE("CdmLicense::PrepareKeyRequest: empty init data provided"); return false; } @@ -192,7 +199,7 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, serialized_service_certificate = service_certificate_; if (privacy_mode_enabled && serialized_service_certificate.empty()) { - init_data_ = init_data; + stored_init_data_ = init_data; return PrepareServiceCertificateRequest(signed_request, server_url); } @@ -308,33 +315,44 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, LicenseRequest_ContentIdentification* content_id = license_request.mutable_content_id(); - LicenseRequest_ContentIdentification_CENC* cenc_content_id = - content_id->mutable_cenc_id(); + if (mime_type == ISO_BMFF_MIME_TYPE) { + LicenseRequest_ContentIdentification_CENC* cenc_content_id = + content_id->mutable_cenc_id(); - if (!init_data.empty()) { - cenc_content_id->add_pssh(init_data); - } else if (privacy_mode_enabled && !init_data_.empty()) { - cenc_content_id->add_pssh(init_data_); + if (!init_data.empty()) { + cenc_content_id->add_pssh(init_data); + } else if (privacy_mode_enabled && !stored_init_data_.empty()) { + cenc_content_id->add_pssh(stored_init_data_); + } else { + LOGE("CdmLicense::PrepareKeyRequest: ISO-CENC init data not available"); + return false; + } + + if (!PrepareContentId(license_type, request_id, cenc_content_id)) { + return false; + } + } else if (mime_type == WEBM_MIME_TYPE) { + LicenseRequest_ContentIdentification_WebM* webm_content_id = + content_id->mutable_webm_id(); + + if (!init_data.empty()) { + webm_content_id->set_header(init_data); + } else if (privacy_mode_enabled && !stored_init_data_.empty()) { + webm_content_id->set_header(stored_init_data_); + } else { + LOGE("CdmLicense::PrepareKeyRequest: WebM init data not available"); + return false; + } + + if (!PrepareContentId(license_type, request_id, webm_content_id)) { + return false; + } } else { - LOGD("CdmLicense::PrepareKeyRequest: init data not available"); + LOGE("CdmLicense::PrepareKeyRequest: no support for MIME type %s", + mime_type.c_str()); return false; } - switch (license_type) { - case kLicenseTypeOffline: - cenc_content_id->set_license_type(video_widevine_server::sdk::OFFLINE); - break; - case kLicenseTypeStreaming: - cenc_content_id->set_license_type(video_widevine_server::sdk::STREAMING); - break; - default: - LOGD("CdmLicense::PrepareKeyRequest: Unknown license type = %d", - license_type); - return false; - break; - } - cenc_content_id->set_request_id(request_id); - // TODO(jfore): The time field will be updated once the cdm wrapper // has been updated to pass us in the time. license_request.set_request_time(0); @@ -766,6 +784,28 @@ CdmResponseType CdmLicense::HandleKeyErrorResponse( } } +template +bool CdmLicense::PrepareContentId(const CdmLicenseType license_type, + const std::string& request_id, + T* content_id) { + switch (license_type) { + case kLicenseTypeOffline: + content_id->set_license_type(video_widevine_server::sdk::OFFLINE); + break; + case kLicenseTypeStreaming: + content_id->set_license_type( + video_widevine_server::sdk::STREAMING); + break; + default: + LOGD("CdmLicense::PrepareKeyRequest: Unknown license type = %d", + license_type); + return false; + } + + content_id->set_request_id(request_id); + return true; +} + bool CdmLicense::IsKeyLoaded(const KeyId& key_id) { return loaded_keys_.find(key_id) != loaded_keys_.end(); } diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 8343363c..63c250bd 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -16,6 +16,7 @@ #include "scoped_ptr.h" #include "string_conversions.h" #include "url_request.h" +#include "wv_cdm_constants.h" #include "wv_cdm_types.h" namespace { @@ -98,20 +99,22 @@ class WvCdmEngineTest : public testing::Test { protected: void GenerateKeyRequest(const std::string& key_system, - const std::string& key_id) { + const std::string& key_id, + const std::string& mime_type) { CdmAppParameterMap app_parameters; std::string server_url; std::string init_data = key_id; CdmKeySetId key_set_id; // TODO(rfrias): Temporary change till b/9465346 is addressed - if (!Properties::extract_pssh_data()) { + if (!Properties::extract_pssh_data() || mime_type != ISO_BMFF_MIME_TYPE) { EXPECT_TRUE(CdmEngine::ExtractWidevinePssh(key_id, &init_data)); } EXPECT_EQ(KEY_MESSAGE, cdm_engine_->GenerateKeyRequest(session_id_, key_set_id, + mime_type, init_data, kLicenseTypeStreaming, app_parameters, @@ -164,7 +167,7 @@ class WvCdmEngineTest : public testing::Test { std::string resp = GetKeyRequestResponse(server_url, client_auth); CdmKeySetId key_set_id; - EXPECT_EQ(cdm_engine_->AddKey(session_id_, resp, &key_set_id), KEY_ADDED); + EXPECT_EQ(KEY_ADDED, cdm_engine_->AddKey(session_id_, resp, &key_set_id)); } void VerifyRenewalKeyResponse(const std::string& server_url, @@ -172,7 +175,7 @@ class WvCdmEngineTest : public testing::Test { std::string& init_data){ std::string resp = GetKeyRequestResponse(server_url, client_auth); - EXPECT_EQ(cdm_engine_->RenewKey(session_id_, resp), wvcdm::KEY_ADDED); + EXPECT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, resp)); } scoped_ptr cdm_engine_; @@ -190,24 +193,36 @@ TEST(WvCdmProvisioningTest, ProvisioningTest) { cdm_engine.HandleProvisioningResponse(kValidJsonProvisioningResponse); } -TEST_F(WvCdmEngineTest, BaseMessageTest) { - GenerateKeyRequest(g_key_system, g_key_id); +TEST_F(WvCdmEngineTest, BaseIsoBmffMessageTest) { + GenerateKeyRequest(g_key_system, g_key_id, "video/mp4"); + GetKeyRequestResponse(g_license_server, g_client_auth); +} + +// TODO(juce): Set up with correct test data. +TEST_F(WvCdmEngineTest, DISABLED_BaseWebmMessageTest) { + GenerateKeyRequest(g_key_system, g_key_id, "video/webm"); GetKeyRequestResponse(g_license_server, g_client_auth); } TEST_F(WvCdmEngineTest, WrongMessageTest) { std::string wrong_message = a2bs_hex(g_wrong_key_id); - GenerateKeyRequest(g_key_system, wrong_message); + GenerateKeyRequest(g_key_system, wrong_message, "video/mp4"); GetKeyRequestResponse(g_license_server, g_client_auth); } -TEST_F(WvCdmEngineTest, NormalDecryption) { - GenerateKeyRequest(g_key_system, g_key_id); +TEST_F(WvCdmEngineTest, NormalDecryptionIsoBmff) { + GenerateKeyRequest(g_key_system, g_key_id, "video/mp4"); + VerifyNewKeyResponse(g_license_server, g_client_auth, g_key_id); +} + +// TODO(juce): Set up with correct test data. +TEST_F(WvCdmEngineTest, DISABLED_NormalDecryptionWebm) { + GenerateKeyRequest(g_key_system, g_key_id, "video/webm"); VerifyNewKeyResponse(g_license_server, g_client_auth, g_key_id); } TEST_F(WvCdmEngineTest, LicenseRenewal) { - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, "video/mp4"); VerifyNewKeyResponse(g_license_server, g_client_auth, g_key_id); GenerateRenewalRequest(g_key_system, g_key_id); diff --git a/libwvdrmengine/cdm/core/test/license_unittest.cpp b/libwvdrmengine/cdm/core/test/license_unittest.cpp index a8f71549..dbdae3a7 100644 --- a/libwvdrmengine/cdm/core/test/license_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/license_unittest.cpp @@ -74,12 +74,29 @@ TEST(LicenseTestSession, InitNullSession) { } // TODO(rfrias): Fix or remove test. -TEST_F(LicenseTest, DISABLED_PrepareKeyRequest) { +TEST_F(LicenseTest, DISABLED_PrepareIsoBmffKeyRequest) { std::string signed_request; CdmAppParameterMap app_parameters; std::string server_url; CdmSessionId session_id; - license_.PrepareKeyRequest(a2bs_hex(kInitData), + license_.PrepareKeyRequest("video/mp4", + a2bs_hex(kInitData), + kLicenseTypeStreaming, + app_parameters, + session_id, + &signed_request, + &server_url); + EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest)); +} + +// 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; + license_.PrepareKeyRequest("video/webm", + a2bs_hex(kInitData), kLicenseTypeStreaming, app_parameters, session_id, @@ -94,7 +111,8 @@ TEST_F(LicenseTest, DISABLED_HandleKeyResponseValid) { CdmAppParameterMap app_parameters; CdmSessionId session_id; std::string server_url; - license_.PrepareKeyRequest(a2bs_hex(kInitData), + license_.PrepareKeyRequest("video/mp4", + a2bs_hex(kInitData), kLicenseTypeStreaming, app_parameters, session_id, @@ -110,7 +128,8 @@ TEST_F(LicenseTest, DISABLED_HandleKeyResponseInvalid) { CdmAppParameterMap app_parameters; CdmSessionId session_id; std::string server_url; - license_.PrepareKeyRequest(a2bs_hex(kInitData), + license_.PrepareKeyRequest("video/mp4", + a2bs_hex(kInitData), kLicenseTypeStreaming, app_parameters, session_id, diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index 83edc51b..df8a558f 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -28,6 +28,7 @@ class WvContentDecryptionModule { // Construct a valid license request. virtual CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id, const CdmKeySetId& key_set_id, + const std::string& mime_type, const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index 27d68608..50afa2d8 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -38,6 +38,7 @@ CdmResponseType WvContentDecryptionModule::CloseSession( CdmResponseType WvContentDecryptionModule::GenerateKeyRequest( const CdmSessionId& session_id, const CdmKeySetId& key_set_id, + const std::string& mime_type, const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, @@ -49,7 +50,7 @@ CdmResponseType WvContentDecryptionModule::GenerateKeyRequest( if (sts != NO_ERROR) return sts; } - sts = cdm_engine_->GenerateKeyRequest(session_id, key_set_id, + sts = cdm_engine_->GenerateKeyRequest(session_id, key_set_id, mime_type, init_data, license_type, app_parameters, key_request, server_url); diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index d0ff5099..4e160dd1 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -298,7 +298,8 @@ class WvCdmRequestLicenseTest : public testing::Test { std::string server_url; std::string key_set_id; EXPECT_EQ(wvcdm::KEY_MESSAGE, - decryptor_.GenerateKeyRequest(session_id_, key_set_id, init_data, + decryptor_.GenerateKeyRequest(session_id_, key_set_id, + "video/mp4", init_data, license_type, app_parameters, &key_msg_, &server_url)); EXPECT_EQ(0u, server_url.size()); @@ -312,7 +313,8 @@ class WvCdmRequestLicenseTest : public testing::Test { std::string init_data; wvcdm::CdmAppParameterMap app_parameters; EXPECT_EQ(wvcdm::KEY_MESSAGE, - decryptor_.GenerateKeyRequest(session_id_, key_set_id_, init_data, + decryptor_.GenerateKeyRequest(session_id_, key_set_id_, + "video/mp4", init_data, license_type, app_parameters, &key_msg_, server_url)); // TODO(edwinwong, rfrias): Add tests cases for when license server url @@ -326,9 +328,10 @@ class WvCdmRequestLicenseTest : public testing::Test { wvcdm::CdmAppParameterMap app_parameters; std::string server_url; EXPECT_EQ(wvcdm::KEY_MESSAGE, - decryptor_.GenerateKeyRequest(session_id, key_set_id, init_data, - kLicenseTypeRelease, app_parameters, - &key_msg_, &server_url)); + decryptor_.GenerateKeyRequest(session_id, key_set_id, "video/mp4", + init_data, kLicenseTypeRelease, + app_parameters, &key_msg_, + &server_url)); } // Post a request and extract the drm message from the response @@ -961,9 +964,10 @@ TEST_F(WvCdmRequestLicenseTest, SecurityLevelPathBackwardCompatibility) { wvcdm::CdmAppParameterMap app_parameters; std::string server_url; EXPECT_EQ(wvcdm::NEED_PROVISIONING, - decryptor_.GenerateKeyRequest(session_id_, key_set_id, g_key_id, - kLicenseTypeStreaming, app_parameters, - &key_msg_, &server_url)); + decryptor_.GenerateKeyRequest(session_id_, key_set_id, "video/mp4", + g_key_id, kLicenseTypeStreaming, + app_parameters, &key_msg_, + &server_url)); EXPECT_EQ(NO_ERROR, decryptor_.GetProvisioningRequest(&key_msg_, &provisioning_server_url)); diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index 83ea7855..f9fe05c0 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -149,22 +149,51 @@ status_t WVDrmPlugin::getKeyRequest( return android::ERROR_DRM_CANNOT_HANDLE; } - // Build PSSH box for PSSH data in initData. - static const char psshPrefix[] = { - 0, 0, 0, 0, // Total size - 'p', 's', 's', 'h', // "PSSH" - 0, 0, 0, 0, // Flags - must be zero - 0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, // Widevine UUID - 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED, - 0, 0, 0, 0 // Size of initData - }; - CdmInitData psshBox(psshPrefix, sizeof(psshPrefix) / sizeof(uint8_t)); - psshBox.append(reinterpret_cast(initData.array()), - initData.size()); - uint32_t* psshBoxSize = reinterpret_cast(&psshBox[0]); - uint32_t* initDataSize = reinterpret_cast(&psshBox[28]); - *initDataSize = htonl(initData.size()); - *psshBoxSize = htonl(psshBox.size()); + string cdmMimeType = mimeType.string(); + + // Provide backwards-compatibility for versions of apps that pass the wrong + // MIME type. + // TODO(b/13731637) - Remove this. + if (cdmMimeType == "") { + ALOGW("ALERT: Empty MIME type is supported for backwards-compatibility " + "only and may go away in the future. Switch to \"video/mp4\" to " + "become compliant."); + cdmMimeType = "video/mp4"; + } else if (cdmMimeType == "video/avc") { + ALOGW("ALERT: MIME type \"video/avc\" is supported for backwards-" + "compatibility only and may go away in the future. Switch to " + "\"video/mp4\" to become compliant."); + cdmMimeType = "video/mp4"; + } + + CdmInitData processedInitData; + if (cdmMimeType == wvcdm::ISO_BMFF_MIME_TYPE) { + // For ISO-BMFF, we need to wrap the init data in a new PSSH header. + static const char psshPrefix[] = { + 0, 0, 0, 0, // Total size + 'p', 's', 's', 'h', // "PSSH" + 0, 0, 0, 0, // Flags - must be zero + 0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, // Widevine UUID + 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED, + 0, 0, 0, 0 // Size of initData + }; + processedInitData.assign(psshPrefix, sizeof(psshPrefix) / sizeof(char)); + processedInitData.append(reinterpret_cast(initData.array()), + initData.size()); + const size_t kPsshBoxSizeLocation = 0; + const size_t kInitDataSizeLocation = + sizeof(psshPrefix) - sizeof(uint32_t); + uint32_t psshBoxSize = htonl(processedInitData.size()); + uint32_t initDataSize = htonl(initData.size()); + memcpy(&processedInitData[kPsshBoxSizeLocation], &psshBoxSize, + sizeof(uint32_t)); + memcpy(&processedInitData[kInitDataSizeLocation], &initDataSize, + sizeof(uint32_t)); + } else { + // For other formats, we can pass the init data through unmodified. + processedInitData.assign(reinterpret_cast(initData.array()), + initData.size()); + } CdmAppParameterMap cdmParameters; for (size_t i = 0; i < optionalParameters.size(); ++i) { @@ -179,11 +208,10 @@ status_t WVDrmPlugin::getKeyRequest( CdmKeyMessage keyRequest; string cdmDefaultUrl; - CdmResponseType res = mCDM->GenerateKeyRequest(cdmSessionId, cdmKeySetId, - psshBox, cdmLicenseType, - cdmParameters, &keyRequest, - &cdmDefaultUrl); + cdmMimeType, processedInitData, + cdmLicenseType, cdmParameters, + &keyRequest, &cdmDefaultUrl); if (isCdmResponseTypeSuccess(res)) { defaultUrl.clear(); diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp index ac2efb23..6b649cf5 100644 --- a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp @@ -31,8 +31,9 @@ class MockCDM : public WvContentDecryptionModule { MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&)); - MOCK_METHOD7(GenerateKeyRequest, CdmResponseType(const CdmSessionId&, + MOCK_METHOD8(GenerateKeyRequest, CdmResponseType(const CdmSessionId&, const CdmKeySetId&, + const std::string&, const CdmInitData&, const CdmLicenseType, CdmAppParameterMap&, @@ -174,7 +175,7 @@ TEST_F(WVDrmPluginTest, ClosesSessions) { ASSERT_EQ(OK, res); } -TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { +TEST_F(WVDrmPluginTest, GeneratesIsoBmffKeyRequests) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); @@ -226,32 +227,33 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { cdmParameters["answer"] = "42"; static const char* kDefaultUrl = "http://google.com/"; + static const char* kMimeType = "video/mp4"; { InSequence calls; - EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", + EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType, ElementsAreArray(psshBox, kPsshBoxSize), kLicenseTypeOffline, cdmParameters, _, _)) - .WillOnce(DoAll(SetArgPointee<5>(cdmRequest), - SetArgPointee<6>(kDefaultUrl), + .WillOnce(DoAll(SetArgPointee<6>(cdmRequest), + SetArgPointee<7>(kDefaultUrl), Return(wvcdm::KEY_MESSAGE))); - EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", + EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType, ElementsAreArray(psshBox, kPsshBoxSize), kLicenseTypeStreaming, cdmParameters, _, _)) - .WillOnce(DoAll(SetArgPointee<5>(cdmRequest), - SetArgPointee<6>(kDefaultUrl), + .WillOnce(DoAll(SetArgPointee<6>(cdmRequest), + SetArgPointee<7>(kDefaultUrl), Return(wvcdm::KEY_MESSAGE))); - EXPECT_CALL(cdm, GenerateKeyRequest("", cdmKeySetId, + EXPECT_CALL(cdm, GenerateKeyRequest("", cdmKeySetId, kMimeType, ElementsAreArray(psshBox, kPsshBoxSize), kLicenseTypeRelease, cdmParameters, _, _)) - .WillOnce(DoAll(SetArgPointee<5>(cdmRequest), - SetArgPointee<6>(kDefaultUrl), + .WillOnce(DoAll(SetArgPointee<6>(cdmRequest), + SetArgPointee<7>(kDefaultUrl), Return(wvcdm::KEY_MESSAGE))); } @@ -259,21 +261,118 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { String8 defaultUrl; status_t res = plugin.getKeyRequest(sessionId, initData, - String8("video/h264"), + String8(kMimeType), DrmPlugin::kKeyType_Offline, parameters, request, defaultUrl); ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); - res = plugin.getKeyRequest(sessionId, initData, String8("video/h264"), + res = plugin.getKeyRequest(sessionId, initData, String8(kMimeType), DrmPlugin::kKeyType_Streaming, parameters, request, defaultUrl); ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); - res = plugin.getKeyRequest(keySetId, initData, String8("video/h264"), + res = plugin.getKeyRequest(keySetId, initData, String8(kMimeType), + DrmPlugin::kKeyType_Release, parameters, + request, defaultUrl); + ASSERT_EQ(OK, res); + EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); + EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); +} + +TEST_F(WVDrmPluginTest, GeneratesWebmKeyRequests) { + StrictMock cdm; + StrictMock crypto; + WVDrmPlugin plugin(&cdm, &crypto); + + static const size_t kInitDataSize = 128; + uint8_t initDataRaw[kInitDataSize]; + static const size_t kRequestSize = 256; + uint8_t requestRaw[kRequestSize]; + static const uint32_t kKeySetIdSize = 32; + uint8_t keySetIdRaw[kKeySetIdSize]; + FILE* fp = fopen("/dev/urandom", "r"); + fread(initDataRaw, sizeof(uint8_t), kInitDataSize, fp); + fread(requestRaw, sizeof(uint8_t), kRequestSize, fp); + fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp); + fclose(fp); + + memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1); + CdmKeySetId cdmKeySetId(reinterpret_cast(keySetIdRaw), kKeySetIdSize); + Vector keySetId; + keySetId.appendArray(keySetIdRaw, kKeySetIdSize); + + Vector initData; + initData.appendArray(initDataRaw, kInitDataSize); + + CdmKeyMessage cdmRequest(requestRaw, requestRaw + kRequestSize); + + KeyedVector parameters; + CdmAppParameterMap cdmParameters; + + parameters.add(String8("paddingScheme"), String8("BUBBLE WRAP")); + cdmParameters["paddingScheme"] = "BUBBLE WRAP"; + parameters.add(String8("favoriteParticle"), String8("higgs boson")); + cdmParameters["favoriteParticle"] = "higgs boson"; + parameters.add(String8("answer"), String8("6 * 9")); + cdmParameters["answer"] = "6 * 9"; + + static const char* kDefaultUrl = "http://google.com/"; + static const char* kMimeType = "video/webm"; + + { + InSequence calls; + + EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType, + ElementsAreArray(initDataRaw, + kInitDataSize), + kLicenseTypeOffline, cdmParameters, _, + _)) + .WillOnce(DoAll(SetArgPointee<6>(cdmRequest), + SetArgPointee<7>(kDefaultUrl), + Return(wvcdm::KEY_MESSAGE))); + + EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType, + ElementsAreArray(initDataRaw, + kInitDataSize), + kLicenseTypeStreaming, cdmParameters, _, + _)) + .WillOnce(DoAll(SetArgPointee<6>(cdmRequest), + SetArgPointee<7>(kDefaultUrl), + Return(wvcdm::KEY_MESSAGE))); + + EXPECT_CALL(cdm, GenerateKeyRequest("", cdmKeySetId, kMimeType, + ElementsAreArray(initDataRaw, + kInitDataSize), + kLicenseTypeRelease, cdmParameters, _, + _)) + .WillOnce(DoAll(SetArgPointee<6>(cdmRequest), + SetArgPointee<7>(kDefaultUrl), + Return(wvcdm::KEY_MESSAGE))); + } + + Vector request; + String8 defaultUrl; + + status_t res = plugin.getKeyRequest(sessionId, initData, + String8(kMimeType), + DrmPlugin::kKeyType_Offline, + parameters, request, defaultUrl); + ASSERT_EQ(OK, res); + EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); + EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); + + res = plugin.getKeyRequest(sessionId, initData, String8(kMimeType), + DrmPlugin::kKeyType_Streaming, parameters, + request, defaultUrl); + ASSERT_EQ(OK, res); + EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); + EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); + + res = plugin.getKeyRequest(keySetId, initData, String8(kMimeType), DrmPlugin::kKeyType_Release, parameters, request, defaultUrl); ASSERT_EQ(OK, res); diff --git a/libwvdrmengine/src/WVDrmFactory.cpp b/libwvdrmengine/src/WVDrmFactory.cpp index addde4f6..50979e7d 100644 --- a/libwvdrmengine/src/WVDrmFactory.cpp +++ b/libwvdrmengine/src/WVDrmFactory.cpp @@ -9,6 +9,7 @@ #include "WVDrmFactory.h" #include "utils/Errors.h" +#include "wv_cdm_constants.h" #include "WVCDMSingleton.h" #include "WVDrmPlugin.h" #include "WVUUID.h" @@ -24,8 +25,9 @@ bool WVDrmFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) { } bool WVDrmFactory::isContentTypeSupported(const String8 &mimeType) { - // For now, only ISO-BMFF is supported, which has MIME-type video/mp4 - return mimeType == "video/mp4"; + // Support ISO-BMFF (video/mp4) and WebM (video/webm). + return mimeType == wvcdm::ISO_BMFF_MIME_TYPE.c_str() || + mimeType == wvcdm::WEBM_MIME_TYPE.c_str(); } status_t WVDrmFactory::createDrmPlugin(const uint8_t uuid[16], diff --git a/libwvdrmengine/test/unit/WVDrmFactory_test.cpp b/libwvdrmengine/test/unit/WVDrmFactory_test.cpp index 8a812313..d6cfd1fa 100644 --- a/libwvdrmengine/test/unit/WVDrmFactory_test.cpp +++ b/libwvdrmengine/test/unit/WVDrmFactory_test.cpp @@ -46,14 +46,14 @@ TEST(WVDrmFactoryTest, SupportsSupportedContainerFormats) { EXPECT_TRUE(factory.isContentTypeSupported(String8("video/mp4"))) << "WVPluginFactory does not support ISO-BMFF"; + + EXPECT_TRUE(factory.isContentTypeSupported(String8("video/webm"))) << + "WVPluginFactory does not support WebM"; } TEST(WVDrmFactoryTest, DoesNotSupportUnsupportedContainerFormats) { WVDrmFactory factory; - EXPECT_FALSE(factory.isContentTypeSupported(String8("video/webm"))) << - "WVPluginFactory incorrectly claims to support Web-M"; - // Taken from Encoding.com's list of the most common internet video MIME-types EXPECT_FALSE(factory.isContentTypeSupported(String8("video/x-matroska"))) << "WVPluginFactory incorrectly claims to support Matroska";