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";