diff --git a/common/BUILD b/common/BUILD index a513e82..c1b310f 100644 --- a/common/BUILD +++ b/common/BUILD @@ -105,6 +105,7 @@ cc_library( ":status", "//base", "//external:protobuf", + "@abseil_repo//absl/strings", "@abseil_repo//absl/synchronization", "//protos/public:client_identification_cc_proto", "//protos/public:device_certificate_status_cc_proto", @@ -134,6 +135,7 @@ cc_test( "//external:protobuf", "//testing:gunit_main", "@abseil_repo//absl/memory", + "@abseil_repo//absl/strings", "//protos/public:device_common_cc_proto", "//protos/public:device_security_profile_data_cc_proto", "//protos/public:device_security_profile_list_cc_proto", @@ -305,6 +307,7 @@ cc_library( "@abseil_repo//absl/memory", "@abseil_repo//absl/strings", "@abseil_repo//absl/synchronization", + "@abseil_repo//absl/time", "//protos/public:drm_certificate_cc_proto", "//protos/public:errors_cc_proto", "//protos/public:signed_drm_certificate_cc_proto", @@ -1046,6 +1049,25 @@ cc_library( ], ) +cc_library( + name = "policy_verifier", + srcs = ["policy_verifier.cc"], + hdrs = ["policy_verifier.h"], + deps = [ + "//protos/public:license_protocol_cc_proto", + ], +) + +cc_test( + name = "policy_verifier_test", + srcs = ["policy_verifier_test.cc"], + deps = [ + ":policy_verifier", + "//testing:gunit_main", + "//protos/public:license_protocol_cc_proto", + ], +) + cc_test( name = "output_protection_util_test", srcs = ["output_protection_util_test.cc"], @@ -1150,6 +1172,7 @@ cc_library( ":sha_util", "//base", "@abseil_repo//absl/strings", + "//common/oemcrypto_core_message/odk", "//common/oemcrypto_core_message/odk:kdo", ], ) @@ -1161,6 +1184,9 @@ cc_test( ":core_message_util", "//testing:gunit_main", "@abseil_repo//absl/strings", + "//common/oemcrypto_core_message/odk", + "//protos/public:certificate_provisioning_cc_proto", + "//protos/public:license_protocol_cc_proto", ], ) diff --git a/common/rsa_key.h b/common/rsa_key.h index 391f57a..1071981 100644 --- a/common/rsa_key.h +++ b/common/rsa_key.h @@ -18,9 +18,7 @@ #include #include -#include -#include "absl/base/macros.h" -#include "openssl/rsa.h" +#include "openssl/pem.h" #include "common/hash_algorithm.h" namespace widevine { diff --git a/common/rsa_util.h b/common/rsa_util.h index a09356b..c01d6a8 100644 --- a/common/rsa_util.h +++ b/common/rsa_util.h @@ -16,7 +16,7 @@ #include -#include "openssl/rsa.h" +#include "openssl/pem.h" namespace widevine { namespace rsa_util { diff --git a/common/security_profile_list.h b/common/security_profile_list.h index e1c91c1..10a4e5b 100644 --- a/common/security_profile_list.h +++ b/common/security_profile_list.h @@ -71,25 +71,26 @@ class SecurityProfileList { // contain single record. For custom DSP, it may contain multiple records // since active dsp and inactive dsp could share the same dsp_name under the // same owner. - bool GetProfileByNameAndOwner( + virtual bool GetProfileByNameAndOwner( const std::string& name, const std::string& owner, std::vector* security_profiles) const; // Populates |security_profiles| owned by the content owner. - int GetProfilesByOwner(const std::string& owner, - std::vector* security_profiles) const; + virtual int GetProfilesByOwner( + const std::string& owner, + std::vector* security_profiles) const; // Populates |owner_list| for security profiles. |is_default_dsp| boolean // indicates the owner_list for default dsp or custom dsp. - int GetProfilesOwnerList(const bool is_default_dsp, - std::vector* owner_list) const; + virtual int GetProfilesOwnerList(const bool is_default_dsp, + std::vector* owner_list) const; // Return the device security capabilities. |drm_info| is populated with // data from |client_id| and |device_info|. |drm_info| must not be null and // is owned by the caller. - bool GetDrmInfo(const ClientIdentification& client_id, - const ProvisionedDeviceInfo& device_info, - SecurityProfile::DrmInfo* drm_info) const; + virtual bool GetDrmInfo(const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info, + SecurityProfile::DrmInfo* drm_info) const; // Return the number of profiles in the list. int NumProfiles() const; @@ -110,6 +111,12 @@ class SecurityProfileList { HashAlgorithm hash_algorithm, const std::string& signature, int* added_profile_num); + // Returns an instance of the Security profile list for default security + // profiles. Default security profiles are owned by Widevine. + // TODO (b/187073516): This singleton can be moved to the "Environment" class + // as a non-static API. + static SecurityProfileList* GetInstanceForDefaultSecurityProfiles(); + protected: void ClearAllProfiles(); diff --git a/example/BUILD b/example/BUILD index 734ba55..7d0205b 100644 --- a/example/BUILD +++ b/example/BUILD @@ -62,6 +62,7 @@ cc_binary( srcs = ["wv_cas_key_fetcher_example.cc"], deps = [ "//common:status", + "//media_cas_packager_sdk/public:wv_cas_curl_key_fetcher", "//media_cas_packager_sdk/public:wv_cas_key_fetcher", ], ) diff --git a/example/test_emmg_messages.h b/example/test_emmg_messages.h index 44b68ef..1d67b72 100644 --- a/example/test_emmg_messages.h +++ b/example/test_emmg_messages.h @@ -151,11 +151,11 @@ const char kTestEmmgPrivateDataProvision[] = { '\x47', '\x40', '\x00', '\x10', '\x0a', '\x0d', '\x77', '\x69', '\x64', '\x65', '\x76', '\x69', '\x6e', '\x65', '\x5f', '\x74', '\x65', '\x73', '\x74', '\x12', '\x09', '\x43', '\x61', '\x73', '\x54', '\x73', '\x46', - '\x61', '\x6b', '\x65', '\x1a', '\x10', '\x66', '\x61', '\x6b', '\x65', - '\x4b', '\x65', '\x79', '\x49', '\x64', '\x31', '\x4b', '\x65', '\x79', - '\x49', '\x64', '\x31', '\x1a', '\x10', '\x66', '\x61', '\x6b', '\x65', - '\x4b', '\x65', '\x79', '\x49', '\x64', '\x32', '\x4b', '\x65', '\x79', - '\x49', '\x64', '\x32', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x61', '\x6b', '\x65', '\x22', '\x0a', '\x66', '\x61', '\x6b', '\x65', + '\x47', '\x72', '\x6f', '\x75', '\x70', '\x31', '\x22', '\x0c', '\x66', + '\x61', '\x6b', '\x65', '\x47', '\x72', '\x6f', '\x75', '\x70', '\x49', + '\x64', '\x32', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', @@ -191,22 +191,22 @@ const char kTestEmmgEmmDataProvision[] = { // Start of the first TS packet (188 bytes). '\x47', '\x40', '\x00', '\x10', // TS packet header (4 bytes). '\x00', // Pointer field (1 byte). - '\x82', '\x70', '\xbe', // Secyion header (3 bytes). + '\x82', '\x70', '\xbc', // Section header (3 bytes). // Start of Widevine EMM. - '\x01', '\x08', '\x00', '\x00', '\x00', '\x00', '\x5f', '\x3d', '\xc1', - '\xfb', '\x00', '\x6b', '\x0a', '\x14', '\x0a', '\x03', '\x43', '\x48', - '\x31', '\x0a', '\x03', '\x43', '\x48', '\x32', '\x12', '\x08', '\x63', - '\x6f', '\x6e', '\x74', '\x72', '\x6f', '\x6c', '\x73', '\x0a', '\x16', - '\x0a', '\x03', '\x43', '\x48', '\x33', '\x12', '\x0f', '\x61', '\x6e', - '\x6f', '\x74', '\x68', '\x65', '\x72', '\x20', '\x63', '\x6f', '\x6e', - '\x74', '\x72', '\x6f', '\x6c', '\x12', '\x18', '\x0a', '\x03', '\x43', - '\x48', '\x31', '\x0a', '\x03', '\x43', '\x48', '\x32', '\x12', '\x06', - '\x47', '\x72', '\x6f', '\x75', '\x70', '\x31', '\x20', '\xd2', '\x85', - '\xd8', '\xcc', '\x04', '\x12', '\x21', '\x0a', '\x03', '\x43', '\x48', - '\x33', '\x12', '\x06', '\x47', '\x72', '\x6f', '\x75', '\x70', '\x32', - '\x12', '\x06', '\x47', '\x72', '\x6f', '\x75', '\x70', '\x33', '\x18', - '\xd2', '\x85', '\xd8', '\xcc', '\x04', '\x20', '\xd3', '\x85', '\xd8', - '\xcc', '\x04', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', + '\x0a', '\x71', '\x0a', '\x14', '\x0a', '\x03', '\x43', '\x48', '\x31', + '\x0a', '\x03', '\x43', '\x48', '\x32', '\x12', '\x08', '\x63', '\x6f', + '\x6e', '\x74', '\x72', '\x6f', '\x6c', '\x73', '\x0a', '\x16', '\x0a', + '\x03', '\x43', '\x48', '\x33', '\x12', '\x0f', '\x61', '\x6e', '\x6f', + '\x74', '\x68', '\x65', '\x72', '\x20', '\x63', '\x6f', '\x6e', '\x74', + '\x72', '\x6f', '\x6c', '\x12', '\x18', '\x0a', '\x03', '\x43', '\x48', + '\x31', '\x0a', '\x03', '\x43', '\x48', '\x32', '\x12', '\x06', '\x47', + '\x72', '\x6f', '\x75', '\x70', '\x31', '\x20', '\xd2', '\x85', '\xd8', + '\xcc', '\x04', '\x12', '\x21', '\x0a', '\x03', '\x43', '\x48', '\x33', + '\x12', '\x06', '\x47', '\x72', '\x6f', '\x75', '\x70', '\x32', '\x12', + '\x06', '\x47', '\x72', '\x6f', '\x75', '\x70', '\x33', '\x18', '\xd2', + '\x85', '\xd8', '\xcc', '\x04', '\x20', '\xd3', '\x85', '\xd8', '\xcc', + '\x04', '\x18', '\xfb', '\x83', '\xf7', '\xf9', '\x05', '\x12', '\x47', + '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', @@ -216,8 +216,8 @@ const char kTestEmmgEmmDataProvision[] = { // Start of the second TS packet (188 bytes). '\x47', '\x00', '\x00', '\x11', // TS packet header (4 bytes). // Continued Widevine EMM. - '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', - '\x78', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', + '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\xff', + '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', diff --git a/example/wv_cas_key_fetcher_example.cc b/example/wv_cas_key_fetcher_example.cc index 7f1e84a..368a945 100644 --- a/example/wv_cas_key_fetcher_example.cc +++ b/example/wv_cas_key_fetcher_example.cc @@ -12,8 +12,11 @@ #include #include "common/status.h" +#include "media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h" #include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h" +const char kCasEncryptionServerUrl[] = + "https://license.uat.widevine.com/cas/getcontentkey/widevine_test"; const char kContentId[] = "21140844"; const char kContentProvider[] = "widevine"; const char kTrackType[] = "SD"; @@ -22,36 +25,16 @@ const char kSigningProvider[] = "widevine_test"; const char kSingingKey[] = "1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9"; const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249"; -const char kHttpResponse[] = - "{\"response\":" - "\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu" - "dF9rZXlzIjpbeyJrZXlfaWQiOiJNUGFndXhNb1hNNkUxUzhEOUF3RkNBPT0iLCJrZXkiOiJoZ1" - "JycmdqeUg4NjQycjY3VHd0OHJ1cU5MUGNMRmtKcWRVSUROdm5GZDBNPSIsInRyYWNrX3R5cGUi" - "OiJTRCIsImtleV9zbG90IjoiU0lOR0xFIn1dfQ==\"}"; using widevine::Status; using widevine::cas::EntitlementKeyInfo; using widevine::cas::EntitlementRequestParams; -using widevine::cas::WvCasKeyFetcher; - -class ExampleKeyFetcher : public WvCasKeyFetcher { - public: - ExampleKeyFetcher(const std::string& signing_provider, - const std::string& signing_key, - const std::string& signing_iv) - : WvCasKeyFetcher(signing_provider, signing_key, signing_iv) {} - - // An example that always returns the same response. - Status MakeHttpRequest(const std::string& signed_request_json, - std::string* http_response_json) const override { - *http_response_json = kHttpResponse; - return widevine::OkStatus(); - } -}; +using widevine::cas::WvCasCurlKeyFetcher; int main(int argc, char** argv) { - // Initialize key fetcher. - ExampleKeyFetcher key_fetcher(kSigningProvider, kSingingKey, kSingingIv); + // Initialize key fetcher with server url. + WvCasCurlKeyFetcher key_fetcher(kCasEncryptionServerUrl, kSigningProvider, + kSingingKey, kSingingIv); // Create request string. std::string request_str; diff --git a/media_cas_packager_sdk/internal/BUILD b/media_cas_packager_sdk/internal/BUILD index da8be16..27464ef 100644 --- a/media_cas_packager_sdk/internal/BUILD +++ b/media_cas_packager_sdk/internal/BUILD @@ -73,6 +73,7 @@ cc_library( "@abseil_repo//absl/container:flat_hash_set", "@abseil_repo//absl/memory", "@abseil_repo//absl/strings", + "@abseil_repo//absl/synchronization", "@abseil_repo//absl/types:optional", "@abseil_repo//absl/types:span", "//common:crypto_util", @@ -172,6 +173,7 @@ cc_library( deps = [ ":util", "//base", + "@abseil_repo//absl/types:span", ], ) @@ -362,3 +364,12 @@ cc_test( "//testing:gunit_main", ], ) + +cc_test( + name = "simulcrypt_util_test", + srcs = ["simulcrypt_util_test.cc"], + deps = [ + ":simulcrypt_util", + "//testing:gunit_main", + ], +) diff --git a/media_cas_packager_sdk/internal/ecm.cc b/media_cas_packager_sdk/internal/ecm.cc index 7eada83..56d4ac2 100644 --- a/media_cas_packager_sdk/internal/ecm.cc +++ b/media_cas_packager_sdk/internal/ecm.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "glog/logging.h" @@ -38,46 +39,29 @@ constexpr uint16_t kWidevineSystemId = 0x4AD4; // New CA System ID range for Widevine, all inclusive. constexpr uint16_t kWidevineNewSystemIdLowerBound = 0x56C0; constexpr uint16_t kWidevineNewSystemIdUpperBound = 0x56C9; - -bool ConvertIvSizeParam(EcmIvSize param, size_t* size) { - if (param == kIvSize8) { - *size = 8; - } else if (param == kIvSize16) { - *size = 16; - } else { - return false; - } - return true; -} - } // namespace Status Ecm::Initialize( const EcmInitParameters& ecm_init_parameters, const std::vector& injected_entitlements) { if (initialized_) { - return {error::INTERNAL, "Already initialized."}; + LOG(INFO) << "Reinitializing ECM."; } if (injected_entitlements.empty()) { return {error::NOT_FOUND, "Empty injected entitlements."}; } - if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size, - &content_iv_size_)) { - return {error::INVALID_ARGUMENT, - "Parameter content_iv_size must be kIvSize8 or kIvSize16."}; - } - - crypto_mode_ = ecm_init_parameters.crypto_mode; - paired_keys_required_ = ecm_init_parameters.key_rotation_enabled; - age_restriction_ = ecm_init_parameters.age_restriction; - ecc_private_signing_key_ = ecm_init_parameters.ecc_private_signing_key; - - cas_id_ = ecm_init_parameters.cas_id; - if (cas_id_ != kWidevineSystemId && - (cas_id_ < kWidevineNewSystemIdLowerBound || - cas_id_ > kWidevineNewSystemIdUpperBound)) { + if (ecm_init_parameters.cas_id != kWidevineSystemId && + (ecm_init_parameters.cas_id < kWidevineNewSystemIdLowerBound || + ecm_init_parameters.cas_id > kWidevineNewSystemIdUpperBound)) { return {error::INVALID_ARGUMENT, "Invalid CA system ID."}; } + if (ecm_init_parameters.entitlement_rotation.rotation_enabled && + ecm_init_parameters.entitlement_rotation.rotation_window_left == 0) { + return {error::INVALID_ARGUMENT, + "Entitlement rotation window left must be non-zero when " + "entitlement rotation is enabled."}; + } + init_params_ = ecm_init_parameters; entitlement_keys_.clear(); for (const auto& entitlement : injected_entitlements) { @@ -119,10 +103,29 @@ void Ecm::SetServiceBlocking(const EcmServiceBlockingParams* service_blocking) { : absl::make_optional(*service_blocking); } +Status Ecm::SetEntitlementRotationWindowLeft( + uint32_t entitlement_rotation_window_left) { + if (!init_params_.entitlement_rotation.rotation_enabled) { + return Status(error::FAILED_PRECONDITION, + "Entitlement key rotation is not enabled."); + } + if (entitlement_rotation_window_left == 0) { + return Status(error::INVALID_ARGUMENT, + "window_left must be greater than 0."); + } + init_params_.entitlement_rotation.rotation_window_left = + entitlement_rotation_window_left; + return OkStatus(); +} + +uint32_t Ecm::GetEntitlementRotationWindowLeft() const { + return init_params_.entitlement_rotation.rotation_window_left; +} + Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, const std::string& track_type, const std::vector& group_ids, - std::string* serialized_ecm) const { + std::string* serialized_ecm) { absl::ReaderMutexLock lock(&ecm_params_mutex_); if (!initialized_) { @@ -134,11 +137,11 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, if (even_key == nullptr) { return {error::INVALID_ARGUMENT, "even_key must not be null."}; } - if (!paired_keys_required_ && odd_key != nullptr) { + if (!init_params_.key_rotation_enabled && odd_key != nullptr) { return {error::INVALID_ARGUMENT, "odd_key should be null as key rotation is disabled."}; } - if (paired_keys_required_ && odd_key == nullptr) { + if (init_params_.key_rotation_enabled && odd_key == nullptr) { return {error::INVALID_ARGUMENT, "odd_key can not be null as key rotation is enabled."}; } @@ -148,9 +151,17 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, const bool has_odd_key = odd_key != nullptr; EcmSerializerParams serializer_params; - serializer_params.crypto_mode = crypto_mode_; - serializer_params.age_restriction = age_restriction_; - serializer_params.cas_id = cas_id_; + serializer_params.crypto_mode = init_params_.crypto_mode; + serializer_params.age_restriction = init_params_.age_restriction; + serializer_params.cas_id = init_params_.cas_id; + serializer_params.entitlement_rotation.rotation_enabled = + init_params_.entitlement_rotation.rotation_enabled; + if (serializer_params.entitlement_rotation.rotation_enabled) { + serializer_params.entitlement_rotation.period_index = + init_params_.entitlement_rotation.period_index; + serializer_params.entitlement_rotation.rotation_window_left = + init_params_.entitlement_rotation.rotation_window_left; + } // Process normal content keys. std::vector content_keys = {*even_key}; @@ -185,20 +196,26 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, serializer_params.fingerprinting = fingerprinting_; serializer_params.service_blocking = service_blocking_; - serializer_params.ecc_private_signing_key = ecc_private_signing_key_; + serializer_params.ecc_private_signing_key = + init_params_.ecc_private_signing_key; status = ecm_serializer_->SerializeEcm(serializer_params, serialized_ecm); if (!status.ok()) { return status; } + // Automatically decrease rotation_window_left by 1 on each successful call to + // GenerateEcm or GenerateSingleKeyEcm, until the value reaches 1. + if (init_params_.entitlement_rotation.rotation_window_left > 1) { + --init_params_.entitlement_rotation.rotation_window_left; + } return OkStatus(); } Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key, const std::string& track_type, const std::vector& group_ids, - std::string* serialized_ecm) const { + std::string* serialized_ecm) { return GenerateEcm(key, nullptr, track_type, group_ids, serialized_ecm); } @@ -326,11 +343,11 @@ Status Ecm::ValidateIv(const std::string& iv, size_t size) const { } bool Ecm::CheckEntitlementKeys() const { + int expected_num_keys = init_params_.key_rotation_enabled ? 2 : 1; for (const auto& track : entitlement_keys_) { - if (track.second.size() != (paired_keys_required_ ? 2 : 1)) { - LOG(ERROR) << " Wrong number of entitlement keys: " - << (paired_keys_required_ ? 2 : 1) << " expected, " - << track.second.size() << " received."; + if (track.second.size() != expected_num_keys) { + LOG(ERROR) << " Wrong number of entitlement keys: " << expected_num_keys + << " expected, " << track.second.size() << " received."; return false; } } diff --git a/media_cas_packager_sdk/internal/ecm.h b/media_cas_packager_sdk/internal/ecm.h index 4d53f24..1f8050c 100644 --- a/media_cas_packager_sdk/internal/ecm.h +++ b/media_cas_packager_sdk/internal/ecm.h @@ -61,6 +61,7 @@ struct EcmInitParameters { // Private signing key used to sign ECM data. Must be an elliptic-curve // cryptography key. std::string ecc_private_signing_key; + EntitlementKeyRotationInfo entitlement_rotation; }; // Generator for producing Widevine CAS-compliant ECMs. Used to construct the @@ -106,6 +107,20 @@ class Ecm { virtual void SetServiceBlocking( const EcmServiceBlockingParams* service_blocking); + // Sets the current value of the entitlement key rotation window left to + // |entitlement_rotation_window_left|. The value will be used in + // GenerateEcm/GenerateSingleKeyEcm if entitlement rotation is enabled as + // specified at initializing, and is automatically decreased by 1 on each call + // to GenerateEcm/GenerateSingleKeyEcm, until it reaches 1. + virtual Status SetEntitlementRotationWindowLeft( + uint32_t entitlement_rotation_window_left); + + // Gets the current value of the entitlement key rotation window left. The + // value is used in GenerateEcm/GenerateSingleKeyEcm, and is automatically + // decreased by 1 on each call to GenerateEcm/GenerateSingleKeyEcm, until it + // reaches 1. + virtual uint32_t GetEntitlementRotationWindowLeft() const; + // Accept keys and IVs and construct an ECM that will fit into a Transport // Stream packet payload. // Args: @@ -119,11 +134,14 @@ class Ecm { // consistent with the initialized settings. // The even_key and odd_key will be wrapped using the appropriate // entitlement key. Wrapping modifies the original structure. + // If entitlement rotation is enabled as specified at initializing, the + // entitlement key rotation window left value will be automatically decreased + // by 1, until it reaches 1. virtual Status GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, const std::string& track_type, const std::vector& group_ids, - std::string* serialized_ecm) const; + std::string* serialized_ecm); // Accept a key and IV and construct an ECM that will fit into a Transport // Stream packet payload. This call is specifically for the case where key @@ -136,10 +154,13 @@ class Ecm { // |serialized_ecm| caller-supplied std::string pointer to receive the ECM. // The |key| contents (specifically IV sizes) must be consistent // with the initialized settings. + // If entitlement rotation is enabled as specified at initializing, the + // entitlement key rotation window left value will be automatically decreased + // by 1, until it reaches 1. virtual Status GenerateSingleKeyEcm(EntitledKeyInfo* key, const std::string& track_type, const std::vector& group_ids, - std::string* serialized_ecm) const; + std::string* serialized_ecm); // Generate a TS packet with the given |ecm| as payload. // @@ -214,14 +235,8 @@ class Ecm { // Set to true when the object has been properly initialized. bool initialized_ = false; - // Content IV size may be 8 or 16. Size is set once during Initialize(). - size_t content_iv_size_ = 8; - // Remember if a pair of keys is required (for key rotation). - bool paired_keys_required_ = false; - CryptoMode crypto_mode_ = CryptoMode::kAesCtr; - uint8_t age_restriction_ = 0; - uint16_t cas_id_ = 0; - std::string ecc_private_signing_key_; + EcmInitParameters init_params_; + // Entitlement keys needed for ECM generation. // The keys are added when the CasEncryptionResponse is processed. // Maps from {track_type, group_id} to one/two EntitlementKeyIdValue with even diff --git a/media_cas_packager_sdk/internal/ecm_serializer.h b/media_cas_packager_sdk/internal/ecm_serializer.h index d1e3060..6b459d0 100644 --- a/media_cas_packager_sdk/internal/ecm_serializer.h +++ b/media_cas_packager_sdk/internal/ecm_serializer.h @@ -53,6 +53,7 @@ struct EcmSerializerParams { // cryptography key. std::string ecc_private_signing_key; std::vector group_keys; + EntitlementKeyRotationInfo entitlement_rotation; }; enum class EcmSerializerVersion { kV2 = 0, kV3 }; diff --git a/media_cas_packager_sdk/internal/ecm_serializer_v3.cc b/media_cas_packager_sdk/internal/ecm_serializer_v3.cc index 5f31dd3..6881f16 100644 --- a/media_cas_packager_sdk/internal/ecm_serializer_v3.cc +++ b/media_cas_packager_sdk/internal/ecm_serializer_v3.cc @@ -181,6 +181,12 @@ Status EcmSerializerV3::SerializeEcm(const EcmSerializerParams& params, if (params.age_restriction != 0) { meta_data->set_age_restriction(params.age_restriction); } + if (params.entitlement_rotation.rotation_enabled) { + meta_data->set_entitlement_period_index( + params.entitlement_rotation.period_index); + meta_data->set_entitlement_rotation_window_left( + params.entitlement_rotation.rotation_window_left); + } EcmKeyData* even_key_data = ecm_payload.mutable_even_key_data(); even_key_data->set_entitlement_key_id(params.even_key.entitlement_key_id); diff --git a/media_cas_packager_sdk/internal/ecm_serializer_v3_test.cc b/media_cas_packager_sdk/internal/ecm_serializer_v3_test.cc index 0c8cb17..e901d81 100644 --- a/media_cas_packager_sdk/internal/ecm_serializer_v3_test.cc +++ b/media_cas_packager_sdk/internal/ecm_serializer_v3_test.cc @@ -573,6 +573,42 @@ TEST_F(EcmSerializerV3Test, GroupKeyInvalidWrappedKeyIvFail) { "Unexpected group wrapped key iv size: 8")); } +TEST_F(EcmSerializerV3Test, EntitlementRotationEnabled) { + EcmSerializerV3 ecm_serializer; + serializer_params_.entitlement_rotation.rotation_enabled = true; + serializer_params_.entitlement_rotation.period_index = 1; + serializer_params_.entitlement_rotation.rotation_window_left = 2; + std::string serialized_ecm; + + ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm)); + + SignedEcmPayload signed_payload; + ASSERT_TRUE(signed_payload.ParseFromString( + serialized_ecm.substr(kEcmProtoStartOffset))); + EcmPayload payload; + ASSERT_TRUE(payload.ParseFromString(signed_payload.serialized_payload())); + EXPECT_EQ(payload.meta_data().entitlement_period_index(), 1); + EXPECT_EQ(payload.meta_data().entitlement_rotation_window_left(), 2); +} + +TEST_F(EcmSerializerV3Test, EntitlementRotationDisabled) { + EcmSerializerV3 ecm_serializer; + serializer_params_.entitlement_rotation.rotation_enabled = false; + serializer_params_.entitlement_rotation.period_index = 1; + serializer_params_.entitlement_rotation.rotation_window_left = 2; + std::string serialized_ecm; + + ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm)); + + SignedEcmPayload signed_payload; + ASSERT_TRUE(signed_payload.ParseFromString( + serialized_ecm.substr(kEcmProtoStartOffset))); + EcmPayload payload; + ASSERT_TRUE(payload.ParseFromString(signed_payload.serialized_payload())); + EXPECT_FALSE(payload.meta_data().has_entitlement_period_index()); + EXPECT_FALSE(payload.meta_data().has_entitlement_rotation_window_left()); +} + } // namespace } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/internal/ecm_test.cc b/media_cas_packager_sdk/internal/ecm_test.cc index edcce9a..bf89c40 100644 --- a/media_cas_packager_sdk/internal/ecm_test.cc +++ b/media_cas_packager_sdk/internal/ecm_test.cc @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include "testing/gmock.h" @@ -25,6 +27,7 @@ namespace cas { namespace { using ::testing::DoAll; +using ::testing::Eq; using ::testing::NotNull; using ::testing::Return; using ::testing::SetArgPointee; @@ -170,15 +173,12 @@ TEST_F(EcmTest, InitFailNotEnoughInjectedEntitlement) { .error_code()); } -TEST_F(EcmTest, SecondInitFail) { +TEST_F(EcmTest, SecondInitOk) { Ecm ecm_gen; ASSERT_OK(ecm_gen.Initialize( params_simple_, {injected_entitlement_one_, injected_entitlement_two_})); - EXPECT_EQ(error::INTERNAL, - ecm_gen - .Initialize(params_simple_, {injected_entitlement_one_, - injected_entitlement_two_}) - .error_code()); + ASSERT_OK(ecm_gen.Initialize( + params_simple_, {injected_entitlement_one_, injected_entitlement_two_})); } TEST_F(EcmTest, InitIvSize16x16OK) { @@ -188,6 +188,19 @@ TEST_F(EcmTest, InitIvSize16x16OK) { ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_})); } +TEST_F(EcmTest, InitWithInvalidRotationWindowFail) { + Ecm ecm_gen; + params_two_keys_.entitlement_rotation.rotation_enabled = true; + params_two_keys_.entitlement_rotation.period_index = 1; + // rotation_window_left should be a positive value. + params_two_keys_.entitlement_rotation.rotation_window_left = 0; + EXPECT_THAT(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_, + injected_entitlement_two_}), + Eq(Status(error::INVALID_ARGUMENT, + "Entitlement rotation window left must be non-zero " + "when entitlement rotation is enabled."))); +} + class EcmInitWithValidCasIdTest : public EcmTest, public testing::WithParamInterface { }; @@ -315,7 +328,13 @@ MATCHER_P(EcmSerializerParamsEq, expected, "") { (arg.age_restriction == expected.age_restriction) && IsEcmSerializerKeyInfoEq(arg.even_key, expected.even_key) && IsEcmSerializerKeyInfoEq(arg.odd_key, expected.odd_key) && - IsEcmSerializerGroupKeyInfoEq(arg.group_keys, expected.group_keys); + IsEcmSerializerGroupKeyInfoEq(arg.group_keys, expected.group_keys) && + arg.entitlement_rotation.rotation_enabled == + expected.entitlement_rotation.rotation_enabled && + arg.entitlement_rotation.period_index == + expected.entitlement_rotation.period_index && + arg.entitlement_rotation.rotation_window_left == + expected.entitlement_rotation.rotation_window_left; } TEST_F(EcmTest, GenerateEcmTwoKeysOK) { @@ -350,6 +369,75 @@ TEST_F(EcmTest, GenerateEcmTwoKeysOK) { EXPECT_EQ(actual_generated_ecm, expected_serialized_ecm); } +TEST_F(EcmTest, GenerateEcmWithEntitlementRotation) { + FakeEcm ecm_gen; + params_two_keys_.entitlement_rotation.rotation_enabled = true; + params_two_keys_.entitlement_rotation.period_index = 1; + params_two_keys_.entitlement_rotation.rotation_window_left = 2; + ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_, + injected_entitlement_two_})); + auto ecm_serializer = absl::make_unique(); + const MockEcmSerializer* ecm_serializer_pointer = ecm_serializer.get(); + ecm_gen.SetEcmSerializer(std::move(ecm_serializer)); + EcmSerializerParams passed_params; + EXPECT_CALL(*ecm_serializer_pointer, SerializeEcm(testing::_, NotNull())) + .WillOnce(DoAll(testing::SaveArg<0>(&passed_params), + SetArgPointee<1>("serialized_ecm"), Return(OkStatus()))); + std::string generated_ecm; + + ASSERT_OK(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_, + kTrackTypeSD, /*group_ids=*/{}, + &generated_ecm)); + + EXPECT_TRUE(passed_params.entitlement_rotation.rotation_enabled); + EXPECT_EQ(passed_params.entitlement_rotation.period_index, 1); + EXPECT_EQ(passed_params.entitlement_rotation.rotation_window_left, 2); +} + +TEST_F(EcmTest, GenerateEcmEntitlementRotationWindowDescreases) { + FakeEcm ecm_gen; + params_two_keys_.entitlement_rotation.rotation_enabled = true; + params_two_keys_.entitlement_rotation.period_index = 1; + params_two_keys_.entitlement_rotation.rotation_window_left = 2; + ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_, + injected_entitlement_two_})); + auto ecm_serializer = absl::make_unique(); + const MockEcmSerializer* ecm_serializer_pointer = ecm_serializer.get(); + ecm_gen.SetEcmSerializer(std::move(ecm_serializer)); + EcmSerializerParams passed_params; + std::string generated_ecm; + // The first call to Generate ECM. + EXPECT_CALL(*ecm_serializer_pointer, SerializeEcm(testing::_, NotNull())) + .WillOnce(DoAll(testing::SaveArg<0>(&passed_params), + SetArgPointee<1>("serialized_ecm"), Return(OkStatus()))); + ASSERT_OK(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_, + kTrackTypeSD, /*group_ids=*/{}, + &generated_ecm)); + EXPECT_TRUE(passed_params.entitlement_rotation.rotation_enabled); + EXPECT_EQ(passed_params.entitlement_rotation.period_index, 1); + EXPECT_EQ(passed_params.entitlement_rotation.rotation_window_left, 2); + // The second call to Generate ECM with descreased rotation_window_left. + EXPECT_CALL(*ecm_serializer_pointer, SerializeEcm(testing::_, NotNull())) + .WillOnce(DoAll(testing::SaveArg<0>(&passed_params), + SetArgPointee<1>("serialized_ecm"), Return(OkStatus()))); + ASSERT_OK(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_, + kTrackTypeSD, /*group_ids=*/{}, + &generated_ecm)); + EXPECT_TRUE(passed_params.entitlement_rotation.rotation_enabled); + EXPECT_EQ(passed_params.entitlement_rotation.period_index, 1); + EXPECT_EQ(passed_params.entitlement_rotation.rotation_window_left, 1); + // The third call to Generate ECM. rotation_window_left remains to be 1. + EXPECT_CALL(*ecm_serializer_pointer, SerializeEcm(testing::_, NotNull())) + .WillOnce(DoAll(testing::SaveArg<0>(&passed_params), + SetArgPointee<1>("serialized_ecm"), Return(OkStatus()))); + ASSERT_OK(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_, + kTrackTypeSD, /*group_ids=*/{}, + &generated_ecm)); + EXPECT_TRUE(passed_params.entitlement_rotation.rotation_enabled); + EXPECT_EQ(passed_params.entitlement_rotation.period_index, 1); + EXPECT_EQ(passed_params.entitlement_rotation.rotation_window_left, 1); +} + TEST_F(EcmTest, GenerateEcmParamsPassedDown) { FakeEcm ecm_gen; EcmInitParameters params; @@ -481,6 +569,59 @@ TEST_F(EcmTest, GenerateEcmWithMissingGroupKeysFail) { .ok()); } +TEST_F(EcmTest, GetEntitlementRotationWindowLeftSuccess) { + Ecm ecm_gen; + EcmInitParameters params; + uint32_t expected_window_left = 10; + params.entitlement_rotation.rotation_enabled = true; + params.entitlement_rotation.period_index = 1; + params.entitlement_rotation.rotation_window_left = expected_window_left; + ASSERT_OK(ecm_gen.Initialize( + params, {injected_entitlement_one_, injected_entitlement_two_})); + + EXPECT_EQ(ecm_gen.GetEntitlementRotationWindowLeft(), expected_window_left); +} + +TEST_F(EcmTest, SetEntitlementRotationWindowLeftSuccess) { + Ecm ecm_gen; + EcmInitParameters params; + params.entitlement_rotation.rotation_enabled = true; + params.entitlement_rotation.period_index = 1; + params.entitlement_rotation.rotation_window_left = 10; + ASSERT_OK(ecm_gen.Initialize( + params, {injected_entitlement_one_, injected_entitlement_two_})); + EXPECT_EQ(ecm_gen.GetEntitlementRotationWindowLeft(), 10); + uint32_t expected_window_left = 100; + + ASSERT_OK(ecm_gen.SetEntitlementRotationWindowLeft(expected_window_left)); + + EXPECT_EQ(ecm_gen.GetEntitlementRotationWindowLeft(), expected_window_left); +} + +TEST_F(EcmTest, SetEntitlementRotationWindowLeftRotationDisabledFail) { + Ecm ecm_gen; + EcmInitParameters params; + params.entitlement_rotation.rotation_enabled = false; + ASSERT_OK(ecm_gen.Initialize( + params, {injected_entitlement_one_, injected_entitlement_two_})); + + EXPECT_EQ(ecm_gen.SetEntitlementRotationWindowLeft(100).error_code(), + error::FAILED_PRECONDITION); +} + +TEST_F(EcmTest, SetEntitlementRotationWindowLeftInvalidValueFail) { + Ecm ecm_gen; + EcmInitParameters params; + params.entitlement_rotation.rotation_enabled = true; + params.entitlement_rotation.period_index = 1; + params.entitlement_rotation.rotation_window_left = 10; + ASSERT_OK(ecm_gen.Initialize( + params, {injected_entitlement_one_, injected_entitlement_two_})); + + EXPECT_EQ(ecm_gen.SetEntitlementRotationWindowLeft(0).error_code(), + error::INVALID_ARGUMENT); +} + class EcmTsPacketTest : public ::testing::Test {}; TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) { diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler.cc b/media_cas_packager_sdk/internal/ecmg_client_handler.cc index 005e223..5ddfa60 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler.cc +++ b/media_cas_packager_sdk/internal/ecmg_client_handler.cc @@ -327,67 +327,50 @@ Status HandleParameters(const char* const request, size_t request_length, } void BuildChannelError(uint16_t channel_id, uint16_t error_status, - const std::string& error_info, char* message, - size_t* message_length) { - DCHECK(message); + const std::string& error_info, + const absl::Span message, size_t* message_length) { DCHECK(message_length); - simulcrypt_util::BuildMessageHeader( - ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_ERROR, message, message_length); - simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, - message_length); - simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message, - message_length); + SimulcryptMessage builder(message, message_length); + builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_ERROR); + builder.AddUint16Param(ECM_CHANNEL_ID, channel_id); + builder.AddUint16Param(ERROR_STATUS, error_status); if (!error_info.empty()) { - simulcrypt_util::AddParam( - ERROR_INFORMATION, reinterpret_cast(error_info.c_str()), - error_info.size(), message, message_length); + builder.AddParam(ERROR_INFORMATION, + reinterpret_cast(error_info.c_str()), + error_info.size()); } - uint16_t total_param_length = *message_length - 5; - Host16ToBigEndian(message + 3, &total_param_length); VLOG(3) << "ChannelError sent. ECM_CHANNEL_ID: " << channel_id << "; ERROR_STATUS: " << error_status << "; ERROR_INFORMATION: " << error_info; } -void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message, +void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, + const absl::Span message, size_t* message_length) { DCHECK(config); - DCHECK(message); DCHECK(message_length); - simulcrypt_util::BuildMessageHeader( - ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_STATUS, message, message_length); - simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, - message_length); - simulcrypt_util::AddUint8Param(SECTION_TSPKT_FLAG, kSectionTSpktFlag, message, - message_length); + SimulcryptMessage builder(message, message_length); + builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_STATUS); + builder.AddUint16Param(ECM_CHANNEL_ID, channel_id); + builder.AddUint8Param(SECTION_TSPKT_FLAG, kSectionTSpktFlag); // No setting AC_delay_start parameter yet. // No setting AC_delay_stop parameter yet. - simulcrypt_util::AddUint16Param(DELAY_START, config->delay_start, message, - message_length); - simulcrypt_util::AddUint16Param(DELAY_STOP, config->delay_stop, message, - message_length); + builder.AddUint16Param(DELAY_START, config->delay_start); + builder.AddUint16Param(DELAY_STOP, config->delay_stop); // No setting transition_delay_start parameter yet. // No setting transition_delay_stop parameter yet. - simulcrypt_util::AddUint16Param(ECM_REP_PERIOD, config->ecm_rep_period, - message, message_length); - simulcrypt_util::AddUint16Param(MAX_STREAMS, kMaxStream, message, - message_length); + builder.AddUint16Param(ECM_REP_PERIOD, config->ecm_rep_period); + builder.AddUint16Param(MAX_STREAMS, kMaxStream); // min_CP_duration needs to be at least max_comp_time. So we just use // max_comp_time here for now. - simulcrypt_util::AddUint16Param(MIN_CP_DURATION, config->max_comp_time, - message, message_length); + builder.AddUint16Param(MIN_CP_DURATION, config->max_comp_time); // LEAD_CW defines the number of control words required in advance to build an // ECM. When CW_PER_MESSAGE is 2, LEAD_CW should be 1; when CW_PER_MESSAGE is // 1, LEAD_CW should be 0. - simulcrypt_util::AddUint8Param(LEAD_CW, config->number_of_content_keys - 1, - message, message_length); - simulcrypt_util::AddUint8Param(CW_PER_MESSAGE, config->number_of_content_keys, - message, message_length); - simulcrypt_util::AddUint16Param(MAX_COMP_TIME, config->max_comp_time, message, - message_length); - uint16_t total_param_length = *message_length - 5; - Host16ToBigEndian(message + 3, &total_param_length); + builder.AddUint8Param(LEAD_CW, config->number_of_content_keys - 1); + builder.AddUint8Param(CW_PER_MESSAGE, config->number_of_content_keys); + builder.AddUint16Param(MAX_COMP_TIME, config->max_comp_time); VLOG(3) << "ChannelStatus sent. ECM_CHANNEL_ID: " << channel_id << "; SECTION_TSPKT_FLAG: " << kSectionTSpktFlag @@ -403,24 +386,18 @@ void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message, void BuildStreamError(uint16_t channel_id, uint16_t stream_id, uint16_t error_status, const std::string& error_info, - char* message, size_t* message_length) { - DCHECK(message); + const absl::Span message, size_t* message_length) { DCHECK(message_length); - simulcrypt_util::BuildMessageHeader( - ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_ERROR, message, message_length); - simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, - message_length); - simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message, - message_length); - simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message, - message_length); + SimulcryptMessage builder(message, message_length); + builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_ERROR); + builder.AddUint16Param(ECM_CHANNEL_ID, channel_id); + builder.AddUint16Param(ECM_STREAM_ID, stream_id); + builder.AddUint16Param(ERROR_STATUS, error_status); if (!error_info.empty()) { - simulcrypt_util::AddParam( - ERROR_INFORMATION, reinterpret_cast(error_info.c_str()), - error_info.size(), message, message_length); + builder.AddParam(ERROR_INFORMATION, + reinterpret_cast(error_info.c_str()), + error_info.size()); } - uint16_t total_param_length = *message_length - 5; - Host16ToBigEndian(message + 3, &total_param_length); VLOG(3) << "StreamError sent. ECM_CHANNEL_ID: " << channel_id << "; ECM_STREAM_ID: " << stream_id @@ -449,22 +426,16 @@ uint16_t StatusToDvbErrorCode(const Status& status) { } void BuildStreamStatus(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id, - uint8_t access_criteria_transfer_mode, char* message, - size_t* message_length) { - DCHECK(message); + uint8_t access_criteria_transfer_mode, + const absl::Span message, size_t* message_length) { DCHECK(message_length); - simulcrypt_util::BuildMessageHeader( - ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_STATUS, message, message_length); - simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, - message_length); - simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message, - message_length); - simulcrypt_util::AddUint16Param(ECM_ID, ecm_id, message, message_length); - simulcrypt_util::AddUint8Param(ACCESS_CRITERIA_TRANSFER_MODE, - access_criteria_transfer_mode, message, - message_length); - uint16_t total_param_length = *message_length - 5; - Host16ToBigEndian(message + 3, &total_param_length); + SimulcryptMessage builder(message, message_length); + builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_STATUS); + builder.AddUint16Param(ECM_CHANNEL_ID, channel_id); + builder.AddUint16Param(ECM_STREAM_ID, stream_id); + builder.AddUint16Param(ECM_ID, ecm_id); + builder.AddUint8Param(ACCESS_CRITERIA_TRANSFER_MODE, + access_criteria_transfer_mode); VLOG(3) << "StreamStatus sent. ECM_CHANNEL_ID: " << channel_id << "; ECM_STREAM_ID: " << stream_id << "; ECM_ID: " << ecm_id @@ -473,18 +444,14 @@ void BuildStreamStatus(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id, } void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id, - char* message, size_t* message_length) { - DCHECK(message); + const absl::Span message, + size_t* message_length) { DCHECK(message_length); - simulcrypt_util::BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, - ECMG_STREAM_CLOSE_RESPONSE, message, - message_length); - simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, - message_length); - simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message, - message_length); - uint16_t total_param_length = *message_length - 5; - Host16ToBigEndian(message + 3, &total_param_length); + SimulcryptMessage builder(message, message_length); + builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, + ECMG_STREAM_CLOSE_RESPONSE); + builder.AddUint16Param(ECM_CHANNEL_ID, channel_id); + builder.AddUint16Param(ECM_STREAM_ID, stream_id); VLOG(3) << "StreamCloseResponse sent. ECM_CHANNEL_ID: " << channel_id << "; ECM_STREAM_ID: " << stream_id; @@ -492,22 +459,15 @@ void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id, void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, uint16_t cp_number, - absl::Span ecm_datagram, char* message, - size_t* message_length) { - DCHECK(message); + absl::Span ecm_datagram, + const absl::Span message, size_t* message_length) { DCHECK(message_length); - simulcrypt_util::BuildMessageHeader( - ECMG_SCS_PROTOCOL_VERSION, ECMG_ECM_RESPONSE, message, message_length); - simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, - message_length); - simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message, - message_length); - simulcrypt_util::AddUint16Param(CP_NUMBER, cp_number, message, - message_length); - simulcrypt_util::AddParam(ECM_DATAGRAM, ecm_datagram.data(), - ecm_datagram.size(), message, message_length); - uint16_t total_param_length = *message_length - 5; - Host16ToBigEndian(message + 3, &total_param_length); + SimulcryptMessage builder(message, message_length); + builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_ECM_RESPONSE); + builder.AddUint16Param(ECM_CHANNEL_ID, channel_id); + builder.AddUint16Param(ECM_STREAM_ID, stream_id); + builder.AddUint16Param(CP_NUMBER, cp_number); + builder.AddParam(ECM_DATAGRAM, ecm_datagram.data(), ecm_datagram.size()); VLOG(3) << "EcmResponse sent. ECM_CHANNEL_ID: " << channel_id << "; ECM_STREAM_ID: " << stream_id << "; CP_NUMBER: " << cp_number; @@ -547,10 +507,9 @@ EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config) } size_t EcmgClientHandler::HandleRequest(const char* const request, - char* response, + const absl::Span response, size_t* response_length) { DCHECK(request); - DCHECK(response); uint8_t protocol_version; uint16_t request_type; @@ -628,12 +587,12 @@ size_t EcmgClientHandler::HandleRequest(const char* const request, } void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params, - char* response, + const absl::Span response, size_t* response_length) { - DCHECK(response); DCHECK(response_length); VLOG(3) << "ChannelSetup request. ECM_channel_id: " << params.ecm_channel_id << "; ECM_stream_id: " << params.ecm_stream_id; + absl::WriterMutexLock lock(&channel_mutex_); // There is always one (and only one) channel per TCP connection. if (channel_id_set_) { @@ -672,11 +631,11 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params, } void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params, - char* response, + const absl::Span response, size_t* response_length) const { - DCHECK(response); DCHECK(response_length); VLOG(3) << "ChannelTest request. ECM_channel_id: " << params.ecm_channel_id; + absl::ReaderMutexLock lock(&channel_mutex_); if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", @@ -687,11 +646,12 @@ void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params, } void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params, - char* response, + const absl::Span response, size_t* response_length) { - DCHECK(response); DCHECK(response_length); VLOG(3) << "ChannelClose request. ECM_channel_id: " << params.ecm_channel_id; + absl::WriterMutexLock lock1(&channel_mutex_); + absl::WriterMutexLock lock2(&stream_mutex_); if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", @@ -705,9 +665,10 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params, } void EcmgClientHandler::HandleChannelError(const EcmgParameters& params, - char* response, + const absl::Span response, size_t* response_length) const { VLOG(3) << "ChannelError request. ECM_channel_id: " << params.ecm_channel_id; + absl::ReaderMutexLock lock(&channel_mutex_); if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", @@ -727,14 +688,15 @@ void EcmgClientHandler::HandleChannelError(const EcmgParameters& params, } void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params, - char* response, + const absl::Span response, size_t* response_length) { - DCHECK(response); DCHECK(response_length); VLOG(3) << "StreamSetup request. ECM_channel_id: " << params.ecm_channel_id << "; ECM_stream_id: " << params.ecm_stream_id << "; ECM_id: " << params.ecm_id << "; nominal_CP_duration" << params.nominal_cp_duration; + absl::ReaderMutexLock lock1(&channel_mutex_); + absl::WriterMutexLock lock2(&stream_mutex_); if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, @@ -782,12 +744,13 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params, } void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params, - char* response, + const absl::Span response, size_t* response_length) const { - DCHECK(response); DCHECK(response_length); VLOG(3) << "StreamTest request. ECM_channel_id: " << params.ecm_channel_id << "; ECM_stream_id: " << params.ecm_stream_id; + absl::ReaderMutexLock lock1(&channel_mutex_); + absl::ReaderMutexLock lock2(&stream_mutex_); if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, @@ -807,13 +770,14 @@ void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params, response_length); } -void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params, - char* response, - size_t* response_length) { - DCHECK(response); +void EcmgClientHandler::HandleStreamCloseRequest( + const EcmgParameters& params, const absl::Span response, + size_t* response_length) { DCHECK(response_length); VLOG(3) << "StreamClose request. ECM_channel_id: " << params.ecm_channel_id << "; ECM_stream_id: " << params.ecm_stream_id; + absl::ReaderMutexLock lock1(&channel_mutex_); + absl::WriterMutexLock lock2(&stream_mutex_); if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, @@ -835,10 +799,12 @@ void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params, } void EcmgClientHandler::HandleStreamError(const EcmgParameters& params, - char* response, + const absl::Span response, size_t* response_length) const { VLOG(3) << "StreamError request. ECM_channel_id: " << params.ecm_channel_id << "; ECM_stream_id: " << params.ecm_stream_id; + absl::ReaderMutexLock lock1(&channel_mutex_); + absl::ReaderMutexLock lock2(&stream_mutex_); if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, @@ -893,18 +859,27 @@ Status EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor( if (!custom_params.service_blocking_groups.empty()) { params.service_blocking_groups = custom_params.service_blocking_groups; } + if (custom_params.entitlement_rotation.rotation_enabled) { + params.entitlement_period_index = + custom_params.entitlement_rotation.period_index; + params.has_entitlement_period_index = true; + params.entitlement_rotation_window_left = + custom_params.entitlement_rotation.rotation_window_left; + params.has_entitlement_rotation_window_left = true; + } return OkStatus(); } void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params, - char* response, + const absl::Span response, size_t* response_length) { VLOG(3) << "CwProvision request. ECM_channel_id: " << params.ecm_channel_id << "; ECM_stream_id: " << params.ecm_stream_id << "; CP_number: " << params.cp_number << "; CP_CW_Combination size: " << params.cp_cw_combinations.size(); + absl::ReaderMutexLock lock1(&channel_mutex_); + absl::WriterMutexLock lock2(&stream_mutex_); - DCHECK(response); DCHECK(response_length); if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, @@ -1060,6 +1035,29 @@ Status EcmgClientHandler::UpdateStreamPrivateParameters( << stream_info->entitlement_keys.size(); } + // A specified entitlement_period_index also indicates entitlement rotation is + // enabled. + if (params.has_entitlement_period_index && + (!stream_info->entitlement_rotation.rotation_enabled || + params.entitlement_period_index != + stream_info->entitlement_rotation.period_index)) { + stream_info->entitlement_rotation.rotation_enabled = true; + stream_info->entitlement_rotation.period_index = + params.entitlement_period_index; + invalidate_ecm_gen = true; + VLOG(1) << "Stream " << params.ecm_stream_id + << " entitlement key rotation period index has been updated to " + << stream_info->entitlement_rotation.period_index; + } + + if (params.has_entitlement_rotation_window_left) { + stream_info->entitlement_rotation.rotation_window_left = + params.entitlement_rotation_window_left; + VLOG(1) << "Stream " << params.ecm_stream_id + << " entitlement key rotation window left has been updated to " + << stream_info->entitlement_rotation.rotation_window_left; + } + if (invalidate_ecm_gen) { stream_info->ecm.reset(); } @@ -1104,6 +1102,7 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) { ecm_init_params.ecm_version = ecmg_config_->ecm_version; ecm_init_params.ecc_private_signing_key = ecmg_config_->ecc_private_signing_key; + ecm_init_params.entitlement_rotation = stream_info->entitlement_rotation; // Override track type. This Simulcrypt ECMg implementation assumes only one // track type -- all entitlement keys passed in will be used to generate ECM. @@ -1210,6 +1209,17 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params, } std::vector group_ids(group_id_set.begin(), group_id_set.end()); + // Override the entitlement rotation window left if there is a change. + if (stream_info->entitlement_rotation.rotation_enabled && + stream_info->entitlement_rotation.rotation_window_left != + stream_info->ecm->GetEntitlementRotationWindowLeft()) { + Status status = stream_info->ecm->SetEntitlementRotationWindowLeft( + stream_info->entitlement_rotation.rotation_window_left); + if (!status.ok()) { + return status; + } + } + Status status; std::string serialized_ecm; if (key_count > 1) { @@ -1224,6 +1234,10 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params, } LOG(INFO) << "ECM generated with size: " << serialized_ecm.size() << "."; + // Record the latest entitlement rotation window left value. + stream_info->entitlement_rotation.rotation_window_left = + stream_info->ecm->GetEntitlementRotationWindowLeft(); + // Make a TS packet carrying the serialized ECM. // According to the standard, it is the head-end's responsibility to fill // the PID field in TS packet header. @@ -1249,5 +1263,42 @@ absl::optional EcmgClientHandler::GenerateRandomWrappedKeyIv() return absl::nullopt; } +WvEcmgChannelStatus EcmgClientHandler::GetChannelStatus() const { + absl::ReaderMutexLock lock1(&channel_mutex_); + absl::ReaderMutexLock lock2(&stream_mutex_); + + WvEcmgChannelStatus status; + status.has_setup = channel_id_set_; + status.channel_id = channel_id_; + status.ca_system_id = cas_id_; + status.num_of_open_streams = streams_info_.size(); + status.ecm_version = ecmg_config_->ecm_version; + status.section_tspkt_flag = kSectionTSpktFlag; + status.delay_start = ecmg_config_->delay_start; + status.delay_stop = ecmg_config_->delay_stop; + status.ecm_pep_period = ecmg_config_->ecm_rep_period; + status.max_streams = kMaxStream; + status.min_cp_duration = ecmg_config_->max_comp_time; + status.lead_cw = ecmg_config_->number_of_content_keys - 1; + status.cw_per_message = ecmg_config_->number_of_content_keys; + status.max_comp_time = ecmg_config_->max_comp_time; + return status; +} + +std::vector EcmgClientHandler::GetStreamStatus() const { + absl::ReaderMutexLock lock(&stream_mutex_); + std::vector result; + result.reserve(streams_info_.size()); + for (const auto& stream_id_info : streams_info_) { + WvEcmgStreamStatus status; + status.stream_id = stream_id_info.first; + status.ecm_id = stream_id_info.second->ecm_id; + status.age_restriction = stream_id_info.second->age_restriction; + status.crypto_mode = stream_id_info.second->crypto_mode; + result.push_back(status); + } + return result; +} + } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler.h b/media_cas_packager_sdk/internal/ecmg_client_handler.h index 982b747..7290e1c 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler.h +++ b/media_cas_packager_sdk/internal/ecmg_client_handler.h @@ -17,6 +17,7 @@ #include #include "absl/container/flat_hash_map.h" +#include "absl/synchronization/mutex.h" #include "absl/types/optional.h" #include "absl/types/span.h" #include "common/status.h" @@ -53,6 +54,10 @@ struct EcmgParameters { std::string access_criteria; std::string fingerprinting_control; std::vector service_blocking_groups; + bool has_entitlement_period_index = false; + uint32_t entitlement_period_index; + bool has_entitlement_rotation_window_left = false; + uint32_t entitlement_rotation_window_left; }; struct EcmgStreamInfo { @@ -64,6 +69,7 @@ struct EcmgStreamInfo { std::vector entitlement_keys; std::unique_ptr ecm; uint8_t age_restriction; + EntitlementKeyRotationInfo entitlement_rotation; }; // A class that handles one (and only one) ECMG client. @@ -79,7 +85,8 @@ class EcmgClientHandler { // If any response is generated, it would returned via |response| // and |response_length|. // Returns length of |request| that has been processed. - virtual size_t HandleRequest(const char* const request, char* response, + virtual size_t HandleRequest(const char* const request, + const absl::Span response, size_t* response_length); // Sets the custom entitlement key fetching function used by ECMG to fetch @@ -115,44 +122,63 @@ class EcmgClientHandler { service_blocking_func_ = service_blocking_func; } + // Retrieves the current channel status; + WvEcmgChannelStatus GetChannelStatus() const; + + // Retrieves the status of all open streams; + std::vector GetStreamStatus() const; + private: - void HandleChannelSetup(const EcmgParameters& params, char* response, + void HandleChannelSetup(const EcmgParameters& params, + const absl::Span response, size_t* response_length); - void HandleChannelTest(const EcmgParameters& params, char* response, + void HandleChannelTest(const EcmgParameters& params, + const absl::Span response, size_t* response_length) const; - void HandleChannelClose(const EcmgParameters& params, char* response, + void HandleChannelClose(const EcmgParameters& params, + const absl::Span response, size_t* response_length); - void HandleChannelError(const EcmgParameters& params, char* response, + void HandleChannelError(const EcmgParameters& params, + const absl::Span response, size_t* response_length) const; - void HandleStreamSetup(const EcmgParameters& params, char* response, + void HandleStreamSetup(const EcmgParameters& params, + const absl::Span response, size_t* response_length); - void HandleStreamTest(const EcmgParameters& params, char* response, + void HandleStreamTest(const EcmgParameters& params, + const absl::Span response, size_t* response_length) const; - void HandleStreamCloseRequest(const EcmgParameters& params, char* response, + void HandleStreamCloseRequest(const EcmgParameters& params, + const absl::Span response, size_t* response_length); - void HandleStreamError(const EcmgParameters& params, char* response, + void HandleStreamError(const EcmgParameters& params, + const absl::Span response, size_t* response_length) const; - void HandleCwProvision(const EcmgParameters& params, char* response, + void HandleCwProvision(const EcmgParameters& params, + const absl::Span response, size_t* response_length); Status UpdateParamsWithCustomAccessCriteriaProcessor( EcmgParameters& params) const; // Update channel level private parameters using |params|. - Status UpdateChannelPrivateParameters(const EcmgParameters& params); + Status UpdateChannelPrivateParameters(const EcmgParameters& params) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(channel_mutex_); // Update stream level private parameters using |params|. If changes in // |params| makes the stream hold Ecm instance (EcmgStreamInfo.ecm) no longer // usable (changing params used in Ecm::Initialize()), it will invalidate // (destroy) it. - Status UpdateStreamPrivateParameters(const EcmgParameters& params); + Status UpdateStreamPrivateParameters(const EcmgParameters& params) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(stream_mutex_); // Check if all required parameters have been set. If so, initialize |ecm_| by // fetching entitlement keys. - Status CheckAndInitializeEcm(const EcmgParameters& params); + Status CheckAndInitializeEcm(const EcmgParameters& params) + ABSL_SHARED_LOCKS_REQUIRED(channel_mutex_, stream_mutex_); // Gather all information needed to build a TS packet |ecm_datagram| // conatianing an ECM. Status BuildEcmDatagram(const EcmgParameters& params, absl::Span ecm_datagram, - ssize_t* bytes_modified) const; + ssize_t* bytes_modified) const + ABSL_SHARED_LOCKS_REQUIRED(stream_mutex_); // Generates a random wrapped key iv string. Returns true on success, false // otherwise. The main purpose for this function is easier testing. @@ -163,17 +189,21 @@ class EcmgClientHandler { } EcmgConfig* ecmg_config_; + mutable absl::Mutex channel_mutex_; + mutable absl::Mutex stream_mutex_ ABSL_ACQUIRED_AFTER(channel_mutex_); + // Per spec, "There is always one (and only one) channel per TCP connection". - bool channel_id_set_ = false; - uint16_t channel_id_; - uint16_t cas_id_; + bool channel_id_set_ ABSL_GUARDED_BY(channel_mutex_) = false; + uint16_t channel_id_ ABSL_GUARDED_BY(channel_mutex_); + uint16_t cas_id_ ABSL_GUARDED_BY(channel_mutex_); // Channel specific information. - uint8_t age_restriction_ = 0; - std::vector content_ivs_; + uint8_t age_restriction_ ABSL_GUARDED_BY(channel_mutex_) = 0; + std::vector content_ivs_ ABSL_GUARDED_BY(channel_mutex_); // Map from ECM_stream_id to EcmgStreamInfo. - absl::flat_hash_map> streams_info_; + absl::flat_hash_map> streams_info_ + ABSL_GUARDED_BY(stream_mutex_); // Used to fetch entitlement keys if none are received from SCS. EntitlementKeyFetcherFunc custom_key_fetcher_; // Used to process custom aceess criteria received in CwProvision message. diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc b/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc index faf2890..fdcbc73 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc +++ b/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc @@ -33,18 +33,15 @@ namespace widevine { namespace cas { namespace { -using simulcrypt_util::AddParam; -using simulcrypt_util::AddUint16Param; -using simulcrypt_util::AddUint32Param; -using simulcrypt_util::AddUint8Param; -using simulcrypt_util::BuildMessageHeader; using ::testing::_; using ::testing::ByMove; +using ::testing::DoAll; using ::testing::ElementsAre; using ::testing::ElementsAreArray; using ::testing::IsEmpty; using ::testing::NotNull; using ::testing::Return; +using ::testing::SaveArg; constexpr size_t kBufferSize = 1024; constexpr uint16_t kWidevineSystemId = 0x4AD4; @@ -122,6 +119,7 @@ class EcmgClientHandlerTest : public ::testing::Test { config_.max_comp_time = 100; config_.access_criteria_transfer_mode = 1; config_.number_of_content_keys = 2; + config_.crypto_mode = CryptoMode::kAesScte; handler_ = absl::make_unique(&config_); EXPECT_CALL(*handler_.get(), CreateEcmInstance).WillRepeatedly([] { return absl::make_unique(); @@ -172,25 +170,23 @@ class EcmgClientHandlerTest : public ::testing::Test { void BuildChannelSetupRequest(uint16_t channel_id, uint32_t super_cas_id, uint8_t age_restriction, - const std::string& crypto_mode, char* message, + const std::string& crypto_mode, + const absl::Span message, size_t* message_length) { - EXPECT_TRUE(message != nullptr); EXPECT_TRUE(message_length != nullptr); - BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_SETUP, message, - message_length); - AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length); - AddUint32Param(SUPER_CAS_ID, super_cas_id, message, message_length); + SimulcryptMessage builder(message, message_length); + builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_SETUP); + builder.AddUint16Param(ECM_CHANNEL_ID, channel_id); + builder.AddUint32Param(SUPER_CAS_ID, super_cas_id); if (age_restriction != 0) { - AddUint8Param(AGE_RESTRICTION, age_restriction, message, message_length); + builder.AddUint8Param(AGE_RESTRICTION, age_restriction); } if (!crypto_mode.empty()) { - AddParam(CRYPTO_MODE, - reinterpret_cast(crypto_mode.c_str()), - crypto_mode.size(), message, message_length); + builder.AddParam(CRYPTO_MODE, + reinterpret_cast(crypto_mode.c_str()), + crypto_mode.size()); } - uint16_t total_param_length = *message_length - 5; - Host16ToBigEndian(message + 3, &total_param_length); } void BuildStreamSetupRequest( @@ -198,77 +194,66 @@ class EcmgClientHandlerTest : public ::testing::Test { uint16_t nominal_CP_duration, const std::vector& entitlements, const std::vector& group_entitlements, - const std::vector& content_ivs, char* message, - size_t* message_length) { - EXPECT_TRUE(message != nullptr); + const std::vector& content_ivs, + const absl::Span message, size_t* message_length) { EXPECT_TRUE(message_length != nullptr); - BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_SETUP, message, - message_length); - AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length); - AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length); - AddUint16Param(ECM_ID, ecm_id, message, message_length); - AddUint16Param(NOMINAL_CP_DURATION, nominal_CP_duration, message, - message_length); + SimulcryptMessage builder(message, message_length); + builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_SETUP); + builder.AddUint16Param(ECM_CHANNEL_ID, channel_id); + builder.AddUint16Param(ECM_STREAM_ID, stream_id); + builder.AddUint16Param(ECM_ID, ecm_id); + builder.AddUint16Param(NOMINAL_CP_DURATION, nominal_CP_duration); if (!entitlements.empty()) { for (const auto& entitlement : entitlements) { - AddParam(ENTITLEMENT_ID_KEY_COMBINATION, - reinterpret_cast(entitlement.c_str()), - entitlement.size(), message, message_length); + builder.AddParam(ENTITLEMENT_ID_KEY_COMBINATION, + reinterpret_cast(entitlement.c_str()), + entitlement.size()); } } if (!group_entitlements.empty()) { for (const auto& entitlement : group_entitlements) { - AddParam(ENTITLEMENT_ID_KEY_GROUP_COMBINATION, - reinterpret_cast(entitlement.c_str()), - entitlement.size(), message, message_length); + builder.AddParam(ENTITLEMENT_ID_KEY_GROUP_COMBINATION, + reinterpret_cast(entitlement.c_str()), + entitlement.size()); } } if (!content_ivs.empty()) { for (auto& content_iv : content_ivs) { - AddParam(CONTENT_IV, - reinterpret_cast(content_iv.c_str()), - content_iv.size(), message, message_length); + builder.AddParam(CONTENT_IV, + reinterpret_cast(content_iv.c_str()), + content_iv.size()); } } - - uint16_t total_param_length = *message_length - 5; - Host16ToBigEndian(message + 3, &total_param_length); } void BuildCwProvisionRequest( uint16_t channel_id, uint16_t stream_id, uint16_t cp_number, const std::vector& cp_cw_combination, const std::string& fingerprinting, - const std::vector& service_blockings, char* message, - size_t* message_length) { - EXPECT_TRUE(message != nullptr); + const std::vector& service_blockings, + const absl::Span message, size_t* message_length) { EXPECT_TRUE(message_length != nullptr); - BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CW_PROVISION, message, - message_length); - AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length); - AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length); - AddUint16Param(CP_NUMBER, cp_number, message, message_length); + SimulcryptMessage builder(message, message_length); + builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CW_PROVISION); + builder.AddUint16Param(ECM_CHANNEL_ID, channel_id); + builder.AddUint16Param(ECM_STREAM_ID, stream_id); + builder.AddUint16Param(CP_NUMBER, cp_number); for (auto& cp_cw : cp_cw_combination) { uint8_t combined[100]; Host16ToBigEndian(combined, &cp_cw.cp); memcpy(combined + 2, cp_cw.cw.c_str(), cp_cw.cw.length()); - AddParam(CP_CW_COMBINATION, combined, 2 + cp_cw.cw.length(), message, - message_length); + builder.AddParam(CP_CW_COMBINATION, combined, 2 + cp_cw.cw.length()); } if (!fingerprinting.empty()) { - AddParam(FINGERPRINTING_CONTROL, - reinterpret_cast(fingerprinting.c_str()), - fingerprinting.size(), message, message_length); + builder.AddParam(FINGERPRINTING_CONTROL, + reinterpret_cast(fingerprinting.c_str()), + fingerprinting.size()); } for (auto const& sb : service_blockings) { - AddParam(SERVICE_BLOCKING_GROUP, - reinterpret_cast(sb.c_str()), sb.size(), message, - message_length); + builder.AddParam(SERVICE_BLOCKING_GROUP, + reinterpret_cast(sb.c_str()), sb.size()); } - - uint16_t total_param_length = *message_length - 5; - Host16ToBigEndian(message + 3, &total_param_length); } void CheckChannelError(uint16_t expected_error_code, @@ -838,12 +823,15 @@ class MockEcm : public Ecm { (override)); MOCK_METHOD(void, SetServiceBlocking, (const EcmServiceBlockingParams*), (override)); + MOCK_METHOD(Status, SetEntitlementRotationWindowLeft, (uint32_t), (override)); + MOCK_METHOD(uint32_t, GetEntitlementRotationWindowLeft, (), + (const, override)); MOCK_METHOD(Status, GenerateEcm, (EntitledKeyInfo * even_key, EntitledKeyInfo* odd_key, const std::string& track_type, const std::vector& group_ids, std::string* serialized_ecm), - (const, override)); + (override)); }; EcmFingerprintingParams FakeFingerprintingSettingFunc(uint16_t channel_id, @@ -1072,6 +1060,161 @@ TEST_F(EcmgClientHandlerTest, CustomAccessCriteriaProcessorReturnsError) { CheckStreamError(INVALID_MESSAGE, response_, response_len_); } +TEST_F(EcmgClientHandlerTest, GetChannelStreamStatusNoChannelSuccess) { + EXPECT_FALSE(handler_->GetChannelStatus().has_setup); + EXPECT_TRUE(handler_->GetStreamStatus().empty()); +} + +TEST_F(EcmgClientHandlerTest, GetChannelStreamStatusNoStreamSuccess) { + SetupValidStandardChannel(); + + WvEcmgChannelStatus status = handler_->GetChannelStatus(); + + EXPECT_TRUE(status.has_setup); + EXPECT_EQ(status.channel_id, kChannelId); + EXPECT_EQ(status.ca_system_id, kWidevineSystemId); + EXPECT_EQ(status.num_of_open_streams, 0); + EXPECT_EQ(status.ecm_version, EcmVersion::kV2); + EXPECT_TRUE(handler_->GetStreamStatus().empty()); +} + +TEST_F(EcmgClientHandlerTest, GetChannelStreamStatusTwoStreamsSuccess) { + SetupValidStandardChannel(); + BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration, + /*entitlements=*/{}, /*group_entitlements=*/{}, + {kContentKeyIvEven, kContentKeyIvOdd}, request_, + &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + EXPECT_EQ(response_len_, sizeof(kTestEcmgStreamStatus)); + BuildStreamSetupRequest( + kChannelId, kStreamId + 1, kEcmId + 1, kNominalCpDuration, + /*entitlements=*/{}, /*group_entitlements=*/{}, + {kContentKeyIvEven, kContentKeyIvOdd}, request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + EXPECT_EQ(response_len_, sizeof(kTestEcmgStreamStatus)); + + WvEcmgChannelStatus channel_status = handler_->GetChannelStatus(); + std::vector stream_status = handler_->GetStreamStatus(); + + EXPECT_TRUE(channel_status.has_setup); + EXPECT_EQ(channel_status.channel_id, kChannelId); + EXPECT_EQ(channel_status.ca_system_id, kWidevineSystemId); + EXPECT_EQ(channel_status.num_of_open_streams, 2); + ASSERT_EQ(stream_status.size(), 2); + EXPECT_NE(stream_status[0].stream_id, stream_status[1].stream_id); + for (const auto& status : stream_status) { + EXPECT_EQ(status.age_restriction, 0); + EXPECT_EQ(status.crypto_mode, CryptoMode::kAesScte); + EXPECT_THAT(status.stream_id, testing::AnyOf(kStreamId, kStreamId + 1)); + EXPECT_THAT(status.ecm_id, testing::AnyOf(kEcmId, kEcmId + 1)); + } +} + +// A fake callback function that has entitlement rotation info. It has the +// following behavior: the returned period_index increases by one on each call. +Status FakeCustomAcProcessorWithEntitlementRotationIndexChangeFunc( + uint16_t /*channel_id*/, uint16_t /*stream_id*/, + const std::string& access_criteria, EcmgCustomParameters& params) { + static int period_index = 100; + params.age_restriction = 0; + params.content_ivs = {kContentKeyIvEven, kContentKeyIvOdd}; + params.crypto_mode = kCryptoMode; + params.entitlement_keys = { + {kTrackTypesSD, /*group_id=*/"", /*is_even_key=*/false, + kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}, + {kTrackTypesSD, /*group_id=*/"", /*is_even_key=*/true, + kEntitlementKeyIdEven, kEntitlementKeyValueEven}}; + params.entitlement_rotation.rotation_enabled = true; + params.entitlement_rotation.period_index = period_index; + params.entitlement_rotation.rotation_window_left = 1000; + + // The next call to this function will return a different period_index. + ++period_index; + return OkStatus(); +} + +TEST_F(EcmgClientHandlerTest, EntitlementRotationIndexChangeSuccess) { + handler_->SetCustomAccessCriteriaProcessFunc( + FakeCustomAcProcessorWithEntitlementRotationIndexChangeFunc); + SetupValidStandardChannelStream(); + auto ecm = absl::make_unique(); + MockEcm* ecm_ptr = ecm.get(); + EXPECT_CALL(*handler_, CreateEcmInstance) + .Times(1) + .WillOnce(Return(ByMove(std::move(ecm)))); + EcmInitParameters actual_init_params; + EXPECT_CALL(*ecm_ptr, Initialize(_, _)) + .Times(1) + .WillOnce(DoAll(SaveArg<0>(&actual_init_params), Return(OkStatus()))); + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); + EXPECT_TRUE(actual_init_params.entitlement_rotation.rotation_enabled); + EXPECT_EQ(actual_init_params.entitlement_rotation.period_index, 100); + EXPECT_EQ(actual_init_params.entitlement_rotation.rotation_window_left, 1000); + + // The second call will reinitialize Ecm with period_index increased by 1. + ecm = absl::make_unique(); + ecm_ptr = ecm.get(); + EXPECT_CALL(*handler_, CreateEcmInstance) + .Times(1) + .WillOnce(Return(ByMove(std::move(ecm)))); + EXPECT_CALL(*ecm_ptr, Initialize(_, _)) + .Times(1) + .WillOnce(DoAll(SaveArg<0>(&actual_init_params), Return(OkStatus()))); + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); + EXPECT_TRUE(actual_init_params.entitlement_rotation.rotation_enabled); + EXPECT_EQ(actual_init_params.entitlement_rotation.period_index, 101); + EXPECT_EQ(actual_init_params.entitlement_rotation.rotation_window_left, 1000); +} + +// A fake callback function that has entitlement rotation info. It has the +// following behavior: the returned period_index and rotation_window_left are +// fixed. +Status FakeCustomAcProcessorWithEntitlementRotationFunc( + uint16_t /*channel_id*/, uint16_t /*stream_id*/, + const std::string& access_criteria, EcmgCustomParameters& params) { + params.age_restriction = 0; + params.content_ivs = {kContentKeyIvEven, kContentKeyIvOdd}; + params.crypto_mode = kCryptoMode; + params.entitlement_keys = { + {kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ false, + kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}, + {kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ true, + kEntitlementKeyIdEven, kEntitlementKeyValueEven}}; + params.entitlement_rotation.rotation_enabled = true; + params.entitlement_rotation.period_index = 100; + params.entitlement_rotation.rotation_window_left = 1000; + + return OkStatus(); +} + +TEST_F(EcmgClientHandlerTest, EntitlementRotationWindowLeftSuccess) { + handler_->SetCustomAccessCriteriaProcessFunc( + FakeCustomAcProcessorWithEntitlementRotationFunc); + SetupValidStandardChannelStream(); + auto ecm = absl::make_unique(); + MockEcm* ecm_ptr = ecm.get(); + EXPECT_CALL(*handler_, CreateEcmInstance) + .Times(1) + .WillOnce(Return(ByMove(std::move(ecm)))); + EcmInitParameters actual_init_params; + EXPECT_CALL(*ecm_ptr, Initialize(_, _)) + .Times(1) + .WillOnce(DoAll(SaveArg<0>(&actual_init_params), Return(OkStatus()))); + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); + EXPECT_TRUE(actual_init_params.entitlement_rotation.rotation_enabled); + EXPECT_EQ(actual_init_params.entitlement_rotation.period_index, 100); + EXPECT_EQ(actual_init_params.entitlement_rotation.rotation_window_left, 1000); + + // Calling again will not reinitialize the ECM object. But call to + // SetEntitlementRotationWindowLeft will be made as the rotation_window_left + // specified did not decrease. + EXPECT_CALL(*ecm_ptr, GetEntitlementRotationWindowLeft()) + .WillRepeatedly(Return(999)); + EXPECT_CALL(*ecm_ptr, SetEntitlementRotationWindowLeft(1000)) + .WillOnce(Return(OkStatus())); + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); +} + } // namespace } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/internal/emm.cc b/media_cas_packager_sdk/internal/emm.cc index 524fb3f..6bfcc45 100644 --- a/media_cas_packager_sdk/internal/emm.cc +++ b/media_cas_packager_sdk/internal/emm.cc @@ -8,7 +8,6 @@ #include "media_cas_packager_sdk/internal/emm.h" -#include #include #include "glog/logging.h" @@ -96,35 +95,33 @@ Status Emm::SetServiceBlocking( return status; } -Status Emm::GenerateEmm(std::string* serialized_emm) const { +Status Emm::GenerateEmm(std::string* serialized_emm) { if (serialized_emm == nullptr) { return {error::INVALID_ARGUMENT, "No return emm std::string pointer."}; } - EmmSerializingParameters serializing_params; - serializing_params.payload = emm_payload_.SerializeAsString(); - serializing_params.timestamp = GenerateTimestampEpochSeconds(); + emm_payload_.set_timestamp_secs(GenerateTimestampEpochSeconds()); - // Generate serialized emm (without signature yet). - Status status = - GenerateSerializedEmmNoSignature(serializing_params, serialized_emm); + SignedEmmPayload signed_emm; + if (!emm_payload_.SerializeToString( + signed_emm.mutable_serialized_payload())) { + return {error::INTERNAL, "Failed to serialize EMM."}; + } + Status status = GenerateSignature(signed_emm.serialized_payload(), + signed_emm.mutable_signature()); if (!status.ok()) { return status; } - // Calculate and append signature. - std::string signature; - status = GenerateSignature(*serialized_emm, &signature); - if (!status.ok()) { - return status; + if (!signed_emm.SerializeToString(serialized_emm)) { + return {error::INTERNAL, "Failed to serialize signed EMM."}; } - absl::StrAppend(serialized_emm, signature); return OkStatus(); } Status Emm::GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter, const absl::Span packet, - ssize_t* bytes_modified) const { + ssize_t* bytes_modified) { if (continuity_counter == nullptr || bytes_modified == nullptr) { return {error::INVALID_ARGUMENT, "continuity_counter and bytes_modified must not be null"}; @@ -154,35 +151,6 @@ Status Emm::GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter, return OkStatus(); } -Status Emm::GenerateSerializedEmmNoSignature( - const EmmSerializingParameters& params, std::string* serialized_emm) const { - if (serialized_emm == nullptr) { - return {error::INVALID_ARGUMENT, "No return emm std::string pointer."}; - } - - std::bitset version(kEmmVersion); - std::bitset header_length( - sizeof(params.timestamp)); - std::bitset timestamp(params.timestamp); - std::bitset payload_length( - params.payload.length()); - - std::string emm_bitset = - absl::StrCat(version.to_string(), header_length.to_string(), - timestamp.to_string(), payload_length.to_string()); - - Status status = - string_util::BitsetStringToBinaryString(emm_bitset, serialized_emm); - if (!status.ok() || serialized_emm->empty()) { - LOG(ERROR) << "Failed to convert EMM bitset to std::string"; - return {error::INTERNAL, "Failed to convert EMM bitset to std::string"}; - } - - // Appends payload. - absl::StrAppend(serialized_emm, params.payload); - return OkStatus(); -} - int64_t Emm::GenerateTimestampEpochSeconds() const { return absl::ToUnixSeconds(absl::Now()); } diff --git a/media_cas_packager_sdk/internal/emm.h b/media_cas_packager_sdk/internal/emm.h index 33cc685..7f78319 100644 --- a/media_cas_packager_sdk/internal/emm.h +++ b/media_cas_packager_sdk/internal/emm.h @@ -59,7 +59,7 @@ class Emm { const std::vector& service_blockings); // Generates serialized EMM to |serialized_emm|. - virtual Status GenerateEmm(std::string* serialized_emm) const; + virtual Status GenerateEmm(std::string* serialized_emm); // Generates serialized EMM and wraps it in TS (transport stream) packets. // Args (all pointer parameters must be not nullptr): @@ -73,17 +73,9 @@ class Emm { // of ECM into |buffer| is successful. virtual Status GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter, const absl::Span packet, - ssize_t* bytes_modified) const; + ssize_t* bytes_modified); private: - struct EmmSerializingParameters { - int64_t timestamp; - std::string payload; - }; - - Status GenerateSerializedEmmNoSignature( - const EmmSerializingParameters& params, - std::string* serialized_emm) const; virtual int64_t GenerateTimestampEpochSeconds() const; virtual Status GenerateSignature(const std::string& message, std::string* signature) const; diff --git a/media_cas_packager_sdk/internal/emm_test.cc b/media_cas_packager_sdk/internal/emm_test.cc index 17aa46c..aabaa84 100644 --- a/media_cas_packager_sdk/internal/emm_test.cc +++ b/media_cas_packager_sdk/internal/emm_test.cc @@ -46,57 +46,9 @@ constexpr uint16_t kTestPid = 1; constexpr int64_t kTestTimestamp = 1597882875; // Hex std::string of kTestTimestamp. constexpr char kTestTimestampHexString[] = "000000005f3dc1fb"; - -constexpr uint8_t kExpectedEmmVersion = 1; -constexpr uint8_t kExpectedHeaderLength = 8; - -constexpr int kVersionStartIndex = 0; -constexpr int kHeaderLengthStartIndex = 1; -constexpr int kTimestampStartIndex = 2; -constexpr int kTimestampLengthBytes = 8; -constexpr int kPayloadLengthStartIndex = 10; -constexpr int kPayloadStartIndex = 12; constexpr int kSignatureLength = 71; -// Length in bytes when there is no payload. -constexpr int kExpectedNoPayloadLengthBytes = - kPayloadStartIndex + kSignatureLength; - constexpr char kFakeSignatureFiller = 'x'; // 0x78 -constexpr char kExpectedEmptyEmmPacket[] = { - // TS header. - '\x47', '\x40', '\x01', '\x10', - // Section header. - '\x00', '\x82', '\x70', '\x53', - // EMM version. - '\x01', - // EMM header size. - '\x08', - // Timestamp (8 bytes) - '\x00', '\x00', '\x00', '\x00', '\x5f', '\x3d', '\xc1', '\xfb', - // Payload length (2 bytes) - '\x00', '\x00', - // Signature (71 bytes) - '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', - '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', - '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', - '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', - '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', - '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', - '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', - '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', - // Padding - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff'}; constexpr char kExpectedMockEmmPacket[] = { // TS header. '\x47', '\x40', '\x01', '\x10', @@ -173,15 +125,15 @@ TEST(GenerateTimestampEpochSecondsTest, TimestampGenerated) { const absl::Time test_start_time = absl::Now(); FakeEmmWithFakeSignature emm_gen; std::string serialized_emm; + EXPECT_OK(emm_gen.GenerateEmm(&serialized_emm)); - uint32_t timestamp = 0; - // Extract timestamp value from serialized EMM. - for (const auto& timestamp_byte : - serialized_emm.substr(kTimestampStartIndex, kTimestampLengthBytes)) { - timestamp = (timestamp << 8) | static_cast(timestamp_byte); - } - EXPECT_NE(timestamp, 0); - EXPECT_GE(timestamp, absl::ToUnixSeconds(test_start_time)); + + SignedEmmPayload signed_emm; + ASSERT_TRUE(signed_emm.ParseFromString(serialized_emm)); + EmmPayload emm_payload; + ASSERT_TRUE(emm_payload.ParseFromString(signed_emm.serialized_payload())); + EXPECT_NE(emm_payload.timestamp_secs(), 0); + EXPECT_GE(emm_payload.timestamp_secs(), absl::ToUnixSeconds(test_start_time)); } class FakeEmm : public Emm { @@ -202,25 +154,10 @@ class EmmTest : public ::testing::Test { protected: EmmTest() : emm_(absl::make_unique()) {} - void VerifiyEmmHeader(const std::string& serialized_emm) { - EXPECT_EQ(static_cast(serialized_emm[kVersionStartIndex]), - kExpectedEmmVersion); - EXPECT_EQ(static_cast(serialized_emm[kHeaderLengthStartIndex]), - kExpectedHeaderLength); - EXPECT_EQ(absl::BytesToHexString(serialized_emm.substr( - kTimestampStartIndex, kTimestampLengthBytes)), - kTestTimestampHexString); - } void VerifiySignature(const std::string& signature) { std::string expected_signature(kSignatureLength, kFakeSignatureFiller); EXPECT_EQ(signature, expected_signature); } - int GetPayloadLength(const std::string& serialized_emm) { - return static_cast(serialized_emm[kPayloadLengthStartIndex]) - << 8 | - static_cast(serialized_emm[kPayloadLengthStartIndex + 1]); - } - std::unique_ptr emm_; }; @@ -232,31 +169,22 @@ TEST_F(EmmTest, GenerateEmmSinglePayloadSuccess) { GetValidServiceBlockingParams(); EXPECT_OK(emm_->SetFingerprinting({fingerprinting})); EXPECT_OK(emm_->SetServiceBlocking({service_blocking})); + std::string serialized_emm; - std::string result; - EXPECT_OK(emm_->GenerateEmm(&result)); - EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes); - VerifiyEmmHeader(result); + EXPECT_OK(emm_->GenerateEmm(&serialized_emm)); - int payload_lengh = GetPayloadLength(result); - ASSERT_GT(payload_lengh, 0); - ASSERT_EQ(result.length(), - kPayloadStartIndex + payload_lengh + kSignatureLength); - - std::string payload_section = - result.substr(kPayloadStartIndex, payload_lengh); - // Parse the payload and validate fields. + SignedEmmPayload signed_emm; + ASSERT_TRUE(signed_emm.ParseFromString(serialized_emm)); + VerifiySignature(signed_emm.signature()); EmmPayload emm_payload; - ASSERT_TRUE(emm_payload.ParseFromString(payload_section)); - + ASSERT_TRUE(emm_payload.ParseFromString(signed_emm.serialized_payload())); EmmPayload expected_payload; LoadExpectedFingerprintingProto(expected_payload.add_fingerprinting()); LoadExpectedServiceBlockingProto(expected_payload.add_service_blocking()); + expected_payload.set_timestamp_secs(kTestTimestamp); std::string serialized_expected_payload; expected_payload.SerializeToString(&serialized_expected_payload); - EXPECT_EQ(payload_section, serialized_expected_payload); - - VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh)); + EXPECT_EQ(signed_emm.serialized_payload(), serialized_expected_payload); } // Verifies GenerateEmm is successful with multiple fingerprinting and @@ -267,7 +195,6 @@ TEST_F(EmmTest, GenerateEmmMultiplePayloadSuccess) { fingerprinting_params.control = kFingerprintingControl; EXPECT_OK(emm_->SetFingerprinting( {GetValidFingerprintingParams(), fingerprinting_params})); - ServiceBlockingInitParameters service_blocking_params; service_blocking_params.channels = {kChannelThree}; service_blocking_params.device_groups = {kDeviceGroupOne, kDeviceGroupTwo}; @@ -275,26 +202,17 @@ TEST_F(EmmTest, GenerateEmmMultiplePayloadSuccess) { service_blocking_params.end_time = kServiceBockingEndTime; EXPECT_OK(emm_->SetServiceBlocking( {GetValidServiceBlockingParams(), service_blocking_params})); + std::string serialized_emm; - std::string result; - EXPECT_OK(emm_->GenerateEmm(&result)); - EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes); - VerifiyEmmHeader(result); + EXPECT_OK(emm_->GenerateEmm(&serialized_emm)); - int payload_lengh = GetPayloadLength(result); - ASSERT_GT(payload_lengh, 0); - ASSERT_EQ(result.length(), - kPayloadStartIndex + payload_lengh + kSignatureLength); - - std::string payload_section = - result.substr(kPayloadStartIndex, payload_lengh); - // Parse the payload and validate fields. + SignedEmmPayload signed_emm; + ASSERT_TRUE(signed_emm.ParseFromString(serialized_emm)); + VerifiySignature(signed_emm.signature()); EmmPayload emm_payload; - ASSERT_TRUE(emm_payload.ParseFromString(payload_section)); + ASSERT_TRUE(emm_payload.ParseFromString(signed_emm.serialized_payload())); EXPECT_EQ(emm_payload.fingerprinting_size(), 2); EXPECT_EQ(emm_payload.service_blocking_size(), 2); - - VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh)); } // Verifies GenerateEmm is successful with only fingerprinting information in @@ -304,29 +222,21 @@ TEST_F(EmmTest, GenerateEmmFingerprintingOnlySuccess) { EXPECT_OK(emm_->SetFingerprinting({fingerprinting})); // OK to be called again. EXPECT_OK(emm_->SetFingerprinting({fingerprinting})); + std::string serialized_emm; - std::string result; - EXPECT_OK(emm_->GenerateEmm(&result)); - EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes); - VerifiyEmmHeader(result); + EXPECT_OK(emm_->GenerateEmm(&serialized_emm)); - int payload_lengh = GetPayloadLength(result); - ASSERT_GT(payload_lengh, 0); - ASSERT_EQ(result.length(), - kPayloadStartIndex + payload_lengh + kSignatureLength); - - std::string payload_section = - result.substr(kPayloadStartIndex, payload_lengh); - // Parse the payload and validate fields. + SignedEmmPayload signed_emm; + ASSERT_TRUE(signed_emm.ParseFromString(serialized_emm)); + VerifiySignature(signed_emm.signature()); EmmPayload emm_payload; - ASSERT_TRUE(emm_payload.ParseFromString(payload_section)); + ASSERT_TRUE(emm_payload.ParseFromString(signed_emm.serialized_payload())); EmmPayload expected_payload; + expected_payload.set_timestamp_secs(kTestTimestamp); LoadExpectedFingerprintingProto(expected_payload.add_fingerprinting()); std::string serialized_expected_payload; expected_payload.SerializeToString(&serialized_expected_payload); - EXPECT_EQ(payload_section, serialized_expected_payload); - - VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh)); + EXPECT_EQ(signed_emm.serialized_payload(), serialized_expected_payload); } // Verifies GenerateEmm is successful with only service_blocking information in @@ -335,54 +245,34 @@ TEST_F(EmmTest, GenerateEmmServiceBlockingOnlySuccess) { ServiceBlockingInitParameters service_blocking = GetValidServiceBlockingParams(); EXPECT_OK(emm_->SetServiceBlocking({service_blocking})); + std::string serialized_emm; - std::string result; - EXPECT_OK(emm_->GenerateEmm(&result)); - EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes); - VerifiyEmmHeader(result); + EXPECT_OK(emm_->GenerateEmm(&serialized_emm)); - int payload_lengh = GetPayloadLength(result); - ASSERT_GT(payload_lengh, 0); - ASSERT_EQ(result.length(), - kPayloadStartIndex + payload_lengh + kSignatureLength); - - std::string payload_section = - result.substr(kPayloadStartIndex, payload_lengh); - // Parse the payload and validate fields. + SignedEmmPayload signed_emm; + ASSERT_TRUE(signed_emm.ParseFromString(serialized_emm)); + VerifiySignature(signed_emm.signature()); EmmPayload emm_payload; - ASSERT_TRUE(emm_payload.ParseFromString(payload_section)); + ASSERT_TRUE(emm_payload.ParseFromString(signed_emm.serialized_payload())); EmmPayload expected_payload; + expected_payload.set_timestamp_secs(kTestTimestamp); LoadExpectedServiceBlockingProto(expected_payload.add_service_blocking()); std::string serialized_expected_payload; expected_payload.SerializeToString(&serialized_expected_payload); - EXPECT_EQ(payload_section, serialized_expected_payload); - - VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh)); + EXPECT_EQ(signed_emm.serialized_payload(), serialized_expected_payload); } // Verifies GenerateEmm is successful with empty payload. TEST_F(EmmTest, GenerateEmmNoPayloadSuccess) { std::string result; EXPECT_OK(emm_->GenerateEmm(&result)); - EXPECT_EQ(result.length(), kExpectedNoPayloadLengthBytes); - VerifiyEmmHeader(result); - int payload_lengh = GetPayloadLength(result); - EXPECT_EQ(payload_lengh, 0); - EXPECT_EQ(result.length(), kPayloadStartIndex + kSignatureLength); - - VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh)); -} - -// Verifies GenerateEmmTsPackets is successful with empty payload. -TEST_F(EmmTest, GenerateEmmTsPacketsNoPayloadSuccess) { - uint8_t counter = 0; - uint8_t packet[kTsPacketSize]; - ssize_t bytes_modified = 0; - EXPECT_OK( - emm_->GenerateEmmTsPackets(kTestPid, &counter, packet, &bytes_modified)); - EXPECT_THAT(packet, ElementsAreArray(kExpectedEmptyEmmPacket)); - EXPECT_EQ(bytes_modified, kTsPacketSize); + SignedEmmPayload signed_emm; + ASSERT_TRUE(signed_emm.ParseFromString(result)); + VerifiySignature(signed_emm.signature()); + EmmPayload emm_payload; + ASSERT_TRUE(emm_payload.ParseFromString(signed_emm.serialized_payload())); + EXPECT_EQ(emm_payload.timestamp_secs(), kTestTimestamp); } // Verifies GenerateSignature is successful with empty payload. @@ -395,17 +285,15 @@ TEST(GenerateSignatureTest, GenerateSignatureSuccess) { EXPECT_OK(emm.SetPrivateSigningKey(test_keys.private_test_key_1_secp256r1())); EXPECT_OK(emm.GenerateEmm(&emm_generated)); - // Empty payload, so payload start is signature start. - std::string message = emm_generated.substr(0, kPayloadStartIndex); - std::string signature = emm_generated.substr(kPayloadStartIndex); - EXPECT_FALSE(signature.empty()); - + SignedEmmPayload signed_emm; + ASSERT_TRUE(signed_emm.ParseFromString(emm_generated)); // Check the signaure can be verified. std::unique_ptr public_key( ECPublicKey::Create(test_keys.public_test_key_1_secp256r1())); ASSERT_TRUE(public_key != nullptr); - EXPECT_TRUE( - public_key->VerifySignature(message, HashAlgorithm::kSha256, signature)); + EXPECT_TRUE(public_key->VerifySignature(signed_emm.serialized_payload(), + HashAlgorithm::kSha256, + signed_emm.signature())); } // Verifies GenerateEmm fails with no singing key set. @@ -439,8 +327,7 @@ TEST(GenerateSignatureTest, InvalidSigningKeyFail) { // GenerateEmmTsPacketsTest. class MockEmmGenerate : public Emm { public: - MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm), - (const, override)); + MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm), (override)); }; // Verifies GenerateEmmTsPackets is successful with mocked EMM. diff --git a/media_cas_packager_sdk/internal/emmg.cc b/media_cas_packager_sdk/internal/emmg.cc index 40e2d4e..a294235 100644 --- a/media_cas_packager_sdk/internal/emmg.cc +++ b/media_cas_packager_sdk/internal/emmg.cc @@ -13,12 +13,14 @@ #include #include #include +#include #include "glog/logging.h" #include "absl/memory/memory.h" #include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "media_cas_packager_sdk/internal/emmg_constants.h" @@ -169,73 +171,43 @@ void Emmg::Start() { } void Emmg::BuildChannelSetup() { - bzero(request_, BUFFER_SIZE); - request_length_ = 0; - simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, - EMMG_CHANNEL_SETUP, request_, - &request_length_); - simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id, - request_, &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_CHANNEL_ID, - emmg_config_->data_channel_id, request_, - &request_length_); - simulcrypt_util::AddUint8Param(EMMG_SECTION_TSPKT_FLAG, - emmg_config_->section_tspkt_flag, request_, - &request_length_); - uint16_t total_param_length = request_length_ - 5; - Host16ToBigEndian(request_ + 3, &total_param_length); + SimulcryptMessage message(request_, &request_length_); + message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, EMMG_CHANNEL_SETUP); + message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id); + message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id); + message.AddUint8Param(EMMG_SECTION_TSPKT_FLAG, + emmg_config_->section_tspkt_flag); } void Emmg::BuildStreamSetup() { - bzero(request_, BUFFER_SIZE); - request_length_ = 0; - simulcrypt_util::BuildMessageHeader( - EMMG_MUX_PROTOCOL_VERSION, EMMG_STREAM_SETUP, request_, &request_length_); - simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id, - request_, &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_CHANNEL_ID, - emmg_config_->data_channel_id, request_, - &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_STREAM_ID, - emmg_config_->data_stream_id, request_, - &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_ID, emmg_config_->data_id, request_, - &request_length_); - simulcrypt_util::AddUint8Param(EMMG_DATA_TYPE, emmg_config_->data_type, - request_, &request_length_); - uint16_t total_param_length = request_length_ - 5; - Host16ToBigEndian(request_ + 3, &total_param_length); + SimulcryptMessage message(request_, &request_length_); + message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, EMMG_STREAM_SETUP); + message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id); + message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id); + message.AddUint16Param(EMMG_DATA_STREAM_ID, emmg_config_->data_stream_id); + message.AddUint16Param(EMMG_DATA_ID, emmg_config_->data_id); + message.AddUint8Param(EMMG_DATA_TYPE, emmg_config_->data_type); } void Emmg::BuildStreamBwRequest() { - bzero(request_, BUFFER_SIZE); - request_length_ = 0; - simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, - EMMG_STREAM_BW_REQUEST, request_, - &request_length_); - simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id, - request_, &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_CHANNEL_ID, - emmg_config_->data_channel_id, request_, - &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_STREAM_ID, - emmg_config_->data_stream_id, request_, - &request_length_); - simulcrypt_util::AddUint16Param(EMMG_BANDWIDTH, emmg_config_->bandwidth, - request_, &request_length_); - uint16_t total_param_length = request_length_ - 5; - Host16ToBigEndian(request_ + 3, &total_param_length); + SimulcryptMessage message(request_, &request_length_); + message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, EMMG_STREAM_BW_REQUEST); + message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id); + message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id); + message.AddUint16Param(EMMG_DATA_STREAM_ID, emmg_config_->data_stream_id); + message.AddUint16Param(EMMG_BANDWIDTH, emmg_config_->bandwidth); } Status Emmg::GeneratePrivateData( - const std::string& content_provider, const std::string& content_id, - const std::vector& entitlement_key_ids) { + absl::string_view content_provider, absl::string_view content_id, + const std::vector& group_ids, + SimulcryptMessage& message) { // Generate payload. CaDescriptorPrivateData private_data; - private_data.set_provider(content_provider); - private_data.set_content_id(content_id); - for (const auto& entitlement_key_id : entitlement_key_ids) { - private_data.add_entitlement_key_ids(entitlement_key_id); + private_data.set_provider(std::string(content_provider)); + private_data.set_content_id(std::string(content_id)); + for (const auto& group_id : group_ids) { + private_data.add_group_ids(std::string(group_id)); } std::string private_data_str = private_data.SerializeAsString(); std::string payload_filler(kMaxTsPayloadSize - private_data_str.size(), 0); @@ -257,14 +229,13 @@ Status Emmg::GeneratePrivateData( return status; } - uint8_t datagram[kTsPacketSize]; - memcpy(datagram, ecm_ts_packet.data(), ecm_ts_packet.size()); - simulcrypt_util::AddParam(EMMG_DATAGRAM, datagram, kTsPacketSize, request_, - &request_length_); + message.AddParam(EMMG_DATAGRAM, + reinterpret_cast(ecm_ts_packet.c_str()), + ecm_ts_packet.size()); return OkStatus(); } -Status Emmg::GenerateEmmData() { +Status Emmg::GenerateEmmData(SimulcryptMessage& message) { Status status; if (!has_configured_emm_impl_) { status = emm_impl_->SetPrivateSigningKey( @@ -293,40 +264,33 @@ Status Emmg::GenerateEmmData() { if (bytes_modified <= 0 || bytes_modified % kTsPacketSize != 0) { return {error::INTERNAL, "Failed to generate EMM TS packet"}; } - simulcrypt_util::AddParam(EMMG_DATAGRAM, datagram, bytes_modified, request_, - &request_length_); + message.AddParam(EMMG_DATAGRAM, datagram, bytes_modified); return OkStatus(); } void Emmg::BuildDataProvision() { - bzero(request_, BUFFER_SIZE); - request_length_ = 0; - simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, - EMMG_DATA_PROVISION, request_, - &request_length_); - simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id, - request_, &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_CHANNEL_ID, - emmg_config_->data_channel_id, request_, - &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_STREAM_ID, - emmg_config_->data_stream_id, request_, - &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_ID, emmg_config_->data_id, request_, - &request_length_); + SimulcryptMessage message(request_, &request_length_); + message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, EMMG_DATA_PROVISION); + message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id); + message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id); + message.AddUint16Param(EMMG_DATA_STREAM_ID, emmg_config_->data_stream_id); + message.AddUint16Param(EMMG_DATA_ID, emmg_config_->data_id); Status status; // Generate and load datagram to |request_| message based on specified // data_type. switch (emmg_config_->data_type) { case kEmmDataType: - status = GenerateEmmData(); + status = GenerateEmmData(message); break; - case kPrivateDataDataType: - status = GeneratePrivateData(emmg_config_->content_provider, - emmg_config_->content_id, - emmg_config_->entitlement_key_ids); + case kPrivateDataDataType: { + const std::vector group_ids( + emmg_config_->group_ids.begin(), emmg_config_->group_ids.end()); + status = + GeneratePrivateData(emmg_config_->content_provider, + emmg_config_->content_id, group_ids, message); break; + } default: LOG(ERROR) << "Unexpected data type: " << emmg_config_->data_type; return; @@ -335,42 +299,22 @@ void Emmg::BuildDataProvision() { LOG(ERROR) << "Fail to generate datagram. " << status.ToString(); return; } - - uint16_t total_param_length = request_length_ - 5; - Host16ToBigEndian(request_ + 3, &total_param_length); } void Emmg::BuildStreamCloseRequest() { - bzero(request_, BUFFER_SIZE); - request_length_ = 0; - simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, - EMMG_STREAM_CLOSE_REQUEST, request_, - &request_length_); - simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id, - request_, &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_CHANNEL_ID, - emmg_config_->data_channel_id, request_, - &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_STREAM_ID, - emmg_config_->data_stream_id, request_, - &request_length_); - uint16_t total_param_length = request_length_ - 5; - Host16ToBigEndian(request_ + 3, &total_param_length); + SimulcryptMessage message(request_, &request_length_); + message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, + EMMG_STREAM_CLOSE_REQUEST); + message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id); + message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id); + message.AddUint16Param(EMMG_DATA_STREAM_ID, emmg_config_->data_stream_id); } void Emmg::BuildChannelClose() { - bzero(request_, BUFFER_SIZE); - request_length_ = 0; - simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, - EMMG_CHANNEL_CLOSE, request_, - &request_length_); - simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id, - request_, &request_length_); - simulcrypt_util::AddUint16Param(EMMG_DATA_CHANNEL_ID, - emmg_config_->data_channel_id, request_, - &request_length_); - uint16_t total_param_length = request_length_ - 5; - Host16ToBigEndian(request_ + 3, &total_param_length); + SimulcryptMessage message(request_, &request_length_); + message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, EMMG_CHANNEL_CLOSE); + message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id); + message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id); } void Emmg::SendChannelSetup() { diff --git a/media_cas_packager_sdk/internal/emmg.h b/media_cas_packager_sdk/internal/emmg.h index b99b0a9..8497c2d 100644 --- a/media_cas_packager_sdk/internal/emmg.h +++ b/media_cas_packager_sdk/internal/emmg.h @@ -15,8 +15,10 @@ #include #include +#include "absl/strings/string_view.h" #include "common/status.h" #include "media_cas_packager_sdk/internal/emm.h" +#include "media_cas_packager_sdk/internal/simulcrypt_util.h" #define BUFFER_SIZE (2048) @@ -33,7 +35,7 @@ struct EmmgConfig { uint8_t data_type; std::string content_provider; std::string content_id; - std::vector entitlement_key_ids; + std::vector group_ids; uint16_t bandwidth; uint32_t max_num_message; std::string ecc_signing_key; @@ -90,10 +92,11 @@ class Emmg { void UpdateSendInterval(uint16_t bandwidth_kbps); - Status GeneratePrivateData( - const std::string& content_provider, const std::string& content_id, - const std::vector& entitlement_key_ids); - Status GenerateEmmData(); + Status GeneratePrivateData(absl::string_view content_provider, + absl::string_view content_id, + const std::vector& group_ids, + SimulcryptMessage& message); + Status GenerateEmmData(SimulcryptMessage& message); void ReceiveResponseAndVerify(uint16_t expected_type); void Send(uint16_t message_type); diff --git a/media_cas_packager_sdk/internal/emmg_test.cc b/media_cas_packager_sdk/internal/emmg_test.cc index 5a2ed27..0652f2e 100644 --- a/media_cas_packager_sdk/internal/emmg_test.cc +++ b/media_cas_packager_sdk/internal/emmg_test.cc @@ -14,6 +14,8 @@ #include #include +#include +#include #include "testing/gunit.h" #include "absl/memory/memory.h" @@ -80,7 +82,7 @@ class EmmgTest : public ::testing::Test { config_.data_type = 0x01; config_.content_provider = "widevine_test"; config_.content_id = "CasTsFake"; - config_.entitlement_key_ids = {"fakeKeyId1KeyId1", "fakeKeyId2KeyId2"}; + config_.group_ids = {"fakeGroup1", "fakeGroupId2"}; } void LoadEmmDataConfigs() { diff --git a/media_cas_packager_sdk/internal/simulcrypt_util.cc b/media_cas_packager_sdk/internal/simulcrypt_util.cc index aa0de2f..6e06697 100644 --- a/media_cas_packager_sdk/internal/simulcrypt_util.cc +++ b/media_cas_packager_sdk/internal/simulcrypt_util.cc @@ -17,77 +17,107 @@ namespace widevine { namespace cas { -namespace simulcrypt_util { -void BuildMessageHeader(uint8_t protocol_version, uint16_t message_type, - char* message, size_t* message_length) { - DCHECK(message); - DCHECK(message_length); - *message_length = 0; - message[*message_length] = protocol_version; - *message_length += PROTOCOL_VERSION_SIZE; - Host16ToBigEndian(message + *message_length, &message_type); - *message_length += MESSAGE_TYPE_SIZE; +// Total length of message header. +static constexpr int kMessageHeaderSize = 5; +// The index location of the message_length field. +static constexpr int kMessageProtocolIndex = 0; +static constexpr int kMessageTypeIndex = 1; +static constexpr int kMessageLengthIndex = 3; +static constexpr int kParameterTypeLengthFieldsSize = 4; + +SimulcryptMessage::SimulcryptMessage(const absl::Span message, + size_t* message_length) + : message_(message), + message_length_(message_length), + is_header_set_(false) { + DCHECK(message_length_); + *message_length_ = 0; + memset(message.data(), 0, message.size()); +} + +void SimulcryptMessage::BuildMessageHeader(uint8_t protocol_version, + uint16_t message_type) { + DCHECK_GE(message_.size(), kMessageHeaderSize); + message_[kMessageProtocolIndex] = protocol_version; + *message_length_ += PROTOCOL_VERSION_SIZE; + + Host16ToBigEndian(message_.data() + kMessageTypeIndex, &message_type); + *message_length_ += MESSAGE_TYPE_SIZE; + // NOTE: 'message_length' needs to be updated later after we have added all // the params so we know the exact length of the all the params. // Use 0 for 'total_param_length' until we know the length of the message. uint16_t total_param_length = 0; - Host16ToBigEndian(message + *message_length, &total_param_length); - *message_length += MESSAGE_LENGTH_SIZE; + Host16ToBigEndian(message_.data() + kMessageLengthIndex, &total_param_length); + *message_length_ += MESSAGE_LENGTH_SIZE; + + is_header_set_ = true; } -void AddUint32Param(uint16_t param_type, uint32_t param_value, char* message, - size_t* message_length) { - DCHECK(message); - DCHECK(message_length); - Host16ToBigEndian(message + *message_length, ¶m_type); - *message_length += 2; - uint16_t param_length = 4; - Host16ToBigEndian(message + *message_length, ¶m_length); - *message_length += 2; - Host32ToBigEndian(message + *message_length, ¶m_value); - *message_length += param_length; +void SimulcryptMessage::AddUint32Param(uint16_t param_type, + uint32_t param_value) { + DCHECK(is_header_set_); + const uint16_t param_length = 4; + DCHECK_GE(message_.size(), + *message_length_ + kParameterTypeLengthFieldsSize + param_length); + AddParamTypeLength(param_type, param_length); + + Host32ToBigEndian(message_.data() + *message_length_, ¶m_value); + *message_length_ += param_length; + UpdateMessageLength(); } -void AddUint16Param(uint16_t param_type, uint16_t param_value, char* message, - size_t* message_length) { - DCHECK(message); - DCHECK(message_length); - Host16ToBigEndian(message + *message_length, ¶m_type); - *message_length += 2; - uint16_t param_length = 2; - Host16ToBigEndian(message + *message_length, ¶m_length); - *message_length += 2; - Host16ToBigEndian(message + *message_length, ¶m_value); - *message_length += param_length; +void SimulcryptMessage::AddUint16Param(uint16_t param_type, + uint16_t param_value) { + DCHECK(is_header_set_); + const uint16_t param_length = 2; + DCHECK_GE(message_.size(), + *message_length_ + kParameterTypeLengthFieldsSize + param_length); + AddParamTypeLength(param_type, param_length); + + Host16ToBigEndian(message_.data() + *message_length_, ¶m_value); + *message_length_ += param_length; + UpdateMessageLength(); } -void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message, - size_t* message_length) { - DCHECK(message); - DCHECK(message_length); - Host16ToBigEndian(message + *message_length, ¶m_type); - *message_length += 2; - uint16_t param_length = 1; - Host16ToBigEndian(message + *message_length, ¶m_length); - *message_length += 2; - memcpy(message + *message_length, ¶m_value, param_length); - *message_length += param_length; +void SimulcryptMessage::AddUint8Param(uint16_t param_type, + uint8_t param_value) { + DCHECK(is_header_set_); + const uint16_t param_length = 1; + DCHECK_GE(message_.size(), + *message_length_ + kParameterTypeLengthFieldsSize + param_length); + return AddParam(param_type, ¶m_value, param_length); } -void AddParam(uint16_t param_type, const uint8_t* param_value, - uint16_t param_length, char* message, size_t* message_length) { +void SimulcryptMessage::AddParam(uint16_t param_type, + const uint8_t* param_value, + uint16_t param_length) { DCHECK(param_value); - DCHECK(message); - DCHECK(message_length); - Host16ToBigEndian(message + *message_length, ¶m_type); - *message_length += 2; - Host16ToBigEndian(message + *message_length, ¶m_length); - *message_length += 2; - memcpy(message + *message_length, param_value, param_length); - *message_length += param_length; + DCHECK(is_header_set_); + DCHECK_GE(message_.size(), + *message_length_ + kParameterTypeLengthFieldsSize + param_length); + AddParamTypeLength(param_type, param_length); + memcpy(message_.data() + *message_length_, param_value, param_length); + *message_length_ += param_length; + UpdateMessageLength(); +} + +void SimulcryptMessage::AddParamTypeLength(uint16_t param_type, + uint16_t param_length) { + // Add parameter type. + Host16ToBigEndian(message_.data() + *message_length_, ¶m_type); + *message_length_ += PARAMETER_TYPE_SIZE; + // Add parameter length. + Host16ToBigEndian(message_.data() + *message_length_, ¶m_length); + *message_length_ += PARAMETER_LENGTH_SIZE; +} + +void SimulcryptMessage::UpdateMessageLength() { + DCHECK(is_header_set_); + uint16_t total_param_length = *message_length_ - kMessageHeaderSize; + Host16ToBigEndian(message_.data() + kMessageLengthIndex, &total_param_length); } -} // namespace simulcrypt_util } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/internal/simulcrypt_util.h b/media_cas_packager_sdk/internal/simulcrypt_util.h index b15dcdd..81b2135 100644 --- a/media_cas_packager_sdk/internal/simulcrypt_util.h +++ b/media_cas_packager_sdk/internal/simulcrypt_util.h @@ -15,40 +15,49 @@ #include #include +#include "absl/types/span.h" namespace widevine { namespace cas { -namespace simulcrypt_util { -// Add 'protocol_version', 'message_type', 'message_length' to the message. -// TODO(user): Per jfore@, consider pass in a pointer to a structure -// #pragma pack(push, 1) // exact fit - no padding -// struct MessageHeader{ -// uint8_t protocol_version; -// uint16_t message_type; -// uint16_t message_length; -// }; -// #pragma pack(pop) // restore previous pack -void BuildMessageHeader(uint8_t protocol_version, uint16_t message_type, - char* message, size_t* message_length); +class SimulcryptMessage { + public: + // Simulcrypt message builder. + // Args: + // - |message| is the buffer to put the generated message. The buffer must be + // large enough to hold the generated message. + // - |message_length| will be updated as the length of the actual message in + // the |message| buffer. + SimulcryptMessage(const absl::Span message, size_t* message_length); + SimulcryptMessage(const SimulcryptMessage&) = delete; + SimulcryptMessage& operator=(const SimulcryptMessage&) = delete; + virtual ~SimulcryptMessage() = default; -// Add a uint32_t parameter to the message. -void AddUint32Param(uint16_t param_type, uint32_t param_value, char* message, - size_t* message_length); + // Sets up the mesaage header field. Must be called before any add operation. + void BuildMessageHeader(uint8_t protocol_version, uint16_t message_type); -// Add a uint16_t parameter to the message. -void AddUint16Param(uint16_t param_type, uint16_t param_value, char* message, - size_t* message_length); + // Adds a uint32_t parameter to the message. + void AddUint32Param(uint16_t param_type, uint32_t param_value); -// Add a uint8_t parameter to the message. -void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message, - size_t* message_length); + // Adds a uint16_t parameter to the message. + void AddUint16Param(uint16_t param_type, uint16_t param_value); -// Add a param that is |param_length| bytes long. -void AddParam(uint16_t param_type, const uint8_t* param_value, - uint16_t param_length, char* message, size_t* message_length); + // Adds a uint8_t parameter to the message. + void AddUint8Param(uint16_t param_type, uint8_t param_value); + + // Adds a param that is |param_length| bytes long. + void AddParam(uint16_t param_type, const uint8_t* param_value, + uint16_t param_length); + + private: + void AddParamTypeLength(uint16_t param_type, uint16_t param_length); + void UpdateMessageLength(); + + const absl::Span message_; + size_t* message_length_; + bool is_header_set_; +}; -} // namespace simulcrypt_util } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/internal/simulcrypt_util_test.cc b/media_cas_packager_sdk/internal/simulcrypt_util_test.cc new file mode 100644 index 0000000..97a5eda --- /dev/null +++ b/media_cas_packager_sdk/internal/simulcrypt_util_test.cc @@ -0,0 +1,154 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2020 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "media_cas_packager_sdk/internal/simulcrypt_util.h" + +#include + +#include "testing/gmock.h" +#include "testing/gunit.h" + +namespace widevine { +namespace cas { +namespace { + +constexpr size_t kBufferSize = 1024; +constexpr int kMessageHeaderSize = 5; +constexpr int kParameterTypeLengthFieldsSize = 4; + +constexpr uint8_t kProtocolVersion = 1; +constexpr uint16_t kMessageType = 0x0123; +constexpr uint16_t kParameterType = 0x4321; +constexpr uint32_t kParameterValue32 = 0x12345678; +constexpr uint16_t kParameterValue16 = 0x1234; +constexpr uint8_t kParameterValue8 = 0x12; +constexpr uint8_t kParameterValueArray[] = {0x12, 0x34}; +constexpr uint8_t kExpectedMixedParametersMessage[] = { + // Header + 0x01, 0x01, 0x23, 0x00, 0x19, + // Param 1 + 0x43, 0x21, 0x00, 0x02, 0x12, 0x34, + // Param 2 + 0x43, 0x21, 0x00, 0x02, 0x12, 0x34, + // Param 3 + 0x43, 0x21, 0x00, 0x01, 0x12, + // Param 4 + 0x43, 0x21, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78}; + +// Verifies that the message header can be built. +TEST(SimulcryptMessageTest, BuildMessageHeader) { + char buffer[kBufferSize]; + size_t message_length = 0; + SimulcryptMessage builder(buffer, &message_length); + builder.BuildMessageHeader(kProtocolVersion, kMessageType); + ASSERT_EQ(message_length, kMessageHeaderSize); + EXPECT_THAT(absl::MakeSpan(buffer).subspan(0, kMessageHeaderSize), + ::testing::ElementsAre(kProtocolVersion, kMessageType >> 8, + kMessageType & 0xff, 0, 0)); +} + +// Verifies that a uint32_t parameter can be appended with message length updated. +TEST(SimulcryptMessageTest, AddUint32Param) { + char buffer[kBufferSize]; + size_t message_length = 0; + SimulcryptMessage builder(buffer, &message_length); + builder.BuildMessageHeader(kProtocolVersion, kMessageType); + builder.AddUint32Param(kParameterType, kParameterValue32); + uint16_t parameter_length = sizeof(kParameterValue32); + uint16_t expected_payload_length = + kParameterTypeLengthFieldsSize + parameter_length; + ASSERT_EQ(message_length, kMessageHeaderSize + expected_payload_length); + EXPECT_THAT( + absl::MakeSpan(buffer).subspan(0, message_length), + ::testing::ElementsAre( + kProtocolVersion, kMessageType >> 8, kMessageType & 0xff, + expected_payload_length >> 8, expected_payload_length & 0xff, + kParameterType >> 8, kParameterType & 0xff, parameter_length >> 8, + parameter_length & 0xff, 0x12, 0x34, 0x56, 0x78)); +} + +// Verifies that a uint16_t parameter can be appended with message length updated. +TEST(SimulcryptMessageTest, AddUint16Param) { + char buffer[kBufferSize]; + size_t message_length = 0; + SimulcryptMessage builder(buffer, &message_length); + builder.BuildMessageHeader(kProtocolVersion, kMessageType); + builder.AddUint16Param(kParameterType, kParameterValue16); + uint16_t parameter_length = sizeof(kParameterValue16); + uint16_t expected_payload_length = + kParameterTypeLengthFieldsSize + parameter_length; + ASSERT_EQ(message_length, kMessageHeaderSize + expected_payload_length); + EXPECT_THAT(absl::MakeSpan(buffer).subspan(0, message_length), + ::testing::ElementsAre( + kProtocolVersion, kMessageType >> 8, kMessageType & 0xff, + expected_payload_length >> 8, expected_payload_length & 0xff, + kParameterType >> 8, kParameterType & 0xff, + parameter_length >> 8, parameter_length & 0xff, + kParameterValue16 >> 8, kParameterValue16 & 0xff)); +} + +// Verifies that a uint8_t parameter can be appended with message length updated. +TEST(SimulcryptMessageTest, AddUint8Param) { + char buffer[kBufferSize]; + size_t message_length = 0; + SimulcryptMessage builder(buffer, &message_length); + builder.BuildMessageHeader(kProtocolVersion, kMessageType); + builder.AddUint8Param(kParameterType, kParameterValue8); + uint16_t parameter_length = sizeof(kParameterValue8); + uint16_t expected_payload_length = + kParameterTypeLengthFieldsSize + parameter_length; + ASSERT_EQ(message_length, kMessageHeaderSize + expected_payload_length); + EXPECT_THAT( + absl::MakeSpan(buffer).subspan(0, message_length), + ::testing::ElementsAre( + kProtocolVersion, kMessageType >> 8, kMessageType & 0xff, + expected_payload_length >> 8, expected_payload_length & 0xff, + kParameterType >> 8, kParameterType & 0xff, parameter_length >> 8, + parameter_length & 0xff, kParameterValue8)); +} + +// Verifies that a uint32_t buffer can be appended with message length updated. +TEST(SimulcryptMessageTest, AddParam) { + char buffer[kBufferSize]; + size_t message_length = 0; + SimulcryptMessage builder(buffer, &message_length); + builder.BuildMessageHeader(kProtocolVersion, kMessageType); + uint16_t parameter_length = sizeof(kParameterValueArray); + builder.AddParam(kParameterType, kParameterValueArray, parameter_length); + uint16_t expected_payload_length = + kParameterTypeLengthFieldsSize + parameter_length; + ASSERT_EQ(message_length, kMessageHeaderSize + expected_payload_length); + EXPECT_THAT(absl::MakeSpan(buffer).subspan(0, message_length), + ::testing::ElementsAre( + kProtocolVersion, kMessageType >> 8, kMessageType & 0xff, + expected_payload_length >> 8, expected_payload_length & 0xff, + kParameterType >> 8, kParameterType & 0xff, + parameter_length >> 8, parameter_length & 0xff, + kParameterValueArray[0], kParameterValueArray[1])); +} + +// Verifies that multiple parameters with different types can be appended with +// message length updated. +TEST(SimulcryptMessageTest, MixedParams) { + char buffer[kBufferSize]; + size_t message_length = 0; + SimulcryptMessage builder(buffer, &message_length); + builder.BuildMessageHeader(kProtocolVersion, kMessageType); + builder.AddParam(kParameterType, kParameterValueArray, + sizeof(kParameterValueArray)); + builder.AddUint16Param(kParameterType, kParameterValue16); + builder.AddUint8Param(kParameterType, kParameterValue8); + builder.AddUint32Param(kParameterType, kParameterValue32); + ASSERT_EQ(message_length, 30); + EXPECT_THAT(absl::MakeSpan(buffer).subspan(0, message_length), + ::testing::ElementsAreArray(kExpectedMixedParametersMessage)); +} + +} // namespace +} // namespace cas +} // namespace widevine diff --git a/media_cas_packager_sdk/public/BUILD b/media_cas_packager_sdk/public/BUILD index e61282e..a2b7f11 100644 --- a/media_cas_packager_sdk/public/BUILD +++ b/media_cas_packager_sdk/public/BUILD @@ -206,6 +206,7 @@ cc_library( "//base", "@abseil_repo//absl/memory", "@abseil_repo//absl/strings", + "@abseil_repo//absl/types:span", "//common:status", "//media_cas_packager_sdk/internal:ecmg_client_handler", ], @@ -242,6 +243,7 @@ cc_test( ":wv_cas_types", "//testing:gunit_main", "@abseil_repo//absl/memory", + "@abseil_repo//absl/types:span", "//common:status", "//media_cas_packager_sdk/internal:ecmg_client_handler", ], diff --git a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc index c0cf19b..69c9fe3 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc @@ -10,6 +10,7 @@ #include #include +#include #include #include "glog/logging.h" @@ -51,16 +52,11 @@ constexpr uint32_t kReservedBit = 0x0007; // The range of valid PIDs, from section 2.4.3.3, and table 2-3. constexpr uint32_t kMinValidPID = 0x0010; constexpr uint32_t kMaxValidPID = 0x1FFE; - -// Maximum number of entitlement key ids shown in private data. -constexpr uint32_t kMaxNumOfEntitlementKeyIds = 2; -// Entitlement key id length is fixed to 16 bytes. -constexpr uint16_t kEntitlementKeyIdLength = 16; } // namespace Status WvCasCaDescriptor::GenerateCaDescriptor( uint16_t ca_pid, const std::string& provider, const std::string& content_id, - const std::vector& entitlement_key_ids, + const std::vector& group_ids, std::string* serialized_ca_desc) const { if (serialized_ca_desc == nullptr) { return {error::INVALID_ARGUMENT, @@ -69,25 +65,11 @@ Status WvCasCaDescriptor::GenerateCaDescriptor( if (ca_pid < kMinValidPID || ca_pid > kMaxValidPID) { return {error::INVALID_ARGUMENT, "PID value is out of the valid range."}; } - if (entitlement_key_ids.size() > kMaxNumOfEntitlementKeyIds) { - return {error::INVALID_ARGUMENT, - absl::StrCat("Number of entitlement key ids shouldn't exceed ", - kMaxNumOfEntitlementKeyIds)}; - } - for (const auto& entitlement_key_id : entitlement_key_ids) { - if (entitlement_key_id.size() != kEntitlementKeyIdLength) { - return {error::INVALID_ARGUMENT, - absl::StrCat("Entitlement key id length must be ", - kEntitlementKeyIdLength, - ". The offending key id is ", entitlement_key_id)}; - } - } - std::string private_data = ""; + std::string private_data; // Field of Entitlement_key_ids could be empty. if (!provider.empty() && !content_id.empty()) { - private_data = - GeneratePrivateData(provider, content_id, entitlement_key_ids); + private_data = GeneratePrivateData(provider, content_id, group_ids); } const size_t descriptor_length = @@ -130,12 +112,12 @@ size_t WvCasCaDescriptor::CaDescriptorBaseSize() const { std::string WvCasCaDescriptor::GeneratePrivateData( const std::string& provider, const std::string& content_id, - const std::vector& entitlement_key_ids) const { + const std::vector& group_ids) const { CaDescriptorPrivateData private_data; private_data.set_provider(provider); private_data.set_content_id(content_id); - for (const auto& entitlement_key_id : entitlement_key_ids) { - private_data.add_entitlement_key_ids(entitlement_key_id); + for (const auto& group_id : group_ids) { + private_data.add_group_ids(group_id); } return private_data.SerializeAsString(); } diff --git a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h index 540a763..f4b75a5 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h @@ -50,9 +50,7 @@ class WvCasCaDescriptor { // |ca_pid| the 13-bit PID of the ECMs // |provider| provider name, put in private data for client to construct pssh // |content_id| content ID, put in private data for client to construct pssh - // |entitlement_key_ids| entitlement key ids, put in private data for client - // to select entitlement keys from single fat license. This field is only used - // when client uses single fat license. + // |group_ids| the groups ids this channel belongs to. Optional. // |serialized_ca_desc| a std::string object to receive the encoded descriptor. // // Notes: @@ -60,11 +58,11 @@ class WvCasCaDescriptor { // section (for an EMM stream) or into a TS Program Map Table section (for an // ECM stream). The descriptor will be 6 bytes plus any bytes added as // (user-defined) private data. - virtual Status GenerateCaDescriptor( - uint16_t ca_pid, const std::string& provider, - const std::string& content_id, - const std::vector& entitlement_key_ids, - std::string* serialized_ca_desc) const; + virtual Status GenerateCaDescriptor(uint16_t ca_pid, + const std::string& provider, + const std::string& content_id, + const std::vector& group_ids, + std::string* serialized_ca_desc) const; // Return the base size (before private data is added) of the CA // descriptor. The user can call this to plan the layout of the Table section @@ -74,7 +72,7 @@ class WvCasCaDescriptor { // Return private data in the CA descriptor. virtual std::string GeneratePrivateData( const std::string& provider, const std::string& content_id, - const std::vector& entitlement_key_ids) const; + const std::vector& group_ids) const; }; } // namespace cas diff --git a/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc b/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc index 744930a..10fb5da 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc @@ -9,6 +9,8 @@ #include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h" #include +#include +#include #include "testing/gmock.h" #include "testing/gunit.h" @@ -20,14 +22,10 @@ namespace widevine { namespace cas { namespace { - // Random value for PID constexpr int kTestPid = 50; constexpr char kProvider[] = "widevine_test"; constexpr char kContentId[] = "1234"; -const std::vector* const kEntitlementKeyIds = - new std::vector({"fakekey1fakekey1", "fakekey2fakekey2"}); - } // namespace class WvCasCaDescriptorTest : public Test { @@ -35,7 +33,6 @@ class WvCasCaDescriptorTest : public Test { WvCasCaDescriptorTest() {} WvCasCaDescriptor ca_descriptor_; std::string actual_ca_descriptor_; - std::vector entitlement_key_ids_; }; TEST_F(WvCasCaDescriptorTest, BaseSize) { @@ -44,7 +41,7 @@ TEST_F(WvCasCaDescriptorTest, BaseSize) { TEST_F(WvCasCaDescriptorTest, BasicGoodGen) { EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - kTestPid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, + kTestPid, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x32", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); @@ -55,7 +52,7 @@ TEST_F(WvCasCaDescriptorTest, NoReturnStringFail) { ca_descriptor_ .GenerateCaDescriptor( kTestPid, /*provider=*/"", /*content_id=*/"", - /*entitlement_key_ids=*/{}, /*serialized_ca_desc=*/nullptr) + /*group_ids=*/{}, /*serialized_ca_desc=*/nullptr) .error_code()); } @@ -65,14 +62,14 @@ TEST_F(WvCasCaDescriptorTest, PidTooLowFail) { error::INVALID_ARGUMENT, ca_descriptor_ .GenerateCaDescriptor(bad_pid, /*provider=*/"", /*content_id=*/"", - entitlement_key_ids_, &actual_ca_descriptor_) + /*group_ids=*/{}, &actual_ca_descriptor_) .error_code()); } TEST_F(WvCasCaDescriptorTest, PidMinOK) { const uint32_t min_pid = 0x10; EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - min_pid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, + min_pid, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x10", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); @@ -81,7 +78,7 @@ TEST_F(WvCasCaDescriptorTest, PidMinOK) { TEST_F(WvCasCaDescriptorTest, PidMaxOK) { const uint32_t max_pid = 0x1FFE; EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - max_pid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, + max_pid, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\xfe"); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); @@ -89,17 +86,17 @@ TEST_F(WvCasCaDescriptorTest, PidMaxOK) { TEST_F(WvCasCaDescriptorTest, PidTooHighFail) { const uint32_t bad_pid = 0x1FFF; - EXPECT_EQ(error::INVALID_ARGUMENT, - ca_descriptor_ - .GenerateCaDescriptor( - bad_pid, /*provider=*/"", /*content_id=*/"", - /*entitlement_key_ids=*/{}, &actual_ca_descriptor_) - .error_code()); + EXPECT_EQ( + error::INVALID_ARGUMENT, + ca_descriptor_ + .GenerateCaDescriptor(bad_pid, /*provider=*/"", /*content_id=*/"", + /*group_ids=*/{}, &actual_ca_descriptor_) + .error_code()); } TEST_F(WvCasCaDescriptorTest, PidOneByte) { EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - 255, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, + 255, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\xff", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); @@ -107,7 +104,7 @@ TEST_F(WvCasCaDescriptorTest, PidOneByte) { TEST_F(WvCasCaDescriptorTest, PidSecondByte) { EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - 0x1F00, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, + 0x1F00, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); @@ -115,7 +112,7 @@ TEST_F(WvCasCaDescriptorTest, PidSecondByte) { TEST_F(WvCasCaDescriptorTest, PidTwelveBits) { EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - 0xFFF, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, + 0xFFF, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xef\xff"); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); @@ -123,7 +120,7 @@ TEST_F(WvCasCaDescriptorTest, PidTwelveBits) { TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) { EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - 0x1000, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, + 0x1000, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xf0\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); @@ -131,7 +128,7 @@ TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) { TEST_F(WvCasCaDescriptorTest, PidTwelthBit) { EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - 0x800, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, + 0x800, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe8\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); @@ -139,7 +136,7 @@ TEST_F(WvCasCaDescriptorTest, PidTwelthBit) { TEST_F(WvCasCaDescriptorTest, PidElevenththBit) { EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - 0x400, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, + 0x400, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe4\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); @@ -147,7 +144,7 @@ TEST_F(WvCasCaDescriptorTest, PidElevenththBit) { TEST_F(WvCasCaDescriptorTest, PidTenthBit) { EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - 0x200, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, + 0x200, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe2\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); @@ -155,7 +152,7 @@ TEST_F(WvCasCaDescriptorTest, PidTenthBit) { TEST_F(WvCasCaDescriptorTest, PidNinthBit) { EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - 0x100, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, + 0x100, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe1\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); @@ -163,14 +160,14 @@ TEST_F(WvCasCaDescriptorTest, PidNinthBit) { TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoContentIdIgnored) { EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - kTestPid, kProvider, "", entitlement_key_ids_, &actual_ca_descriptor_)); + kTestPid, kProvider, "", /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoProviderIgnored) { EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( - kTestPid, "", kContentId, entitlement_key_ids_, &actual_ca_descriptor_)); + kTestPid, "", kContentId, /*group_ids=*/{}, &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } @@ -186,39 +183,16 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoEntitlementKeyIds) { actual_ca_descriptor_); } -TEST_F(WvCasCaDescriptorTest, - PrivateDataFailedWhenNumberOfEntitlementKeyIdsExceedLimit) { - const std::vector entitlement_key_ids = { - "fakekey1fakekey1", "fakekey2fakekey2", "fakekey3fakekey3"}; - Status status = {error::INVALID_ARGUMENT, - "Number of entitlement key ids shouldn't exceed 2"}; - EXPECT_EQ(status, ca_descriptor_.GenerateCaDescriptor( - kTestPid, kProvider, kContentId, entitlement_key_ids, - &actual_ca_descriptor_)); -} - -TEST_F(WvCasCaDescriptorTest, - PrivateDataFailedWhenEntitlementKeyIdLengthExceedLimit) { - const std::vector entitlement_key_ids = { - "fakekey1fakekey1", "fakekey2fakekey2fakekey2"}; - Status status = {error::INVALID_ARGUMENT, - "Entitlement key id length must be 16. The offending key id " - "is fakekey2fakekey2fakekey2"}; - EXPECT_EQ(status, ca_descriptor_.GenerateCaDescriptor( - kTestPid, kProvider, kContentId, entitlement_key_ids, - &actual_ca_descriptor_)); -} - TEST_F(WvCasCaDescriptorTest, PrivateData) { - EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, kContentId, - *kEntitlementKeyIds, - &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x3d\x4a\xd4\xe0\x32", 6); + std::vector group_ids = {"group1", "group2"}; + EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( + kTestPid, kProvider, kContentId, group_ids, &actual_ca_descriptor_)); + const std::string expected_ca_descriptor("\x09\x29\x4a\xd4\xe0\x32", 6); CaDescriptorPrivateData private_data; private_data.set_provider(kProvider); private_data.set_content_id(kContentId); - for (const auto& entitlementKeyId : *kEntitlementKeyIds) { - private_data.add_entitlement_key_ids(entitlementKeyId); + for (const auto& group_id : group_ids) { + private_data.add_group_ids(group_id); } EXPECT_EQ(expected_ca_descriptor + private_data.SerializeAsString(), actual_ca_descriptor_); @@ -232,7 +206,7 @@ class FakePrivateDataCaDescriptor : public WvCasCaDescriptor { std::string GeneratePrivateData( const std::string& provider, const std::string& content_id, - const std::vector& entitlement_key_ids) const override { + const std::vector& group_ids) const override { return private_data_; } @@ -243,9 +217,9 @@ class FakePrivateDataCaDescriptor : public WvCasCaDescriptor { TEST_F(WvCasCaDescriptorTest, PrivateDataOneByte) { FakePrivateDataCaDescriptor fake_descriptor; fake_descriptor.set_private_data("X"); - EXPECT_OK(fake_descriptor.GenerateCaDescriptor( - kTestPid, kProvider, kContentId, *kEntitlementKeyIds, - &actual_ca_descriptor_)); + EXPECT_OK(fake_descriptor.GenerateCaDescriptor(kTestPid, kProvider, + kContentId, /*group_ids=*/{}, + &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x05\x4a\xd4\xe0\x32X", 7); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } @@ -254,9 +228,9 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMultipleBytes) { const std::string private_data_bytes("X1234abcde"); FakePrivateDataCaDescriptor fake_descriptor; fake_descriptor.set_private_data(private_data_bytes); - EXPECT_OK(fake_descriptor.GenerateCaDescriptor( - kTestPid, kProvider, kContentId, *kEntitlementKeyIds, - &actual_ca_descriptor_)); + EXPECT_OK(fake_descriptor.GenerateCaDescriptor(kTestPid, kProvider, + kContentId, /*group_ids=*/{}, + &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\x0e\x4a\xd4\xe0\x32", 6); EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_); } @@ -265,9 +239,9 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMaxNumberBytes) { const std::string private_data_bytes(251, 'x'); FakePrivateDataCaDescriptor fake_descriptor; fake_descriptor.set_private_data(private_data_bytes); - EXPECT_OK(fake_descriptor.GenerateCaDescriptor( - kTestPid, kProvider, kContentId, *kEntitlementKeyIds, - &actual_ca_descriptor_)); + EXPECT_OK(fake_descriptor.GenerateCaDescriptor(kTestPid, kProvider, + kContentId, /*group_ids=*/{}, + &actual_ca_descriptor_)); const std::string expected_ca_descriptor("\x09\xff\x4a\xd4\xe0\x32", 6); EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_); } @@ -276,12 +250,11 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataTooManyBytesFail) { const std::string private_data_bytes(252, 'X'); FakePrivateDataCaDescriptor fake_descriptor; fake_descriptor.set_private_data(private_data_bytes); - EXPECT_EQ( - error::INVALID_ARGUMENT, - fake_descriptor - .GenerateCaDescriptor(kTestPid, kProvider, kContentId, - *kEntitlementKeyIds, &actual_ca_descriptor_) - .error_code()); + EXPECT_EQ(error::INVALID_ARGUMENT, + fake_descriptor + .GenerateCaDescriptor(kTestPid, kProvider, kContentId, + /*group_ids=*/{}, &actual_ca_descriptor_) + .error_code()); } } // namespace cas diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.cc b/media_cas_packager_sdk/public/wv_cas_ecm.cc index f06217d..5ab6a82 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm.cc @@ -41,6 +41,7 @@ EcmInitParameters ConvertToEcmInitParameters( init_params.cas_id = ecm_parameters.cas_id; init_params.ecm_version = ecm_parameters.ecm_version; init_params.ecc_private_signing_key = ecm_parameters.ecc_private_signing_key; + init_params.entitlement_rotation = ecm_parameters.entitlement_rotation; return init_params; } @@ -67,6 +68,16 @@ void WvCasEcm::SetServiceBlocking( ecm_->SetServiceBlocking(service_blocking); } +Status WvCasEcm::SetEntitlementRotationWindowLeft( + uint32_t entitlement_rotation_window_left) { + return ecm_->SetEntitlementRotationWindowLeft( + entitlement_rotation_window_left); +} + +uint32_t WvCasEcm::GetEntitlementRotationWindowLeft() const { + return ecm_->GetEntitlementRotationWindowLeft(); +} + Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key, const WvCasContentKeyInfo& odd_key, const std::string& track_type, diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.h b/media_cas_packager_sdk/public/wv_cas_ecm.h index 35a5342..2ed1676 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.h +++ b/media_cas_packager_sdk/public/wv_cas_ecm.h @@ -61,6 +61,7 @@ struct WvCasEcmParameters { uint16_t cas_id = 0x4AD4; EcmVersion ecm_version = EcmVersion::kV2; std::string ecc_private_signing_key; + EntitlementKeyRotationInfo entitlement_rotation; }; // Class for generating Widevine CAS ECMs. @@ -94,6 +95,20 @@ class WvCasEcm { virtual void SetServiceBlocking( const EcmServiceBlockingParams* service_blocking); + // Sets the current value of the entitlement key rotation window left to + // |entitlement_rotation_window_left|. The value will be used in + // GenerateEcm/GenerateSingleKeyEcm if entitlement rotation is enabled as + // specified at initializing, and is automatically decreased by 1 on each call + // to GenerateEcm/GenerateSingleKeyEcm, until it reaches 1. + virtual Status SetEntitlementRotationWindowLeft( + uint32_t entitlement_rotation_window_left); + + // Gets the current value of the entitlement key rotation window left. The + // value is used in GenerateEcm/GenerateSingleKeyEcm, and is automatically + // decreased by 1 on each call to GenerateEcm/GenerateSingleKeyEcm, until it + // reaches 1. + virtual uint32_t GetEntitlementRotationWindowLeft() const; + // Constructs a Widevine ECM using the provided key info. // Args: // |even_key| information for even key to be encoded into ECM. @@ -106,6 +121,9 @@ class WvCasEcm { // consistent with the initialized settings. // The even_key and odd_key will be wrapped using the appropriate // entitlement key. + // If entitlement rotation is enabled as specified at initializing, the + // entitlement key rotation window left value will be automatically decreased + // by 1, until it reaches 1. virtual Status GenerateEcm(const WvCasContentKeyInfo& even_key, const WvCasContentKeyInfo& odd_key, const std::string& track_type, @@ -122,6 +140,9 @@ class WvCasEcm { // |serialized_ecm| caller-supplied std::string pointer to receive the ECM. // The |key| contents (specifically IV sizes) must be consistent // with the initialized settings. + // If entitlement rotation is enabled as specified at initializing, the + // entitlement key rotation window left value will be automatically decreased + // by 1, until it reaches 1. virtual Status GenerateSingleKeyEcm(const WvCasContentKeyInfo& key, const std::string& track_type, const std::vector& group_ids, diff --git a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.cc b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.cc index ed745d3..a109c9c 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.cc @@ -11,6 +11,7 @@ #include "glog/logging.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" +#include "absl/types/span.h" #include "common/status.h" #include "media_cas_packager_sdk/internal/ecmg_client_handler.h" @@ -81,7 +82,8 @@ Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size, return {error::INVALID_ARGUMENT, error_message}; } processed_request_length = inner_handler_->HandleRequest( - request_buffer, response_buffer, &response_length); + request_buffer, absl::MakeSpan(response_buffer, response_buffer_size), + &response_length); if (processed_request_length == 0) { return {error::INTERNAL, "request not processed."}; } @@ -89,5 +91,14 @@ Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size, return OkStatus(); } +WvEcmgChannelStatus WvCasEcmgClientHandler::GetChannelStatus() const { + return inner_handler_->GetChannelStatus(); +} + +std::vector WvCasEcmgClientHandler::GetStreamStatus() + const { + return inner_handler_->GetStreamStatus(); +} + } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h index df27548..ad8e25f 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h +++ b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h @@ -74,6 +74,12 @@ class WvCasEcmgClientHandler { size_t& response_length, size_t& processed_request_length); + // Retrieves the current channel status; + WvEcmgChannelStatus GetChannelStatus() const; + + // Retrieves the status of all open streams; + std::vector GetStreamStatus() const; + protected: // For unit test only. explicit WvCasEcmgClientHandler( diff --git a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler_test.cc b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler_test.cc index 0f03218..c0be729 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler_test.cc @@ -11,6 +11,7 @@ #include "testing/gmock.h" #include "testing/gunit.h" #include "absl/memory/memory.h" +#include "absl/types/span.h" #include "common/status.h" #include "media_cas_packager_sdk/internal/ecmg_client_handler.h" #include "media_cas_packager_sdk/public/wv_cas_types.h" @@ -25,7 +26,7 @@ class MockEcmgClientHandler : public EcmgClientHandler { public: MockEcmgClientHandler() : EcmgClientHandler(&config_) {} MOCK_METHOD(size_t, HandleRequest, - (const char* const request, char* response, + (const char* const request, const absl::Span response, size_t* response_length), (override)); @@ -50,7 +51,9 @@ TEST(WvCasEcmgClientHandlerTest, HandleRequestSuccess) { size_t processed_length = 0; auto mock_internal_handler = absl::make_unique(); EXPECT_CALL(*mock_internal_handler, - HandleRequest(request_buffer, response_buffer, &response_length)) + HandleRequest(request_buffer, + absl::MakeSpan(response_buffer, kBufferSize), + &response_length)) .WillOnce(testing::DoAll(testing::Return(100))); auto handler = absl::make_unique( diff --git a/media_cas_packager_sdk/public/wv_cas_emm_test.cc b/media_cas_packager_sdk/public/wv_cas_emm_test.cc index 1c1da7d..8b14dfa 100644 --- a/media_cas_packager_sdk/public/wv_cas_emm_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_emm_test.cc @@ -26,7 +26,6 @@ using ::testing::NotNull; using ::testing::Pointwise; using ::testing::Return; using ::testing::SetArgPointee; -using ::testing::SetArrayArgument; using ::testing::WithArg; namespace widevine { @@ -62,12 +61,11 @@ class MockEmm : public Emm { Status, SetServiceBlocking, (const std::vector& service_blockings), (override)); - MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm), - (const, override)); + MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm), (override)); MOCK_METHOD(Status, GenerateEmmTsPackets, (uint16_t pid, uint8_t* continuity_counter, const absl::Span packet, ssize_t* bytes_modified), - (const, override)); + (override)); }; class MockWvCasEmm : public WvCasEmm { diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc b/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc index 2aab593..69f5634 100644 --- a/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc @@ -39,8 +39,9 @@ WvCasKeyFetcher::WvCasKeyFetcher(const std::string& signing_provider, Status WvCasKeyFetcher::CreateEntitlementRequest( const EntitlementRequestParams& request_params, std::string* signed_request_json) const { - if (request_params.content_id.empty()) { - return {error::INVALID_ARGUMENT, "Content ID is empty."}; + if (request_params.content_id.empty() && request_params.group_id.empty()) { + return {error::INVALID_ARGUMENT, + "Either content ID or group ID must be specified."}; } if (request_params.content_provider.empty()) { return {error::INVALID_ARGUMENT, "Content Provider is empty."}; @@ -58,16 +59,21 @@ Status WvCasKeyFetcher::CreateEntitlementRequest( } CasEncryptionRequest request; - request.set_content_id(request_params.content_id); - request.set_provider(request_params.content_provider); - request.set_key_rotation(request_params.key_rotation); if (!request_params.group_id.empty()) { request.set_group_id(request_params.group_id); + } else { + request.set_content_id(request_params.content_id); } + request.set_provider(request_params.content_provider); + request.set_key_rotation(request_params.key_rotation); // Add labels for tracks. for (const auto& track_type : request_params.track_types) { request.add_track_types(track_type); } + if (request_params.entitlement_rotation_enabled) { + request.set_entitlement_period_index( + request_params.entitlement_period_index); + } std::string request_json; JsonPrintOptions print_options; @@ -162,6 +168,9 @@ Status WvCasKeyFetcher::ParseEntitlementResponse( entitlement->key_id = key.key_id(); entitlement->key_value = key.key(); entitlement->track_type = key.track_type(); + if (!response.group_id().empty()) { + entitlement->group_id = response.group_id(); + } // EVEN or SINGLE are both marked as is_even_key. entitlement->is_even_key = key.key_slot() != CasEncryptionResponse::KeyInfo::ODD; diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher.h b/media_cas_packager_sdk/public/wv_cas_key_fetcher.h index 8f8213c..7f0f49d 100644 --- a/media_cas_packager_sdk/public/wv_cas_key_fetcher.h +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher.h @@ -29,12 +29,17 @@ namespace cas { // |group_id| optional field indicates if this is a key used for a group of // contents. If this field is not empty, entitlement key would be generated // for the group instead of the single content. +// |entitlement_rotation_enabled| Whether entitlement rotation is enabled. +// |entitlement_period_index| The requested entitlement period index if +// entitlement rotation is enabled. struct EntitlementRequestParams { std::string content_id; std::string content_provider; std::vector track_types; bool key_rotation; std::string group_id; + bool entitlement_rotation_enabled = false; + uint32_t entitlement_period_index; }; // WV CAS KeyFetcher. Performs the communication with the Widevine License diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc b/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc index 6a6c360..70a82e8 100644 --- a/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc @@ -8,6 +8,8 @@ #include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h" +#include + #include "glog/logging.h" #include "google/protobuf/util/json_util.h" #include "testing/gmock.h" @@ -38,12 +40,12 @@ const char kSignedCasEncryptionRequest[] = "RyYWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2V9\",\"signature\":" "\"JyTnKEy1w98HRP1lL78+OIEiqtIXyfCN8iudvNXIWhw=\",\"signer\":\"widevine_" "test\"}"; -absl::string_view kSignedCasEncryptionRequestForGroupKey = +constexpr absl::string_view kSignedCasEncryptionRequestForGroupKey = "{\"request\":" - "\"eyJjb250ZW50X2lkIjoiTWpFeE5EQTRORFE9IiwicHJvdmlkZXIiOiJ3aWRldmluZSIsInRy" - "YWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2UsImdyb3VwX2lkIjoiWjNKdm" - "RYQXgifQ==\",\"signature\":\"iDaAr74ldEV6X1X9kwyLoZ/" - "hfP5RJOkXEzucq6IXyfQ=\",\"signer\":\"widevine_test\"}"; + "\"eyJwcm92aWRlciI6IndpZGV2aW5lIiwidHJhY2tfdHlwZXMiOlsiU0QiXSwia2V5X3JvdG" + "F0aW9uIjpmYWxzZSwiZ3JvdXBfaWQiOiJaM0p2ZFhBeCJ9\",\"signature\":" + "\"vRZ2qt65UC171S9pVEBib2KyTAbTGO+AjsCgV4d+4a0=\",\"signer\":\"widevine_" + "test\"}"; const char kHttpResponse[] = "{\"response\":" "\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu" @@ -57,6 +59,8 @@ const char kContentId[] = "21140844"; const char kGroupId[] = "group1"; const char kEntitlementKeyId[] = "fake_key_id....."; const char kEntitlementKey[] = "fakefakefakefakefakefakefakefake"; +const uint32_t kEntitlementPeriod = 123; +const char kEntitlementKeyIdForEntitlementPeriod123[] = "1234567812345678"; } // namespace namespace widevine { @@ -100,10 +104,11 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher { return Status(error::INTERNAL); } // Validate the parameters shown in the request. - EXPECT_EQ(request.content_id(), kContentId); EXPECT_EQ(request.provider(), kProvider); if (request.has_group_id()) { EXPECT_EQ(request.group_id(), kGroupId); + } else { + EXPECT_EQ(request.content_id(), kContentId); } CasEncryptionResponse response; @@ -119,13 +124,19 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher { if (request.key_rotation()) { // Add the Even key. auto key = response.add_entitlement_keys(); - key->set_key_id(kEntitlementKeyId); + key->set_key_id(request.entitlement_period_index() == + kEntitlementPeriod + ? kEntitlementKeyIdForEntitlementPeriod123 + : kEntitlementKeyId); key->set_key(kEntitlementKey); key->set_track_type(track_type); key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN); // Add the Odd key. key = response.add_entitlement_keys(); - key->set_key_id(kEntitlementKeyId); + key->set_key_id(request.entitlement_period_index() == + kEntitlementPeriod + ? kEntitlementKeyIdForEntitlementPeriod123 + : kEntitlementKeyId); key->set_key(kEntitlementKey); key->set_track_type(track_type); key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD); @@ -212,6 +223,7 @@ TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) { CHECK(absl::Base64Unescape("hgRrrgjyH8642r67Twt8ruqNLPcLFkJqdUIDNvnFd0M=", &expected_key_value)); EXPECT_EQ(entitlement.key_value, expected_key_value); + EXPECT_TRUE(entitlement.group_id.empty()); } TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) { @@ -230,7 +242,6 @@ TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) { CasEncryptionRequest request; ASSERT_OK( google::protobuf::util::JsonStringToMessage(signed_request.request(), &request)); - EXPECT_EQ(request.content_id(), kContentId); EXPECT_EQ(request.provider(), kProvider); EXPECT_EQ(request.group_id(), kGroupId); EXPECT_EQ(request.track_types_size(), 1); @@ -247,7 +258,6 @@ TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) { CasEncryptionResponse response; ASSERT_OK( google::protobuf::util::JsonStringToMessage(signed_response.response(), &response)); - EXPECT_EQ(response.content_id(), kContentId); EXPECT_EQ(response.status(), CasEncryptionResponse::OK); EXPECT_EQ(response.group_id(), kGroupId); @@ -261,6 +271,7 @@ TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) { EXPECT_EQ(entitlement.is_even_key, true); EXPECT_EQ(entitlement.key_id, kEntitlementKeyId); EXPECT_EQ(entitlement.key_value, kEntitlementKey); + EXPECT_EQ(entitlement.group_id, kGroupId); } TEST_F(WvCasKeyFetcherTest, OneKeyOK) { @@ -348,5 +359,27 @@ TEST_F(WvCasKeyFetcherTest, BadResponseFail) { .error_code()); } +TEST_F(WvCasKeyFetcherTest, EntitlementRotationSuccess) { + MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey, + kSingingIv); + std::string request; + EntitlementRequestParams request_params = + CreateRequestParamsStruct(kContentId, kProvider, /*group_id=*/"", + {kTrackTypeSD}, /*key_rotation=*/true); + request_params.entitlement_rotation_enabled = true; + request_params.entitlement_period_index = kEntitlementPeriod; + + ASSERT_OK( + mock_key_fetcher.CreateEntitlementRequest(request_params, &request)); + std::string response; + ASSERT_OK(mock_key_fetcher.MakeHttpRequest(request, &response)); + std::vector entitlements; + ASSERT_OK(mock_key_fetcher.ParseEntitlementResponse(response, &entitlements)); + + ASSERT_EQ(entitlements.size(), 2); + EXPECT_EQ(entitlements[0].key_id, kEntitlementKeyIdForEntitlementPeriod123); + EXPECT_EQ(entitlements[1].key_id, kEntitlementKeyIdForEntitlementPeriod123); +} + } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/public/wv_cas_types.h b/media_cas_packager_sdk/public/wv_cas_types.h index 41d439a..08256f2 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.h +++ b/media_cas_packager_sdk/public/wv_cas_types.h @@ -86,6 +86,23 @@ struct EntitlementKeyInfo { enum class EcmVersion : int { kV2 = 0, kV3 = 1 }; +// Used for generating ECMs if entitlement key rotation is enabled. +struct EntitlementKeyRotationInfo { + // Indicates if entitlement key rotation feature is used. If enabled, + // |period_index| and |rotation_window_left| must also be specified and will + // be carried in generated ECMs. + bool rotation_enabled = false; + // The current entitlement key period index. Only used if |rotation_enabled| + // is set to true. + uint32_t period_index = 0; + // It tells the client how many crypto periods (unique ECMs) left (including + // this ECM) that a new license request must be made. Must be a positive + // value. Only used if |rotation_enabled| is set to true. + // The value will automatically decrease by 1 on each call to GenerateEcm or + // GenerateSingleKeyEcm, until the value reaches 1. + uint32_t rotation_window_left = 0; +}; + // A struct that captures the Simulcrypt ECMG configurations. Most fields are // Simulcrypt standard fields (See ETSI TS 103 197 V1.5.1 (2008-10) // Section 5.3). @@ -177,6 +194,8 @@ struct EcmgCustomParameters { // when the ECM is received, and stops util the device is no longer in // |device_groups|. std::vector service_blocking_groups; + // Used if entitlement key rotation is enabled. + EntitlementKeyRotationInfo entitlement_rotation; }; // A custom access control processing function used by ECMG to get information @@ -233,6 +252,33 @@ typedef std::function ServiceBlockingSettingFunc; +struct WvEcmgChannelStatus { + // If the channel has been set up or not. + bool has_setup; + uint16_t channel_id; + uint16_t ca_system_id; + int num_of_open_streams; + EcmVersion ecm_version; + // Below are Simulcrypt parameters values (see ETSI TS 103 197 V1.5.1 + // (2008-10) Section 5.3). + uint8_t section_tspkt_flag; + uint16_t delay_start; + uint16_t delay_stop; + uint16_t ecm_pep_period; + uint16_t max_streams; + uint16_t min_cp_duration; + uint8_t lead_cw; + uint8_t cw_per_message; + uint16_t max_comp_time; +}; + +struct WvEcmgStreamStatus { + uint16_t stream_id; + uint16_t ecm_id; + CryptoMode crypto_mode; + int age_restriction; +}; + struct WvCasEncryptionRequest { std::string content_id; std::string provider; diff --git a/media_cas_packager_sdk/public/wv_emmg.cc b/media_cas_packager_sdk/public/wv_emmg.cc index f795bec..9754aed 100644 --- a/media_cas_packager_sdk/public/wv_emmg.cc +++ b/media_cas_packager_sdk/public/wv_emmg.cc @@ -26,10 +26,6 @@ #include "media_cas_packager_sdk/internal/emmg.h" namespace { -// Maximum number of entitlement key ids shown in private data. -constexpr uint16_t kMaxNumOfEntitlementKeyIds = 2; -// Entitlement key id length is fixed to 16 bytes. -constexpr uint16_t kEntitlementKeyIdLengthBytes = 16; constexpr int32_t kEmmDataType = 0; constexpr int32_t kPrivateDataType = 1; } // namespace @@ -58,8 +54,8 @@ ABSL_FLAG(std::string, content_provider, "", "Content provider to put into priavte data."); ABSL_FLAG(std::string, content_id, "", "Content id to put into priavte data"); ABSL_FLAG( - std::vector, entitlement_key_ids, {}, - "Comma-separated list of entitlement_key_ids to put into private data"); + std::vector, group_ids, {}, + "Comma-separated list of group_ids to put into private data. Optional."); ABSL_FLAG(int32_t, bandwidth, 100, "Requested bandwidth in kbps"); ABSL_FLAG(int32_t, max_num_message, 100, "Maximum number of messages that can be sent"); @@ -93,8 +89,8 @@ void CheckEmmgUsage(const widevine::cas::EmmgConfig& config) { "data)."; LOG_IF(WARNING, !config.content_id.empty()) << "content_id is set but ignored as data_type is set to 0 (EMM data)."; - LOG_IF(WARNING, !config.entitlement_key_ids.empty()) - << "entitlement_key_ids is set but ignored as data_type is set to 0 " + LOG_IF(WARNING, !config.group_ids.empty()) + << "group_ids is set but ignored as data_type is set to 0 " "(EMM data)."; } @@ -130,17 +126,7 @@ void BuildEmmgConfig(widevine::cas::EmmgConfig* config) { // Below are private data specific configurations. config->content_provider = absl::GetFlag(FLAGS_content_provider); config->content_id = absl::GetFlag(FLAGS_content_id); - // Check and set entitlement_key_ids. - config->entitlement_key_ids = absl::GetFlag(FLAGS_entitlement_key_ids); - CHECK_LE(config->entitlement_key_ids.size(), kMaxNumOfEntitlementKeyIds) - << absl::StrCat("Number of entitlement key ids shouldn't exceed ", - kMaxNumOfEntitlementKeyIds); - for (const auto& entitlement_key_id : config->entitlement_key_ids) { - CHECK_EQ(entitlement_key_id.size(), kEntitlementKeyIdLengthBytes) - << absl::StrCat("Entitlement key id length must be ", - kEntitlementKeyIdLengthBytes, - ". The offending key id is ", entitlement_key_id); - } + config->group_ids = absl::GetFlag(FLAGS_group_ids); // Below are EMM data specific configurations. config->ecc_signing_key = absl::GetFlag(FLAGS_ecc_signing_key); diff --git a/protos/public/media_cas.proto b/protos/public/media_cas.proto index 33dfbb3..9427e59 100644 --- a/protos/public/media_cas.proto +++ b/protos/public/media_cas.proto @@ -19,9 +19,8 @@ message CaDescriptorPrivateData { // Content ID. optional bytes content_id = 2; - // Entitlement key IDs for current content per track. Each track will allow up - // to 2 entitlement key ids (odd and even entitlement keys). - repeated bytes entitlement_key_ids = 3; + // Deprecated. + repeated bytes deprecated_entitlement_key_ids = 3; // The groups ids this channel belongs to. repeated bytes group_ids = 4; @@ -78,6 +77,17 @@ message EcmMetaData { // Optional. The minimum age required to watch the content. The value // represents actual age, with 0 means no restriction. optional uint32 age_restriction = 2 [default = 0]; + // If specified, it means entitlement key rotation is enabled. The value will + // be included in the license request. The server is expected to return + // entitlement keys accordingly (e.g., keys for |entitlement_period_index| and + // |entitlement_period_index| + 1). + optional uint32 entitlement_period_index = 3; + // Used only if entitlement key rotation is enabled. This parameter controls + // the probability of requesting a new license by clients upon receiving this + // ECM. The purpose is to spread out requests to avoid request storms. A + // client will request a new license with possibility = 1 / + // |entitlement_rotation_window_left|. + optional uint32 entitlement_rotation_window_left = 4 [default = 1]; } message EcmKeyData {