Entitlement rotation support

Updates also include:
- Add APIs to query current Simulcrypt channel & stream status;
- EMM format change (used only to carry fingerprinting and service
blocking info);
- Key fetcher example to use curl key fetcher.
This commit is contained in:
Lu Chen
2021-06-29 14:51:49 -07:00
parent f04e15c48c
commit b3a5fff77d
42 changed files with 1425 additions and 876 deletions

View File

@@ -105,6 +105,7 @@ cc_library(
":status", ":status",
"//base", "//base",
"//external:protobuf", "//external:protobuf",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/synchronization", "@abseil_repo//absl/synchronization",
"//protos/public:client_identification_cc_proto", "//protos/public:client_identification_cc_proto",
"//protos/public:device_certificate_status_cc_proto", "//protos/public:device_certificate_status_cc_proto",
@@ -134,6 +135,7 @@ cc_test(
"//external:protobuf", "//external:protobuf",
"//testing:gunit_main", "//testing:gunit_main",
"@abseil_repo//absl/memory", "@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"//protos/public:device_common_cc_proto", "//protos/public:device_common_cc_proto",
"//protos/public:device_security_profile_data_cc_proto", "//protos/public:device_security_profile_data_cc_proto",
"//protos/public:device_security_profile_list_cc_proto", "//protos/public:device_security_profile_list_cc_proto",
@@ -305,6 +307,7 @@ cc_library(
"@abseil_repo//absl/memory", "@abseil_repo//absl/memory",
"@abseil_repo//absl/strings", "@abseil_repo//absl/strings",
"@abseil_repo//absl/synchronization", "@abseil_repo//absl/synchronization",
"@abseil_repo//absl/time",
"//protos/public:drm_certificate_cc_proto", "//protos/public:drm_certificate_cc_proto",
"//protos/public:errors_cc_proto", "//protos/public:errors_cc_proto",
"//protos/public:signed_drm_certificate_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( cc_test(
name = "output_protection_util_test", name = "output_protection_util_test",
srcs = ["output_protection_util_test.cc"], srcs = ["output_protection_util_test.cc"],
@@ -1150,6 +1172,7 @@ cc_library(
":sha_util", ":sha_util",
"//base", "//base",
"@abseil_repo//absl/strings", "@abseil_repo//absl/strings",
"//common/oemcrypto_core_message/odk",
"//common/oemcrypto_core_message/odk:kdo", "//common/oemcrypto_core_message/odk:kdo",
], ],
) )
@@ -1161,6 +1184,9 @@ cc_test(
":core_message_util", ":core_message_util",
"//testing:gunit_main", "//testing:gunit_main",
"@abseil_repo//absl/strings", "@abseil_repo//absl/strings",
"//common/oemcrypto_core_message/odk",
"//protos/public:certificate_provisioning_cc_proto",
"//protos/public:license_protocol_cc_proto",
], ],
) )

View File

@@ -18,9 +18,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <cstdint> #include "openssl/pem.h"
#include "absl/base/macros.h"
#include "openssl/rsa.h"
#include "common/hash_algorithm.h" #include "common/hash_algorithm.h"
namespace widevine { namespace widevine {

View File

@@ -16,7 +16,7 @@
#include <string> #include <string>
#include "openssl/rsa.h" #include "openssl/pem.h"
namespace widevine { namespace widevine {
namespace rsa_util { namespace rsa_util {

View File

@@ -71,25 +71,26 @@ class SecurityProfileList {
// contain single record. For custom DSP, it may contain multiple records // 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 // since active dsp and inactive dsp could share the same dsp_name under the
// same owner. // same owner.
bool GetProfileByNameAndOwner( virtual bool GetProfileByNameAndOwner(
const std::string& name, const std::string& owner, const std::string& name, const std::string& owner,
std::vector<SecurityProfile>* security_profiles) const; std::vector<SecurityProfile>* security_profiles) const;
// Populates |security_profiles| owned by the content owner. // Populates |security_profiles| owned by the content owner.
int GetProfilesByOwner(const std::string& owner, virtual int GetProfilesByOwner(
std::vector<SecurityProfile>* security_profiles) const; const std::string& owner,
std::vector<SecurityProfile>* security_profiles) const;
// Populates |owner_list| for security profiles. |is_default_dsp| boolean // Populates |owner_list| for security profiles. |is_default_dsp| boolean
// indicates the owner_list for default dsp or custom dsp. // indicates the owner_list for default dsp or custom dsp.
int GetProfilesOwnerList(const bool is_default_dsp, virtual int GetProfilesOwnerList(const bool is_default_dsp,
std::vector<std::string>* owner_list) const; std::vector<std::string>* owner_list) const;
// Return the device security capabilities. |drm_info| is populated with // Return the device security capabilities. |drm_info| is populated with
// data from |client_id| and |device_info|. |drm_info| must not be null and // data from |client_id| and |device_info|. |drm_info| must not be null and
// is owned by the caller. // is owned by the caller.
bool GetDrmInfo(const ClientIdentification& client_id, virtual bool GetDrmInfo(const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info, const ProvisionedDeviceInfo& device_info,
SecurityProfile::DrmInfo* drm_info) const; SecurityProfile::DrmInfo* drm_info) const;
// Return the number of profiles in the list. // Return the number of profiles in the list.
int NumProfiles() const; int NumProfiles() const;
@@ -110,6 +111,12 @@ class SecurityProfileList {
HashAlgorithm hash_algorithm, const std::string& signature, HashAlgorithm hash_algorithm, const std::string& signature,
int* added_profile_num); 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: protected:
void ClearAllProfiles(); void ClearAllProfiles();

View File

@@ -62,6 +62,7 @@ cc_binary(
srcs = ["wv_cas_key_fetcher_example.cc"], srcs = ["wv_cas_key_fetcher_example.cc"],
deps = [ deps = [
"//common:status", "//common:status",
"//media_cas_packager_sdk/public:wv_cas_curl_key_fetcher",
"//media_cas_packager_sdk/public:wv_cas_key_fetcher", "//media_cas_packager_sdk/public:wv_cas_key_fetcher",
], ],
) )

View File

@@ -151,11 +151,11 @@ const char kTestEmmgPrivateDataProvision[] = {
'\x47', '\x40', '\x00', '\x10', '\x0a', '\x0d', '\x77', '\x69', '\x64', '\x47', '\x40', '\x00', '\x10', '\x0a', '\x0d', '\x77', '\x69', '\x64',
'\x65', '\x76', '\x69', '\x6e', '\x65', '\x5f', '\x74', '\x65', '\x73', '\x65', '\x76', '\x69', '\x6e', '\x65', '\x5f', '\x74', '\x65', '\x73',
'\x74', '\x12', '\x09', '\x43', '\x61', '\x73', '\x54', '\x73', '\x46', '\x74', '\x12', '\x09', '\x43', '\x61', '\x73', '\x54', '\x73', '\x46',
'\x61', '\x6b', '\x65', '\x1a', '\x10', '\x66', '\x61', '\x6b', '\x65', '\x61', '\x6b', '\x65', '\x22', '\x0a', '\x66', '\x61', '\x6b', '\x65',
'\x4b', '\x65', '\x79', '\x49', '\x64', '\x31', '\x4b', '\x65', '\x79', '\x47', '\x72', '\x6f', '\x75', '\x70', '\x31', '\x22', '\x0c', '\x66',
'\x49', '\x64', '\x31', '\x1a', '\x10', '\x66', '\x61', '\x6b', '\x65', '\x61', '\x6b', '\x65', '\x47', '\x72', '\x6f', '\x75', '\x70', '\x49',
'\x4b', '\x65', '\x79', '\x49', '\x64', '\x32', '\x4b', '\x65', '\x79', '\x64', '\x32', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\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', '\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). // Start of the first TS packet (188 bytes).
'\x47', '\x40', '\x00', '\x10', // TS packet header (4 bytes). '\x47', '\x40', '\x00', '\x10', // TS packet header (4 bytes).
'\x00', // Pointer field (1 byte). '\x00', // Pointer field (1 byte).
'\x82', '\x70', '\xbe', // Secyion header (3 bytes). '\x82', '\x70', '\xbc', // Section header (3 bytes).
// Start of Widevine EMM. // Start of Widevine EMM.
'\x01', '\x08', '\x00', '\x00', '\x00', '\x00', '\x5f', '\x3d', '\xc1', '\x0a', '\x71', '\x0a', '\x14', '\x0a', '\x03', '\x43', '\x48', '\x31',
'\xfb', '\x00', '\x6b', '\x0a', '\x14', '\x0a', '\x03', '\x43', '\x48', '\x0a', '\x03', '\x43', '\x48', '\x32', '\x12', '\x08', '\x63', '\x6f',
'\x31', '\x0a', '\x03', '\x43', '\x48', '\x32', '\x12', '\x08', '\x63', '\x6e', '\x74', '\x72', '\x6f', '\x6c', '\x73', '\x0a', '\x16', '\x0a',
'\x6f', '\x6e', '\x74', '\x72', '\x6f', '\x6c', '\x73', '\x0a', '\x16', '\x03', '\x43', '\x48', '\x33', '\x12', '\x0f', '\x61', '\x6e', '\x6f',
'\x0a', '\x03', '\x43', '\x48', '\x33', '\x12', '\x0f', '\x61', '\x6e', '\x74', '\x68', '\x65', '\x72', '\x20', '\x63', '\x6f', '\x6e', '\x74',
'\x6f', '\x74', '\x68', '\x65', '\x72', '\x20', '\x63', '\x6f', '\x6e', '\x72', '\x6f', '\x6c', '\x12', '\x18', '\x0a', '\x03', '\x43', '\x48',
'\x74', '\x72', '\x6f', '\x6c', '\x12', '\x18', '\x0a', '\x03', '\x43', '\x31', '\x0a', '\x03', '\x43', '\x48', '\x32', '\x12', '\x06', '\x47',
'\x48', '\x31', '\x0a', '\x03', '\x43', '\x48', '\x32', '\x12', '\x06', '\x72', '\x6f', '\x75', '\x70', '\x31', '\x20', '\xd2', '\x85', '\xd8',
'\x47', '\x72', '\x6f', '\x75', '\x70', '\x31', '\x20', '\xd2', '\x85', '\xcc', '\x04', '\x12', '\x21', '\x0a', '\x03', '\x43', '\x48', '\x33',
'\xd8', '\xcc', '\x04', '\x12', '\x21', '\x0a', '\x03', '\x43', '\x48', '\x12', '\x06', '\x47', '\x72', '\x6f', '\x75', '\x70', '\x32', '\x12',
'\x33', '\x12', '\x06', '\x47', '\x72', '\x6f', '\x75', '\x70', '\x32', '\x06', '\x47', '\x72', '\x6f', '\x75', '\x70', '\x33', '\x18', '\xd2',
'\x12', '\x06', '\x47', '\x72', '\x6f', '\x75', '\x70', '\x33', '\x18', '\x85', '\xd8', '\xcc', '\x04', '\x20', '\xd3', '\x85', '\xd8', '\xcc',
'\xd2', '\x85', '\xd8', '\xcc', '\x04', '\x20', '\xd3', '\x85', '\xd8', '\x04', '\x18', '\xfb', '\x83', '\xf7', '\xf9', '\x05', '\x12', '\x47',
'\xcc', '\x04', '\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',
@@ -216,8 +216,8 @@ const char kTestEmmgEmmDataProvision[] = {
// Start of the second TS packet (188 bytes). // Start of the second TS packet (188 bytes).
'\x47', '\x00', '\x00', '\x11', // TS packet header (4 bytes). '\x47', '\x00', '\x00', '\x11', // TS packet header (4 bytes).
// Continued Widevine EMM. // Continued Widevine EMM.
'\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\x78', '\xff',
'\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', '\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',

View File

@@ -12,8 +12,11 @@
#include <vector> #include <vector>
#include "common/status.h" #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" #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 kContentId[] = "21140844";
const char kContentProvider[] = "widevine"; const char kContentProvider[] = "widevine";
const char kTrackType[] = "SD"; const char kTrackType[] = "SD";
@@ -22,36 +25,16 @@ const char kSigningProvider[] = "widevine_test";
const char kSingingKey[] = const char kSingingKey[] =
"1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9"; "1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9";
const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249"; const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249";
const char kHttpResponse[] =
"{\"response\":"
"\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu"
"dF9rZXlzIjpbeyJrZXlfaWQiOiJNUGFndXhNb1hNNkUxUzhEOUF3RkNBPT0iLCJrZXkiOiJoZ1"
"JycmdqeUg4NjQycjY3VHd0OHJ1cU5MUGNMRmtKcWRVSUROdm5GZDBNPSIsInRyYWNrX3R5cGUi"
"OiJTRCIsImtleV9zbG90IjoiU0lOR0xFIn1dfQ==\"}";
using widevine::Status; using widevine::Status;
using widevine::cas::EntitlementKeyInfo; using widevine::cas::EntitlementKeyInfo;
using widevine::cas::EntitlementRequestParams; using widevine::cas::EntitlementRequestParams;
using widevine::cas::WvCasKeyFetcher; using widevine::cas::WvCasCurlKeyFetcher;
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();
}
};
int main(int argc, char** argv) { int main(int argc, char** argv) {
// Initialize key fetcher. // Initialize key fetcher with server url.
ExampleKeyFetcher key_fetcher(kSigningProvider, kSingingKey, kSingingIv); WvCasCurlKeyFetcher key_fetcher(kCasEncryptionServerUrl, kSigningProvider,
kSingingKey, kSingingIv);
// Create request string. // Create request string.
std::string request_str; std::string request_str;

View File

@@ -73,6 +73,7 @@ cc_library(
"@abseil_repo//absl/container:flat_hash_set", "@abseil_repo//absl/container:flat_hash_set",
"@abseil_repo//absl/memory", "@abseil_repo//absl/memory",
"@abseil_repo//absl/strings", "@abseil_repo//absl/strings",
"@abseil_repo//absl/synchronization",
"@abseil_repo//absl/types:optional", "@abseil_repo//absl/types:optional",
"@abseil_repo//absl/types:span", "@abseil_repo//absl/types:span",
"//common:crypto_util", "//common:crypto_util",
@@ -172,6 +173,7 @@ cc_library(
deps = [ deps = [
":util", ":util",
"//base", "//base",
"@abseil_repo//absl/types:span",
], ],
) )
@@ -362,3 +364,12 @@ cc_test(
"//testing:gunit_main", "//testing:gunit_main",
], ],
) )
cc_test(
name = "simulcrypt_util_test",
srcs = ["simulcrypt_util_test.cc"],
deps = [
":simulcrypt_util",
"//testing:gunit_main",
],
)

View File

@@ -13,6 +13,7 @@
#include <bitset> #include <bitset>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <string>
#include <utility> #include <utility>
#include "glog/logging.h" #include "glog/logging.h"
@@ -38,46 +39,29 @@ constexpr uint16_t kWidevineSystemId = 0x4AD4;
// New CA System ID range for Widevine, all inclusive. // New CA System ID range for Widevine, all inclusive.
constexpr uint16_t kWidevineNewSystemIdLowerBound = 0x56C0; constexpr uint16_t kWidevineNewSystemIdLowerBound = 0x56C0;
constexpr uint16_t kWidevineNewSystemIdUpperBound = 0x56C9; 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 } // namespace
Status Ecm::Initialize( Status Ecm::Initialize(
const EcmInitParameters& ecm_init_parameters, const EcmInitParameters& ecm_init_parameters,
const std::vector<EntitlementKeyInfo>& injected_entitlements) { const std::vector<EntitlementKeyInfo>& injected_entitlements) {
if (initialized_) { if (initialized_) {
return {error::INTERNAL, "Already initialized."}; LOG(INFO) << "Reinitializing ECM.";
} }
if (injected_entitlements.empty()) { if (injected_entitlements.empty()) {
return {error::NOT_FOUND, "Empty injected entitlements."}; return {error::NOT_FOUND, "Empty injected entitlements."};
} }
if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size, if (ecm_init_parameters.cas_id != kWidevineSystemId &&
&content_iv_size_)) { (ecm_init_parameters.cas_id < kWidevineNewSystemIdLowerBound ||
return {error::INVALID_ARGUMENT, ecm_init_parameters.cas_id > kWidevineNewSystemIdUpperBound)) {
"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)) {
return {error::INVALID_ARGUMENT, "Invalid CA system ID."}; 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(); entitlement_keys_.clear();
for (const auto& entitlement : injected_entitlements) { for (const auto& entitlement : injected_entitlements) {
@@ -119,10 +103,29 @@ void Ecm::SetServiceBlocking(const EcmServiceBlockingParams* service_blocking) {
: absl::make_optional(*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, Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids, const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const { std::string* serialized_ecm) {
absl::ReaderMutexLock lock(&ecm_params_mutex_); absl::ReaderMutexLock lock(&ecm_params_mutex_);
if (!initialized_) { if (!initialized_) {
@@ -134,11 +137,11 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
if (even_key == nullptr) { if (even_key == nullptr) {
return {error::INVALID_ARGUMENT, "even_key must not be null."}; 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, return {error::INVALID_ARGUMENT,
"odd_key should be null as key rotation is disabled."}; "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, return {error::INVALID_ARGUMENT,
"odd_key can not be null as key rotation is enabled."}; "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; const bool has_odd_key = odd_key != nullptr;
EcmSerializerParams serializer_params; EcmSerializerParams serializer_params;
serializer_params.crypto_mode = crypto_mode_; serializer_params.crypto_mode = init_params_.crypto_mode;
serializer_params.age_restriction = age_restriction_; serializer_params.age_restriction = init_params_.age_restriction;
serializer_params.cas_id = cas_id_; 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. // Process normal content keys.
std::vector<EntitledKeyInfo> content_keys = {*even_key}; std::vector<EntitledKeyInfo> content_keys = {*even_key};
@@ -185,20 +196,26 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
serializer_params.fingerprinting = fingerprinting_; serializer_params.fingerprinting = fingerprinting_;
serializer_params.service_blocking = service_blocking_; 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); status = ecm_serializer_->SerializeEcm(serializer_params, serialized_ecm);
if (!status.ok()) { if (!status.ok()) {
return status; 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(); return OkStatus();
} }
Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key, Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids, const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const { std::string* serialized_ecm) {
return GenerateEcm(key, nullptr, track_type, group_ids, 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 { bool Ecm::CheckEntitlementKeys() const {
int expected_num_keys = init_params_.key_rotation_enabled ? 2 : 1;
for (const auto& track : entitlement_keys_) { for (const auto& track : entitlement_keys_) {
if (track.second.size() != (paired_keys_required_ ? 2 : 1)) { if (track.second.size() != expected_num_keys) {
LOG(ERROR) << " Wrong number of entitlement keys: " LOG(ERROR) << " Wrong number of entitlement keys: " << expected_num_keys
<< (paired_keys_required_ ? 2 : 1) << " expected, " << " expected, " << track.second.size() << " received.";
<< track.second.size() << " received.";
return false; return false;
} }
} }

View File

@@ -61,6 +61,7 @@ struct EcmInitParameters {
// Private signing key used to sign ECM data. Must be an elliptic-curve // Private signing key used to sign ECM data. Must be an elliptic-curve
// cryptography key. // cryptography key.
std::string ecc_private_signing_key; std::string ecc_private_signing_key;
EntitlementKeyRotationInfo entitlement_rotation;
}; };
// Generator for producing Widevine CAS-compliant ECMs. Used to construct the // Generator for producing Widevine CAS-compliant ECMs. Used to construct the
@@ -106,6 +107,20 @@ class Ecm {
virtual void SetServiceBlocking( virtual void SetServiceBlocking(
const EcmServiceBlockingParams* service_blocking); 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 // Accept keys and IVs and construct an ECM that will fit into a Transport
// Stream packet payload. // Stream packet payload.
// Args: // Args:
@@ -119,11 +134,14 @@ class Ecm {
// consistent with the initialized settings. // consistent with the initialized settings.
// The even_key and odd_key will be wrapped using the appropriate // The even_key and odd_key will be wrapped using the appropriate
// entitlement key. Wrapping modifies the original structure. // 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, virtual Status GenerateEcm(EntitledKeyInfo* even_key,
EntitledKeyInfo* odd_key, EntitledKeyInfo* odd_key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids, const std::vector<std::string>& 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 // 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 // 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. // |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
// The |key| contents (specifically IV sizes) must be consistent // The |key| contents (specifically IV sizes) must be consistent
// with the initialized settings. // 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, virtual Status GenerateSingleKeyEcm(EntitledKeyInfo* key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids, const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const; std::string* serialized_ecm);
// Generate a TS packet with the given |ecm| as payload. // 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. // Set to true when the object has been properly initialized.
bool initialized_ = false; bool initialized_ = false;
// Content IV size may be 8 or 16. Size is set once during Initialize(). EcmInitParameters init_params_;
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_;
// Entitlement keys needed for ECM generation. // Entitlement keys needed for ECM generation.
// The keys are added when the CasEncryptionResponse is processed. // The keys are added when the CasEncryptionResponse is processed.
// Maps from {track_type, group_id} to one/two EntitlementKeyIdValue with even // Maps from {track_type, group_id} to one/two EntitlementKeyIdValue with even

View File

@@ -53,6 +53,7 @@ struct EcmSerializerParams {
// cryptography key. // cryptography key.
std::string ecc_private_signing_key; std::string ecc_private_signing_key;
std::vector<EcmSerializerGroupKeyInfo> group_keys; std::vector<EcmSerializerGroupKeyInfo> group_keys;
EntitlementKeyRotationInfo entitlement_rotation;
}; };
enum class EcmSerializerVersion { kV2 = 0, kV3 }; enum class EcmSerializerVersion { kV2 = 0, kV3 };

View File

@@ -181,6 +181,12 @@ Status EcmSerializerV3::SerializeEcm(const EcmSerializerParams& params,
if (params.age_restriction != 0) { if (params.age_restriction != 0) {
meta_data->set_age_restriction(params.age_restriction); 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(); EcmKeyData* even_key_data = ecm_payload.mutable_even_key_data();
even_key_data->set_entitlement_key_id(params.even_key.entitlement_key_id); even_key_data->set_entitlement_key_id(params.even_key.entitlement_key_id);

View File

@@ -573,6 +573,42 @@ TEST_F(EcmSerializerV3Test, GroupKeyInvalidWrappedKeyIvFail) {
"Unexpected group wrapped key iv size: 8")); "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
} // namespace cas } // namespace cas
} // namespace widevine } // namespace widevine

View File

@@ -10,6 +10,8 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <string>
#include <utility>
#include <vector> #include <vector>
#include "testing/gmock.h" #include "testing/gmock.h"
@@ -25,6 +27,7 @@ namespace cas {
namespace { namespace {
using ::testing::DoAll; using ::testing::DoAll;
using ::testing::Eq;
using ::testing::NotNull; using ::testing::NotNull;
using ::testing::Return; using ::testing::Return;
using ::testing::SetArgPointee; using ::testing::SetArgPointee;
@@ -170,15 +173,12 @@ TEST_F(EcmTest, InitFailNotEnoughInjectedEntitlement) {
.error_code()); .error_code());
} }
TEST_F(EcmTest, SecondInitFail) { TEST_F(EcmTest, SecondInitOk) {
Ecm ecm_gen; Ecm ecm_gen;
ASSERT_OK(ecm_gen.Initialize( ASSERT_OK(ecm_gen.Initialize(
params_simple_, {injected_entitlement_one_, injected_entitlement_two_})); params_simple_, {injected_entitlement_one_, injected_entitlement_two_}));
EXPECT_EQ(error::INTERNAL, ASSERT_OK(ecm_gen.Initialize(
ecm_gen params_simple_, {injected_entitlement_one_, injected_entitlement_two_}));
.Initialize(params_simple_, {injected_entitlement_one_,
injected_entitlement_two_})
.error_code());
} }
TEST_F(EcmTest, InitIvSize16x16OK) { TEST_F(EcmTest, InitIvSize16x16OK) {
@@ -188,6 +188,19 @@ TEST_F(EcmTest, InitIvSize16x16OK) {
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_})); 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, class EcmInitWithValidCasIdTest : public EcmTest,
public testing::WithParamInterface<uint16_t> { public testing::WithParamInterface<uint16_t> {
}; };
@@ -315,7 +328,13 @@ MATCHER_P(EcmSerializerParamsEq, expected, "") {
(arg.age_restriction == expected.age_restriction) && (arg.age_restriction == expected.age_restriction) &&
IsEcmSerializerKeyInfoEq(arg.even_key, expected.even_key) && IsEcmSerializerKeyInfoEq(arg.even_key, expected.even_key) &&
IsEcmSerializerKeyInfoEq(arg.odd_key, expected.odd_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) { TEST_F(EcmTest, GenerateEcmTwoKeysOK) {
@@ -350,6 +369,75 @@ TEST_F(EcmTest, GenerateEcmTwoKeysOK) {
EXPECT_EQ(actual_generated_ecm, expected_serialized_ecm); 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<MockEcmSerializer>();
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<MockEcmSerializer>();
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) { TEST_F(EcmTest, GenerateEcmParamsPassedDown) {
FakeEcm ecm_gen; FakeEcm ecm_gen;
EcmInitParameters params; EcmInitParameters params;
@@ -481,6 +569,59 @@ TEST_F(EcmTest, GenerateEcmWithMissingGroupKeysFail) {
.ok()); .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 {}; class EcmTsPacketTest : public ::testing::Test {};
TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) { TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) {

View File

@@ -327,67 +327,50 @@ Status HandleParameters(const char* const request, size_t request_length,
} }
void BuildChannelError(uint16_t channel_id, uint16_t error_status, void BuildChannelError(uint16_t channel_id, uint16_t error_status,
const std::string& error_info, char* message, const std::string& error_info,
size_t* message_length) { const absl::Span<char> message, size_t* message_length) {
DCHECK(message);
DCHECK(message_length); DCHECK(message_length);
simulcrypt_util::BuildMessageHeader( SimulcryptMessage builder(message, message_length);
ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_ERROR, message, message_length); builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_ERROR);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
message_length); builder.AddUint16Param(ERROR_STATUS, error_status);
simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message,
message_length);
if (!error_info.empty()) { if (!error_info.empty()) {
simulcrypt_util::AddParam( builder.AddParam(ERROR_INFORMATION,
ERROR_INFORMATION, reinterpret_cast<const uint8_t*>(error_info.c_str()), reinterpret_cast<const uint8_t*>(error_info.c_str()),
error_info.size(), message, message_length); 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 VLOG(3) << "ChannelError sent. ECM_CHANNEL_ID: " << channel_id
<< "; ERROR_STATUS: " << error_status << "; ERROR_STATUS: " << error_status
<< "; ERROR_INFORMATION: " << error_info; << "; 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<char> message,
size_t* message_length) { size_t* message_length) {
DCHECK(config); DCHECK(config);
DCHECK(message);
DCHECK(message_length); DCHECK(message_length);
simulcrypt_util::BuildMessageHeader( SimulcryptMessage builder(message, message_length);
ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_STATUS, message, message_length); builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_STATUS);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
message_length); builder.AddUint8Param(SECTION_TSPKT_FLAG, kSectionTSpktFlag);
simulcrypt_util::AddUint8Param(SECTION_TSPKT_FLAG, kSectionTSpktFlag, message,
message_length);
// No setting AC_delay_start parameter yet. // No setting AC_delay_start parameter yet.
// No setting AC_delay_stop parameter yet. // No setting AC_delay_stop parameter yet.
simulcrypt_util::AddUint16Param(DELAY_START, config->delay_start, message, builder.AddUint16Param(DELAY_START, config->delay_start);
message_length); builder.AddUint16Param(DELAY_STOP, config->delay_stop);
simulcrypt_util::AddUint16Param(DELAY_STOP, config->delay_stop, message,
message_length);
// No setting transition_delay_start parameter yet. // No setting transition_delay_start parameter yet.
// No setting transition_delay_stop parameter yet. // No setting transition_delay_stop parameter yet.
simulcrypt_util::AddUint16Param(ECM_REP_PERIOD, config->ecm_rep_period, builder.AddUint16Param(ECM_REP_PERIOD, config->ecm_rep_period);
message, message_length); builder.AddUint16Param(MAX_STREAMS, kMaxStream);
simulcrypt_util::AddUint16Param(MAX_STREAMS, kMaxStream, message,
message_length);
// min_CP_duration needs to be at least max_comp_time. So we just use // min_CP_duration needs to be at least max_comp_time. So we just use
// max_comp_time here for now. // max_comp_time here for now.
simulcrypt_util::AddUint16Param(MIN_CP_DURATION, config->max_comp_time, builder.AddUint16Param(MIN_CP_DURATION, config->max_comp_time);
message, message_length);
// LEAD_CW defines the number of control words required in advance to build an // 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 // ECM. When CW_PER_MESSAGE is 2, LEAD_CW should be 1; when CW_PER_MESSAGE is
// 1, LEAD_CW should be 0. // 1, LEAD_CW should be 0.
simulcrypt_util::AddUint8Param(LEAD_CW, config->number_of_content_keys - 1, builder.AddUint8Param(LEAD_CW, config->number_of_content_keys - 1);
message, message_length); builder.AddUint8Param(CW_PER_MESSAGE, config->number_of_content_keys);
simulcrypt_util::AddUint8Param(CW_PER_MESSAGE, config->number_of_content_keys, builder.AddUint16Param(MAX_COMP_TIME, config->max_comp_time);
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);
VLOG(3) << "ChannelStatus sent. ECM_CHANNEL_ID: " << channel_id VLOG(3) << "ChannelStatus sent. ECM_CHANNEL_ID: " << channel_id
<< "; SECTION_TSPKT_FLAG: " << kSectionTSpktFlag << "; 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, void BuildStreamError(uint16_t channel_id, uint16_t stream_id,
uint16_t error_status, const std::string& error_info, uint16_t error_status, const std::string& error_info,
char* message, size_t* message_length) { const absl::Span<char> message, size_t* message_length) {
DCHECK(message);
DCHECK(message_length); DCHECK(message_length);
simulcrypt_util::BuildMessageHeader( SimulcryptMessage builder(message, message_length);
ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_ERROR, message, message_length); builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_ERROR);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
message_length); builder.AddUint16Param(ECM_STREAM_ID, stream_id);
simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message, builder.AddUint16Param(ERROR_STATUS, error_status);
message_length);
simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message,
message_length);
if (!error_info.empty()) { if (!error_info.empty()) {
simulcrypt_util::AddParam( builder.AddParam(ERROR_INFORMATION,
ERROR_INFORMATION, reinterpret_cast<const uint8_t*>(error_info.c_str()), reinterpret_cast<const uint8_t*>(error_info.c_str()),
error_info.size(), message, message_length); 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 VLOG(3) << "StreamError sent. ECM_CHANNEL_ID: " << channel_id
<< "; ECM_STREAM_ID: " << stream_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, void BuildStreamStatus(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id,
uint8_t access_criteria_transfer_mode, char* message, uint8_t access_criteria_transfer_mode,
size_t* message_length) { const absl::Span<char> message, size_t* message_length) {
DCHECK(message);
DCHECK(message_length); DCHECK(message_length);
simulcrypt_util::BuildMessageHeader( SimulcryptMessage builder(message, message_length);
ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_STATUS, message, message_length); builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_STATUS);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
message_length); builder.AddUint16Param(ECM_STREAM_ID, stream_id);
simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message, builder.AddUint16Param(ECM_ID, ecm_id);
message_length); builder.AddUint8Param(ACCESS_CRITERIA_TRANSFER_MODE,
simulcrypt_util::AddUint16Param(ECM_ID, ecm_id, message, message_length); access_criteria_transfer_mode);
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);
VLOG(3) << "StreamStatus sent. ECM_CHANNEL_ID: " << channel_id VLOG(3) << "StreamStatus sent. ECM_CHANNEL_ID: " << channel_id
<< "; ECM_STREAM_ID: " << stream_id << "; ECM_ID: " << ecm_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, void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id,
char* message, size_t* message_length) { const absl::Span<char> message,
DCHECK(message); size_t* message_length) {
DCHECK(message_length); DCHECK(message_length);
simulcrypt_util::BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, SimulcryptMessage builder(message, message_length);
ECMG_STREAM_CLOSE_RESPONSE, message, builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION,
message_length); ECMG_STREAM_CLOSE_RESPONSE);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
message_length); builder.AddUint16Param(ECM_STREAM_ID, stream_id);
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);
VLOG(3) << "StreamCloseResponse sent. ECM_CHANNEL_ID: " << channel_id VLOG(3) << "StreamCloseResponse sent. ECM_CHANNEL_ID: " << channel_id
<< "; ECM_STREAM_ID: " << stream_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, void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id,
uint16_t cp_number, uint16_t cp_number,
absl::Span<const uint8_t> ecm_datagram, char* message, absl::Span<const uint8_t> ecm_datagram,
size_t* message_length) { const absl::Span<char> message, size_t* message_length) {
DCHECK(message);
DCHECK(message_length); DCHECK(message_length);
simulcrypt_util::BuildMessageHeader( SimulcryptMessage builder(message, message_length);
ECMG_SCS_PROTOCOL_VERSION, ECMG_ECM_RESPONSE, message, message_length); builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_ECM_RESPONSE);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message, builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
message_length); builder.AddUint16Param(ECM_STREAM_ID, stream_id);
simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message, builder.AddUint16Param(CP_NUMBER, cp_number);
message_length); builder.AddParam(ECM_DATAGRAM, ecm_datagram.data(), ecm_datagram.size());
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);
VLOG(3) << "EcmResponse sent. ECM_CHANNEL_ID: " << channel_id VLOG(3) << "EcmResponse sent. ECM_CHANNEL_ID: " << channel_id
<< "; ECM_STREAM_ID: " << stream_id << "; CP_NUMBER: " << cp_number; << "; 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, size_t EcmgClientHandler::HandleRequest(const char* const request,
char* response, const absl::Span<char> response,
size_t* response_length) { size_t* response_length) {
DCHECK(request); DCHECK(request);
DCHECK(response);
uint8_t protocol_version; uint8_t protocol_version;
uint16_t request_type; uint16_t request_type;
@@ -628,12 +587,12 @@ size_t EcmgClientHandler::HandleRequest(const char* const request,
} }
void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params, void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
char* response, const absl::Span<char> response,
size_t* response_length) { size_t* response_length) {
DCHECK(response);
DCHECK(response_length); DCHECK(response_length);
VLOG(3) << "ChannelSetup request. ECM_channel_id: " << params.ecm_channel_id VLOG(3) << "ChannelSetup request. ECM_channel_id: " << params.ecm_channel_id
<< "; ECM_stream_id: " << params.ecm_stream_id; << "; ECM_stream_id: " << params.ecm_stream_id;
absl::WriterMutexLock lock(&channel_mutex_);
// There is always one (and only one) channel per TCP connection. // There is always one (and only one) channel per TCP connection.
if (channel_id_set_) { if (channel_id_set_) {
@@ -672,11 +631,11 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
} }
void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params, void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params,
char* response, const absl::Span<char> response,
size_t* response_length) const { size_t* response_length) const {
DCHECK(response);
DCHECK(response_length); DCHECK(response_length);
VLOG(3) << "ChannelTest request. ECM_channel_id: " << params.ecm_channel_id; 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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", 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, void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
char* response, const absl::Span<char> response,
size_t* response_length) { size_t* response_length) {
DCHECK(response);
DCHECK(response_length); DCHECK(response_length);
VLOG(3) << "ChannelClose request. ECM_channel_id: " << params.ecm_channel_id; 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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", 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, void EcmgClientHandler::HandleChannelError(const EcmgParameters& params,
char* response, const absl::Span<char> response,
size_t* response_length) const { size_t* response_length) const {
VLOG(3) << "ChannelError request. ECM_channel_id: " << params.ecm_channel_id; 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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", 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, void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
char* response, const absl::Span<char> response,
size_t* response_length) { size_t* response_length) {
DCHECK(response);
DCHECK(response_length); DCHECK(response_length);
VLOG(3) << "StreamSetup request. ECM_channel_id: " << params.ecm_channel_id VLOG(3) << "StreamSetup request. ECM_channel_id: " << params.ecm_channel_id
<< "; ECM_stream_id: " << params.ecm_stream_id << "; ECM_stream_id: " << params.ecm_stream_id
<< "; ECM_id: " << params.ecm_id << "; nominal_CP_duration" << "; ECM_id: " << params.ecm_id << "; nominal_CP_duration"
<< params.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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_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, void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params,
char* response, const absl::Span<char> response,
size_t* response_length) const { size_t* response_length) const {
DCHECK(response);
DCHECK(response_length); DCHECK(response_length);
VLOG(3) << "StreamTest request. ECM_channel_id: " << params.ecm_channel_id VLOG(3) << "StreamTest request. ECM_channel_id: " << params.ecm_channel_id
<< "; ECM_stream_id: " << params.ecm_stream_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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
@@ -807,13 +770,14 @@ void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params,
response_length); response_length);
} }
void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params, void EcmgClientHandler::HandleStreamCloseRequest(
char* response, const EcmgParameters& params, const absl::Span<char> response,
size_t* response_length) { size_t* response_length) {
DCHECK(response);
DCHECK(response_length); DCHECK(response_length);
VLOG(3) << "StreamClose request. ECM_channel_id: " << params.ecm_channel_id VLOG(3) << "StreamClose request. ECM_channel_id: " << params.ecm_channel_id
<< "; ECM_stream_id: " << params.ecm_stream_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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_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, void EcmgClientHandler::HandleStreamError(const EcmgParameters& params,
char* response, const absl::Span<char> response,
size_t* response_length) const { size_t* response_length) const {
VLOG(3) << "StreamError request. ECM_channel_id: " << params.ecm_channel_id VLOG(3) << "StreamError request. ECM_channel_id: " << params.ecm_channel_id
<< "; ECM_stream_id: " << params.ecm_stream_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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
@@ -893,18 +859,27 @@ Status EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor(
if (!custom_params.service_blocking_groups.empty()) { if (!custom_params.service_blocking_groups.empty()) {
params.service_blocking_groups = custom_params.service_blocking_groups; 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(); return OkStatus();
} }
void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params, void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
char* response, const absl::Span<char> response,
size_t* response_length) { size_t* response_length) {
VLOG(3) << "CwProvision request. ECM_channel_id: " << params.ecm_channel_id VLOG(3) << "CwProvision request. ECM_channel_id: " << params.ecm_channel_id
<< "; ECM_stream_id: " << params.ecm_stream_id << "; ECM_stream_id: " << params.ecm_stream_id
<< "; CP_number: " << params.cp_number << "; CP_number: " << params.cp_number
<< "; CP_CW_Combination size: " << params.cp_cw_combinations.size(); << "; CP_CW_Combination size: " << params.cp_cw_combinations.size();
absl::ReaderMutexLock lock1(&channel_mutex_);
absl::WriterMutexLock lock2(&stream_mutex_);
DCHECK(response);
DCHECK(response_length); DCHECK(response_length);
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
@@ -1060,6 +1035,29 @@ Status EcmgClientHandler::UpdateStreamPrivateParameters(
<< stream_info->entitlement_keys.size(); << 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) { if (invalidate_ecm_gen) {
stream_info->ecm.reset(); 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.ecm_version = ecmg_config_->ecm_version;
ecm_init_params.ecc_private_signing_key = ecm_init_params.ecc_private_signing_key =
ecmg_config_->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 // Override track type. This Simulcrypt ECMg implementation assumes only one
// track type -- all entitlement keys passed in will be used to generate ECM. // 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<std::string> group_ids(group_id_set.begin(), group_id_set.end()); std::vector<std::string> 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; Status status;
std::string serialized_ecm; std::string serialized_ecm;
if (key_count > 1) { if (key_count > 1) {
@@ -1224,6 +1234,10 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
} }
LOG(INFO) << "ECM generated with size: " << serialized_ecm.size() << "."; 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. // Make a TS packet carrying the serialized ECM.
// According to the standard, it is the head-end's responsibility to fill // According to the standard, it is the head-end's responsibility to fill
// the PID field in TS packet header. // the PID field in TS packet header.
@@ -1249,5 +1263,42 @@ absl::optional<std::string> EcmgClientHandler::GenerateRandomWrappedKeyIv()
return absl::nullopt; 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<WvEcmgStreamStatus> EcmgClientHandler::GetStreamStatus() const {
absl::ReaderMutexLock lock(&stream_mutex_);
std::vector<WvEcmgStreamStatus> 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 cas
} // namespace widevine } // namespace widevine

View File

@@ -17,6 +17,7 @@
#include <cstdint> #include <cstdint>
#include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_map.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/optional.h" #include "absl/types/optional.h"
#include "absl/types/span.h" #include "absl/types/span.h"
#include "common/status.h" #include "common/status.h"
@@ -53,6 +54,10 @@ struct EcmgParameters {
std::string access_criteria; std::string access_criteria;
std::string fingerprinting_control; std::string fingerprinting_control;
std::vector<std::string> service_blocking_groups; std::vector<std::string> 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 { struct EcmgStreamInfo {
@@ -64,6 +69,7 @@ struct EcmgStreamInfo {
std::vector<EntitlementKeyInfo> entitlement_keys; std::vector<EntitlementKeyInfo> entitlement_keys;
std::unique_ptr<Ecm> ecm; std::unique_ptr<Ecm> ecm;
uint8_t age_restriction; uint8_t age_restriction;
EntitlementKeyRotationInfo entitlement_rotation;
}; };
// A class that handles one (and only one) ECMG client. // 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| // If any response is generated, it would returned via |response|
// and |response_length|. // and |response_length|.
// Returns length of |request| that has been processed. // 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<char> response,
size_t* response_length); size_t* response_length);
// Sets the custom entitlement key fetching function used by ECMG to fetch // Sets the custom entitlement key fetching function used by ECMG to fetch
@@ -115,44 +122,63 @@ class EcmgClientHandler {
service_blocking_func_ = service_blocking_func; service_blocking_func_ = service_blocking_func;
} }
// Retrieves the current channel status;
WvEcmgChannelStatus GetChannelStatus() const;
// Retrieves the status of all open streams;
std::vector<WvEcmgStreamStatus> GetStreamStatus() const;
private: private:
void HandleChannelSetup(const EcmgParameters& params, char* response, void HandleChannelSetup(const EcmgParameters& params,
const absl::Span<char> response,
size_t* response_length); size_t* response_length);
void HandleChannelTest(const EcmgParameters& params, char* response, void HandleChannelTest(const EcmgParameters& params,
const absl::Span<char> response,
size_t* response_length) const; size_t* response_length) const;
void HandleChannelClose(const EcmgParameters& params, char* response, void HandleChannelClose(const EcmgParameters& params,
const absl::Span<char> response,
size_t* response_length); size_t* response_length);
void HandleChannelError(const EcmgParameters& params, char* response, void HandleChannelError(const EcmgParameters& params,
const absl::Span<char> response,
size_t* response_length) const; size_t* response_length) const;
void HandleStreamSetup(const EcmgParameters& params, char* response, void HandleStreamSetup(const EcmgParameters& params,
const absl::Span<char> response,
size_t* response_length); size_t* response_length);
void HandleStreamTest(const EcmgParameters& params, char* response, void HandleStreamTest(const EcmgParameters& params,
const absl::Span<char> response,
size_t* response_length) const; size_t* response_length) const;
void HandleStreamCloseRequest(const EcmgParameters& params, char* response, void HandleStreamCloseRequest(const EcmgParameters& params,
const absl::Span<char> response,
size_t* response_length); size_t* response_length);
void HandleStreamError(const EcmgParameters& params, char* response, void HandleStreamError(const EcmgParameters& params,
const absl::Span<char> response,
size_t* response_length) const; size_t* response_length) const;
void HandleCwProvision(const EcmgParameters& params, char* response, void HandleCwProvision(const EcmgParameters& params,
const absl::Span<char> response,
size_t* response_length); size_t* response_length);
Status UpdateParamsWithCustomAccessCriteriaProcessor( Status UpdateParamsWithCustomAccessCriteriaProcessor(
EcmgParameters& params) const; EcmgParameters& params) const;
// Update channel level private parameters using |params|. // 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 // Update stream level private parameters using |params|. If changes in
// |params| makes the stream hold Ecm instance (EcmgStreamInfo.ecm) no longer // |params| makes the stream hold Ecm instance (EcmgStreamInfo.ecm) no longer
// usable (changing params used in Ecm::Initialize()), it will invalidate // usable (changing params used in Ecm::Initialize()), it will invalidate
// (destroy) it. // (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 // Check if all required parameters have been set. If so, initialize |ecm_| by
// fetching entitlement keys. // 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| // Gather all information needed to build a TS packet |ecm_datagram|
// conatianing an ECM. // conatianing an ECM.
Status BuildEcmDatagram(const EcmgParameters& params, Status BuildEcmDatagram(const EcmgParameters& params,
absl::Span<uint8_t> ecm_datagram, absl::Span<uint8_t> 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 // Generates a random wrapped key iv string. Returns true on success, false
// otherwise. The main purpose for this function is easier testing. // otherwise. The main purpose for this function is easier testing.
@@ -163,17 +189,21 @@ class EcmgClientHandler {
} }
EcmgConfig* ecmg_config_; 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". // Per spec, "There is always one (and only one) channel per TCP connection".
bool channel_id_set_ = false; bool channel_id_set_ ABSL_GUARDED_BY(channel_mutex_) = false;
uint16_t channel_id_; uint16_t channel_id_ ABSL_GUARDED_BY(channel_mutex_);
uint16_t cas_id_; uint16_t cas_id_ ABSL_GUARDED_BY(channel_mutex_);
// Channel specific information. // Channel specific information.
uint8_t age_restriction_ = 0; uint8_t age_restriction_ ABSL_GUARDED_BY(channel_mutex_) = 0;
std::vector<std::string> content_ivs_; std::vector<std::string> content_ivs_ ABSL_GUARDED_BY(channel_mutex_);
// Map from ECM_stream_id to EcmgStreamInfo. // Map from ECM_stream_id to EcmgStreamInfo.
absl::flat_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_; absl::flat_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_
ABSL_GUARDED_BY(stream_mutex_);
// Used to fetch entitlement keys if none are received from SCS. // Used to fetch entitlement keys if none are received from SCS.
EntitlementKeyFetcherFunc custom_key_fetcher_; EntitlementKeyFetcherFunc custom_key_fetcher_;
// Used to process custom aceess criteria received in CwProvision message. // Used to process custom aceess criteria received in CwProvision message.

View File

@@ -33,18 +33,15 @@ namespace widevine {
namespace cas { namespace cas {
namespace { 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::_;
using ::testing::ByMove; using ::testing::ByMove;
using ::testing::DoAll;
using ::testing::ElementsAre; using ::testing::ElementsAre;
using ::testing::ElementsAreArray; using ::testing::ElementsAreArray;
using ::testing::IsEmpty; using ::testing::IsEmpty;
using ::testing::NotNull; using ::testing::NotNull;
using ::testing::Return; using ::testing::Return;
using ::testing::SaveArg;
constexpr size_t kBufferSize = 1024; constexpr size_t kBufferSize = 1024;
constexpr uint16_t kWidevineSystemId = 0x4AD4; constexpr uint16_t kWidevineSystemId = 0x4AD4;
@@ -122,6 +119,7 @@ class EcmgClientHandlerTest : public ::testing::Test {
config_.max_comp_time = 100; config_.max_comp_time = 100;
config_.access_criteria_transfer_mode = 1; config_.access_criteria_transfer_mode = 1;
config_.number_of_content_keys = 2; config_.number_of_content_keys = 2;
config_.crypto_mode = CryptoMode::kAesScte;
handler_ = absl::make_unique<MockEcmgClientHandler>(&config_); handler_ = absl::make_unique<MockEcmgClientHandler>(&config_);
EXPECT_CALL(*handler_.get(), CreateEcmInstance).WillRepeatedly([] { EXPECT_CALL(*handler_.get(), CreateEcmInstance).WillRepeatedly([] {
return absl::make_unique<Ecm>(); return absl::make_unique<Ecm>();
@@ -172,25 +170,23 @@ class EcmgClientHandlerTest : public ::testing::Test {
void BuildChannelSetupRequest(uint16_t channel_id, uint32_t super_cas_id, void BuildChannelSetupRequest(uint16_t channel_id, uint32_t super_cas_id,
uint8_t age_restriction, uint8_t age_restriction,
const std::string& crypto_mode, char* message, const std::string& crypto_mode,
const absl::Span<char> message,
size_t* message_length) { size_t* message_length) {
EXPECT_TRUE(message != nullptr);
EXPECT_TRUE(message_length != nullptr); EXPECT_TRUE(message_length != nullptr);
BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_SETUP, message, SimulcryptMessage builder(message, message_length);
message_length); builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_SETUP);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length); builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
AddUint32Param(SUPER_CAS_ID, super_cas_id, message, message_length); builder.AddUint32Param(SUPER_CAS_ID, super_cas_id);
if (age_restriction != 0) { if (age_restriction != 0) {
AddUint8Param(AGE_RESTRICTION, age_restriction, message, message_length); builder.AddUint8Param(AGE_RESTRICTION, age_restriction);
} }
if (!crypto_mode.empty()) { if (!crypto_mode.empty()) {
AddParam(CRYPTO_MODE, builder.AddParam(CRYPTO_MODE,
reinterpret_cast<const uint8_t*>(crypto_mode.c_str()), reinterpret_cast<const uint8_t*>(crypto_mode.c_str()),
crypto_mode.size(), message, message_length); crypto_mode.size());
} }
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
} }
void BuildStreamSetupRequest( void BuildStreamSetupRequest(
@@ -198,77 +194,66 @@ class EcmgClientHandlerTest : public ::testing::Test {
uint16_t nominal_CP_duration, uint16_t nominal_CP_duration,
const std::vector<std::string>& entitlements, const std::vector<std::string>& entitlements,
const std::vector<std::string>& group_entitlements, const std::vector<std::string>& group_entitlements,
const std::vector<std::string>& content_ivs, char* message, const std::vector<std::string>& content_ivs,
size_t* message_length) { const absl::Span<char> message, size_t* message_length) {
EXPECT_TRUE(message != nullptr);
EXPECT_TRUE(message_length != nullptr); EXPECT_TRUE(message_length != nullptr);
BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_SETUP, message, SimulcryptMessage builder(message, message_length);
message_length); builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_SETUP);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length); builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length); builder.AddUint16Param(ECM_STREAM_ID, stream_id);
AddUint16Param(ECM_ID, ecm_id, message, message_length); builder.AddUint16Param(ECM_ID, ecm_id);
AddUint16Param(NOMINAL_CP_DURATION, nominal_CP_duration, message, builder.AddUint16Param(NOMINAL_CP_DURATION, nominal_CP_duration);
message_length);
if (!entitlements.empty()) { if (!entitlements.empty()) {
for (const auto& entitlement : entitlements) { for (const auto& entitlement : entitlements) {
AddParam(ENTITLEMENT_ID_KEY_COMBINATION, builder.AddParam(ENTITLEMENT_ID_KEY_COMBINATION,
reinterpret_cast<const uint8_t*>(entitlement.c_str()), reinterpret_cast<const uint8_t*>(entitlement.c_str()),
entitlement.size(), message, message_length); entitlement.size());
} }
} }
if (!group_entitlements.empty()) { if (!group_entitlements.empty()) {
for (const auto& entitlement : group_entitlements) { for (const auto& entitlement : group_entitlements) {
AddParam(ENTITLEMENT_ID_KEY_GROUP_COMBINATION, builder.AddParam(ENTITLEMENT_ID_KEY_GROUP_COMBINATION,
reinterpret_cast<const uint8_t*>(entitlement.c_str()), reinterpret_cast<const uint8_t*>(entitlement.c_str()),
entitlement.size(), message, message_length); entitlement.size());
} }
} }
if (!content_ivs.empty()) { if (!content_ivs.empty()) {
for (auto& content_iv : content_ivs) { for (auto& content_iv : content_ivs) {
AddParam(CONTENT_IV, builder.AddParam(CONTENT_IV,
reinterpret_cast<const uint8_t*>(content_iv.c_str()), reinterpret_cast<const uint8_t*>(content_iv.c_str()),
content_iv.size(), message, message_length); content_iv.size());
} }
} }
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
} }
void BuildCwProvisionRequest( void BuildCwProvisionRequest(
uint16_t channel_id, uint16_t stream_id, uint16_t cp_number, uint16_t channel_id, uint16_t stream_id, uint16_t cp_number,
const std::vector<EcmgCpCwCombination>& cp_cw_combination, const std::vector<EcmgCpCwCombination>& cp_cw_combination,
const std::string& fingerprinting, const std::string& fingerprinting,
const std::vector<std::string>& service_blockings, char* message, const std::vector<std::string>& service_blockings,
size_t* message_length) { const absl::Span<char> message, size_t* message_length) {
EXPECT_TRUE(message != nullptr);
EXPECT_TRUE(message_length != nullptr); EXPECT_TRUE(message_length != nullptr);
BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CW_PROVISION, message, SimulcryptMessage builder(message, message_length);
message_length); builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CW_PROVISION);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length); builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length); builder.AddUint16Param(ECM_STREAM_ID, stream_id);
AddUint16Param(CP_NUMBER, cp_number, message, message_length); builder.AddUint16Param(CP_NUMBER, cp_number);
for (auto& cp_cw : cp_cw_combination) { for (auto& cp_cw : cp_cw_combination) {
uint8_t combined[100]; uint8_t combined[100];
Host16ToBigEndian(combined, &cp_cw.cp); Host16ToBigEndian(combined, &cp_cw.cp);
memcpy(combined + 2, cp_cw.cw.c_str(), cp_cw.cw.length()); memcpy(combined + 2, cp_cw.cw.c_str(), cp_cw.cw.length());
AddParam(CP_CW_COMBINATION, combined, 2 + cp_cw.cw.length(), message, builder.AddParam(CP_CW_COMBINATION, combined, 2 + cp_cw.cw.length());
message_length);
} }
if (!fingerprinting.empty()) { if (!fingerprinting.empty()) {
AddParam(FINGERPRINTING_CONTROL, builder.AddParam(FINGERPRINTING_CONTROL,
reinterpret_cast<const uint8_t*>(fingerprinting.c_str()), reinterpret_cast<const uint8_t*>(fingerprinting.c_str()),
fingerprinting.size(), message, message_length); fingerprinting.size());
} }
for (auto const& sb : service_blockings) { for (auto const& sb : service_blockings) {
AddParam(SERVICE_BLOCKING_GROUP, builder.AddParam(SERVICE_BLOCKING_GROUP,
reinterpret_cast<const uint8_t*>(sb.c_str()), sb.size(), message, reinterpret_cast<const uint8_t*>(sb.c_str()), sb.size());
message_length);
} }
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
} }
void CheckChannelError(uint16_t expected_error_code, void CheckChannelError(uint16_t expected_error_code,
@@ -838,12 +823,15 @@ class MockEcm : public Ecm {
(override)); (override));
MOCK_METHOD(void, SetServiceBlocking, (const EcmServiceBlockingParams*), MOCK_METHOD(void, SetServiceBlocking, (const EcmServiceBlockingParams*),
(override)); (override));
MOCK_METHOD(Status, SetEntitlementRotationWindowLeft, (uint32_t), (override));
MOCK_METHOD(uint32_t, GetEntitlementRotationWindowLeft, (),
(const, override));
MOCK_METHOD(Status, GenerateEcm, MOCK_METHOD(Status, GenerateEcm,
(EntitledKeyInfo * even_key, EntitledKeyInfo* odd_key, (EntitledKeyInfo * even_key, EntitledKeyInfo* odd_key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids, const std::vector<std::string>& group_ids,
std::string* serialized_ecm), std::string* serialized_ecm),
(const, override)); (override));
}; };
EcmFingerprintingParams FakeFingerprintingSettingFunc(uint16_t channel_id, EcmFingerprintingParams FakeFingerprintingSettingFunc(uint16_t channel_id,
@@ -1072,6 +1060,161 @@ TEST_F(EcmgClientHandlerTest, CustomAccessCriteriaProcessorReturnsError) {
CheckStreamError(INVALID_MESSAGE, response_, response_len_); 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<WvEcmgStreamStatus> 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>();
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<MockEcm>();
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>();
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
} // namespace cas } // namespace cas
} // namespace widevine } // namespace widevine

View File

@@ -8,7 +8,6 @@
#include "media_cas_packager_sdk/internal/emm.h" #include "media_cas_packager_sdk/internal/emm.h"
#include <bitset>
#include <cstdint> #include <cstdint>
#include "glog/logging.h" #include "glog/logging.h"
@@ -96,35 +95,33 @@ Status Emm::SetServiceBlocking(
return status; return status;
} }
Status Emm::GenerateEmm(std::string* serialized_emm) const { Status Emm::GenerateEmm(std::string* serialized_emm) {
if (serialized_emm == nullptr) { if (serialized_emm == nullptr) {
return {error::INVALID_ARGUMENT, "No return emm std::string pointer."}; return {error::INVALID_ARGUMENT, "No return emm std::string pointer."};
} }
EmmSerializingParameters serializing_params; emm_payload_.set_timestamp_secs(GenerateTimestampEpochSeconds());
serializing_params.payload = emm_payload_.SerializeAsString();
serializing_params.timestamp = GenerateTimestampEpochSeconds();
// Generate serialized emm (without signature yet). SignedEmmPayload signed_emm;
Status status = if (!emm_payload_.SerializeToString(
GenerateSerializedEmmNoSignature(serializing_params, serialized_emm); 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()) { if (!status.ok()) {
return status; return status;
} }
// Calculate and append signature. if (!signed_emm.SerializeToString(serialized_emm)) {
std::string signature; return {error::INTERNAL, "Failed to serialize signed EMM."};
status = GenerateSignature(*serialized_emm, &signature);
if (!status.ok()) {
return status;
} }
absl::StrAppend(serialized_emm, signature);
return OkStatus(); return OkStatus();
} }
Status Emm::GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter, Status Emm::GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter,
const absl::Span<uint8_t> packet, const absl::Span<uint8_t> packet,
ssize_t* bytes_modified) const { ssize_t* bytes_modified) {
if (continuity_counter == nullptr || bytes_modified == nullptr) { if (continuity_counter == nullptr || bytes_modified == nullptr) {
return {error::INVALID_ARGUMENT, return {error::INVALID_ARGUMENT,
"continuity_counter and bytes_modified must not be null"}; "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(); 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<kNumBitsVersionField> version(kEmmVersion);
std::bitset<kNumBitsHeaderLengthField> header_length(
sizeof(params.timestamp));
std::bitset<kNumBitsTimestampLengthField> timestamp(params.timestamp);
std::bitset<kNumBitsPayloadLengthField> 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 { int64_t Emm::GenerateTimestampEpochSeconds() const {
return absl::ToUnixSeconds(absl::Now()); return absl::ToUnixSeconds(absl::Now());
} }

View File

@@ -59,7 +59,7 @@ class Emm {
const std::vector<ServiceBlockingInitParameters>& service_blockings); const std::vector<ServiceBlockingInitParameters>& service_blockings);
// Generates serialized EMM to |serialized_emm|. // 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. // Generates serialized EMM and wraps it in TS (transport stream) packets.
// Args (all pointer parameters must be not nullptr): // Args (all pointer parameters must be not nullptr):
@@ -73,17 +73,9 @@ class Emm {
// of ECM into |buffer| is successful. // of ECM into |buffer| is successful.
virtual Status GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter, virtual Status GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter,
const absl::Span<uint8_t> packet, const absl::Span<uint8_t> packet,
ssize_t* bytes_modified) const; ssize_t* bytes_modified);
private: 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 int64_t GenerateTimestampEpochSeconds() const;
virtual Status GenerateSignature(const std::string& message, virtual Status GenerateSignature(const std::string& message,
std::string* signature) const; std::string* signature) const;

View File

@@ -46,57 +46,9 @@ constexpr uint16_t kTestPid = 1;
constexpr int64_t kTestTimestamp = 1597882875; constexpr int64_t kTestTimestamp = 1597882875;
// Hex std::string of kTestTimestamp. // Hex std::string of kTestTimestamp.
constexpr char kTestTimestampHexString[] = "000000005f3dc1fb"; 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; constexpr int kSignatureLength = 71;
// Length in bytes when there is no payload.
constexpr int kExpectedNoPayloadLengthBytes =
kPayloadStartIndex + kSignatureLength;
constexpr char kFakeSignatureFiller = 'x'; // 0x78 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[] = { constexpr char kExpectedMockEmmPacket[] = {
// TS header. // TS header.
'\x47', '\x40', '\x01', '\x10', '\x47', '\x40', '\x01', '\x10',
@@ -173,15 +125,15 @@ TEST(GenerateTimestampEpochSecondsTest, TimestampGenerated) {
const absl::Time test_start_time = absl::Now(); const absl::Time test_start_time = absl::Now();
FakeEmmWithFakeSignature emm_gen; FakeEmmWithFakeSignature emm_gen;
std::string serialized_emm; std::string serialized_emm;
EXPECT_OK(emm_gen.GenerateEmm(&serialized_emm)); EXPECT_OK(emm_gen.GenerateEmm(&serialized_emm));
uint32_t timestamp = 0;
// Extract timestamp value from serialized EMM. SignedEmmPayload signed_emm;
for (const auto& timestamp_byte : ASSERT_TRUE(signed_emm.ParseFromString(serialized_emm));
serialized_emm.substr(kTimestampStartIndex, kTimestampLengthBytes)) { EmmPayload emm_payload;
timestamp = (timestamp << 8) | static_cast<uint8_t>(timestamp_byte); ASSERT_TRUE(emm_payload.ParseFromString(signed_emm.serialized_payload()));
} EXPECT_NE(emm_payload.timestamp_secs(), 0);
EXPECT_NE(timestamp, 0); EXPECT_GE(emm_payload.timestamp_secs(), absl::ToUnixSeconds(test_start_time));
EXPECT_GE(timestamp, absl::ToUnixSeconds(test_start_time));
} }
class FakeEmm : public Emm { class FakeEmm : public Emm {
@@ -202,25 +154,10 @@ class EmmTest : public ::testing::Test {
protected: protected:
EmmTest() : emm_(absl::make_unique<FakeEmm>()) {} EmmTest() : emm_(absl::make_unique<FakeEmm>()) {}
void VerifiyEmmHeader(const std::string& serialized_emm) {
EXPECT_EQ(static_cast<uint8_t>(serialized_emm[kVersionStartIndex]),
kExpectedEmmVersion);
EXPECT_EQ(static_cast<uint8_t>(serialized_emm[kHeaderLengthStartIndex]),
kExpectedHeaderLength);
EXPECT_EQ(absl::BytesToHexString(serialized_emm.substr(
kTimestampStartIndex, kTimestampLengthBytes)),
kTestTimestampHexString);
}
void VerifiySignature(const std::string& signature) { void VerifiySignature(const std::string& signature) {
std::string expected_signature(kSignatureLength, kFakeSignatureFiller); std::string expected_signature(kSignatureLength, kFakeSignatureFiller);
EXPECT_EQ(signature, expected_signature); EXPECT_EQ(signature, expected_signature);
} }
int GetPayloadLength(const std::string& serialized_emm) {
return static_cast<uint16_t>(serialized_emm[kPayloadLengthStartIndex])
<< 8 |
static_cast<uint8_t>(serialized_emm[kPayloadLengthStartIndex + 1]);
}
std::unique_ptr<Emm> emm_; std::unique_ptr<Emm> emm_;
}; };
@@ -232,31 +169,22 @@ TEST_F(EmmTest, GenerateEmmSinglePayloadSuccess) {
GetValidServiceBlockingParams(); GetValidServiceBlockingParams();
EXPECT_OK(emm_->SetFingerprinting({fingerprinting})); EXPECT_OK(emm_->SetFingerprinting({fingerprinting}));
EXPECT_OK(emm_->SetServiceBlocking({service_blocking})); EXPECT_OK(emm_->SetServiceBlocking({service_blocking}));
std::string serialized_emm;
std::string result; EXPECT_OK(emm_->GenerateEmm(&serialized_emm));
EXPECT_OK(emm_->GenerateEmm(&result));
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
VerifiyEmmHeader(result);
int payload_lengh = GetPayloadLength(result); SignedEmmPayload signed_emm;
ASSERT_GT(payload_lengh, 0); ASSERT_TRUE(signed_emm.ParseFromString(serialized_emm));
ASSERT_EQ(result.length(), VerifiySignature(signed_emm.signature());
kPayloadStartIndex + payload_lengh + kSignatureLength);
std::string payload_section =
result.substr(kPayloadStartIndex, payload_lengh);
// Parse the payload and validate fields.
EmmPayload emm_payload; EmmPayload emm_payload;
ASSERT_TRUE(emm_payload.ParseFromString(payload_section)); ASSERT_TRUE(emm_payload.ParseFromString(signed_emm.serialized_payload()));
EmmPayload expected_payload; EmmPayload expected_payload;
LoadExpectedFingerprintingProto(expected_payload.add_fingerprinting()); LoadExpectedFingerprintingProto(expected_payload.add_fingerprinting());
LoadExpectedServiceBlockingProto(expected_payload.add_service_blocking()); LoadExpectedServiceBlockingProto(expected_payload.add_service_blocking());
expected_payload.set_timestamp_secs(kTestTimestamp);
std::string serialized_expected_payload; std::string serialized_expected_payload;
expected_payload.SerializeToString(&serialized_expected_payload); expected_payload.SerializeToString(&serialized_expected_payload);
EXPECT_EQ(payload_section, serialized_expected_payload); EXPECT_EQ(signed_emm.serialized_payload(), serialized_expected_payload);
VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh));
} }
// Verifies GenerateEmm is successful with multiple fingerprinting and // Verifies GenerateEmm is successful with multiple fingerprinting and
@@ -267,7 +195,6 @@ TEST_F(EmmTest, GenerateEmmMultiplePayloadSuccess) {
fingerprinting_params.control = kFingerprintingControl; fingerprinting_params.control = kFingerprintingControl;
EXPECT_OK(emm_->SetFingerprinting( EXPECT_OK(emm_->SetFingerprinting(
{GetValidFingerprintingParams(), fingerprinting_params})); {GetValidFingerprintingParams(), fingerprinting_params}));
ServiceBlockingInitParameters service_blocking_params; ServiceBlockingInitParameters service_blocking_params;
service_blocking_params.channels = {kChannelThree}; service_blocking_params.channels = {kChannelThree};
service_blocking_params.device_groups = {kDeviceGroupOne, kDeviceGroupTwo}; service_blocking_params.device_groups = {kDeviceGroupOne, kDeviceGroupTwo};
@@ -275,26 +202,17 @@ TEST_F(EmmTest, GenerateEmmMultiplePayloadSuccess) {
service_blocking_params.end_time = kServiceBockingEndTime; service_blocking_params.end_time = kServiceBockingEndTime;
EXPECT_OK(emm_->SetServiceBlocking( EXPECT_OK(emm_->SetServiceBlocking(
{GetValidServiceBlockingParams(), service_blocking_params})); {GetValidServiceBlockingParams(), service_blocking_params}));
std::string serialized_emm;
std::string result; EXPECT_OK(emm_->GenerateEmm(&serialized_emm));
EXPECT_OK(emm_->GenerateEmm(&result));
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
VerifiyEmmHeader(result);
int payload_lengh = GetPayloadLength(result); SignedEmmPayload signed_emm;
ASSERT_GT(payload_lengh, 0); ASSERT_TRUE(signed_emm.ParseFromString(serialized_emm));
ASSERT_EQ(result.length(), VerifiySignature(signed_emm.signature());
kPayloadStartIndex + payload_lengh + kSignatureLength);
std::string payload_section =
result.substr(kPayloadStartIndex, payload_lengh);
// Parse the payload and validate fields.
EmmPayload emm_payload; 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.fingerprinting_size(), 2);
EXPECT_EQ(emm_payload.service_blocking_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 // Verifies GenerateEmm is successful with only fingerprinting information in
@@ -304,29 +222,21 @@ TEST_F(EmmTest, GenerateEmmFingerprintingOnlySuccess) {
EXPECT_OK(emm_->SetFingerprinting({fingerprinting})); EXPECT_OK(emm_->SetFingerprinting({fingerprinting}));
// OK to be called again. // OK to be called again.
EXPECT_OK(emm_->SetFingerprinting({fingerprinting})); EXPECT_OK(emm_->SetFingerprinting({fingerprinting}));
std::string serialized_emm;
std::string result; EXPECT_OK(emm_->GenerateEmm(&serialized_emm));
EXPECT_OK(emm_->GenerateEmm(&result));
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
VerifiyEmmHeader(result);
int payload_lengh = GetPayloadLength(result); SignedEmmPayload signed_emm;
ASSERT_GT(payload_lengh, 0); ASSERT_TRUE(signed_emm.ParseFromString(serialized_emm));
ASSERT_EQ(result.length(), VerifiySignature(signed_emm.signature());
kPayloadStartIndex + payload_lengh + kSignatureLength);
std::string payload_section =
result.substr(kPayloadStartIndex, payload_lengh);
// Parse the payload and validate fields.
EmmPayload emm_payload; EmmPayload emm_payload;
ASSERT_TRUE(emm_payload.ParseFromString(payload_section)); ASSERT_TRUE(emm_payload.ParseFromString(signed_emm.serialized_payload()));
EmmPayload expected_payload; EmmPayload expected_payload;
expected_payload.set_timestamp_secs(kTestTimestamp);
LoadExpectedFingerprintingProto(expected_payload.add_fingerprinting()); LoadExpectedFingerprintingProto(expected_payload.add_fingerprinting());
std::string serialized_expected_payload; std::string serialized_expected_payload;
expected_payload.SerializeToString(&serialized_expected_payload); expected_payload.SerializeToString(&serialized_expected_payload);
EXPECT_EQ(payload_section, serialized_expected_payload); EXPECT_EQ(signed_emm.serialized_payload(), serialized_expected_payload);
VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh));
} }
// Verifies GenerateEmm is successful with only service_blocking information in // Verifies GenerateEmm is successful with only service_blocking information in
@@ -335,54 +245,34 @@ TEST_F(EmmTest, GenerateEmmServiceBlockingOnlySuccess) {
ServiceBlockingInitParameters service_blocking = ServiceBlockingInitParameters service_blocking =
GetValidServiceBlockingParams(); GetValidServiceBlockingParams();
EXPECT_OK(emm_->SetServiceBlocking({service_blocking})); EXPECT_OK(emm_->SetServiceBlocking({service_blocking}));
std::string serialized_emm;
std::string result; EXPECT_OK(emm_->GenerateEmm(&serialized_emm));
EXPECT_OK(emm_->GenerateEmm(&result));
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
VerifiyEmmHeader(result);
int payload_lengh = GetPayloadLength(result); SignedEmmPayload signed_emm;
ASSERT_GT(payload_lengh, 0); ASSERT_TRUE(signed_emm.ParseFromString(serialized_emm));
ASSERT_EQ(result.length(), VerifiySignature(signed_emm.signature());
kPayloadStartIndex + payload_lengh + kSignatureLength);
std::string payload_section =
result.substr(kPayloadStartIndex, payload_lengh);
// Parse the payload and validate fields.
EmmPayload emm_payload; EmmPayload emm_payload;
ASSERT_TRUE(emm_payload.ParseFromString(payload_section)); ASSERT_TRUE(emm_payload.ParseFromString(signed_emm.serialized_payload()));
EmmPayload expected_payload; EmmPayload expected_payload;
expected_payload.set_timestamp_secs(kTestTimestamp);
LoadExpectedServiceBlockingProto(expected_payload.add_service_blocking()); LoadExpectedServiceBlockingProto(expected_payload.add_service_blocking());
std::string serialized_expected_payload; std::string serialized_expected_payload;
expected_payload.SerializeToString(&serialized_expected_payload); expected_payload.SerializeToString(&serialized_expected_payload);
EXPECT_EQ(payload_section, serialized_expected_payload); EXPECT_EQ(signed_emm.serialized_payload(), serialized_expected_payload);
VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh));
} }
// Verifies GenerateEmm is successful with empty payload. // Verifies GenerateEmm is successful with empty payload.
TEST_F(EmmTest, GenerateEmmNoPayloadSuccess) { TEST_F(EmmTest, GenerateEmmNoPayloadSuccess) {
std::string result; std::string result;
EXPECT_OK(emm_->GenerateEmm(&result)); EXPECT_OK(emm_->GenerateEmm(&result));
EXPECT_EQ(result.length(), kExpectedNoPayloadLengthBytes);
VerifiyEmmHeader(result);
int payload_lengh = GetPayloadLength(result); SignedEmmPayload signed_emm;
EXPECT_EQ(payload_lengh, 0); ASSERT_TRUE(signed_emm.ParseFromString(result));
EXPECT_EQ(result.length(), kPayloadStartIndex + kSignatureLength); VerifiySignature(signed_emm.signature());
EmmPayload emm_payload;
VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh)); ASSERT_TRUE(emm_payload.ParseFromString(signed_emm.serialized_payload()));
} EXPECT_EQ(emm_payload.timestamp_secs(), kTestTimestamp);
// 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);
} }
// Verifies GenerateSignature is successful with empty payload. // 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.SetPrivateSigningKey(test_keys.private_test_key_1_secp256r1()));
EXPECT_OK(emm.GenerateEmm(&emm_generated)); EXPECT_OK(emm.GenerateEmm(&emm_generated));
// Empty payload, so payload start is signature start. SignedEmmPayload signed_emm;
std::string message = emm_generated.substr(0, kPayloadStartIndex); ASSERT_TRUE(signed_emm.ParseFromString(emm_generated));
std::string signature = emm_generated.substr(kPayloadStartIndex);
EXPECT_FALSE(signature.empty());
// Check the signaure can be verified. // Check the signaure can be verified.
std::unique_ptr<ECPublicKey> public_key( std::unique_ptr<ECPublicKey> public_key(
ECPublicKey::Create(test_keys.public_test_key_1_secp256r1())); ECPublicKey::Create(test_keys.public_test_key_1_secp256r1()));
ASSERT_TRUE(public_key != nullptr); ASSERT_TRUE(public_key != nullptr);
EXPECT_TRUE( EXPECT_TRUE(public_key->VerifySignature(signed_emm.serialized_payload(),
public_key->VerifySignature(message, HashAlgorithm::kSha256, signature)); HashAlgorithm::kSha256,
signed_emm.signature()));
} }
// Verifies GenerateEmm fails with no singing key set. // Verifies GenerateEmm fails with no singing key set.
@@ -439,8 +327,7 @@ TEST(GenerateSignatureTest, InvalidSigningKeyFail) {
// GenerateEmmTsPacketsTest. // GenerateEmmTsPacketsTest.
class MockEmmGenerate : public Emm { class MockEmmGenerate : public Emm {
public: public:
MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm), MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm), (override));
(const, override));
}; };
// Verifies GenerateEmmTsPackets is successful with mocked EMM. // Verifies GenerateEmmTsPackets is successful with mocked EMM.

View File

@@ -13,12 +13,14 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <string>
#include "glog/logging.h" #include "glog/logging.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "absl/strings/escaping.h" #include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h" #include "absl/time/clock.h"
#include "absl/time/time.h" #include "absl/time/time.h"
#include "media_cas_packager_sdk/internal/emmg_constants.h" #include "media_cas_packager_sdk/internal/emmg_constants.h"
@@ -169,73 +171,43 @@ void Emmg::Start() {
} }
void Emmg::BuildChannelSetup() { void Emmg::BuildChannelSetup() {
bzero(request_, BUFFER_SIZE); SimulcryptMessage message(request_, &request_length_);
request_length_ = 0; message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, EMMG_CHANNEL_SETUP);
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id);
EMMG_CHANNEL_SETUP, request_, message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id);
&request_length_); message.AddUint8Param(EMMG_SECTION_TSPKT_FLAG,
simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id, emmg_config_->section_tspkt_flag);
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);
} }
void Emmg::BuildStreamSetup() { void Emmg::BuildStreamSetup() {
bzero(request_, BUFFER_SIZE); SimulcryptMessage message(request_, &request_length_);
request_length_ = 0; message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, EMMG_STREAM_SETUP);
simulcrypt_util::BuildMessageHeader( message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id);
EMMG_MUX_PROTOCOL_VERSION, EMMG_STREAM_SETUP, request_, &request_length_); message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id);
simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id, message.AddUint16Param(EMMG_DATA_STREAM_ID, emmg_config_->data_stream_id);
request_, &request_length_); message.AddUint16Param(EMMG_DATA_ID, emmg_config_->data_id);
simulcrypt_util::AddUint16Param(EMMG_DATA_CHANNEL_ID, message.AddUint8Param(EMMG_DATA_TYPE, emmg_config_->data_type);
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);
} }
void Emmg::BuildStreamBwRequest() { void Emmg::BuildStreamBwRequest() {
bzero(request_, BUFFER_SIZE); SimulcryptMessage message(request_, &request_length_);
request_length_ = 0; message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, EMMG_STREAM_BW_REQUEST);
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id);
EMMG_STREAM_BW_REQUEST, request_, message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id);
&request_length_); message.AddUint16Param(EMMG_DATA_STREAM_ID, emmg_config_->data_stream_id);
simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id, message.AddUint16Param(EMMG_BANDWIDTH, emmg_config_->bandwidth);
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);
} }
Status Emmg::GeneratePrivateData( Status Emmg::GeneratePrivateData(
const std::string& content_provider, const std::string& content_id, absl::string_view content_provider, absl::string_view content_id,
const std::vector<std::string>& entitlement_key_ids) { const std::vector<absl::string_view>& group_ids,
SimulcryptMessage& message) {
// Generate payload. // Generate payload.
CaDescriptorPrivateData private_data; CaDescriptorPrivateData private_data;
private_data.set_provider(content_provider); private_data.set_provider(std::string(content_provider));
private_data.set_content_id(content_id); private_data.set_content_id(std::string(content_id));
for (const auto& entitlement_key_id : entitlement_key_ids) { for (const auto& group_id : group_ids) {
private_data.add_entitlement_key_ids(entitlement_key_id); private_data.add_group_ids(std::string(group_id));
} }
std::string private_data_str = private_data.SerializeAsString(); std::string private_data_str = private_data.SerializeAsString();
std::string payload_filler(kMaxTsPayloadSize - private_data_str.size(), 0); std::string payload_filler(kMaxTsPayloadSize - private_data_str.size(), 0);
@@ -257,14 +229,13 @@ Status Emmg::GeneratePrivateData(
return status; return status;
} }
uint8_t datagram[kTsPacketSize]; message.AddParam(EMMG_DATAGRAM,
memcpy(datagram, ecm_ts_packet.data(), ecm_ts_packet.size()); reinterpret_cast<const uint8_t*>(ecm_ts_packet.c_str()),
simulcrypt_util::AddParam(EMMG_DATAGRAM, datagram, kTsPacketSize, request_, ecm_ts_packet.size());
&request_length_);
return OkStatus(); return OkStatus();
} }
Status Emmg::GenerateEmmData() { Status Emmg::GenerateEmmData(SimulcryptMessage& message) {
Status status; Status status;
if (!has_configured_emm_impl_) { if (!has_configured_emm_impl_) {
status = emm_impl_->SetPrivateSigningKey( status = emm_impl_->SetPrivateSigningKey(
@@ -293,40 +264,33 @@ Status Emmg::GenerateEmmData() {
if (bytes_modified <= 0 || bytes_modified % kTsPacketSize != 0) { if (bytes_modified <= 0 || bytes_modified % kTsPacketSize != 0) {
return {error::INTERNAL, "Failed to generate EMM TS packet"}; return {error::INTERNAL, "Failed to generate EMM TS packet"};
} }
simulcrypt_util::AddParam(EMMG_DATAGRAM, datagram, bytes_modified, request_, message.AddParam(EMMG_DATAGRAM, datagram, bytes_modified);
&request_length_);
return OkStatus(); return OkStatus();
} }
void Emmg::BuildDataProvision() { void Emmg::BuildDataProvision() {
bzero(request_, BUFFER_SIZE); SimulcryptMessage message(request_, &request_length_);
request_length_ = 0; message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, EMMG_DATA_PROVISION);
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id);
EMMG_DATA_PROVISION, request_, message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id);
&request_length_); message.AddUint16Param(EMMG_DATA_STREAM_ID, emmg_config_->data_stream_id);
simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id, message.AddUint16Param(EMMG_DATA_ID, emmg_config_->data_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_);
Status status; Status status;
// Generate and load datagram to |request_| message based on specified // Generate and load datagram to |request_| message based on specified
// data_type. // data_type.
switch (emmg_config_->data_type) { switch (emmg_config_->data_type) {
case kEmmDataType: case kEmmDataType:
status = GenerateEmmData(); status = GenerateEmmData(message);
break; break;
case kPrivateDataDataType: case kPrivateDataDataType: {
status = GeneratePrivateData(emmg_config_->content_provider, const std::vector<absl::string_view> group_ids(
emmg_config_->content_id, emmg_config_->group_ids.begin(), emmg_config_->group_ids.end());
emmg_config_->entitlement_key_ids); status =
GeneratePrivateData(emmg_config_->content_provider,
emmg_config_->content_id, group_ids, message);
break; break;
}
default: default:
LOG(ERROR) << "Unexpected data type: " << emmg_config_->data_type; LOG(ERROR) << "Unexpected data type: " << emmg_config_->data_type;
return; return;
@@ -335,42 +299,22 @@ void Emmg::BuildDataProvision() {
LOG(ERROR) << "Fail to generate datagram. " << status.ToString(); LOG(ERROR) << "Fail to generate datagram. " << status.ToString();
return; return;
} }
uint16_t total_param_length = request_length_ - 5;
Host16ToBigEndian(request_ + 3, &total_param_length);
} }
void Emmg::BuildStreamCloseRequest() { void Emmg::BuildStreamCloseRequest() {
bzero(request_, BUFFER_SIZE); SimulcryptMessage message(request_, &request_length_);
request_length_ = 0; message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION,
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, EMMG_STREAM_CLOSE_REQUEST);
EMMG_STREAM_CLOSE_REQUEST, request_, message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id);
&request_length_); message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id);
simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id, message.AddUint16Param(EMMG_DATA_STREAM_ID, emmg_config_->data_stream_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);
} }
void Emmg::BuildChannelClose() { void Emmg::BuildChannelClose() {
bzero(request_, BUFFER_SIZE); SimulcryptMessage message(request_, &request_length_);
request_length_ = 0; message.BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, EMMG_CHANNEL_CLOSE);
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION, message.AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id);
EMMG_CHANNEL_CLOSE, request_, message.AddUint16Param(EMMG_DATA_CHANNEL_ID, emmg_config_->data_channel_id);
&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);
} }
void Emmg::SendChannelSetup() { void Emmg::SendChannelSetup() {

View File

@@ -15,8 +15,10 @@
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>
#include "absl/strings/string_view.h"
#include "common/status.h" #include "common/status.h"
#include "media_cas_packager_sdk/internal/emm.h" #include "media_cas_packager_sdk/internal/emm.h"
#include "media_cas_packager_sdk/internal/simulcrypt_util.h"
#define BUFFER_SIZE (2048) #define BUFFER_SIZE (2048)
@@ -33,7 +35,7 @@ struct EmmgConfig {
uint8_t data_type; uint8_t data_type;
std::string content_provider; std::string content_provider;
std::string content_id; std::string content_id;
std::vector<std::string> entitlement_key_ids; std::vector<std::string> group_ids;
uint16_t bandwidth; uint16_t bandwidth;
uint32_t max_num_message; uint32_t max_num_message;
std::string ecc_signing_key; std::string ecc_signing_key;
@@ -90,10 +92,11 @@ class Emmg {
void UpdateSendInterval(uint16_t bandwidth_kbps); void UpdateSendInterval(uint16_t bandwidth_kbps);
Status GeneratePrivateData( Status GeneratePrivateData(absl::string_view content_provider,
const std::string& content_provider, const std::string& content_id, absl::string_view content_id,
const std::vector<std::string>& entitlement_key_ids); const std::vector<absl::string_view>& group_ids,
Status GenerateEmmData(); SimulcryptMessage& message);
Status GenerateEmmData(SimulcryptMessage& message);
void ReceiveResponseAndVerify(uint16_t expected_type); void ReceiveResponseAndVerify(uint16_t expected_type);
void Send(uint16_t message_type); void Send(uint16_t message_type);

View File

@@ -14,6 +14,8 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <string>
#include <utility>
#include "testing/gunit.h" #include "testing/gunit.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
@@ -80,7 +82,7 @@ class EmmgTest : public ::testing::Test {
config_.data_type = 0x01; config_.data_type = 0x01;
config_.content_provider = "widevine_test"; config_.content_provider = "widevine_test";
config_.content_id = "CasTsFake"; config_.content_id = "CasTsFake";
config_.entitlement_key_ids = {"fakeKeyId1KeyId1", "fakeKeyId2KeyId2"}; config_.group_ids = {"fakeGroup1", "fakeGroupId2"};
} }
void LoadEmmDataConfigs() { void LoadEmmDataConfigs() {

View File

@@ -17,77 +17,107 @@
namespace widevine { namespace widevine {
namespace cas { namespace cas {
namespace simulcrypt_util {
void BuildMessageHeader(uint8_t protocol_version, uint16_t message_type, // Total length of message header.
char* message, size_t* message_length) { static constexpr int kMessageHeaderSize = 5;
DCHECK(message); // The index location of the message_length field.
DCHECK(message_length); static constexpr int kMessageProtocolIndex = 0;
*message_length = 0; static constexpr int kMessageTypeIndex = 1;
message[*message_length] = protocol_version; static constexpr int kMessageLengthIndex = 3;
*message_length += PROTOCOL_VERSION_SIZE; static constexpr int kParameterTypeLengthFieldsSize = 4;
Host16ToBigEndian(message + *message_length, &message_type);
*message_length += MESSAGE_TYPE_SIZE; SimulcryptMessage::SimulcryptMessage(const absl::Span<char> 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 // 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. // 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. // Use 0 for 'total_param_length' until we know the length of the message.
uint16_t total_param_length = 0; uint16_t total_param_length = 0;
Host16ToBigEndian(message + *message_length, &total_param_length); Host16ToBigEndian(message_.data() + kMessageLengthIndex, &total_param_length);
*message_length += MESSAGE_LENGTH_SIZE; *message_length_ += MESSAGE_LENGTH_SIZE;
is_header_set_ = true;
} }
void AddUint32Param(uint16_t param_type, uint32_t param_value, char* message, void SimulcryptMessage::AddUint32Param(uint16_t param_type,
size_t* message_length) { uint32_t param_value) {
DCHECK(message); DCHECK(is_header_set_);
DCHECK(message_length); const uint16_t param_length = 4;
Host16ToBigEndian(message + *message_length, &param_type); DCHECK_GE(message_.size(),
*message_length += 2; *message_length_ + kParameterTypeLengthFieldsSize + param_length);
uint16_t param_length = 4; AddParamTypeLength(param_type, param_length);
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2; Host32ToBigEndian(message_.data() + *message_length_, &param_value);
Host32ToBigEndian(message + *message_length, &param_value); *message_length_ += param_length;
*message_length += param_length; UpdateMessageLength();
} }
void AddUint16Param(uint16_t param_type, uint16_t param_value, char* message, void SimulcryptMessage::AddUint16Param(uint16_t param_type,
size_t* message_length) { uint16_t param_value) {
DCHECK(message); DCHECK(is_header_set_);
DCHECK(message_length); const uint16_t param_length = 2;
Host16ToBigEndian(message + *message_length, &param_type); DCHECK_GE(message_.size(),
*message_length += 2; *message_length_ + kParameterTypeLengthFieldsSize + param_length);
uint16_t param_length = 2; AddParamTypeLength(param_type, param_length);
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2; Host16ToBigEndian(message_.data() + *message_length_, &param_value);
Host16ToBigEndian(message + *message_length, &param_value); *message_length_ += param_length;
*message_length += param_length; UpdateMessageLength();
} }
void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message, void SimulcryptMessage::AddUint8Param(uint16_t param_type,
size_t* message_length) { uint8_t param_value) {
DCHECK(message); DCHECK(is_header_set_);
DCHECK(message_length); const uint16_t param_length = 1;
Host16ToBigEndian(message + *message_length, &param_type); DCHECK_GE(message_.size(),
*message_length += 2; *message_length_ + kParameterTypeLengthFieldsSize + param_length);
uint16_t param_length = 1; return AddParam(param_type, &param_value, param_length);
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2;
memcpy(message + *message_length, &param_value, param_length);
*message_length += param_length;
} }
void AddParam(uint16_t param_type, const uint8_t* param_value, void SimulcryptMessage::AddParam(uint16_t param_type,
uint16_t param_length, char* message, size_t* message_length) { const uint8_t* param_value,
uint16_t param_length) {
DCHECK(param_value); DCHECK(param_value);
DCHECK(message); DCHECK(is_header_set_);
DCHECK(message_length); DCHECK_GE(message_.size(),
Host16ToBigEndian(message + *message_length, &param_type); *message_length_ + kParameterTypeLengthFieldsSize + param_length);
*message_length += 2; AddParamTypeLength(param_type, param_length);
Host16ToBigEndian(message + *message_length, &param_length); memcpy(message_.data() + *message_length_, param_value, param_length);
*message_length += 2; *message_length_ += param_length;
memcpy(message + *message_length, param_value, param_length); UpdateMessageLength();
*message_length += param_length; }
void SimulcryptMessage::AddParamTypeLength(uint16_t param_type,
uint16_t param_length) {
// Add parameter type.
Host16ToBigEndian(message_.data() + *message_length_, &param_type);
*message_length_ += PARAMETER_TYPE_SIZE;
// Add parameter length.
Host16ToBigEndian(message_.data() + *message_length_, &param_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 cas
} // namespace widevine } // namespace widevine

View File

@@ -15,40 +15,49 @@
#include <cstdint> #include <cstdint>
#include <cstdint> #include <cstdint>
#include "absl/types/span.h"
namespace widevine { namespace widevine {
namespace cas { namespace cas {
namespace simulcrypt_util {
// Add 'protocol_version', 'message_type', 'message_length' to the message. class SimulcryptMessage {
// TODO(user): Per jfore@, consider pass in a pointer to a structure public:
// #pragma pack(push, 1) // exact fit - no padding // Simulcrypt message builder.
// struct MessageHeader{ // Args:
// uint8_t protocol_version; // - |message| is the buffer to put the generated message. The buffer must be
// uint16_t message_type; // large enough to hold the generated message.
// uint16_t message_length; // - |message_length| will be updated as the length of the actual message in
// }; // the |message| buffer.
// #pragma pack(pop) // restore previous pack SimulcryptMessage(const absl::Span<char> message, size_t* message_length);
void BuildMessageHeader(uint8_t protocol_version, uint16_t message_type, SimulcryptMessage(const SimulcryptMessage&) = delete;
char* message, size_t* message_length); SimulcryptMessage& operator=(const SimulcryptMessage&) = delete;
virtual ~SimulcryptMessage() = default;
// Add a uint32_t parameter to the message. // Sets up the mesaage header field. Must be called before any add operation.
void AddUint32Param(uint16_t param_type, uint32_t param_value, char* message, void BuildMessageHeader(uint8_t protocol_version, uint16_t message_type);
size_t* message_length);
// Add a uint16_t parameter to the message. // Adds a uint32_t parameter to the message.
void AddUint16Param(uint16_t param_type, uint16_t param_value, char* message, void AddUint32Param(uint16_t param_type, uint32_t param_value);
size_t* message_length);
// Add a uint8_t parameter to the message. // Adds a uint16_t parameter to the message.
void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message, void AddUint16Param(uint16_t param_type, uint16_t param_value);
size_t* message_length);
// Add a param that is |param_length| bytes long. // Adds a uint8_t parameter to the message.
void AddParam(uint16_t param_type, const uint8_t* param_value, void AddUint8Param(uint16_t param_type, uint8_t param_value);
uint16_t param_length, char* message, size_t* message_length);
// 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<char> message_;
size_t* message_length_;
bool is_header_set_;
};
} // namespace simulcrypt_util
} // namespace cas } // namespace cas
} // namespace widevine } // namespace widevine

View File

@@ -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 <cstdint>
#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

View File

@@ -206,6 +206,7 @@ cc_library(
"//base", "//base",
"@abseil_repo//absl/memory", "@abseil_repo//absl/memory",
"@abseil_repo//absl/strings", "@abseil_repo//absl/strings",
"@abseil_repo//absl/types:span",
"//common:status", "//common:status",
"//media_cas_packager_sdk/internal:ecmg_client_handler", "//media_cas_packager_sdk/internal:ecmg_client_handler",
], ],
@@ -242,6 +243,7 @@ cc_test(
":wv_cas_types", ":wv_cas_types",
"//testing:gunit_main", "//testing:gunit_main",
"@abseil_repo//absl/memory", "@abseil_repo//absl/memory",
"@abseil_repo//absl/types:span",
"//common:status", "//common:status",
"//media_cas_packager_sdk/internal:ecmg_client_handler", "//media_cas_packager_sdk/internal:ecmg_client_handler",
], ],

View File

@@ -10,6 +10,7 @@
#include <bitset> #include <bitset>
#include <cstdint> #include <cstdint>
#include <string>
#include <vector> #include <vector>
#include "glog/logging.h" #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. // The range of valid PIDs, from section 2.4.3.3, and table 2-3.
constexpr uint32_t kMinValidPID = 0x0010; constexpr uint32_t kMinValidPID = 0x0010;
constexpr uint32_t kMaxValidPID = 0x1FFE; 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 } // namespace
Status WvCasCaDescriptor::GenerateCaDescriptor( Status WvCasCaDescriptor::GenerateCaDescriptor(
uint16_t ca_pid, const std::string& provider, const std::string& content_id, uint16_t ca_pid, const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids, const std::vector<std::string>& group_ids,
std::string* serialized_ca_desc) const { std::string* serialized_ca_desc) const {
if (serialized_ca_desc == nullptr) { if (serialized_ca_desc == nullptr) {
return {error::INVALID_ARGUMENT, return {error::INVALID_ARGUMENT,
@@ -69,25 +65,11 @@ Status WvCasCaDescriptor::GenerateCaDescriptor(
if (ca_pid < kMinValidPID || ca_pid > kMaxValidPID) { if (ca_pid < kMinValidPID || ca_pid > kMaxValidPID) {
return {error::INVALID_ARGUMENT, "PID value is out of the valid range."}; 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. // Field of Entitlement_key_ids could be empty.
if (!provider.empty() && !content_id.empty()) { if (!provider.empty() && !content_id.empty()) {
private_data = private_data = GeneratePrivateData(provider, content_id, group_ids);
GeneratePrivateData(provider, content_id, entitlement_key_ids);
} }
const size_t descriptor_length = const size_t descriptor_length =
@@ -130,12 +112,12 @@ size_t WvCasCaDescriptor::CaDescriptorBaseSize() const {
std::string WvCasCaDescriptor::GeneratePrivateData( std::string WvCasCaDescriptor::GeneratePrivateData(
const std::string& provider, const std::string& content_id, const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids) const { const std::vector<std::string>& group_ids) const {
CaDescriptorPrivateData private_data; CaDescriptorPrivateData private_data;
private_data.set_provider(provider); private_data.set_provider(provider);
private_data.set_content_id(content_id); private_data.set_content_id(content_id);
for (const auto& entitlement_key_id : entitlement_key_ids) { for (const auto& group_id : group_ids) {
private_data.add_entitlement_key_ids(entitlement_key_id); private_data.add_group_ids(group_id);
} }
return private_data.SerializeAsString(); return private_data.SerializeAsString();
} }

View File

@@ -50,9 +50,7 @@ class WvCasCaDescriptor {
// |ca_pid| the 13-bit PID of the ECMs // |ca_pid| the 13-bit PID of the ECMs
// |provider| provider name, put in private data for client to construct pssh // |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 // |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 // |group_ids| the groups ids this channel belongs to. Optional.
// to select entitlement keys from single fat license. This field is only used
// when client uses single fat license.
// |serialized_ca_desc| a std::string object to receive the encoded descriptor. // |serialized_ca_desc| a std::string object to receive the encoded descriptor.
// //
// Notes: // Notes:
@@ -60,11 +58,11 @@ class WvCasCaDescriptor {
// section (for an EMM stream) or into a TS Program Map Table section (for an // 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 // ECM stream). The descriptor will be 6 bytes plus any bytes added as
// (user-defined) private data. // (user-defined) private data.
virtual Status GenerateCaDescriptor( virtual Status GenerateCaDescriptor(uint16_t ca_pid,
uint16_t ca_pid, const std::string& provider, const std::string& provider,
const std::string& content_id, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids, const std::vector<std::string>& group_ids,
std::string* serialized_ca_desc) const; std::string* serialized_ca_desc) const;
// Return the base size (before private data is added) of the CA // 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 // 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. // Return private data in the CA descriptor.
virtual std::string GeneratePrivateData( virtual std::string GeneratePrivateData(
const std::string& provider, const std::string& content_id, const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids) const; const std::vector<std::string>& group_ids) const;
}; };
} // namespace cas } // namespace cas

View File

@@ -9,6 +9,8 @@
#include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h" #include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h"
#include <cstdint> #include <cstdint>
#include <string>
#include <vector>
#include "testing/gmock.h" #include "testing/gmock.h"
#include "testing/gunit.h" #include "testing/gunit.h"
@@ -20,14 +22,10 @@ namespace widevine {
namespace cas { namespace cas {
namespace { namespace {
// Random value for PID // Random value for PID
constexpr int kTestPid = 50; constexpr int kTestPid = 50;
constexpr char kProvider[] = "widevine_test"; constexpr char kProvider[] = "widevine_test";
constexpr char kContentId[] = "1234"; constexpr char kContentId[] = "1234";
const std::vector<std::string>* const kEntitlementKeyIds =
new std::vector<std::string>({"fakekey1fakekey1", "fakekey2fakekey2"});
} // namespace } // namespace
class WvCasCaDescriptorTest : public Test { class WvCasCaDescriptorTest : public Test {
@@ -35,7 +33,6 @@ class WvCasCaDescriptorTest : public Test {
WvCasCaDescriptorTest() {} WvCasCaDescriptorTest() {}
WvCasCaDescriptor ca_descriptor_; WvCasCaDescriptor ca_descriptor_;
std::string actual_ca_descriptor_; std::string actual_ca_descriptor_;
std::vector<std::string> entitlement_key_ids_;
}; };
TEST_F(WvCasCaDescriptorTest, BaseSize) { TEST_F(WvCasCaDescriptorTest, BaseSize) {
@@ -44,7 +41,7 @@ TEST_F(WvCasCaDescriptorTest, BaseSize) {
TEST_F(WvCasCaDescriptorTest, BasicGoodGen) { TEST_F(WvCasCaDescriptorTest, BasicGoodGen) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
kTestPid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, kTestPid, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x32", 6); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x32", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -55,7 +52,7 @@ TEST_F(WvCasCaDescriptorTest, NoReturnStringFail) {
ca_descriptor_ ca_descriptor_
.GenerateCaDescriptor( .GenerateCaDescriptor(
kTestPid, /*provider=*/"", /*content_id=*/"", kTestPid, /*provider=*/"", /*content_id=*/"",
/*entitlement_key_ids=*/{}, /*serialized_ca_desc=*/nullptr) /*group_ids=*/{}, /*serialized_ca_desc=*/nullptr)
.error_code()); .error_code());
} }
@@ -65,14 +62,14 @@ TEST_F(WvCasCaDescriptorTest, PidTooLowFail) {
error::INVALID_ARGUMENT, error::INVALID_ARGUMENT,
ca_descriptor_ ca_descriptor_
.GenerateCaDescriptor(bad_pid, /*provider=*/"", /*content_id=*/"", .GenerateCaDescriptor(bad_pid, /*provider=*/"", /*content_id=*/"",
entitlement_key_ids_, &actual_ca_descriptor_) /*group_ids=*/{}, &actual_ca_descriptor_)
.error_code()); .error_code());
} }
TEST_F(WvCasCaDescriptorTest, PidMinOK) { TEST_F(WvCasCaDescriptorTest, PidMinOK) {
const uint32_t min_pid = 0x10; const uint32_t min_pid = 0x10;
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
min_pid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, min_pid, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x10", 6); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x10", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -81,7 +78,7 @@ TEST_F(WvCasCaDescriptorTest, PidMinOK) {
TEST_F(WvCasCaDescriptorTest, PidMaxOK) { TEST_F(WvCasCaDescriptorTest, PidMaxOK) {
const uint32_t max_pid = 0x1FFE; const uint32_t max_pid = 0x1FFE;
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
max_pid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, max_pid, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\xfe"); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\xfe");
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -89,17 +86,17 @@ TEST_F(WvCasCaDescriptorTest, PidMaxOK) {
TEST_F(WvCasCaDescriptorTest, PidTooHighFail) { TEST_F(WvCasCaDescriptorTest, PidTooHighFail) {
const uint32_t bad_pid = 0x1FFF; const uint32_t bad_pid = 0x1FFF;
EXPECT_EQ(error::INVALID_ARGUMENT, EXPECT_EQ(
ca_descriptor_ error::INVALID_ARGUMENT,
.GenerateCaDescriptor( ca_descriptor_
bad_pid, /*provider=*/"", /*content_id=*/"", .GenerateCaDescriptor(bad_pid, /*provider=*/"", /*content_id=*/"",
/*entitlement_key_ids=*/{}, &actual_ca_descriptor_) /*group_ids=*/{}, &actual_ca_descriptor_)
.error_code()); .error_code());
} }
TEST_F(WvCasCaDescriptorTest, PidOneByte) { TEST_F(WvCasCaDescriptorTest, PidOneByte) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
255, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, 255, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\xff", 6); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\xff", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -107,7 +104,7 @@ TEST_F(WvCasCaDescriptorTest, PidOneByte) {
TEST_F(WvCasCaDescriptorTest, PidSecondByte) { TEST_F(WvCasCaDescriptorTest, PidSecondByte) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x1F00, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, 0x1F00, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\x00", 6); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -115,7 +112,7 @@ TEST_F(WvCasCaDescriptorTest, PidSecondByte) {
TEST_F(WvCasCaDescriptorTest, PidTwelveBits) { TEST_F(WvCasCaDescriptorTest, PidTwelveBits) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0xFFF, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, 0xFFF, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xef\xff"); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xef\xff");
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -123,7 +120,7 @@ TEST_F(WvCasCaDescriptorTest, PidTwelveBits) {
TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) { TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x1000, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, 0x1000, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xf0\x00", 6); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xf0\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -131,7 +128,7 @@ TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) {
TEST_F(WvCasCaDescriptorTest, PidTwelthBit) { TEST_F(WvCasCaDescriptorTest, PidTwelthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x800, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, 0x800, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe8\x00", 6); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe8\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -139,7 +136,7 @@ TEST_F(WvCasCaDescriptorTest, PidTwelthBit) {
TEST_F(WvCasCaDescriptorTest, PidElevenththBit) { TEST_F(WvCasCaDescriptorTest, PidElevenththBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x400, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, 0x400, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe4\x00", 6); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe4\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -147,7 +144,7 @@ TEST_F(WvCasCaDescriptorTest, PidElevenththBit) {
TEST_F(WvCasCaDescriptorTest, PidTenthBit) { TEST_F(WvCasCaDescriptorTest, PidTenthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x200, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, 0x200, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe2\x00", 6); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe2\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -155,7 +152,7 @@ TEST_F(WvCasCaDescriptorTest, PidTenthBit) {
TEST_F(WvCasCaDescriptorTest, PidNinthBit) { TEST_F(WvCasCaDescriptorTest, PidNinthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x100, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{}, 0x100, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe1\x00", 6); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe1\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -163,14 +160,14 @@ TEST_F(WvCasCaDescriptorTest, PidNinthBit) {
TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoContentIdIgnored) { TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoContentIdIgnored) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( 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); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
} }
TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoProviderIgnored) { TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoProviderIgnored) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor( 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); const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
} }
@@ -186,39 +183,16 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoEntitlementKeyIds) {
actual_ca_descriptor_); actual_ca_descriptor_);
} }
TEST_F(WvCasCaDescriptorTest,
PrivateDataFailedWhenNumberOfEntitlementKeyIdsExceedLimit) {
const std::vector<std::string> 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<std::string> 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) { TEST_F(WvCasCaDescriptorTest, PrivateData) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, kContentId, std::vector<std::string> group_ids = {"group1", "group2"};
*kEntitlementKeyIds, EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
&actual_ca_descriptor_)); kTestPid, kProvider, kContentId, group_ids, &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x3d\x4a\xd4\xe0\x32", 6); const std::string expected_ca_descriptor("\x09\x29\x4a\xd4\xe0\x32", 6);
CaDescriptorPrivateData private_data; CaDescriptorPrivateData private_data;
private_data.set_provider(kProvider); private_data.set_provider(kProvider);
private_data.set_content_id(kContentId); private_data.set_content_id(kContentId);
for (const auto& entitlementKeyId : *kEntitlementKeyIds) { for (const auto& group_id : group_ids) {
private_data.add_entitlement_key_ids(entitlementKeyId); private_data.add_group_ids(group_id);
} }
EXPECT_EQ(expected_ca_descriptor + private_data.SerializeAsString(), EXPECT_EQ(expected_ca_descriptor + private_data.SerializeAsString(),
actual_ca_descriptor_); actual_ca_descriptor_);
@@ -232,7 +206,7 @@ class FakePrivateDataCaDescriptor : public WvCasCaDescriptor {
std::string GeneratePrivateData( std::string GeneratePrivateData(
const std::string& provider, const std::string& content_id, const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids) const override { const std::vector<std::string>& group_ids) const override {
return private_data_; return private_data_;
} }
@@ -243,9 +217,9 @@ class FakePrivateDataCaDescriptor : public WvCasCaDescriptor {
TEST_F(WvCasCaDescriptorTest, PrivateDataOneByte) { TEST_F(WvCasCaDescriptorTest, PrivateDataOneByte) {
FakePrivateDataCaDescriptor fake_descriptor; FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data("X"); fake_descriptor.set_private_data("X");
EXPECT_OK(fake_descriptor.GenerateCaDescriptor( EXPECT_OK(fake_descriptor.GenerateCaDescriptor(kTestPid, kProvider,
kTestPid, kProvider, kContentId, *kEntitlementKeyIds, kContentId, /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x05\x4a\xd4\xe0\x32X", 7); const std::string expected_ca_descriptor("\x09\x05\x4a\xd4\xe0\x32X", 7);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
} }
@@ -254,9 +228,9 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMultipleBytes) {
const std::string private_data_bytes("X1234abcde"); const std::string private_data_bytes("X1234abcde");
FakePrivateDataCaDescriptor fake_descriptor; FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data(private_data_bytes); fake_descriptor.set_private_data(private_data_bytes);
EXPECT_OK(fake_descriptor.GenerateCaDescriptor( EXPECT_OK(fake_descriptor.GenerateCaDescriptor(kTestPid, kProvider,
kTestPid, kProvider, kContentId, *kEntitlementKeyIds, kContentId, /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x0e\x4a\xd4\xe0\x32", 6); const std::string expected_ca_descriptor("\x09\x0e\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_); 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'); const std::string private_data_bytes(251, 'x');
FakePrivateDataCaDescriptor fake_descriptor; FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data(private_data_bytes); fake_descriptor.set_private_data(private_data_bytes);
EXPECT_OK(fake_descriptor.GenerateCaDescriptor( EXPECT_OK(fake_descriptor.GenerateCaDescriptor(kTestPid, kProvider,
kTestPid, kProvider, kContentId, *kEntitlementKeyIds, kContentId, /*group_ids=*/{},
&actual_ca_descriptor_)); &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\xff\x4a\xd4\xe0\x32", 6); const std::string expected_ca_descriptor("\x09\xff\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_); 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'); const std::string private_data_bytes(252, 'X');
FakePrivateDataCaDescriptor fake_descriptor; FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data(private_data_bytes); fake_descriptor.set_private_data(private_data_bytes);
EXPECT_EQ( EXPECT_EQ(error::INVALID_ARGUMENT,
error::INVALID_ARGUMENT, fake_descriptor
fake_descriptor .GenerateCaDescriptor(kTestPid, kProvider, kContentId,
.GenerateCaDescriptor(kTestPid, kProvider, kContentId, /*group_ids=*/{}, &actual_ca_descriptor_)
*kEntitlementKeyIds, &actual_ca_descriptor_) .error_code());
.error_code());
} }
} // namespace cas } // namespace cas

View File

@@ -41,6 +41,7 @@ EcmInitParameters ConvertToEcmInitParameters(
init_params.cas_id = ecm_parameters.cas_id; init_params.cas_id = ecm_parameters.cas_id;
init_params.ecm_version = ecm_parameters.ecm_version; init_params.ecm_version = ecm_parameters.ecm_version;
init_params.ecc_private_signing_key = ecm_parameters.ecc_private_signing_key; init_params.ecc_private_signing_key = ecm_parameters.ecc_private_signing_key;
init_params.entitlement_rotation = ecm_parameters.entitlement_rotation;
return init_params; return init_params;
} }
@@ -67,6 +68,16 @@ void WvCasEcm::SetServiceBlocking(
ecm_->SetServiceBlocking(service_blocking); 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, Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key,
const WvCasContentKeyInfo& odd_key, const WvCasContentKeyInfo& odd_key,
const std::string& track_type, const std::string& track_type,

View File

@@ -61,6 +61,7 @@ struct WvCasEcmParameters {
uint16_t cas_id = 0x4AD4; uint16_t cas_id = 0x4AD4;
EcmVersion ecm_version = EcmVersion::kV2; EcmVersion ecm_version = EcmVersion::kV2;
std::string ecc_private_signing_key; std::string ecc_private_signing_key;
EntitlementKeyRotationInfo entitlement_rotation;
}; };
// Class for generating Widevine CAS ECMs. // Class for generating Widevine CAS ECMs.
@@ -94,6 +95,20 @@ class WvCasEcm {
virtual void SetServiceBlocking( virtual void SetServiceBlocking(
const EcmServiceBlockingParams* service_blocking); 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. // Constructs a Widevine ECM using the provided key info.
// Args: // Args:
// |even_key| information for even key to be encoded into ECM. // |even_key| information for even key to be encoded into ECM.
@@ -106,6 +121,9 @@ class WvCasEcm {
// consistent with the initialized settings. // consistent with the initialized settings.
// The even_key and odd_key will be wrapped using the appropriate // The even_key and odd_key will be wrapped using the appropriate
// entitlement key. // 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, virtual Status GenerateEcm(const WvCasContentKeyInfo& even_key,
const WvCasContentKeyInfo& odd_key, const WvCasContentKeyInfo& odd_key,
const std::string& track_type, const std::string& track_type,
@@ -122,6 +140,9 @@ class WvCasEcm {
// |serialized_ecm| caller-supplied std::string pointer to receive the ECM. // |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
// The |key| contents (specifically IV sizes) must be consistent // The |key| contents (specifically IV sizes) must be consistent
// with the initialized settings. // 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, virtual Status GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids, const std::vector<std::string>& group_ids,

View File

@@ -11,6 +11,7 @@
#include "glog/logging.h" #include "glog/logging.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "common/status.h" #include "common/status.h"
#include "media_cas_packager_sdk/internal/ecmg_client_handler.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}; return {error::INVALID_ARGUMENT, error_message};
} }
processed_request_length = inner_handler_->HandleRequest( 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) { if (processed_request_length == 0) {
return {error::INTERNAL, "request not processed."}; return {error::INTERNAL, "request not processed."};
} }
@@ -89,5 +91,14 @@ Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size,
return OkStatus(); return OkStatus();
} }
WvEcmgChannelStatus WvCasEcmgClientHandler::GetChannelStatus() const {
return inner_handler_->GetChannelStatus();
}
std::vector<WvEcmgStreamStatus> WvCasEcmgClientHandler::GetStreamStatus()
const {
return inner_handler_->GetStreamStatus();
}
} // namespace cas } // namespace cas
} // namespace widevine } // namespace widevine

View File

@@ -74,6 +74,12 @@ class WvCasEcmgClientHandler {
size_t& response_length, size_t& response_length,
size_t& processed_request_length); size_t& processed_request_length);
// Retrieves the current channel status;
WvEcmgChannelStatus GetChannelStatus() const;
// Retrieves the status of all open streams;
std::vector<WvEcmgStreamStatus> GetStreamStatus() const;
protected: protected:
// For unit test only. // For unit test only.
explicit WvCasEcmgClientHandler( explicit WvCasEcmgClientHandler(

View File

@@ -11,6 +11,7 @@
#include "testing/gmock.h" #include "testing/gmock.h"
#include "testing/gunit.h" #include "testing/gunit.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "absl/types/span.h"
#include "common/status.h" #include "common/status.h"
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h" #include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h" #include "media_cas_packager_sdk/public/wv_cas_types.h"
@@ -25,7 +26,7 @@ class MockEcmgClientHandler : public EcmgClientHandler {
public: public:
MockEcmgClientHandler() : EcmgClientHandler(&config_) {} MockEcmgClientHandler() : EcmgClientHandler(&config_) {}
MOCK_METHOD(size_t, HandleRequest, MOCK_METHOD(size_t, HandleRequest,
(const char* const request, char* response, (const char* const request, const absl::Span<char> response,
size_t* response_length), size_t* response_length),
(override)); (override));
@@ -50,7 +51,9 @@ TEST(WvCasEcmgClientHandlerTest, HandleRequestSuccess) {
size_t processed_length = 0; size_t processed_length = 0;
auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>(); auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>();
EXPECT_CALL(*mock_internal_handler, 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))); .WillOnce(testing::DoAll(testing::Return(100)));
auto handler = absl::make_unique<TestWvCasEcmgClientHandler>( auto handler = absl::make_unique<TestWvCasEcmgClientHandler>(

View File

@@ -26,7 +26,6 @@ using ::testing::NotNull;
using ::testing::Pointwise; using ::testing::Pointwise;
using ::testing::Return; using ::testing::Return;
using ::testing::SetArgPointee; using ::testing::SetArgPointee;
using ::testing::SetArrayArgument;
using ::testing::WithArg; using ::testing::WithArg;
namespace widevine { namespace widevine {
@@ -62,12 +61,11 @@ class MockEmm : public Emm {
Status, SetServiceBlocking, Status, SetServiceBlocking,
(const std::vector<ServiceBlockingInitParameters>& service_blockings), (const std::vector<ServiceBlockingInitParameters>& service_blockings),
(override)); (override));
MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm), MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm), (override));
(const, override));
MOCK_METHOD(Status, GenerateEmmTsPackets, MOCK_METHOD(Status, GenerateEmmTsPackets,
(uint16_t pid, uint8_t* continuity_counter, (uint16_t pid, uint8_t* continuity_counter,
const absl::Span<uint8_t> packet, ssize_t* bytes_modified), const absl::Span<uint8_t> packet, ssize_t* bytes_modified),
(const, override)); (override));
}; };
class MockWvCasEmm : public WvCasEmm { class MockWvCasEmm : public WvCasEmm {

View File

@@ -39,8 +39,9 @@ WvCasKeyFetcher::WvCasKeyFetcher(const std::string& signing_provider,
Status WvCasKeyFetcher::CreateEntitlementRequest( Status WvCasKeyFetcher::CreateEntitlementRequest(
const EntitlementRequestParams& request_params, const EntitlementRequestParams& request_params,
std::string* signed_request_json) const { std::string* signed_request_json) const {
if (request_params.content_id.empty()) { if (request_params.content_id.empty() && request_params.group_id.empty()) {
return {error::INVALID_ARGUMENT, "Content ID is empty."}; return {error::INVALID_ARGUMENT,
"Either content ID or group ID must be specified."};
} }
if (request_params.content_provider.empty()) { if (request_params.content_provider.empty()) {
return {error::INVALID_ARGUMENT, "Content Provider is empty."}; return {error::INVALID_ARGUMENT, "Content Provider is empty."};
@@ -58,16 +59,21 @@ Status WvCasKeyFetcher::CreateEntitlementRequest(
} }
CasEncryptionRequest request; 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()) { if (!request_params.group_id.empty()) {
request.set_group_id(request_params.group_id); 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. // Add labels for tracks.
for (const auto& track_type : request_params.track_types) { for (const auto& track_type : request_params.track_types) {
request.add_track_types(track_type); 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; std::string request_json;
JsonPrintOptions print_options; JsonPrintOptions print_options;
@@ -162,6 +168,9 @@ Status WvCasKeyFetcher::ParseEntitlementResponse(
entitlement->key_id = key.key_id(); entitlement->key_id = key.key_id();
entitlement->key_value = key.key(); entitlement->key_value = key.key();
entitlement->track_type = key.track_type(); 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. // EVEN or SINGLE are both marked as is_even_key.
entitlement->is_even_key = entitlement->is_even_key =
key.key_slot() != CasEncryptionResponse::KeyInfo::ODD; key.key_slot() != CasEncryptionResponse::KeyInfo::ODD;

View File

@@ -29,12 +29,17 @@ namespace cas {
// |group_id| optional field indicates if this is a key used for a group of // |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 // contents. If this field is not empty, entitlement key would be generated
// for the group instead of the single content. // 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 { struct EntitlementRequestParams {
std::string content_id; std::string content_id;
std::string content_provider; std::string content_provider;
std::vector<std::string> track_types; std::vector<std::string> track_types;
bool key_rotation; bool key_rotation;
std::string group_id; std::string group_id;
bool entitlement_rotation_enabled = false;
uint32_t entitlement_period_index;
}; };
// WV CAS KeyFetcher. Performs the communication with the Widevine License // WV CAS KeyFetcher. Performs the communication with the Widevine License

View File

@@ -8,6 +8,8 @@
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h" #include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
#include <string>
#include "glog/logging.h" #include "glog/logging.h"
#include "google/protobuf/util/json_util.h" #include "google/protobuf/util/json_util.h"
#include "testing/gmock.h" #include "testing/gmock.h"
@@ -38,12 +40,12 @@ const char kSignedCasEncryptionRequest[] =
"RyYWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2V9\",\"signature\":" "RyYWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2V9\",\"signature\":"
"\"JyTnKEy1w98HRP1lL78+OIEiqtIXyfCN8iudvNXIWhw=\",\"signer\":\"widevine_" "\"JyTnKEy1w98HRP1lL78+OIEiqtIXyfCN8iudvNXIWhw=\",\"signer\":\"widevine_"
"test\"}"; "test\"}";
absl::string_view kSignedCasEncryptionRequestForGroupKey = constexpr absl::string_view kSignedCasEncryptionRequestForGroupKey =
"{\"request\":" "{\"request\":"
"\"eyJjb250ZW50X2lkIjoiTWpFeE5EQTRORFE9IiwicHJvdmlkZXIiOiJ3aWRldmluZSIsInRy" "\"eyJwcm92aWRlciI6IndpZGV2aW5lIiwidHJhY2tfdHlwZXMiOlsiU0QiXSwia2V5X3JvdG"
"YWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2UsImdyb3VwX2lkIjoiWjNKdm" "F0aW9uIjpmYWxzZSwiZ3JvdXBfaWQiOiJaM0p2ZFhBeCJ9\",\"signature\":"
"RYQXgifQ==\",\"signature\":\"iDaAr74ldEV6X1X9kwyLoZ/" "\"vRZ2qt65UC171S9pVEBib2KyTAbTGO+AjsCgV4d+4a0=\",\"signer\":\"widevine_"
"hfP5RJOkXEzucq6IXyfQ=\",\"signer\":\"widevine_test\"}"; "test\"}";
const char kHttpResponse[] = const char kHttpResponse[] =
"{\"response\":" "{\"response\":"
"\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu" "\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu"
@@ -57,6 +59,8 @@ const char kContentId[] = "21140844";
const char kGroupId[] = "group1"; const char kGroupId[] = "group1";
const char kEntitlementKeyId[] = "fake_key_id....."; const char kEntitlementKeyId[] = "fake_key_id.....";
const char kEntitlementKey[] = "fakefakefakefakefakefakefakefake"; const char kEntitlementKey[] = "fakefakefakefakefakefakefakefake";
const uint32_t kEntitlementPeriod = 123;
const char kEntitlementKeyIdForEntitlementPeriod123[] = "1234567812345678";
} // namespace } // namespace
namespace widevine { namespace widevine {
@@ -100,10 +104,11 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher {
return Status(error::INTERNAL); return Status(error::INTERNAL);
} }
// Validate the parameters shown in the request. // Validate the parameters shown in the request.
EXPECT_EQ(request.content_id(), kContentId);
EXPECT_EQ(request.provider(), kProvider); EXPECT_EQ(request.provider(), kProvider);
if (request.has_group_id()) { if (request.has_group_id()) {
EXPECT_EQ(request.group_id(), kGroupId); EXPECT_EQ(request.group_id(), kGroupId);
} else {
EXPECT_EQ(request.content_id(), kContentId);
} }
CasEncryptionResponse response; CasEncryptionResponse response;
@@ -119,13 +124,19 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher {
if (request.key_rotation()) { if (request.key_rotation()) {
// Add the Even key. // Add the Even key.
auto key = response.add_entitlement_keys(); 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_key(kEntitlementKey);
key->set_track_type(track_type); key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN); key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
// Add the Odd key. // Add the Odd key.
key = response.add_entitlement_keys(); 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_key(kEntitlementKey);
key->set_track_type(track_type); key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD); key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
@@ -212,6 +223,7 @@ TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
CHECK(absl::Base64Unescape("hgRrrgjyH8642r67Twt8ruqNLPcLFkJqdUIDNvnFd0M=", CHECK(absl::Base64Unescape("hgRrrgjyH8642r67Twt8ruqNLPcLFkJqdUIDNvnFd0M=",
&expected_key_value)); &expected_key_value));
EXPECT_EQ(entitlement.key_value, expected_key_value); EXPECT_EQ(entitlement.key_value, expected_key_value);
EXPECT_TRUE(entitlement.group_id.empty());
} }
TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) { TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) {
@@ -230,7 +242,6 @@ TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) {
CasEncryptionRequest request; CasEncryptionRequest request;
ASSERT_OK( ASSERT_OK(
google::protobuf::util::JsonStringToMessage(signed_request.request(), &request)); google::protobuf::util::JsonStringToMessage(signed_request.request(), &request));
EXPECT_EQ(request.content_id(), kContentId);
EXPECT_EQ(request.provider(), kProvider); EXPECT_EQ(request.provider(), kProvider);
EXPECT_EQ(request.group_id(), kGroupId); EXPECT_EQ(request.group_id(), kGroupId);
EXPECT_EQ(request.track_types_size(), 1); EXPECT_EQ(request.track_types_size(), 1);
@@ -247,7 +258,6 @@ TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) {
CasEncryptionResponse response; CasEncryptionResponse response;
ASSERT_OK( ASSERT_OK(
google::protobuf::util::JsonStringToMessage(signed_response.response(), &response)); google::protobuf::util::JsonStringToMessage(signed_response.response(), &response));
EXPECT_EQ(response.content_id(), kContentId);
EXPECT_EQ(response.status(), CasEncryptionResponse::OK); EXPECT_EQ(response.status(), CasEncryptionResponse::OK);
EXPECT_EQ(response.group_id(), kGroupId); EXPECT_EQ(response.group_id(), kGroupId);
@@ -261,6 +271,7 @@ TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) {
EXPECT_EQ(entitlement.is_even_key, true); EXPECT_EQ(entitlement.is_even_key, true);
EXPECT_EQ(entitlement.key_id, kEntitlementKeyId); EXPECT_EQ(entitlement.key_id, kEntitlementKeyId);
EXPECT_EQ(entitlement.key_value, kEntitlementKey); EXPECT_EQ(entitlement.key_value, kEntitlementKey);
EXPECT_EQ(entitlement.group_id, kGroupId);
} }
TEST_F(WvCasKeyFetcherTest, OneKeyOK) { TEST_F(WvCasKeyFetcherTest, OneKeyOK) {
@@ -348,5 +359,27 @@ TEST_F(WvCasKeyFetcherTest, BadResponseFail) {
.error_code()); .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<EntitlementKeyInfo> 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 cas
} // namespace widevine } // namespace widevine

View File

@@ -86,6 +86,23 @@ struct EntitlementKeyInfo {
enum class EcmVersion : int { kV2 = 0, kV3 = 1 }; 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 // A struct that captures the Simulcrypt ECMG configurations. Most fields are
// Simulcrypt standard fields (See ETSI TS 103 197 V1.5.1 (2008-10) // Simulcrypt standard fields (See ETSI TS 103 197 V1.5.1 (2008-10)
// Section 5.3). // Section 5.3).
@@ -177,6 +194,8 @@ struct EcmgCustomParameters {
// when the ECM is received, and stops util the device is no longer in // when the ECM is received, and stops util the device is no longer in
// |device_groups|. // |device_groups|.
std::vector<std::string> service_blocking_groups; std::vector<std::string> 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 // A custom access control processing function used by ECMG to get information
@@ -233,6 +252,33 @@ typedef std::function<EcmServiceBlockingParams(uint16_t channel_id,
uint16_t stream_id)> uint16_t stream_id)>
ServiceBlockingSettingFunc; 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 { struct WvCasEncryptionRequest {
std::string content_id; std::string content_id;
std::string provider; std::string provider;

View File

@@ -26,10 +26,6 @@
#include "media_cas_packager_sdk/internal/emmg.h" #include "media_cas_packager_sdk/internal/emmg.h"
namespace { 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 kEmmDataType = 0;
constexpr int32_t kPrivateDataType = 1; constexpr int32_t kPrivateDataType = 1;
} // namespace } // namespace
@@ -58,8 +54,8 @@ ABSL_FLAG(std::string, content_provider, "",
"Content provider to put into priavte data."); "Content provider to put into priavte data.");
ABSL_FLAG(std::string, content_id, "", "Content id to put into priavte data"); ABSL_FLAG(std::string, content_id, "", "Content id to put into priavte data");
ABSL_FLAG( ABSL_FLAG(
std::vector<std::string>, entitlement_key_ids, {}, std::vector<std::string>, group_ids, {},
"Comma-separated list of entitlement_key_ids to put into private data"); "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, bandwidth, 100, "Requested bandwidth in kbps");
ABSL_FLAG(int32_t, max_num_message, 100, ABSL_FLAG(int32_t, max_num_message, 100,
"Maximum number of messages that can be sent"); "Maximum number of messages that can be sent");
@@ -93,8 +89,8 @@ void CheckEmmgUsage(const widevine::cas::EmmgConfig& config) {
"data)."; "data).";
LOG_IF(WARNING, !config.content_id.empty()) LOG_IF(WARNING, !config.content_id.empty())
<< "content_id is set but ignored as data_type is set to 0 (EMM data)."; << "content_id is set but ignored as data_type is set to 0 (EMM data).";
LOG_IF(WARNING, !config.entitlement_key_ids.empty()) LOG_IF(WARNING, !config.group_ids.empty())
<< "entitlement_key_ids is set but ignored as data_type is set to 0 " << "group_ids is set but ignored as data_type is set to 0 "
"(EMM data)."; "(EMM data).";
} }
@@ -130,17 +126,7 @@ void BuildEmmgConfig(widevine::cas::EmmgConfig* config) {
// Below are private data specific configurations. // Below are private data specific configurations.
config->content_provider = absl::GetFlag(FLAGS_content_provider); config->content_provider = absl::GetFlag(FLAGS_content_provider);
config->content_id = absl::GetFlag(FLAGS_content_id); config->content_id = absl::GetFlag(FLAGS_content_id);
// Check and set entitlement_key_ids. config->group_ids = absl::GetFlag(FLAGS_group_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);
}
// Below are EMM data specific configurations. // Below are EMM data specific configurations.
config->ecc_signing_key = absl::GetFlag(FLAGS_ecc_signing_key); config->ecc_signing_key = absl::GetFlag(FLAGS_ecc_signing_key);

View File

@@ -19,9 +19,8 @@ message CaDescriptorPrivateData {
// Content ID. // Content ID.
optional bytes content_id = 2; optional bytes content_id = 2;
// Entitlement key IDs for current content per track. Each track will allow up // Deprecated.
// to 2 entitlement key ids (odd and even entitlement keys). repeated bytes deprecated_entitlement_key_ids = 3;
repeated bytes entitlement_key_ids = 3;
// The groups ids this channel belongs to. // The groups ids this channel belongs to.
repeated bytes group_ids = 4; repeated bytes group_ids = 4;
@@ -78,6 +77,17 @@ message EcmMetaData {
// Optional. The minimum age required to watch the content. The value // Optional. The minimum age required to watch the content. The value
// represents actual age, with 0 means no restriction. // represents actual age, with 0 means no restriction.
optional uint32 age_restriction = 2 [default = 0]; 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 { message EcmKeyData {