Replace hardcoded parameters
This commit is contained in:
@@ -32,8 +32,8 @@ cc_library(
|
||||
"//common:status",
|
||||
"//common:string_util",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
"//protos/public:media_cas_proto",
|
||||
"//protos/public:media_cas_cc_proto",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -46,7 +46,7 @@ cc_test(
|
||||
"//testing:gunit_main",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -71,7 +71,7 @@ cc_test(
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"//common:aes_cbc_util",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -85,19 +85,23 @@ cc_library(
|
||||
],
|
||||
deps = [
|
||||
":ecm",
|
||||
":ecm_generator",
|
||||
":fixed_key_fetcher",
|
||||
":key_fetcher",
|
||||
":mpeg2ts",
|
||||
":simulcrypt_util",
|
||||
":util",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/container:node_hash_map",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/strings:str_format",
|
||||
"//common:crypto_util",
|
||||
"//common:random_util",
|
||||
"//common:status",
|
||||
"//example:constants",
|
||||
"//media_cas_packager_sdk/public:wv_cas_ecm",
|
||||
"//media_cas_packager_sdk/public:wv_cas_key_fetcher",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
],
|
||||
)
|
||||
@@ -108,8 +112,12 @@ cc_test(
|
||||
srcs = ["ecmg_client_handler_test.cc"],
|
||||
deps = [
|
||||
":ecmg_client_handler",
|
||||
":simulcrypt_util",
|
||||
":util",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/strings:str_format",
|
||||
"//example:test_ecmg_messages",
|
||||
],
|
||||
)
|
||||
@@ -127,6 +135,7 @@ cc_library(
|
||||
":util",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings:str_format",
|
||||
"//common:status",
|
||||
"//example:test_emmg_messages",
|
||||
],
|
||||
@@ -140,6 +149,7 @@ cc_test(
|
||||
":emmg",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings:str_format",
|
||||
"//example:test_emmg_messages",
|
||||
],
|
||||
)
|
||||
@@ -155,7 +165,7 @@ cc_library(
|
||||
deps = [
|
||||
":key_fetcher",
|
||||
"//common:status",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -37,14 +37,12 @@ static constexpr int kNumBitsCaSystemIdField = 16;
|
||||
static constexpr int kNumBitsEcmVersionField = 8;
|
||||
|
||||
// Byte 3
|
||||
static constexpr int kNumBitsEcmGenerationCountField = 5;
|
||||
// Unused bits (mbz, must be zero)
|
||||
static constexpr int kNumBitsUnusedFieldByte3 = 3;
|
||||
// Values for Decrypt Mode are from enum CryptoMode
|
||||
static constexpr int kNumBitsDecryptModeField = 2;
|
||||
static constexpr int kNumBitsDecryptModeField = 4;
|
||||
static constexpr int kNumBitsRotationEnabledField = 1;
|
||||
|
||||
static constexpr int kMaxGeneration =
|
||||
(1 << kNumBitsEcmGenerationCountField) - 1;
|
||||
|
||||
// Byte 4
|
||||
// Size of IVs.
|
||||
// Values for IV size fields are from enum EcmIvSize
|
||||
@@ -52,8 +50,12 @@ static constexpr int kMaxGeneration =
|
||||
// always be set to 1.
|
||||
static constexpr int kNumBitsWrappedKeyIvSizeField = 1;
|
||||
static constexpr int kNumBitsContentIvSizeField = 1;
|
||||
static constexpr int kNumBitsAgeRestriction = 5;
|
||||
// Unused bits (mbz, must be zero)
|
||||
static constexpr int kNumBitsUnusedField = 6;
|
||||
static constexpr int kNumBitsUnusedFieldByte4 = 1;
|
||||
|
||||
// Total bits equals number of bytes * 8.
|
||||
static constexpr int kBitSetTotalSize = 40;
|
||||
|
||||
// Remaining bytes (starting from the 6th byte) hold entitled key info.
|
||||
static constexpr size_t kKeyIdSizeBytes = 16;
|
||||
@@ -71,7 +73,7 @@ static constexpr int kWvCasCaSystemId = 0x4AD4;
|
||||
|
||||
// Version - this should be incremented if there are non-backwards compatible
|
||||
// changes to the ECM.
|
||||
static constexpr int kEcmVersion = 1;
|
||||
static constexpr int kEcmVersion = 2;
|
||||
|
||||
// Settings for RotationEnabled field.
|
||||
static constexpr int kRotationDisabled = 0;
|
||||
@@ -144,10 +146,7 @@ Status Ecm::Initialize(const std::string& content_id,
|
||||
content_provider_ = content_provider;
|
||||
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
|
||||
track_types_ = ecm_init_parameters.track_types;
|
||||
// Generation is incremented before the ECM is generated.
|
||||
// Initializing to kMaxGeneration ensures the first generated ECM has a gen
|
||||
// count of zero.
|
||||
generation_ = kMaxGeneration;
|
||||
age_restriction_ = ecm_init_parameters.age_restriction;
|
||||
|
||||
// Construct and return CasEncryptionRequest message for caller to use.
|
||||
Status status = CreateEntitlementRequest(key_request_message);
|
||||
@@ -162,6 +161,43 @@ Status Ecm::Initialize(const std::string& content_id,
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::Initialize(
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
const std::vector<InjectedEntitlementKeyInfo>& injected_entitlements) {
|
||||
if (initialized_) {
|
||||
return {error::INTERNAL, "Already initialized."};
|
||||
}
|
||||
if (ecm_init_parameters.track_types.empty()) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Parameter track_ids must be set with one or more Track IDs."};
|
||||
}
|
||||
if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size,
|
||||
&content_iv_size_)) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Parameter content_iv_size must be kIvSize8 or kIvSize16."};
|
||||
}
|
||||
|
||||
crypto_mode_ = ecm_init_parameters.crypto_mode;
|
||||
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
|
||||
track_types_ = ecm_init_parameters.track_types;
|
||||
age_restriction_ = ecm_init_parameters.age_restriction;
|
||||
|
||||
ClearEntitlementKeys();
|
||||
for (const auto& entitlement : injected_entitlements) {
|
||||
PushEntitlementKey(entitlement.track_type, entitlement.is_even_key,
|
||||
{entitlement.key_id, entitlement.key_value});
|
||||
}
|
||||
|
||||
if (!CheckEntitlementKeys()) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
"Improper injected entitlement keys.");
|
||||
}
|
||||
|
||||
// Everything is set up including entitlement keys.
|
||||
initialized_ = true;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::ProcessCasEncryptionResponse(const std::string& response) {
|
||||
if (!initialized_) {
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
@@ -173,8 +209,8 @@ Status Ecm::ProcessCasEncryptionResponse(const std::string& response) {
|
||||
}
|
||||
|
||||
Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type, std::string* serialized_ecm,
|
||||
uint32_t* generation) {
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const {
|
||||
if (!initialized_) {
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
@@ -190,11 +226,12 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
|
||||
keys.push_back(even_key);
|
||||
keys.push_back(odd_key);
|
||||
|
||||
return GenerateEcmCommon(keys, track_type, serialized_ecm, generation);
|
||||
return GenerateEcmCommon(keys, track_type, serialized_ecm);
|
||||
}
|
||||
|
||||
Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key, const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation) {
|
||||
Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const {
|
||||
if (!initialized_) {
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
@@ -209,18 +246,15 @@ Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key, const std::string& track_
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(key);
|
||||
|
||||
return GenerateEcmCommon(keys, track_type, serialized_ecm, generation);
|
||||
return GenerateEcmCommon(keys, track_type, serialized_ecm);
|
||||
}
|
||||
|
||||
Status Ecm::GenerateEcmCommon(const std::vector<EntitledKeyInfo*>& keys,
|
||||
const std::string& track_type, std::string* serialized_ecm,
|
||||
uint32_t* generation) {
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const {
|
||||
if (serialized_ecm == nullptr) {
|
||||
return {error::INVALID_ARGUMENT, "No return ecm std::string pointer."};
|
||||
}
|
||||
if (generation == nullptr) {
|
||||
return {error::INVALID_ARGUMENT, "No return generation pointer."};
|
||||
}
|
||||
|
||||
Status status = ValidateKeys(keys);
|
||||
if (!status.ok()) {
|
||||
@@ -240,29 +274,13 @@ Status Ecm::GenerateEcmCommon(const std::vector<EntitledKeyInfo*>& keys,
|
||||
// TODO(user): validate inputs, compare against current values
|
||||
// TODO(user): replace current values.
|
||||
|
||||
// Updates complete, we are complete and consistent.
|
||||
// Update generation before serializing.
|
||||
uint32_t previous_generation = generation_;
|
||||
IncrementGeneration();
|
||||
|
||||
// Generate TS packet payload for ECM, pass back to caller.
|
||||
serialized_ecm->assign(SerializeEcm(keys));
|
||||
if (kMaxEcmSizeBytes < serialized_ecm->size()) {
|
||||
generation_ = previous_generation;
|
||||
serialized_ecm->clear();
|
||||
return Status(error::INTERNAL, "Maximum size of ECM has been exceeded.");
|
||||
}
|
||||
*generation = generation_;
|
||||
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
void Ecm::IncrementGeneration() {
|
||||
generation_ = (generation_ >= kMaxGeneration) ? 0 : generation_ + 1;
|
||||
}
|
||||
|
||||
Status Ecm::WrapEntitledKeys(const std::string& track_type,
|
||||
const std::vector<EntitledKeyInfo*> keys) {
|
||||
const std::vector<EntitledKeyInfo*>& keys) const {
|
||||
if (!initialized_) {
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
@@ -296,8 +314,10 @@ Status Ecm::WrapEntitledKeys(const std::string& track_type,
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::WrapKey(const std::string& wrapping_key, const std::string& wrapping_iv,
|
||||
const std::string& key_value, std::string* wrapped_key) {
|
||||
Status Ecm::WrapKey(const std::string& wrapping_key,
|
||||
const std::string& wrapping_iv,
|
||||
const std::string& key_value,
|
||||
std::string* wrapped_key) const {
|
||||
Status status = ValidateKeyValue(wrapping_key, kWrappingKeySizeBytes);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -319,7 +339,7 @@ Status Ecm::WrapKey(const std::string& wrapping_key, const std::string& wrapping
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::ValidateKeys(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
Status Ecm::ValidateKeys(const std::vector<EntitledKeyInfo*>& keys) const {
|
||||
for (const auto& key : keys) {
|
||||
Status status;
|
||||
status = ValidateKeyId(key->key_id);
|
||||
@@ -342,7 +362,8 @@ Status Ecm::ValidateKeys(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::ValidateWrappedKeys(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
Status Ecm::ValidateWrappedKeys(
|
||||
const std::vector<EntitledKeyInfo*>& keys) const {
|
||||
for (const auto& key : keys) {
|
||||
Status status;
|
||||
status = ValidateKeyId(key->key_id);
|
||||
@@ -366,14 +387,15 @@ Status Ecm::ValidateWrappedKeys(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::ValidateKeyId(const std::string& key_id) {
|
||||
Status Ecm::ValidateKeyId(const std::string& key_id) const {
|
||||
if (key_id.size() != kKeyIdSizeBytes) {
|
||||
return {error::INVALID_ARGUMENT, "Key ID must be 16 bytes."};
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::ValidateKeyValue(const std::string& key_value, size_t key_value_size) {
|
||||
Status Ecm::ValidateKeyValue(const std::string& key_value,
|
||||
size_t key_value_size) const {
|
||||
if (key_value.size() != key_value_size) {
|
||||
return Status(
|
||||
error::INVALID_ARGUMENT,
|
||||
@@ -382,19 +404,18 @@ Status Ecm::ValidateKeyValue(const std::string& key_value, size_t key_value_size
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::ValidateIv(const std::string& iv, size_t size) {
|
||||
Status Ecm::ValidateIv(const std::string& iv, size_t size) const {
|
||||
if (iv.size() != size) {
|
||||
return {error::INVALID_ARGUMENT, "IV is wrong size."};
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
std::string Ecm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
std::string Ecm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) const {
|
||||
// Five bytes (40 bits including padding)
|
||||
std::bitset<kNumBitsCaSystemIdField> ca_system_id(kWvCasCaSystemId);
|
||||
std::bitset<kNumBitsEcmVersionField> ecm_version(kEcmVersion);
|
||||
std::bitset<kNumBitsEcmGenerationCountField> ecm_generation_count(
|
||||
generation());
|
||||
std::bitset<kNumBitsUnusedFieldByte3> unused(kUnusedZero);
|
||||
std::bitset<kNumBitsDecryptModeField> decrypt_mode(
|
||||
static_cast<int>(crypto_mode()));
|
||||
std::bitset<kNumBitsRotationEnabledField> rotation_enabled(
|
||||
@@ -403,21 +424,23 @@ std::string Ecm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
IvSizeFieldValue(kWrappedKeyIvSizeBytes));
|
||||
std::bitset<kNumBitsContentIvSizeField> content_iv_size(
|
||||
IvSizeFieldValue(this->content_iv_size()));
|
||||
std::bitset<kNumBitsUnusedField> padding(kUnusedZero);
|
||||
std::bitset<kNumBitsAgeRestriction> age_restriction(
|
||||
static_cast<int>(this->age_restriction()));
|
||||
std::bitset<kNumBitsUnusedFieldByte4> padding(kUnusedZero);
|
||||
|
||||
// Converts bitset to string.
|
||||
std::string ecm_bitset = absl::StrCat(
|
||||
ca_system_id.to_string(), ecm_version.to_string(),
|
||||
ecm_generation_count.to_string(), decrypt_mode.to_string(),
|
||||
rotation_enabled.to_string(), wrapped_key_iv_size.to_string(),
|
||||
content_iv_size.to_string(), padding.to_string());
|
||||
if (ecm_bitset.size() != 40) {
|
||||
LOG(FATAL) << "ECM bitset incorret size: " << ecm_bitset.size();
|
||||
ca_system_id.to_string(), ecm_version.to_string(), unused.to_string(),
|
||||
decrypt_mode.to_string(), rotation_enabled.to_string(),
|
||||
wrapped_key_iv_size.to_string(), content_iv_size.to_string(),
|
||||
age_restriction.to_string(), padding.to_string());
|
||||
if (ecm_bitset.size() != kBitSetTotalSize) {
|
||||
LOG(FATAL) << "ECM bitset incorrect size: " << ecm_bitset.size();
|
||||
}
|
||||
std::string serialized_ecm;
|
||||
Status status =
|
||||
string_util::BitsetStringToBinaryString(ecm_bitset, &serialized_ecm);
|
||||
if (!status.ok()) {
|
||||
if (!status.ok() || serialized_ecm.empty()) {
|
||||
LOG(FATAL) << "Failed to convert ECM bitset to std::string";
|
||||
}
|
||||
|
||||
@@ -429,7 +452,7 @@ std::string Ecm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
return serialized_ecm;
|
||||
}
|
||||
|
||||
Status Ecm::CreateEntitlementRequest(std::string* request_string) {
|
||||
Status Ecm::CreateEntitlementRequest(std::string* request_string) const {
|
||||
CasEncryptionRequest request;
|
||||
|
||||
request.set_content_id(content_id_);
|
||||
@@ -460,7 +483,7 @@ Status Ecm::ParseEntitlementResponse(const std::string& response_string) {
|
||||
if (!response.ParseFromString(signed_response.response())) {
|
||||
return {error::INTERNAL, "Failure parsing signed response."};
|
||||
}
|
||||
if (response.status() != CasEncryptionResponse_Status_OK) {
|
||||
if (response.status() != CasEncryptionResponse::OK) {
|
||||
return Status(error::INTERNAL, absl::StrCat("Failure reported by server: ",
|
||||
response.status(), " : ",
|
||||
response.status_message()));
|
||||
@@ -507,13 +530,13 @@ Status Ecm::ParseEntitlementResponse(const std::string& response_string) {
|
||||
continue;
|
||||
}
|
||||
if (paired_keys_required()) {
|
||||
if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_EVEN) {
|
||||
if (key.key_slot() == CasEncryptionResponse::KeyInfo::EVEN) {
|
||||
PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey);
|
||||
} else if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_ODD) {
|
||||
} else if (key.key_slot() == CasEncryptionResponse::KeyInfo::ODD) {
|
||||
PushEntitlementKey(key.track_type(), /* is_even_key= */ false, ekey);
|
||||
}
|
||||
} else {
|
||||
if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_SINGLE) {
|
||||
if (key.key_slot() == CasEncryptionResponse::KeyInfo::SINGLE) {
|
||||
PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,11 +53,22 @@ enum EcmIvSize { kIvSize8 = 0, kIvSize16 = 1 };
|
||||
// A constant of type CryptoMode.
|
||||
// |track_types| a vector of track ID (std::string) that specify the set of track
|
||||
// types of interest; controls the entitlement keys returned by the server.
|
||||
// |age_restriction| minimum age required; the value represents actual age.
|
||||
struct EcmInitParameters {
|
||||
EcmIvSize content_iv_size = kIvSize8;
|
||||
bool key_rotation_enabled = true;
|
||||
CryptoMode crypto_mode = CryptoMode::kAesCtr;
|
||||
std::vector<std::string> track_types;
|
||||
uint8_t age_restriction = 0;
|
||||
};
|
||||
|
||||
// Information needed for the injected entitlement keys. Used for Ecm
|
||||
// initialization.
|
||||
struct InjectedEntitlementKeyInfo {
|
||||
std::string track_type;
|
||||
bool is_even_key;
|
||||
std::string key_id; // must be 16 bytes.
|
||||
std::string key_value; // must be 32 bytes.
|
||||
};
|
||||
|
||||
// Generator for producing Widevine CAS-compliant ECMs. Used to construct the
|
||||
@@ -83,7 +94,7 @@ class Ecm {
|
||||
// Args:
|
||||
// |content_id| uniquely identifies the content (with |content_provider|)
|
||||
// |content_provider| unique std::string for provider of the content stream.
|
||||
// |wv_ECM_parameters| encryption-related parameters for configuring
|
||||
// |ecm_init_parameters| encryption-related parameters for configuring
|
||||
// the ECM stream.
|
||||
// |key_request_message| pointer to a std::string to receive a CasEncryptionRequest
|
||||
// message.
|
||||
@@ -95,6 +106,16 @@ class Ecm {
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
std::string* key_request_message);
|
||||
|
||||
// Perform initialization for a new ECM stream with injected entitlement keys.
|
||||
// Args:
|
||||
// |ecm_init_parameters| encryption-related parameters for configuring
|
||||
// the ECM stream.
|
||||
// |injected_entitlement| entitlement key info directly provided. No need to
|
||||
// fetch keys from server.
|
||||
virtual Status Initialize(
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
const std::vector<InjectedEntitlementKeyInfo>& injected_entitlements);
|
||||
|
||||
// Parse a CasEncryptionResponse message holding the entitlement keys for
|
||||
// generating the ECM stream. The entitlement keys are used to encrypt the
|
||||
// keys conveyed in the ECM. The entitlement key IDs are also part of the ECM.
|
||||
@@ -110,16 +131,14 @@ class Ecm {
|
||||
// |odd_key| information for odd key to be encoded into ECM.
|
||||
// |track_type| the track that the keys are being used to encrypt.
|
||||
// |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
|
||||
// |generation| pointer to uint32_t to receive the generation number of the ECM.
|
||||
// The |even_key| and |odd_key| contents (specifically IV sizes) must be
|
||||
// consistent with the initialized settings.
|
||||
// The even_key and odd_key will be wrapped using the appropriate
|
||||
// entitlement key. Wrapping modifies the original structure.
|
||||
// Generation is a mod 32 counter. If the ECM has any changes from the
|
||||
// previous ECM, the generation is increased by one.
|
||||
virtual Status GenerateEcm(EntitledKeyInfo* even_key,
|
||||
EntitledKeyInfo* odd_key, const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation);
|
||||
EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const;
|
||||
|
||||
// Accept a key and IV and construct an ECM that will fit into a Transport
|
||||
// Stream packet payload (184 bytes). This call is specifically for the case
|
||||
@@ -128,23 +147,17 @@ class Ecm {
|
||||
// |key| information for key to be encoded into ECM.
|
||||
// |track_type| the track that the key is being used to encrypt.
|
||||
// |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
|
||||
// |generation| pointer to uint32_t to receive the generation number of the ECM.
|
||||
// The |key| contents (specifically IV sizes) must be consistent
|
||||
// with the initialized settings.
|
||||
// Generation is a mod 32 counter. If the ECM has any changes from the
|
||||
// previous ECM, the generation is increased by one.
|
||||
virtual Status GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm,
|
||||
uint32_t* generation);
|
||||
std::string* serialized_ecm) const;
|
||||
|
||||
protected: // For unit tests.
|
||||
// Take the input entitled |keys| and our current state, and generate
|
||||
// the ECM representing the keys.
|
||||
virtual std::string SerializeEcm(const std::vector<EntitledKeyInfo*>& keys);
|
||||
|
||||
// Increment the ECM's generation count by one (mod kMaxGeneration).
|
||||
virtual void IncrementGeneration();
|
||||
virtual std::string SerializeEcm(
|
||||
const std::vector<EntitledKeyInfo*>& keys) const;
|
||||
|
||||
// Apply the Entitlement key with the given track type and polarity
|
||||
// (even vs. odd key) to wrap the given Entitled key.
|
||||
@@ -152,8 +165,9 @@ class Ecm {
|
||||
// |track_type| the track type for the keys. The type_track must match one
|
||||
// of the |EcmInitParameters::track_types| strings passed into Initialize().
|
||||
// |key| the Entitled Key to be wrapped.
|
||||
virtual Status WrapEntitledKeys(const std::string& track_type,
|
||||
const std::vector<EntitledKeyInfo*> keys);
|
||||
virtual Status WrapEntitledKeys(
|
||||
const std::string& track_type,
|
||||
const std::vector<EntitledKeyInfo*>& keys) const;
|
||||
|
||||
private:
|
||||
// Entitlement key - |key_value| is used to wrap the content key, and |key_id|
|
||||
@@ -194,36 +208,37 @@ class Ecm {
|
||||
// Common helper for GenerateEcm() and GenerateSingleKeyEcm()
|
||||
virtual Status GenerateEcmCommon(const std::vector<EntitledKeyInfo*>& keys,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation);
|
||||
std::string* serialized_ecm) const;
|
||||
|
||||
// Wrap |key_value| using |wrapping_key| (entitlement key) and |wrapping_iv|.
|
||||
// Returns the resulting wrapped key in |wrapped_key|.
|
||||
// Return a status indicating whether there has been any error.
|
||||
virtual Status WrapKey(const std::string& wrapping_key, const std::string& wrapping_iv,
|
||||
const std::string& key_value, std::string* wrapped_key);
|
||||
virtual Status WrapKey(const std::string& wrapping_key,
|
||||
const std::string& wrapping_iv,
|
||||
const std::string& key_value,
|
||||
std::string* wrapped_key) const;
|
||||
|
||||
virtual Status ValidateKeys(const std::vector<EntitledKeyInfo*>& keys);
|
||||
virtual Status ValidateWrappedKeys(const std::vector<EntitledKeyInfo*>& keys);
|
||||
virtual Status ValidateKeys(const std::vector<EntitledKeyInfo*>& keys) const;
|
||||
virtual Status ValidateWrappedKeys(
|
||||
const std::vector<EntitledKeyInfo*>& keys) const;
|
||||
|
||||
Status ValidateKeyId(const std::string& key_id);
|
||||
Status ValidateKeyValue(const std::string& key_value, size_t key_value_size);
|
||||
Status ValidateIv(const std::string& iv, size_t size);
|
||||
Status ValidateKeyId(const std::string& key_id) const;
|
||||
Status ValidateKeyValue(const std::string& key_value,
|
||||
size_t key_value_size) const;
|
||||
Status ValidateIv(const std::string& iv, size_t size) const;
|
||||
|
||||
// TODO(user): need unit tests for CreateEntitlementRequest.
|
||||
virtual Status CreateEntitlementRequest(std::string* request_string);
|
||||
virtual Status CreateEntitlementRequest(std::string* request_string) const;
|
||||
// TODO(user): need unit tests for ParseEntitlementResponse.
|
||||
virtual Status ParseEntitlementResponse(const std::string& response_string);
|
||||
|
||||
virtual uint32_t generation() const { return generation_; }
|
||||
virtual CryptoMode crypto_mode() const { return crypto_mode_; }
|
||||
virtual uint8_t age_restriction() const { return age_restriction_; }
|
||||
virtual bool paired_keys_required() const { return paired_keys_required_; }
|
||||
virtual size_t content_iv_size() const { return content_iv_size_; }
|
||||
|
||||
// Set to true when the object has been properly initialized.
|
||||
bool initialized_ = false;
|
||||
// Current generation. This will be incremented (mod 32) each time a new
|
||||
// ECM has changes from the previous ECM.
|
||||
uint32_t generation_ = 0;
|
||||
// Content ID for this ECM stream.
|
||||
std::string content_id_;
|
||||
// Provider ID for this ECM stream.
|
||||
@@ -235,6 +250,7 @@ class Ecm {
|
||||
// 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;
|
||||
// Entitlement keys needed for ECM generation.
|
||||
// The keys are added when the CasEncryptionResponse is processed.
|
||||
std::map<std::string, std::list<EntitlementKeyInfo>> entitlement_keys_;
|
||||
|
||||
@@ -26,15 +26,11 @@ std::string EcmGenerator::GenerateEcm(const EcmParameters& params) {
|
||||
return "";
|
||||
}
|
||||
std::string serialized_ecm;
|
||||
uint32_t generation;
|
||||
// TODO(user): need track_type
|
||||
std::string track_type = "SD";
|
||||
std::string track_type = params.track_type;
|
||||
if (params.rotation_enabled) {
|
||||
status = ecm_->GenerateEcm(&keys[0], &keys[1], track_type, &serialized_ecm,
|
||||
&generation);
|
||||
status = ecm_->GenerateEcm(&keys[0], &keys[1], track_type, &serialized_ecm);
|
||||
} else {
|
||||
status = ecm_->GenerateSingleKeyEcm(&keys[0], track_type, &serialized_ecm,
|
||||
&generation);
|
||||
status = ecm_->GenerateSingleKeyEcm(&keys[0], track_type, &serialized_ecm);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << " Generate ECM call failed: " << status;
|
||||
@@ -55,6 +51,11 @@ Status EcmGenerator::ProcessEcmParameters(const EcmParameters& ecm_params,
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Number of supplied keys is wrong (check rotation periods)."};
|
||||
}
|
||||
// If content_iv_size_ is zero, use the size of first content IV as the
|
||||
// expected size for all future IVs in this stream.
|
||||
if (content_iv_size_ == 0) {
|
||||
content_iv_size_ = ecm_params.key_params[0].content_iv.size();
|
||||
}
|
||||
for (int i = 0; i < keys_needed; i++) {
|
||||
Status status = ValidateKeyParameters(ecm_params.key_params[i]);
|
||||
if (!status.ok()) {
|
||||
@@ -65,7 +66,7 @@ Status EcmGenerator::ProcessEcmParameters(const EcmParameters& ecm_params,
|
||||
key.key_id = ecm_params.key_params[i].key_id;
|
||||
key.key_value = ecm_params.key_params[i].key_data;
|
||||
key.wrapped_key_iv = ecm_params.key_params[i].wrapped_key_iv;
|
||||
key.content_iv = ecm_params.key_params[i].content_ivs[0];
|
||||
key.content_iv = ecm_params.key_params[i].content_iv;
|
||||
}
|
||||
current_key_index_ = 0;
|
||||
current_key_even_ = true;
|
||||
@@ -73,7 +74,7 @@ Status EcmGenerator::ProcessEcmParameters(const EcmParameters& ecm_params,
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status EcmGenerator::ValidateKeyId(const std::string& id) {
|
||||
Status EcmGenerator::ValidateKeyId(const std::string& id) const {
|
||||
if (id.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Key id is empty."};
|
||||
}
|
||||
@@ -83,7 +84,7 @@ Status EcmGenerator::ValidateKeyId(const std::string& id) {
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status EcmGenerator::ValidateKeyData(const std::string& key_data) {
|
||||
Status EcmGenerator::ValidateKeyData(const std::string& key_data) const {
|
||||
if (key_data.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Key data is empty."};
|
||||
}
|
||||
@@ -93,7 +94,8 @@ Status EcmGenerator::ValidateKeyData(const std::string& key_data) {
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status EcmGenerator::ValidateIv(const std::string& iv, size_t required_size) {
|
||||
Status EcmGenerator::ValidateIv(const std::string& iv,
|
||||
size_t required_size) const {
|
||||
if (iv.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "IV is empty."};
|
||||
}
|
||||
@@ -107,7 +109,7 @@ Status EcmGenerator::ValidateIv(const std::string& iv, size_t required_size) {
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status EcmGenerator::ValidateWrappedKeyIv(const std::string& iv) {
|
||||
Status EcmGenerator::ValidateWrappedKeyIv(const std::string& iv) const {
|
||||
// All wrapped key IVs must be 16 bytes.
|
||||
Status status = ValidateIv(iv, kIvSize16);
|
||||
if (!status.ok()) {
|
||||
@@ -116,12 +118,7 @@ Status EcmGenerator::ValidateWrappedKeyIv(const std::string& iv) {
|
||||
return status;
|
||||
}
|
||||
|
||||
Status EcmGenerator::ValidateContentIv(const std::string& iv) {
|
||||
// If content_iv_size_ is zero, use this IV as the size for all future IVs in
|
||||
// this stream.
|
||||
if (content_iv_size_ == 0) {
|
||||
content_iv_size_ = iv.size();
|
||||
}
|
||||
Status EcmGenerator::ValidateContentIv(const std::string& iv) const {
|
||||
Status status = ValidateIv(iv, content_iv_size_);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << " Content IV is not valid: " << status;
|
||||
@@ -129,18 +126,12 @@ Status EcmGenerator::ValidateContentIv(const std::string& iv) {
|
||||
return status;
|
||||
}
|
||||
|
||||
Status EcmGenerator::ValidateKeyParameters(const KeyParameters& key_params) {
|
||||
Status status;
|
||||
status = ValidateKeyId(key_params.key_id);
|
||||
Status EcmGenerator::ValidateKeyParameters(
|
||||
const KeyParameters& key_params) const {
|
||||
Status status = ValidateKeyId(key_params.key_id);
|
||||
if (!status.ok()) return status;
|
||||
if (key_params.content_ivs.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Content IVs is empty."};
|
||||
}
|
||||
for (int i = 0; i < key_params.content_ivs.size(); i++) {
|
||||
status = ValidateContentIv(key_params.content_ivs[i]);
|
||||
if (!status.ok()) return status;
|
||||
}
|
||||
return OkStatus();
|
||||
|
||||
return ValidateContentIv(key_params.content_iv);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -27,14 +28,8 @@ namespace cas {
|
||||
struct KeyParameters {
|
||||
std::string key_id;
|
||||
std::string key_data;
|
||||
// TODO(user): wrapped_key_data does not seem to be used, but assumed
|
||||
// to exist by unit tests.
|
||||
std::string wrapped_key_data;
|
||||
// wrapped_key_iv is randomly generated right before it is used to encrypt
|
||||
// key_data in ecm.cc.
|
||||
std::string wrapped_key_iv;
|
||||
// TODO(user): Probably only need a single content_iv instead of a vector.
|
||||
std::vector<std::string> content_ivs;
|
||||
std::string content_iv;
|
||||
};
|
||||
|
||||
// EcmParameters holds information that is needed by the EcmGenerator. It is
|
||||
@@ -48,6 +43,7 @@ struct EcmParameters {
|
||||
// TODO(user): rotation_periods does not seem to be used, but assumed
|
||||
// to exist by unit tests.
|
||||
uint32_t rotation_periods;
|
||||
std::string track_type;
|
||||
// TODO(user): Consider changing the vector to just two variables,
|
||||
// one for even key, the other for odd key.
|
||||
std::vector<KeyParameters> key_params;
|
||||
@@ -64,8 +60,8 @@ class EcmGenerator {
|
||||
virtual std::string GenerateEcm(const EcmParameters& params);
|
||||
|
||||
// Query the state of this ECM Generator
|
||||
bool initialized() { return initialized_; }
|
||||
bool rotation_enabled() { return rotation_enabled_; }
|
||||
bool initialized() const { return initialized_; }
|
||||
bool rotation_enabled() const { return rotation_enabled_; }
|
||||
|
||||
void set_ecm(std::unique_ptr<Ecm> ecm) { ecm_ = std::move(ecm); }
|
||||
|
||||
@@ -75,16 +71,14 @@ class EcmGenerator {
|
||||
Status ProcessEcmParameters(const EcmParameters& ecm_params,
|
||||
std::vector<EntitledKeyInfo>* keys);
|
||||
|
||||
Status ProcessEcmParameters(const EcmParameters& ecm_params);
|
||||
Status ValidateKeyId(const std::string& id);
|
||||
Status ValidateKeyData(const std::string& key_data);
|
||||
Status ValidateWrappedKeyIv(const std::string& iv);
|
||||
Status ValidateIv(const std::string& iv, size_t required_size);
|
||||
Status ValidateContentIv(const std::string& iv);
|
||||
Status ValidateKeyParameters(const KeyParameters& key_params);
|
||||
Status ValidateKeyId(const std::string& id) const;
|
||||
Status ValidateKeyData(const std::string& key_data) const;
|
||||
Status ValidateWrappedKeyIv(const std::string& iv) const;
|
||||
Status ValidateIv(const std::string& iv, size_t required_size) const;
|
||||
Status ValidateContentIv(const std::string& iv) const;
|
||||
Status ValidateKeyParameters(const KeyParameters& key_params) const;
|
||||
|
||||
bool initialized_ = false;
|
||||
uint32_t generation_ = 0;
|
||||
bool rotation_enabled_ = false;
|
||||
uint32_t current_key_index_ = 0;
|
||||
bool current_key_even_ = true;
|
||||
|
||||
@@ -34,19 +34,16 @@ constexpr char kEntitlementKeyDouble[] = "testEKIdabcdefgh";
|
||||
|
||||
constexpr char kEcmKeyIdSingle[] = "key-id-One123456";
|
||||
constexpr char kEcmKeyDataSingle[] = "0123456701234567";
|
||||
constexpr char kEcmWrappedKeySingle[] = "1234567890123456";
|
||||
constexpr char kEcmWrappedKeyIvSingle[] = "abcdefghacbdefgh";
|
||||
constexpr char kEcmContentIvSingle[] = "ABCDEFGH";
|
||||
|
||||
constexpr char kEcmKeyIdEven[] = "key-Id-One123456";
|
||||
constexpr char kEcmKeyDataEven[] = "KKEEYYDDAATTAAee";
|
||||
constexpr char kEcmWrappedKeyEven[] = "1234567890123456";
|
||||
constexpr char kEcmWrappedKeyIvEven[] = "abcdefghacbdefgh";
|
||||
constexpr char kEcmContentIvEven[] = "ABCDEFGH";
|
||||
|
||||
constexpr char kEcmKeyIdOdd[] = "key-Id-Two456789";
|
||||
constexpr char kEcmKeyDataOdd[] = "kkeeyyddaattaaOO";
|
||||
constexpr char kEcmWrappedKeyOdd[] = "9876543210654321";
|
||||
constexpr char kEcmWrappedKeyIvOdd[] = "a1c2e3g4a1c2e3g4";
|
||||
constexpr char kEcmContentIvOdd[] = "AaCbEcGd";
|
||||
|
||||
@@ -54,13 +51,15 @@ constexpr char kFakeCasEncryptionResponseKeyId[] = "fake_key_id.....";
|
||||
constexpr char kFakeCasEncryptionResponseKeyData[] =
|
||||
"fakefakefakefakefakefakefakefake";
|
||||
|
||||
constexpr char kTrackType[] = "SD";
|
||||
|
||||
Status HandleCasEncryptionRequest(const std::string& request_string,
|
||||
std::string* signed_response_string) {
|
||||
CasEncryptionRequest request;
|
||||
request.ParseFromString(request_string);
|
||||
|
||||
CasEncryptionResponse response;
|
||||
response.set_status(CasEncryptionResponse_Status_OK);
|
||||
response.set_status(CasEncryptionResponse::OK);
|
||||
response.set_content_id(request.content_id());
|
||||
for (const auto& track_type : request.track_types()) {
|
||||
if (request.key_rotation()) {
|
||||
@@ -69,19 +68,19 @@ Status HandleCasEncryptionRequest(const std::string& request_string,
|
||||
key->set_key_id(kFakeCasEncryptionResponseKeyId);
|
||||
key->set_key(kFakeCasEncryptionResponseKeyData);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
|
||||
// Add the Odd key.
|
||||
key = response.add_entitlement_keys();
|
||||
key->set_key_id(kFakeCasEncryptionResponseKeyId);
|
||||
key->set_key(kFakeCasEncryptionResponseKeyData);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_ODD);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
|
||||
} else {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id(kFakeCasEncryptionResponseKeyId);
|
||||
key->set_key(kFakeCasEncryptionResponseKeyData);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
|
||||
}
|
||||
}
|
||||
std::string response_string;
|
||||
@@ -96,8 +95,7 @@ Status HandleCasEncryptionRequest(const std::string& request_string,
|
||||
|
||||
class EcmGeneratorTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
}
|
||||
void SetUp() override {}
|
||||
|
||||
Status ProcessEcmParameters(const EcmParameters& params,
|
||||
std::vector<EntitledKeyInfo>* keys) {
|
||||
@@ -112,6 +110,7 @@ class EcmGeneratorTest : public testing::Test {
|
||||
params->entitlement_key_id = kEntitlementKeySingle;
|
||||
params->rotation_enabled = false;
|
||||
params->key_params.push_back(kKeyParamsSingle);
|
||||
params->track_type = kTrackType;
|
||||
}
|
||||
|
||||
void SetTestConfig2(EcmParameters* params) {
|
||||
@@ -122,6 +121,7 @@ class EcmGeneratorTest : public testing::Test {
|
||||
params->rotation_periods = 2;
|
||||
params->key_params.push_back(kKeyParamsEven);
|
||||
params->key_params.push_back(kKeyParamsOdd);
|
||||
params->track_type = kTrackType;
|
||||
}
|
||||
|
||||
// Call this to setup the guts (Ecm) of the ECM Generator.
|
||||
@@ -130,7 +130,7 @@ class EcmGeneratorTest : public testing::Test {
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
ecm_init_params_.key_rotation_enabled = key_rotation_enabled;
|
||||
ecm_init_params_.track_types.push_back("SD");
|
||||
ecm_init_params_.track_types.push_back(kTrackType);
|
||||
ASSERT_OK(ecm_->Initialize(kContentId, kProvider, ecm_init_params_,
|
||||
&entitlement_request));
|
||||
ASSERT_OK(
|
||||
@@ -141,17 +141,14 @@ class EcmGeneratorTest : public testing::Test {
|
||||
|
||||
const KeyParameters kKeyParamsSingle{kEcmKeyIdSingle,
|
||||
kEcmKeyDataSingle,
|
||||
kEcmWrappedKeySingle,
|
||||
kEcmWrappedKeyIvSingle,
|
||||
{kEcmContentIvSingle}};
|
||||
const KeyParameters kKeyParamsEven{kEcmKeyIdEven,
|
||||
kEcmKeyDataEven,
|
||||
kEcmWrappedKeyEven,
|
||||
kEcmWrappedKeyIvEven,
|
||||
{kEcmContentIvEven}};
|
||||
const KeyParameters kKeyParamsOdd{kEcmKeyIdOdd,
|
||||
kEcmKeyDataOdd,
|
||||
kEcmWrappedKeyOdd,
|
||||
kEcmWrappedKeyIvOdd,
|
||||
{kEcmContentIvOdd}};
|
||||
|
||||
@@ -189,7 +186,7 @@ TEST_F(EcmGeneratorTest, GenerateNoRotation) {
|
||||
// Expected size (bytes):
|
||||
// CA system ID: 2 bytes
|
||||
// version: 1 byte
|
||||
// generation + flags: 1 byte
|
||||
// flags: 1 byte
|
||||
// flags: 1 byte
|
||||
// entitlement key ID: 16 bytes
|
||||
// Single key: ID (16), Data (16), IV (16), IV (8) = 56
|
||||
@@ -197,12 +194,13 @@ TEST_F(EcmGeneratorTest, GenerateNoRotation) {
|
||||
ASSERT_EQ(77, ecm_string.size());
|
||||
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
|
||||
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
|
||||
EXPECT_EQ('\x01', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x02', ecm_string[3]); // generation + flags
|
||||
EXPECT_EQ('\x02', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x02', ecm_string[3]); // flags
|
||||
EXPECT_EQ('\x80', ecm_string[4]); // flags
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
|
||||
EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16));
|
||||
EXPECT_NE(kEcmWrappedKeySingle, ecm_string.substr(37, 16));
|
||||
// Key data has been wrapped (encrypted).
|
||||
EXPECT_NE(kEcmKeyDataSingle, ecm_string.substr(37, 16));
|
||||
EXPECT_EQ(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16));
|
||||
// Unwrap key and compare with original.
|
||||
std::string wrapping_key = kFakeCasEncryptionResponseKeyData;
|
||||
@@ -223,16 +221,16 @@ TEST_F(EcmGeneratorTest, Generate2NoRotation) {
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
|
||||
// Second generate should have higher generation number.
|
||||
ASSERT_EQ(77, ecm_string.size());
|
||||
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
|
||||
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
|
||||
EXPECT_EQ('\x01', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x0A', ecm_string[3]); // generation + flags
|
||||
EXPECT_EQ('\x02', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x02', ecm_string[3]); // flags
|
||||
EXPECT_EQ('\x80', ecm_string[4]); // flags
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
|
||||
EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16));
|
||||
EXPECT_NE(kEcmWrappedKeySingle, ecm_string.substr(37, 16));
|
||||
// Key data has been wrapped (encrypted).
|
||||
EXPECT_NE(kEcmKeyDataSingle, ecm_string.substr(37, 16));
|
||||
EXPECT_EQ(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16));
|
||||
// Unwrap key and compare with original.
|
||||
std::string wrapping_key = kFakeCasEncryptionResponseKeyData;
|
||||
@@ -278,17 +276,19 @@ TEST_F(EcmGeneratorTest, GenerateSimpleRotation) {
|
||||
ASSERT_EQ(149, ecm_string.size());
|
||||
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
|
||||
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
|
||||
EXPECT_EQ('\x01', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x03', ecm_string[3]); // generation + flags
|
||||
EXPECT_EQ('\x02', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x03', ecm_string[3]); // flags
|
||||
EXPECT_EQ('\x80', ecm_string[4]); // flags
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
|
||||
EXPECT_EQ(kEcmKeyIdEven, ecm_string.substr(21, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyEven, ecm_string.substr(37, 16));
|
||||
// Key data has been wrapped (encrypted).
|
||||
EXPECT_NE(kEcmKeyDataEven, ecm_string.substr(37, 16));
|
||||
EXPECT_EQ(kEcmWrappedKeyIvEven, ecm_string.substr(53, 16));
|
||||
EXPECT_EQ(kEcmContentIvEven, ecm_string.substr(69, 8));
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(77, 16));
|
||||
EXPECT_EQ(kEcmKeyIdOdd, ecm_string.substr(93, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyOdd, ecm_string.substr(109, 16));
|
||||
// Key data has been wrapped (encrypted).
|
||||
EXPECT_NE(kEcmKeyDataOdd, ecm_string.substr(109, 16));
|
||||
EXPECT_EQ(kEcmWrappedKeyIvOdd, ecm_string.substr(125, 16));
|
||||
EXPECT_EQ(kEcmContentIvOdd, ecm_string.substr(141, 8));
|
||||
// Unwrap even key and compare with original.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
@@ -61,7 +62,7 @@ class MockEcm : public Ecm {
|
||||
MockEcm() = default;
|
||||
~MockEcm() override = default;
|
||||
|
||||
MOCK_CONST_METHOD0(generation, uint32_t());
|
||||
MOCK_CONST_METHOD0(age_restriction, uint8_t());
|
||||
MOCK_CONST_METHOD0(crypto_mode, CryptoMode());
|
||||
MOCK_CONST_METHOD0(paired_keys_required, bool());
|
||||
MOCK_CONST_METHOD0(content_iv_size, size_t());
|
||||
@@ -71,7 +72,8 @@ class MockEcm : public Ecm {
|
||||
}
|
||||
|
||||
virtual Status MockWrapEntitledKeys(
|
||||
const std::string& track_type, const std::vector<EntitledKeyInfo*>& keys) {
|
||||
const std::string& track_type,
|
||||
const std::vector<EntitledKeyInfo*>& keys) {
|
||||
for (auto entitled_key : keys) {
|
||||
entitled_key->entitlement_key_id = "entitlement_Mock";
|
||||
entitled_key->wrapped_key_value = "MockMockMockMock";
|
||||
@@ -80,9 +82,11 @@ class MockEcm : public Ecm {
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
void MockSetup(bool two_keys, const std::string& track_type, uint32_t generation,
|
||||
CryptoMode crypto_mode, size_t civ_size) {
|
||||
EXPECT_CALL(*this, generation()).WillRepeatedly(Return(generation));
|
||||
void MockSetup(bool two_keys, const std::string& track_type,
|
||||
uint8_t age_restriction, CryptoMode crypto_mode,
|
||||
size_t civ_size) {
|
||||
EXPECT_CALL(*this, age_restriction())
|
||||
.WillRepeatedly(Return(age_restriction));
|
||||
EXPECT_CALL(*this, crypto_mode()).WillRepeatedly(Return(crypto_mode));
|
||||
EXPECT_CALL(*this, paired_keys_required()).WillRepeatedly(Return(two_keys));
|
||||
EXPECT_CALL(*this, content_iv_size()).WillRepeatedly(Return(civ_size));
|
||||
@@ -108,27 +112,28 @@ class EcmTest : public testing::Test {
|
||||
params_simple_.track_types.push_back(kTrackTypeSD);
|
||||
}
|
||||
|
||||
virtual void InitParams(EcmInitParameters* params, const std::string& track_type) {
|
||||
virtual void InitParams(EcmInitParameters* params,
|
||||
const std::string& track_type) {
|
||||
params->track_types.push_back(track_type);
|
||||
}
|
||||
|
||||
virtual void InitParams(EcmInitParameters* params, const std::string& track_type,
|
||||
EcmIvSize content_iv) {
|
||||
virtual void InitParams(EcmInitParameters* params,
|
||||
const std::string& track_type, EcmIvSize content_iv) {
|
||||
params->track_types.push_back(track_type);
|
||||
params->content_iv_size = content_iv;
|
||||
}
|
||||
|
||||
virtual void ServerCall(const std::string& request_string,
|
||||
std::string* signed_response_string, bool report_status_ok,
|
||||
bool generate_valid_ecm) {
|
||||
std::string* signed_response_string,
|
||||
bool report_status_ok, bool generate_valid_ecm) {
|
||||
CasEncryptionRequest request;
|
||||
ASSERT_TRUE(request.ParseFromString(request_string));
|
||||
|
||||
CasEncryptionResponse response;
|
||||
if (!report_status_ok) {
|
||||
response.set_status(CasEncryptionResponse_Status_INTERNAL_ERROR);
|
||||
response.set_status(CasEncryptionResponse::INTERNAL_ERROR);
|
||||
} else {
|
||||
response.set_status(CasEncryptionResponse_Status_OK);
|
||||
response.set_status(CasEncryptionResponse::OK);
|
||||
response.set_content_id(request.content_id());
|
||||
for (const auto& track_type : request.track_types()) {
|
||||
if (request.key_rotation()) {
|
||||
@@ -137,18 +142,18 @@ class EcmTest : public testing::Test {
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
|
||||
// Add the Odd key.
|
||||
key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_ODD);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
|
||||
if (!generate_valid_ecm) {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
|
||||
key->set_track_type(track_type);
|
||||
}
|
||||
} else {
|
||||
@@ -156,12 +161,12 @@ class EcmTest : public testing::Test {
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
|
||||
if (!generate_valid_ecm) {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
|
||||
key->set_track_type(track_type);
|
||||
}
|
||||
}
|
||||
@@ -189,37 +194,41 @@ class EcmTest : public testing::Test {
|
||||
|
||||
class EcmSerializeEcmTest : public EcmTest {
|
||||
public:
|
||||
void ValidateEcmHeaderFields(const std::string& ecm_string, bool rotation_enabled,
|
||||
int gen, CryptoMode crypto_mode,
|
||||
EcmIvSize content_iv) {
|
||||
void ValidateEcmHeaderFields(const std::string& ecm_string,
|
||||
bool rotation_enabled, int age_restriction,
|
||||
CryptoMode crypto_mode, EcmIvSize content_iv) {
|
||||
EXPECT_THAT('\x4A', ecm_string[0]);
|
||||
EXPECT_THAT('\xD4', ecm_string[1]);
|
||||
EXPECT_THAT('\x01', ecm_string[2]); // version
|
||||
EXPECT_THAT(gen, ((ecm_string[3] >> 3) & 31));
|
||||
EXPECT_THAT(static_cast<int>(crypto_mode), ((ecm_string[3] >> 1) & 3));
|
||||
EXPECT_THAT('\x02', ecm_string[2]); // version
|
||||
EXPECT_THAT(static_cast<int>(crypto_mode), ((ecm_string[3] >> 1) & 0xf));
|
||||
EXPECT_THAT(rotation_enabled ? 1 : 0, ecm_string[3] & 1);
|
||||
EXPECT_THAT(1, ((ecm_string[4] >> 7) & 1)); // wrapped key IV size, MB 1
|
||||
EXPECT_THAT(static_cast<int>(content_iv), ((ecm_string[4] >> 6) & 1));
|
||||
EXPECT_THAT(0, ecm_string[4] & 63); // zero padding
|
||||
EXPECT_THAT(age_restriction, ((ecm_string[4] >> 1) & 0x1f));
|
||||
EXPECT_THAT(0, ecm_string[4] & 1); // zero padding
|
||||
}
|
||||
|
||||
void ValidateEcmFieldsOneKey(const std::string& buf_string, int gen,
|
||||
CryptoMode crypto_mode, EcmIvSize content_iv) {
|
||||
void ValidateEcmFieldsOneKey(const std::string& buf_string,
|
||||
int age_restriction, CryptoMode crypto_mode,
|
||||
EcmIvSize content_iv) {
|
||||
size_t expected_size = kEcmHeaderSize + kEcmKeyInfoSize + kEcmIvSize16 +
|
||||
IvExpectedSize(content_iv);
|
||||
size_t ecm_len = buf_string.size();
|
||||
EXPECT_THAT(expected_size, ecm_len);
|
||||
ValidateEcmHeaderFields(buf_string, false, gen, crypto_mode, content_iv);
|
||||
ValidateEcmHeaderFields(buf_string, false, age_restriction, crypto_mode,
|
||||
content_iv);
|
||||
}
|
||||
|
||||
void ValidateEcmFieldsTwoKeys(const std::string& buf_string, int gen,
|
||||
CryptoMode crypto_mode, EcmIvSize content_iv) {
|
||||
void ValidateEcmFieldsTwoKeys(const std::string& buf_string,
|
||||
int age_restriction, CryptoMode crypto_mode,
|
||||
EcmIvSize content_iv) {
|
||||
size_t expected_size =
|
||||
kEcmHeaderSize +
|
||||
(2 * (kEcmKeyInfoSize + kEcmIvSize16 + IvExpectedSize(content_iv)));
|
||||
size_t ecm_len = buf_string.size();
|
||||
EXPECT_THAT(expected_size, ecm_len);
|
||||
ValidateEcmHeaderFields(buf_string, true, gen, crypto_mode, content_iv);
|
||||
ValidateEcmHeaderFields(buf_string, true, age_restriction, crypto_mode,
|
||||
content_iv);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -227,9 +236,8 @@ TEST_F(EcmTest, GenerateEcmNotInitialized) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1;
|
||||
std::string ecm_data;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm_data, &gen)
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm_data)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
@@ -238,10 +246,9 @@ TEST_F(EcmTest, GenerateEcm2NotInitialized) {
|
||||
EntitledKeyInfo key1;
|
||||
EntitledKeyInfo key2;
|
||||
std::string ecm_data;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data, &gen)
|
||||
.error_code());
|
||||
EXPECT_EQ(
|
||||
error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, SetResponseNotInitialized) {
|
||||
@@ -314,12 +321,11 @@ TEST_F(EcmTest, GenerateWithNoEntitlementOneKeyFail) {
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
// This will fail because the entitlement key has not been acquired
|
||||
// (via server call and ProcessCasEncryptionResponse()).
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen)
|
||||
.error_code());
|
||||
EXPECT_EQ(
|
||||
error::INTERNAL,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateWithNoEntitlementTwoKeysFail) {
|
||||
@@ -332,12 +338,10 @@ TEST_F(EcmTest, GenerateWithNoEntitlementTwoKeysFail) {
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
// This will fail because the entitlement keys have not been acquired
|
||||
// (via server call and ProcessCasEncryptionResponse()).
|
||||
EXPECT_EQ(
|
||||
error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, RequestNullFail) {
|
||||
@@ -361,8 +365,7 @@ TEST_F(EcmTest, GenerateOneKeyOK) {
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen));
|
||||
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, BadResponseFail) {
|
||||
@@ -395,27 +398,7 @@ TEST_F(EcmTest, GenerateTwoKeysOK) {
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateOneKeyNoGenFail) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, nullptr)
|
||||
.error_code());
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateOneKeyBadKeyIdFail) {
|
||||
@@ -432,10 +415,9 @@ TEST_F(EcmTest, GenerateOneKeyBadKeyIdFail) {
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen)
|
||||
.error_code());
|
||||
EXPECT_EQ(
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateOneKeyWrong) {
|
||||
@@ -452,13 +434,10 @@ TEST_F(EcmTest, GenerateOneKeyWrong) {
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen));
|
||||
EXPECT_THAT(0, gen);
|
||||
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm));
|
||||
EXPECT_THAT(77, ecm.size());
|
||||
EXPECT_EQ(
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateEcm(&key1, &key1, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateEcm(&key1, &key1, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateTwoKeysIvSizeFail) {
|
||||
@@ -477,10 +456,8 @@ TEST_F(EcmTest, GenerateTwoKeysIvSizeFail) {
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateTwoKeysIvSize16x8OK) {
|
||||
@@ -498,12 +475,9 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x8OK) {
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen));
|
||||
EXPECT_THAT(0, gen);
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
EXPECT_THAT(149, ecm.size());
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen));
|
||||
EXPECT_THAT(1, gen);
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
EXPECT_THAT(149, ecm.size());
|
||||
}
|
||||
|
||||
@@ -523,12 +497,9 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x16OK) {
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen));
|
||||
EXPECT_THAT(0, gen);
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
EXPECT_THAT(165, ecm.size());
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen));
|
||||
EXPECT_THAT(1, gen);
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
EXPECT_THAT(165, ecm.size());
|
||||
}
|
||||
|
||||
@@ -548,10 +519,8 @@ TEST_F(EcmTest, GenerateThreeKeysIvSize16x16Fail) {
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(
|
||||
error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
// TODO(user): Add more unit tests for error paths around SerializeEcm.
|
||||
@@ -568,14 +537,10 @@ TEST_F(EcmSerializeEcmTest, SerializeEcmDoubleKey16ByteIvs) {
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize16);
|
||||
|
||||
EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1));
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 1, CryptoMode::kAesCtr, kIvSize16);
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize16);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializeEcmTest, SerializeEcmSingleKey16ByteIvs) {
|
||||
@@ -589,14 +554,10 @@ TEST_F(EcmSerializeEcmTest, SerializeEcmSingleKey16ByteIvs) {
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize16);
|
||||
|
||||
EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1));
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsOneKey(buf_string, 1, CryptoMode::kAesCtr, kIvSize16);
|
||||
ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize16);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializeEcmTest, SerializeEcmDoubleKey16x8ByteIvs) {
|
||||
@@ -612,14 +573,10 @@ TEST_F(EcmSerializeEcmTest, SerializeEcmDoubleKey16x8ByteIvs) {
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize8);
|
||||
|
||||
EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1));
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 1, CryptoMode::kAesCtr, kIvSize8);
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize8);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializeEcmTest, SerializeEcmSingleKey16x8ByteIvs) {
|
||||
@@ -633,14 +590,35 @@ TEST_F(EcmSerializeEcmTest, SerializeEcmSingleKey16x8ByteIvs) {
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize8);
|
||||
|
||||
EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1));
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize8);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializeEcmTest, SerializeEcmAgeRestriction) {
|
||||
MockEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid3_iv_16_16_;
|
||||
EntitledKeyInfo key2 = valid4_iv_16_16_;
|
||||
uint8_t age_restriction = 18;
|
||||
|
||||
ecm_gen.MockSetup(true, kTrackTypeSD, age_restriction, CryptoMode::kAesCtr,
|
||||
16);
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(&key1);
|
||||
keys.push_back(&key2);
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
EXPECT_CALL(ecm_gen, age_restriction())
|
||||
.WillRepeatedly(Return(age_restriction));
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsTwoKeys(buf_string, age_restriction, CryptoMode::kAesCtr,
|
||||
kIvSize16);
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsOneKey(buf_string, 1, CryptoMode::kAesCtr, kIvSize8);
|
||||
ValidateEcmFieldsTwoKeys(buf_string, age_restriction, CryptoMode::kAesCtr,
|
||||
kIvSize16);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -19,18 +19,23 @@
|
||||
#include "glog/logging.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "common/crypto_util.h"
|
||||
#include "common/random_util.h"
|
||||
#include "common/status.h"
|
||||
#include "example/constants.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_generator.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_util.h"
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
// CA System ID for Widevine.
|
||||
static constexpr uint16_t kWidevineSystemId = 0x4AD4;
|
||||
// 'section_TSpkt_flag' defines the format of the ECM.
|
||||
// We only support MPEG-2 transport stream packet format for now.
|
||||
// We do NOT support MPEG-2 section format yet.
|
||||
@@ -40,66 +45,145 @@ static constexpr uint8_t kSectionTSpktFlag = 0x01;
|
||||
// suppported by an ECMG on a channel.
|
||||
// A value of 0 means that this maximum is not known.
|
||||
static constexpr uint16_t kMaxStream = 0;
|
||||
// 'lead_CW' parameter defines the number of contro lwords required in
|
||||
// advance to build an ECM.
|
||||
// TODO(user): Support other values of 'lead_CW' in combination with
|
||||
// other values for 'CW_per_msg'.
|
||||
static constexpr uint8_t kLeadCw = 1;
|
||||
// ' CW_per_msg' parameter defines the number of control words needed by the
|
||||
// ECMG per control word provision message.
|
||||
static constexpr uint8_t kCwPerMsg = 2;
|
||||
// Size of Wrapped_Key_IV (IV for decrypting the wrapped key) in bytes.
|
||||
static constexpr size_t kWrappedKeyIvSizeBytes = 16;
|
||||
// Largest age restriction value allowed.
|
||||
static constexpr size_t kMaxAllowedAgeRestriction = 99;
|
||||
// Size of entitlement key id in bytes.
|
||||
static constexpr size_t kEntitlementKeyIdSizeBytes = 16;
|
||||
// Size of entitlement key value in bytes.
|
||||
static constexpr size_t kEntitlementKeyValueSizeBytes = 32;
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
Status ProcessPrivateParameters(const char* const request, uint16_t param_type,
|
||||
uint16_t param_length, size_t* offset,
|
||||
EcmgParameters* params) {
|
||||
switch (param_type) {
|
||||
case CRYPTO_MODE:
|
||||
params->crypto_mode = std::string(request + *offset, param_length);
|
||||
*offset += param_length;
|
||||
break;
|
||||
case TRACK_TYPES:
|
||||
params->track_types.push_back(
|
||||
std::string(request + *offset, param_length));
|
||||
*offset += param_length;
|
||||
break;
|
||||
case STREAM_TRACK_TYPE:
|
||||
params->stream_track_type = std::string(request + *offset, param_length);
|
||||
*offset += param_length;
|
||||
break;
|
||||
case CONTENT_ID:
|
||||
params->content_id = std::string(request + *offset, param_length);
|
||||
*offset += param_length;
|
||||
break;
|
||||
case CONTENT_PROVIDER:
|
||||
params->content_provider = std::string(request + *offset, param_length);
|
||||
*offset += param_length;
|
||||
break;
|
||||
case CONTENT_IV:
|
||||
if (params->content_ivs.size() >= 2) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
"We only support up to 2 content ivs in the "
|
||||
"CW_provision request");
|
||||
}
|
||||
params->content_ivs.push_back(
|
||||
std::string(request + *offset, param_length));
|
||||
*offset += param_length;
|
||||
break;
|
||||
case AGE_RESTRICTION:
|
||||
if (param_length != AGE_RESTRICTION_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
params->age_restriction = request[*offset];
|
||||
*offset += param_length;
|
||||
break;
|
||||
case ENTITLEMENT_ID_KEY_COMBINATION: {
|
||||
if (param_length !=
|
||||
kEntitlementKeyIdSizeBytes + kEntitlementKeyValueSizeBytes) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
params->entitlement_comb.emplace_back();
|
||||
EntitlementIdKeyComb* combination = ¶ms->entitlement_comb.back();
|
||||
combination->key_id =
|
||||
std::string(request + *offset, kEntitlementKeyIdSizeBytes);
|
||||
*offset += kEntitlementKeyIdSizeBytes;
|
||||
combination->key_value =
|
||||
std::string(request + *offset, kEntitlementKeyValueSizeBytes);
|
||||
*offset += kEntitlementKeyValueSizeBytes;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return Status(
|
||||
error::UNIMPLEMENTED,
|
||||
absl::StrCat("No implementation yet to process parameter of type ",
|
||||
param_type));
|
||||
break;
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status HandleAccessCriteria(const char* const request, size_t request_length,
|
||||
EcmgParameters* params) {
|
||||
DCHECK(request);
|
||||
DCHECK(params);
|
||||
Status status;
|
||||
uint16_t param_type;
|
||||
uint16_t param_length;
|
||||
size_t offset = 0;
|
||||
while (offset < request_length) {
|
||||
BigEndianToHost16(¶m_type, request + offset);
|
||||
offset += PARAMETER_TYPE_SIZE;
|
||||
BigEndianToHost16(¶m_length, request + offset);
|
||||
offset += PARAMETER_LENGTH_SIZE;
|
||||
status = ProcessPrivateParameters(request, param_type, param_length,
|
||||
&offset, params);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
// Local helper function that processes all the params in an ECMG request.
|
||||
Status HandleParameters(const char* const request, size_t request_length,
|
||||
EcmgParameters* params) {
|
||||
DCHECK(request);
|
||||
DCHECK(params);
|
||||
Status status;
|
||||
uint16_t param_type;
|
||||
uint16_t param_length;
|
||||
// 'offset' is used to track where we are within |request|.
|
||||
size_t offset = 0;
|
||||
// There could be CW_per_msg instances of CP_CW_combinations,
|
||||
// so we need to track how many we have processed so far
|
||||
// in order to know where to store the next CP_CW_combination.
|
||||
int current_cp_cw_combination_index = 0;
|
||||
while (offset != request_length) {
|
||||
while (offset < request_length) {
|
||||
BigEndianToHost16(¶m_type, request + offset);
|
||||
offset += PARAMETER_TYPE_SIZE;
|
||||
BigEndianToHost16(¶m_length, request + offset);
|
||||
offset += PARAMETER_LENGTH_SIZE;
|
||||
switch (param_type) {
|
||||
case ACCESS_CRITERIA: {
|
||||
LOG(WARNING) << "Ignoring access_criteria parameter of " << param_length
|
||||
<< " bytes long";
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case CP_CW_COMBINATION: {
|
||||
if (current_cp_cw_combination_index > 2) {
|
||||
// We can have at most 3 CP_CW_Combinations.
|
||||
if (params->cp_cw_combinations.size() >= 2) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
"We only support up to 2 control words in the "
|
||||
"CW_provision request");
|
||||
}
|
||||
EcmgCpCwCombination* combination =
|
||||
¶ms->cp_cw_combinations[current_cp_cw_combination_index++];
|
||||
params->cp_cw_combinations.emplace_back();
|
||||
EcmgCpCwCombination* combination = ¶ms->cp_cw_combinations.back();
|
||||
BigEndianToHost16(&combination->cp, request + offset);
|
||||
offset += CP_SIZE;
|
||||
size_t cw_size = param_length - CP_SIZE;
|
||||
combination->cw = std::string(request + offset, cw_size);
|
||||
offset += cw_size;
|
||||
// TODO(user): This is a temporary hack to let the ECM generator
|
||||
// know how many keys to include in the ECM.
|
||||
// CW_per_msg should have been set during channel set-up instead.
|
||||
params->cw_per_msg = current_cp_cw_combination_index;
|
||||
break;
|
||||
}
|
||||
case CP_DURATION: {
|
||||
case CP_DURATION:
|
||||
if (param_length != CP_DURATION_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
@@ -108,8 +192,7 @@ Status HandleParameters(const char* const request, size_t request_length,
|
||||
BigEndianToHost16(¶ms->cp_duration, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case CP_NUMBER: {
|
||||
case CP_NUMBER:
|
||||
if (param_length != CP_NUMBER_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
@@ -118,14 +201,12 @@ Status HandleParameters(const char* const request, size_t request_length,
|
||||
BigEndianToHost16(¶ms->cp_number, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case CW_ENCRYPTION: {
|
||||
case CW_ENCRYPTION:
|
||||
LOG(WARNING) << "Ignoring CW_encryption parameter of " << param_length
|
||||
<< " bytes long";
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case ECM_CHANNEL_ID: {
|
||||
case ECM_CHANNEL_ID:
|
||||
if (param_length != ECM_CHANNEL_ID_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
@@ -134,8 +215,7 @@ Status HandleParameters(const char* const request, size_t request_length,
|
||||
BigEndianToHost16(¶ms->ecm_channel_id, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case ECM_ID: {
|
||||
case ECM_ID:
|
||||
if (param_length != ECM_ID_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
@@ -144,8 +224,7 @@ Status HandleParameters(const char* const request, size_t request_length,
|
||||
BigEndianToHost16(¶ms->ecm_id, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case ECM_STREAM_ID: {
|
||||
case ECM_STREAM_ID:
|
||||
if (param_length != ECM_STREAM_ID_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
@@ -154,8 +233,7 @@ Status HandleParameters(const char* const request, size_t request_length,
|
||||
BigEndianToHost16(¶ms->ecm_stream_id, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case NOMINAL_CP_DURATION: {
|
||||
case NOMINAL_CP_DURATION:
|
||||
if (param_length != NOMINAL_CP_DURATION_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
@@ -164,8 +242,7 @@ Status HandleParameters(const char* const request, size_t request_length,
|
||||
BigEndianToHost16(¶ms->nominal_cp_duration, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case SUPER_CAS_ID: {
|
||||
case SUPER_CAS_ID:
|
||||
if (param_length != SUPER_CAS_ID_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
@@ -174,20 +251,26 @@ Status HandleParameters(const char* const request, size_t request_length,
|
||||
BigEndianToHost32(¶ms->super_cas_id, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return Status(
|
||||
error::UNIMPLEMENTED,
|
||||
absl::StrCat("No implementation yet to process parameter of type ",
|
||||
param_type));
|
||||
case ACCESS_CRITERIA:
|
||||
status = HandleAccessCriteria(request + offset, param_length, params);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
status = ProcessPrivateParameters(request, param_type, param_length,
|
||||
&offset, params);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
void BuildChannelError(uint16_t channel_id, uint16_t error_status, char* message,
|
||||
void BuildChannelError(uint16_t channel_id, uint16_t error_status,
|
||||
const std::string& error_info, char* message,
|
||||
size_t* message_length) {
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
@@ -197,7 +280,11 @@ void BuildChannelError(uint16_t channel_id, uint16_t error_status, char* message
|
||||
message_length);
|
||||
simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message,
|
||||
message_length);
|
||||
// No setting Error_information parameter yet.
|
||||
if (!error_info.empty()) {
|
||||
simulcrypt_util::AddParam(
|
||||
ERROR_INFORMATION, reinterpret_cast<const uint8_t*>(error_info.c_str()),
|
||||
error_info.size(), message, message_length);
|
||||
}
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
@@ -229,9 +316,13 @@ void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message,
|
||||
// max_comp_time here for now.
|
||||
simulcrypt_util::AddUint16Param(MIN_CP_DURATION, config->max_comp_time,
|
||||
message, message_length);
|
||||
simulcrypt_util::AddUint8Param(LEAD_CW, kLeadCw, message, message_length);
|
||||
simulcrypt_util::AddUint8Param(CW_PER_MESSAGE, kCwPerMsg, message,
|
||||
message_length);
|
||||
// LEAD_CW defines the number of control words required in advance to build an
|
||||
// ECM. When CW_PER_MESSAGE is 2, LEAD_CW should be 1; when CW_PER_MESSAGE is
|
||||
// 1, LEAD_CW should be 0.
|
||||
simulcrypt_util::AddUint8Param(LEAD_CW, config->number_of_content_keys - 1,
|
||||
message, message_length);
|
||||
simulcrypt_util::AddUint8Param(CW_PER_MESSAGE, config->number_of_content_keys,
|
||||
message, message_length);
|
||||
simulcrypt_util::AddUint16Param(MAX_COMP_TIME, config->max_comp_time, message,
|
||||
message_length);
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
@@ -239,7 +330,8 @@ void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message,
|
||||
}
|
||||
|
||||
void BuildStreamError(uint16_t channel_id, uint16_t stream_id, uint16_t error_status,
|
||||
char* message, size_t* message_length) {
|
||||
const std::string& error_info, char* message,
|
||||
size_t* message_length) {
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
simulcrypt_util::BuildMessageHeader(
|
||||
@@ -250,11 +342,31 @@ void BuildStreamError(uint16_t channel_id, uint16_t stream_id, uint16_t error_st
|
||||
message_length);
|
||||
simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message,
|
||||
message_length);
|
||||
// No setting Error_information parameter yet.
|
||||
if (!error_info.empty()) {
|
||||
simulcrypt_util::AddParam(
|
||||
ERROR_INFORMATION, reinterpret_cast<const uint8_t*>(error_info.c_str()),
|
||||
error_info.size(), message, message_length);
|
||||
}
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
uint16_t StatusToDvbErrorCode(const Status& status) {
|
||||
if (status.ok()) {
|
||||
LOG(ERROR) << "Converting OK status to error code.";
|
||||
return 0;
|
||||
}
|
||||
switch (status.error_code()) {
|
||||
case error::INVALID_ARGUMENT:
|
||||
return INVALID_VALUE_FOR_DVB_PARAMETER;
|
||||
case error::NOT_FOUND:
|
||||
return MISSING_MANDATORY_DVB_PARAMETER;
|
||||
case error::INTERNAL:
|
||||
default:
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void BuildStreamStatus(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id,
|
||||
uint8_t access_criteria_transfer_mode, char* message,
|
||||
size_t* message_length) {
|
||||
@@ -329,7 +441,8 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
size_t offset = 0;
|
||||
memcpy(&protocol_version, request, PROTOCOL_VERSION_SIZE);
|
||||
if (protocol_version != ECMG_SCS_PROTOCOL_VERSION) {
|
||||
// TODO(user): Should send an error response.
|
||||
BuildChannelError(0, UNSUPPORTED_PROTOCOL_VERSION, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
offset += PROTOCOL_VERSION_SIZE;
|
||||
@@ -340,7 +453,23 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
EcmgParameters params;
|
||||
Status status = HandleParameters(request + offset, request_length, ¶ms);
|
||||
if (!status.ok()) {
|
||||
// TODO(user): Should send an error response.
|
||||
LOG(ERROR) << status.ToString();
|
||||
switch (status.error_code()) {
|
||||
case error::INVALID_ARGUMENT:
|
||||
// TODO(user): Should use INCONSISTENT_LENGTH_FOR_DVB_PARAMETER in most
|
||||
// cases.
|
||||
BuildChannelError(params.ecm_channel_id,
|
||||
INVALID_VALUE_FOR_DVB_PARAMETER,
|
||||
status.error_message(), response, response_length);
|
||||
break;
|
||||
case error::UNIMPLEMENTED:
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_PARAMETER_TYPE_VALUE,
|
||||
status.error_message(), response, response_length);
|
||||
break;
|
||||
default:
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_ERROR,
|
||||
status.error_message(), response, response_length);
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (request_type) {
|
||||
@@ -348,6 +477,10 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
HandleChannelSetup(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_CHANNEL_TEST: {
|
||||
HandleChannelTest(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_CHANNEL_CLOSE: {
|
||||
HandleChannelClose(params, response, response_length);
|
||||
break;
|
||||
@@ -356,6 +489,10 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
HandleStreamSetup(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_STREAM_TEST: {
|
||||
HandleStreamTest(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_STREAM_CLOSE_REQUEST: {
|
||||
HandleStreamCloseRequest(params, response, response_length);
|
||||
break;
|
||||
@@ -365,7 +502,8 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Unhandled or unknown request types.
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_MESSAGE_TYPE_VALUE, "",
|
||||
response, response_length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -376,13 +514,19 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
// TODO(user): Check SUPER_CAS_ID, if it is unexpected, return
|
||||
// UNKNOWN_SUPER_CAS_ID_VALUE error.
|
||||
// There is always one (and only one) channel per TCP connection.
|
||||
if (channel_id_set_) {
|
||||
BuildChannelError(params.ecm_channel_id, INVAID_MESSAGE, response,
|
||||
BuildChannelError(params.ecm_channel_id, INVALID_MESSAGE, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
// The super_cas_id is a 32-bit identifier formed by the concatenation of the
|
||||
// CA_system_id (16 bit) and the CA_subsystem_id (16 bit).
|
||||
if ((params.super_cas_id >> 16) != kWidevineSystemId) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_SUPER_CAS_ID_VALUE, "",
|
||||
response, response_length);
|
||||
return;
|
||||
}
|
||||
// TODO(user): To support multi-threading of serving concurrent clients,
|
||||
// there needs to be a set of channel_ids shared by multiple client handlers
|
||||
// threads.
|
||||
@@ -390,8 +534,29 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
|
||||
// yes, return an error.
|
||||
channel_id_ = params.ecm_channel_id;
|
||||
channel_id_set_ = true;
|
||||
BuildChannelStatus(params.ecm_channel_id, ecmg_config_, response,
|
||||
response_length);
|
||||
|
||||
Status status = UpdatePrivateParameters(params, false);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << status.ToString();
|
||||
BuildChannelError(params.ecm_channel_id, StatusToDvbErrorCode(status),
|
||||
status.error_message(), response, response_length);
|
||||
return;
|
||||
}
|
||||
|
||||
BuildChannelStatus(channel_id_, ecmg_config_, response, response_length);
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) const {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
|
||||
response, response_length);
|
||||
return;
|
||||
}
|
||||
BuildChannelStatus(channel_id_, ecmg_config_, response, response_length);
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
|
||||
@@ -399,13 +564,13 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
if (channel_id_ != params.ecm_channel_id) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
UNKNOWN_ECM_CHANNEL_ID_VALUE, response, response_length);
|
||||
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
|
||||
response, response_length);
|
||||
return;
|
||||
}
|
||||
channel_id_set_ = false;
|
||||
streams_.clear();
|
||||
streams_info_.clear();
|
||||
*response_length = 0;
|
||||
}
|
||||
|
||||
@@ -414,46 +579,77 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
if (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,
|
||||
UNKNOWN_ECM_CHANNEL_ID_VALUE, response, response_length);
|
||||
return;
|
||||
}
|
||||
// TODO(user): To support multi-threading of serving concurrent clients,
|
||||
// there needs to be a set of stream_ids shared by multiple client handlers
|
||||
// threads.
|
||||
// And here we need to check if the stream_id is already in the set, if
|
||||
// yes, return an error.
|
||||
if (streams_.count(params.ecm_stream_id) > 0) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
ECM_STREAM_ID_VALUE_ALREADY_IN_USE, response,
|
||||
UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
// TODO(user): What do I do with the ECM_id here, currently not doing
|
||||
// anything with it?
|
||||
streams_[params.ecm_stream_id] = params.ecm_id;
|
||||
if (streams_info_.contains(params.ecm_stream_id)) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
ECM_STREAM_ID_VALUE_ALREADY_IN_USE, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
|
||||
streams_info_[params.ecm_stream_id] = absl::make_unique<EcmgStreamInfo>();
|
||||
streams_info_[params.ecm_stream_id]->ecm_id = params.ecm_id;
|
||||
|
||||
Status status = UpdatePrivateParameters(params, true);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << status.ToString();
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
StatusToDvbErrorCode(status), status.error_message(),
|
||||
response, response_length);
|
||||
return;
|
||||
}
|
||||
|
||||
BuildStreamStatus(params.ecm_channel_id, params.ecm_stream_id, params.ecm_id,
|
||||
ecmg_config_->access_criteria_transfer_mode, response,
|
||||
response_length);
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) const {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
if (!streams_info_.contains(params.ecm_stream_id)) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
UNKNOWN_ECM_STREAM_ID_VALUE, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
BuildStreamStatus(params.ecm_channel_id, params.ecm_stream_id,
|
||||
streams_info_.at(params.ecm_stream_id)->ecm_id,
|
||||
ecmg_config_->access_criteria_transfer_mode, response,
|
||||
response_length);
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
if (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,
|
||||
UNKNOWN_ECM_CHANNEL_ID_VALUE, response, response_length);
|
||||
UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
if (streams_.count(params.ecm_stream_id) == 0) {
|
||||
if (!streams_info_.contains(params.ecm_stream_id)) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
UNKNOWN_ECM_STREAM_ID_VALUE, response, response_length);
|
||||
UNKNOWN_ECM_STREAM_ID_VALUE, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
streams_.erase(params.ecm_stream_id);
|
||||
streams_info_.erase(params.ecm_stream_id);
|
||||
BuildStreamCloseResponse(params.ecm_channel_id, params.ecm_stream_id,
|
||||
response, response_length);
|
||||
}
|
||||
@@ -463,120 +659,270 @@ void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
if (channel_id_ != params.ecm_channel_id) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE,
|
||||
response, response_length);
|
||||
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
if (streams_.count(params.ecm_stream_id) == 0) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_STREAM_ID_VALUE,
|
||||
response, response_length);
|
||||
if (!streams_info_.contains(params.ecm_stream_id)) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
UNKNOWN_ECM_STREAM_ID_VALUE, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
if (params.cw_per_msg < kCwPerMsg) {
|
||||
BuildChannelError(params.ecm_channel_id,
|
||||
NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM, response,
|
||||
response_length);
|
||||
if (params.cp_cw_combinations.size() < ecmg_config_->number_of_content_keys) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
if (params.cp_cw_combinations.size() > ecmg_config_->number_of_content_keys) {
|
||||
LOG(WARNING) << "Too many control words have been received. Expecting "
|
||||
<< static_cast<int>(ecmg_config_->number_of_content_keys)
|
||||
<< " but received " << params.cp_cw_combinations.size() << ".";
|
||||
}
|
||||
|
||||
// Update private parameters based on access_criteria if any.
|
||||
Status status = UpdatePrivateParameters(params, true);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << status.ToString();
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
StatusToDvbErrorCode(status), status.error_message(),
|
||||
response, response_length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Request entitlement keys if they do not exist yet.
|
||||
if (ecm_ == nullptr) {
|
||||
status = CheckAndInitializeEcm(params);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << status.ToString();
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
StatusToDvbErrorCode(status), status.error_message(),
|
||||
response, response_length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Build Ecm datagram.
|
||||
uint8_t ecm_datagram[kTsPacketSize];
|
||||
BuildEcmDatagram(params, ecm_datagram);
|
||||
status = BuildEcmDatagram(params, ecm_datagram);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << status.ToString();
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
StatusToDvbErrorCode(status), "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Success. Send response message containing Ecm datagram.
|
||||
BuildEcmResponse(params.ecm_channel_id, params.ecm_stream_id,
|
||||
params.cp_number, ecm_datagram, response, response_length);
|
||||
}
|
||||
|
||||
void EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
uint8_t* ecm_datagram) {
|
||||
DCHECK(ecm_datagram);
|
||||
// TODO(user): Remove debug loop below.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
std::cout << "CW: ";
|
||||
for (int j = 0; j < params.cp_cw_combinations[i].cw.size(); j++) {
|
||||
printf("'\\x%02x', ",
|
||||
static_cast<uint16_t>(params.cp_cw_combinations[i].cw[j]));
|
||||
Status EcmgClientHandler::UpdatePrivateParameters(const EcmgParameters& params,
|
||||
bool stream_specific) {
|
||||
EcmgStreamInfo* stream_info =
|
||||
stream_specific ? streams_info_[params.ecm_stream_id].get() : nullptr;
|
||||
|
||||
if (params.age_restriction != 0xff) {
|
||||
if (params.age_restriction > kMaxAllowedAgeRestriction) {
|
||||
return {error::INVALID_ARGUMENT, "Age restriction too large."};
|
||||
}
|
||||
std::cout << std::endl;
|
||||
age_restriction_ = params.age_restriction;
|
||||
}
|
||||
|
||||
// Step 1: Generate entitlement keys.
|
||||
bool key_rotation_enabled = params.cw_per_msg > 1;
|
||||
// Create an instance of Ecm in order to set the entitlement keys.
|
||||
// TODO(user): The section of code below for constructing Ecm should
|
||||
// be optimized. There should be a single instance of Ecm for each stream.
|
||||
// Right now, this is hard to do because EcmGenerator contains the Ecm.
|
||||
std::unique_ptr<Ecm> ecm = absl::make_unique<Ecm>();
|
||||
// TODO(user): Revisit this hardcoded ecm_init_params.
|
||||
if (!params.crypto_mode.empty()) {
|
||||
if (!StringToCryptoMode(params.crypto_mode,
|
||||
stream_specific ? &stream_info->crypto_mode
|
||||
: &ecmg_config_->crypto_mode)) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Unknown crypto mode: ", params.crypto_mode, ".")};
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.track_types.empty()) {
|
||||
track_types_.assign(params.track_types.begin(), params.track_types.end());
|
||||
}
|
||||
|
||||
if (!params.stream_track_type.empty()) {
|
||||
if (stream_specific) {
|
||||
stream_info->track_type = params.stream_track_type;
|
||||
} else {
|
||||
LOG(WARNING) << "Ignoring stream track type received in channel config.";
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.content_id.empty()) {
|
||||
content_id_ = params.content_id;
|
||||
}
|
||||
|
||||
if (!params.content_provider.empty()) {
|
||||
content_provider_ = params.content_provider;
|
||||
}
|
||||
|
||||
if (!params.content_ivs.empty()) {
|
||||
if (params.content_ivs.size() < ecmg_config_->number_of_content_keys) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Not enough content ivs, expecting ",
|
||||
ecmg_config_->number_of_content_keys, ".")};
|
||||
}
|
||||
if (params.content_ivs[0].size() != 8 &&
|
||||
params.content_ivs[0].size() != 16) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Wrong content iv size: must be 8 or 16."};
|
||||
}
|
||||
for (size_t i = 1; i < ecmg_config_->number_of_content_keys; i++) {
|
||||
if (params.content_ivs[0].size() != params.content_ivs[i].size()) {
|
||||
return {error::INVALID_ARGUMENT, "Size of content ivs must match."};
|
||||
}
|
||||
}
|
||||
if (stream_specific) {
|
||||
stream_info->content_ivs.assign(params.content_ivs.begin(),
|
||||
params.content_ivs.end());
|
||||
} else {
|
||||
content_ivs_.assign(params.content_ivs.begin(), params.content_ivs.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.entitlement_comb.empty()) {
|
||||
entitlement_comb_.assign(params.entitlement_comb.begin(),
|
||||
params.entitlement_comb.end());
|
||||
}
|
||||
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
|
||||
DCHECK(ecm_ == nullptr);
|
||||
EcmgStreamInfo* stream_info = streams_info_.at(params.ecm_stream_id).get();
|
||||
if (stream_info->content_ivs.empty() && content_ivs_.empty()) {
|
||||
return {error::NOT_FOUND, "Content iv not specified."};
|
||||
}
|
||||
if (track_types_.empty()) {
|
||||
return {error::NOT_FOUND, "Track type not specified."};
|
||||
}
|
||||
|
||||
bool key_rotation = ecmg_config_->number_of_content_keys > 1;
|
||||
EcmInitParameters ecm_init_params;
|
||||
ecm_init_params.key_rotation_enabled = key_rotation;
|
||||
ecm_init_params.age_restriction = age_restriction_;
|
||||
ecm_init_params.crypto_mode = stream_info->crypto_mode == CryptoMode::kInvalid
|
||||
? ecmg_config_->crypto_mode
|
||||
: stream_info->crypto_mode;
|
||||
ecm_init_params.content_iv_size = kIvSize8;
|
||||
ecm_init_params.key_rotation_enabled = key_rotation_enabled;
|
||||
// TODO(user): Allow crypto mode to be set to different value? Via flag?
|
||||
ecm_init_params.crypto_mode = CryptoMode::kDvbCsa2;
|
||||
// Only encrypt one video track.
|
||||
// TODO(user): Support multiple tracks? How?
|
||||
ecm_init_params.track_types.push_back(kDefaultTrackTypeSd);
|
||||
if ((!stream_info->content_ivs.empty() &&
|
||||
stream_info->content_ivs[0].size() == 16) ||
|
||||
(!content_ivs_.empty() && content_ivs_[0].size() == 16)) {
|
||||
ecm_init_params.content_iv_size = kIvSize16;
|
||||
}
|
||||
ecm_init_params.track_types.assign(track_types_.begin(), track_types_.end());
|
||||
|
||||
ecm_ = absl::make_unique<Ecm>();
|
||||
if (!entitlement_comb_.empty()) {
|
||||
// Using injected entitlement keys.
|
||||
if (entitlement_comb_.size() !=
|
||||
track_types_.size() * ecmg_config_->number_of_content_keys) {
|
||||
return {error::NOT_FOUND,
|
||||
"Number of injected entitlement keys must equal to number of "
|
||||
"track types * number of content keys per ecm."};
|
||||
}
|
||||
std::vector<InjectedEntitlementKeyInfo> entitlements;
|
||||
entitlements.reserve(entitlement_comb_.size());
|
||||
for (size_t i = 0; i < entitlement_comb_.size(); i++) {
|
||||
entitlements.emplace_back();
|
||||
InjectedEntitlementKeyInfo* entitlement = &entitlements.back();
|
||||
entitlement->track_type = track_types_.at(key_rotation ? i / 2 : i);
|
||||
entitlement->is_even_key = key_rotation ? i % 2 == 0 : true;
|
||||
entitlement->key_id = entitlement_comb_[i].key_id;
|
||||
entitlement->key_value = entitlement_comb_[i].key_value;
|
||||
}
|
||||
|
||||
return ecm_->Initialize(ecm_init_params, entitlements);
|
||||
}
|
||||
|
||||
// No injected enetitlement keys. Fetching entitlement keys from server.
|
||||
if (content_id_.empty()) {
|
||||
return {error::NOT_FOUND, "Content id not specified."};
|
||||
}
|
||||
if (content_provider_.empty()) {
|
||||
return {error::NOT_FOUND, "Content provider not specified."};
|
||||
}
|
||||
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
// 'content_id' and 'provider' are used in entitlement key request/response
|
||||
// only, NOT needed for generating ECM.
|
||||
// So for initial demo, we can just use hardcoded value because we are using
|
||||
// hardcoded entitlement key anyway.
|
||||
// TODO(user): When we want to retrieve entitlement key from License Server
|
||||
// we need to figure out a way to provide real 'content_id' and 'provder'
|
||||
// to this function here from the Simulcrypt API.
|
||||
Status status = ecm_->Initialize(content_id_, content_provider_,
|
||||
ecm_init_params, &entitlement_request);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (key_fetcher_ == nullptr) {
|
||||
if (ecmg_config_->use_fixed_fetcher) {
|
||||
key_fetcher_ = absl::make_unique<FixedKeyFetcher>();
|
||||
} else {
|
||||
key_fetcher_ = absl::make_unique<WvCasKeyFetcher>();
|
||||
}
|
||||
}
|
||||
status = key_fetcher_->RequestEntitlementKey(entitlement_request,
|
||||
&entitlement_response);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
return ecm_->ProcessCasEncryptionResponse(entitlement_response);
|
||||
}
|
||||
|
||||
Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
uint8_t* ecm_datagram) const {
|
||||
DCHECK(ecm_datagram);
|
||||
DCHECK(ecm_);
|
||||
EcmgStreamInfo* stream_info = streams_info_.at(params.ecm_stream_id).get();
|
||||
|
||||
// Generate serialized ECM.
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
keys.reserve(ecmg_config_->number_of_content_keys);
|
||||
for (size_t i = 0; i < ecmg_config_->number_of_content_keys; i++) {
|
||||
DCHECK(params.cp_cw_combinations[i].cp == params.cp_number + i);
|
||||
keys.emplace_back();
|
||||
keys[i].key_value = params.cp_cw_combinations[i].cw;
|
||||
keys[i].key_id = crypto_util::DeriveKeyId(keys[i].key_value);
|
||||
keys[i].content_iv = stream_info->content_ivs.empty()
|
||||
? content_ivs_[i]
|
||||
: stream_info->content_ivs[i];
|
||||
if (!RandomBytes(kWrappedKeyIvSizeBytes, &keys[i].wrapped_key_iv)) {
|
||||
return {error::INTERNAL, "Unable to generate random wrapped key iv."};
|
||||
}
|
||||
}
|
||||
|
||||
Status status;
|
||||
if (!(status = ecm->Initialize(kDefaultContentId, kDefaultProvider,
|
||||
ecm_init_params, &entitlement_request))
|
||||
.ok()) {
|
||||
// TODO(user): Should send an error response.
|
||||
return;
|
||||
std::string serialized_ecm;
|
||||
if (ecmg_config_->number_of_content_keys > 1) {
|
||||
status = ecm_->GenerateEcm(&keys[0], &keys[1], stream_info->track_type,
|
||||
&serialized_ecm);
|
||||
} else {
|
||||
status = ecm_->GenerateSingleKeyEcm(&keys[0], stream_info->track_type,
|
||||
&serialized_ecm);
|
||||
}
|
||||
if (!(status = fixed_key_fetcher_.RequestEntitlementKey(
|
||||
entitlement_request, &entitlement_response))
|
||||
.ok()) {
|
||||
// TODO(user): Should send an error Channel_status.
|
||||
return;
|
||||
}
|
||||
if (!(status = ecm->ProcessCasEncryptionResponse(entitlement_response))
|
||||
.ok()) {
|
||||
// TODO(user): Should send an error Channel_status.
|
||||
return;
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
LOG(INFO) << "ECM generated with size: " << serialized_ecm.size() << ".";
|
||||
|
||||
// Step 2: Generate serialized ECM.
|
||||
EcmGenerator ecm_generator;
|
||||
ecm_generator.set_ecm(std::move(ecm));
|
||||
EcmParameters ecm_param;
|
||||
ecm_param.rotation_enabled = key_rotation_enabled;
|
||||
for (int i = 0; i <= params.cw_per_msg; i++) {
|
||||
ecm_param.key_params.emplace_back();
|
||||
ecm_param.key_params[i].key_data = params.cp_cw_combinations[i].cw;
|
||||
ecm_param.key_params[i].key_id = widevine::crypto_util::DeriveKeyId(
|
||||
ecm_param.key_params[i].key_data);
|
||||
// TODO(user): How to derive this content_iv, maybe based on key?
|
||||
std::string content_iv = "12345678";
|
||||
ecm_param.key_params[i].content_ivs.push_back(content_iv);
|
||||
}
|
||||
std::string serialized_ecm = ecm_generator.GenerateEcm(ecm_param);
|
||||
std::cout << "serialized_ecm: " << serialized_ecm << std::endl;
|
||||
for (int i = 0; i < serialized_ecm.size(); i++) {
|
||||
printf("'\\x%x', ", static_cast<uint16_t>(serialized_ecm.at(i)));
|
||||
}
|
||||
std::cout << std::endl;
|
||||
LOG(INFO) << "ECM size: " << serialized_ecm.size();
|
||||
|
||||
// Step 3: Make a TS packet carrying the serialized ECM.
|
||||
// TODO(user): Is it correct to set 'pid' using ECM_id?
|
||||
// TODO(user): Where do I get the continuity_counter?
|
||||
uint8_t continuity_counter = 0;
|
||||
// Make a TS packet carrying the serialized ECM.
|
||||
// According to the standard, it is the head-end's responsibility to fill
|
||||
// the PID field in TS packet header.
|
||||
uint16_t pid = 0;
|
||||
uint8_t continuity_counter = params.cp_number & 0xff;
|
||||
WvCasEcm wv_cas_ecm;
|
||||
WvCasStatus cas_status = wv_cas_ecm.GenerateTsPacket(
|
||||
serialized_ecm, params.ecm_id,
|
||||
serialized_ecm, pid,
|
||||
params.cp_number % 2 == 0 ? kTsPacketTableId80 : kTsPacketTableId81,
|
||||
&continuity_counter, ecm_datagram);
|
||||
if (cas_status != OK) {
|
||||
// TODO(user): Should send an error Channel_status.
|
||||
return;
|
||||
return {error::INTERNAL, "GenerateTsPacket failed."};
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -13,15 +13,18 @@
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include "absl/container/node_hash_map.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
|
||||
#include "media_cas_packager_sdk/internal/key_fetcher.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
@@ -33,27 +36,54 @@ struct EcmgConfig {
|
||||
uint16_t ecm_rep_period;
|
||||
uint16_t max_comp_time;
|
||||
uint8_t access_criteria_transfer_mode;
|
||||
uint8_t number_of_content_keys;
|
||||
CryptoMode crypto_mode;
|
||||
bool use_fixed_fetcher = false;
|
||||
};
|
||||
|
||||
// A struct that represent a CP_CW_Combination.
|
||||
struct EcmgCpCwCombination {
|
||||
uint16_t cp; // crypto period
|
||||
std::string cw; // control word
|
||||
uint16_t cp; // crypto period
|
||||
std::string cw; // control word
|
||||
};
|
||||
|
||||
// A struct that represent a Entitlement_Id_Key_Combination.
|
||||
struct EntitlementIdKeyComb {
|
||||
std::string key_id; // must be 16 bytes
|
||||
std::string key_value; // must be 32 bytes
|
||||
};
|
||||
|
||||
// A struct that is used to hold all possible params for a ECMG request.
|
||||
struct EcmgParameters {
|
||||
// CW_per_msg could 1, 2, or 3,
|
||||
// so there can be up to 3 CP_CW_Combinations
|
||||
EcmgCpCwCombination cp_cw_combinations[3];
|
||||
std::vector<EcmgCpCwCombination> cp_cw_combinations; // Size is 1 or 2.
|
||||
uint16_t cp_duration; // crypto period duration
|
||||
uint16_t cp_number; // crypto period number
|
||||
uint8_t cw_per_msg;
|
||||
uint16_t ecm_channel_id;
|
||||
uint16_t ecm_stream_id;
|
||||
uint16_t ecm_id;
|
||||
uint16_t nominal_cp_duration;
|
||||
uint32_t super_cas_id;
|
||||
|
||||
// User defined paremeters below.
|
||||
uint8_t age_restriction = 0xff; // Assume 0xff (255) is an invalid value.
|
||||
std::string crypto_mode;
|
||||
// All track types that need to be supported in the channel.
|
||||
// Used to request entitlement keys.
|
||||
std::vector<std::string> track_types;
|
||||
std::string stream_track_type;
|
||||
std::string content_id;
|
||||
std::string content_provider;
|
||||
std::vector<std::string> content_ivs; // 8 or 16 bytes, one for each key.
|
||||
std::vector<EntitlementIdKeyComb> entitlement_comb;
|
||||
};
|
||||
|
||||
struct EcmgStreamInfo {
|
||||
uint16_t ecm_id;
|
||||
std::string track_type;
|
||||
// Will use |ecmg_config_|.crypto_mode if invalid.
|
||||
CryptoMode crypto_mode = CryptoMode::kInvalid;
|
||||
// 8 or 16 bytes, one for each key. Will use |content_ivs_| if empty.
|
||||
std::vector<std::string> content_ivs;
|
||||
};
|
||||
|
||||
// A class that handles one (and only one) ECMG client.
|
||||
@@ -74,25 +104,49 @@ class EcmgClientHandler {
|
||||
private:
|
||||
void HandleChannelSetup(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
// TODO(user): HandleChannelTest()
|
||||
void HandleChannelTest(const EcmgParameters& params, char* response,
|
||||
size_t* response_length) const;
|
||||
void HandleChannelClose(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
void HandleStreamSetup(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
// TODO(user): HandleStreamTest()
|
||||
void HandleStreamTest(const EcmgParameters& params, char* response,
|
||||
size_t* response_length) const;
|
||||
void HandleStreamCloseRequest(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
void HandleCwProvision(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
void BuildEcmDatagram(const EcmgParameters& params, uint8_t* ecm_datagram);
|
||||
// Update private paprameters using |params|. |stream_specific| indicates if
|
||||
// |params| is for a single stream or the whole channel. If |stream_specific|
|
||||
// is true, |params| will only affect values of this stream. If it is false,
|
||||
// |param| will affect all streams of the channel.
|
||||
Status UpdatePrivateParameters(const EcmgParameters& params,
|
||||
bool stream_specific);
|
||||
// Check if all required parameters have been set. If so, initialize |ecm_| by
|
||||
// fetching entitlement keys.
|
||||
Status CheckAndInitializeEcm(const EcmgParameters& params);
|
||||
// Gather all information needed to build a TS packet |ecm_datagram|
|
||||
// conatianing an ECM.
|
||||
Status BuildEcmDatagram(const EcmgParameters& params,
|
||||
uint8_t* ecm_datagram) const;
|
||||
|
||||
EcmgConfig* ecmg_config_;
|
||||
// Per spec, "There is always one (and only one) channel per TCP connection".
|
||||
bool channel_id_set_;
|
||||
uint16_t channel_id_;
|
||||
// Map from ECM_stream_id to ECM_id.
|
||||
std::unordered_map<uint16_t, uint16_t> streams_;
|
||||
FixedKeyFetcher fixed_key_fetcher_;
|
||||
|
||||
// Channel specific information.
|
||||
std::unique_ptr<Ecm> ecm_; // |ecm_| is shared within the channel.
|
||||
uint8_t age_restriction_ = 0;
|
||||
std::vector<std::string> track_types_;
|
||||
std::vector<EntitlementIdKeyComb> entitlement_comb_;
|
||||
std::string content_id_;
|
||||
std::string content_provider_;
|
||||
std::vector<std::string> content_ivs_;
|
||||
|
||||
// Map from ECM_stream_id to EcmgStreamInfo.
|
||||
absl::node_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_;
|
||||
std::unique_ptr<KeyFetcher> key_fetcher_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -13,13 +13,44 @@
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "example/test_ecmg_messages.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_util.h"
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
namespace {
|
||||
|
||||
#define BUFFER_SIZE (1024)
|
||||
using simulcrypt_util::AddParam;
|
||||
using simulcrypt_util::AddUint16Param;
|
||||
using simulcrypt_util::AddUint32Param;
|
||||
using simulcrypt_util::AddUint8Param;
|
||||
using simulcrypt_util::BuildMessageHeader;
|
||||
|
||||
static constexpr size_t kBufferSize = 1024;
|
||||
static constexpr size_t kSuperCasId = 0x4AD40000;
|
||||
static constexpr size_t kChannelId = 1;
|
||||
static constexpr size_t kStreamId = 1;
|
||||
static constexpr size_t kEcmId = 2;
|
||||
static constexpr size_t kNominalCpDuration = 0x64;
|
||||
static constexpr size_t kCpNumber = 0;
|
||||
static constexpr char kContentKeyEven[] = "0123456701234567";
|
||||
static constexpr char kContentKeyOdd[] = "abcdefghabcdefgh";
|
||||
static constexpr char kEntitlementKeyIdEven[] = "0123456701234567";
|
||||
static constexpr char kEntitlementKeyValueEven[] =
|
||||
"01234567012345670123456701234567";
|
||||
static constexpr char kEntitlementKeyIdOdd[] = "abcdefghabcdefgh";
|
||||
static constexpr char kEntitlementKeyValueOdd[] =
|
||||
"abcdefghabcdefghabcdefghabcdefgh";
|
||||
static constexpr size_t kAgeRestriction = 3;
|
||||
static constexpr char kCryptoMode[] = "AesScte";
|
||||
static constexpr char kTrackTypesSD[] = "SD";
|
||||
static constexpr char kTrackTypesHD[] = "HD";
|
||||
static constexpr char kContentId[] = "CasTsFake";
|
||||
static constexpr char kContentProvider[] = "widevine_test";
|
||||
|
||||
class EcmgClientHandlerTest : public ::testing::Test {
|
||||
protected:
|
||||
@@ -29,59 +60,399 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
config_.ecm_rep_period = 100;
|
||||
config_.max_comp_time = 100;
|
||||
config_.access_criteria_transfer_mode = 1;
|
||||
client_handler_ = absl::make_unique<EcmgClientHandler>(&config_);
|
||||
config_.number_of_content_keys = 2;
|
||||
config_.use_fixed_fetcher = true;
|
||||
handler_ = absl::make_unique<EcmgClientHandler>(&config_);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Helper function for debugging the tests.
|
||||
void PrintMessage(const std::string &description, const char *const message,
|
||||
void PrintMessage(const std::string& description, const char* const message,
|
||||
size_t length) {
|
||||
printf("%s ", description.c_str());
|
||||
absl::PrintF("%s ", description);
|
||||
fflush(stdout);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
printf("'\\x%02x', ", static_cast<uint16_t>(*(message + i)));
|
||||
absl::PrintF("'\\x%02x', ", static_cast<uint16_t>(*(message + i)));
|
||||
fflush(stdout);
|
||||
}
|
||||
printf("\n");
|
||||
absl::PrintF("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void SetupValidChannel() {
|
||||
handler_->HandleRequest(kTestEcmgChannelSetup, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
}
|
||||
|
||||
void SetupValidChannelStream() {
|
||||
SetupValidChannel();
|
||||
handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
|
||||
}
|
||||
|
||||
void BuildChannelSetupRequest(uint16_t channel_id, uint32_t super_cas_id,
|
||||
uint8_t age_restriction,
|
||||
const std::string& crypto_mode,
|
||||
const std::vector<std::string>& track_types,
|
||||
const std::string& content_id,
|
||||
const std::string& content_provider,
|
||||
const std::vector<std::string>& entitlements,
|
||||
char* message, size_t* message_length) {
|
||||
EXPECT_TRUE(message != nullptr);
|
||||
EXPECT_TRUE(message_length != nullptr);
|
||||
BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_SETUP, message,
|
||||
message_length);
|
||||
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
|
||||
AddUint32Param(SUPER_CAS_ID, super_cas_id, message, message_length);
|
||||
|
||||
if (age_restriction != 0) {
|
||||
AddUint8Param(AGE_RESTRICTION, age_restriction, message, message_length);
|
||||
}
|
||||
if (!crypto_mode.empty()) {
|
||||
AddParam(CRYPTO_MODE, reinterpret_cast<const uint8_t*>(crypto_mode.c_str()),
|
||||
crypto_mode.size(), message, message_length);
|
||||
}
|
||||
if (!track_types.empty()) {
|
||||
for (const auto& track_type : track_types) {
|
||||
AddParam(TRACK_TYPES,
|
||||
reinterpret_cast<const uint8_t*>(track_type.c_str()),
|
||||
track_type.size(), message, message_length);
|
||||
}
|
||||
}
|
||||
if (!content_id.empty()) {
|
||||
AddParam(CONTENT_ID, reinterpret_cast<const uint8_t*>(content_id.c_str()),
|
||||
content_id.size(), message, message_length);
|
||||
}
|
||||
if (!content_provider.empty()) {
|
||||
AddParam(CONTENT_PROVIDER,
|
||||
reinterpret_cast<const uint8_t*>(content_provider.c_str()),
|
||||
content_provider.size(), message, message_length);
|
||||
}
|
||||
if (!entitlements.empty()) {
|
||||
for (const auto& entitlement : entitlements) {
|
||||
AddParam(ENTITLEMENT_ID_KEY_COMBINATION,
|
||||
reinterpret_cast<const uint8_t*>(entitlement.c_str()),
|
||||
entitlement.size(), message, message_length);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
void BuildStreamSetupRequest(uint16_t channel_id, uint16_t stream_id,
|
||||
uint16_t ecm_id, uint16_t nominal_CP_duration,
|
||||
const std::string& stream_track_type,
|
||||
const std::vector<std::string>& content_ivs,
|
||||
char* message, size_t* message_length) {
|
||||
EXPECT_TRUE(message != nullptr);
|
||||
EXPECT_TRUE(message_length != nullptr);
|
||||
BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_SETUP, message,
|
||||
message_length);
|
||||
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
|
||||
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
|
||||
AddUint16Param(ECM_ID, ecm_id, message, message_length);
|
||||
AddUint16Param(NOMINAL_CP_DURATION, nominal_CP_duration, message,
|
||||
message_length);
|
||||
|
||||
if (!stream_track_type.empty()) {
|
||||
AddParam(STREAM_TRACK_TYPE,
|
||||
reinterpret_cast<const uint8_t*>(stream_track_type.c_str()),
|
||||
stream_track_type.size(), message, message_length);
|
||||
}
|
||||
if (!content_ivs.empty()) {
|
||||
for (auto& content_iv : content_ivs) {
|
||||
AddParam(CONTENT_IV, reinterpret_cast<const uint8_t*>(content_iv.c_str()),
|
||||
content_iv.size(), message, message_length);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
void BuildCwProvisionRequest(
|
||||
uint16_t channel_id, uint16_t stream_id, uint16_t cp_number,
|
||||
const std::vector<EcmgCpCwCombination>& cp_cw_combination, char* message,
|
||||
size_t* message_length) {
|
||||
EXPECT_TRUE(message != nullptr);
|
||||
EXPECT_TRUE(message_length != nullptr);
|
||||
BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CW_PROVISION, message,
|
||||
message_length);
|
||||
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
|
||||
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
|
||||
AddUint16Param(CP_NUMBER, cp_number, message, message_length);
|
||||
for (auto& cp_cw : cp_cw_combination) {
|
||||
uint8_t combined[100];
|
||||
Host16ToBigEndian(combined, &cp_cw.cp);
|
||||
memcpy(combined + 2, cp_cw.cw.c_str(), cp_cw.cw.length());
|
||||
AddParam(CP_CW_COMBINATION, combined, 2 + cp_cw.cw.length(), message,
|
||||
message_length);
|
||||
}
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
void CheckChannelError(uint16_t expected_error_code, const char* const response,
|
||||
size_t response_length) {
|
||||
EXPECT_LE(sizeof(kTestChannelErrorResponse), response_length);
|
||||
// Message version and message type
|
||||
EXPECT_EQ(0, memcmp(kTestChannelErrorResponse, response, 3));
|
||||
// Parameter_type - error_status, and its length
|
||||
EXPECT_EQ(0, memcmp(&kTestChannelErrorResponse[11], response + 11, 4));
|
||||
// Error status code.
|
||||
EXPECT_EQ(expected_error_code >> 8, response[15]);
|
||||
EXPECT_EQ(expected_error_code & 0xff, response[16]);
|
||||
}
|
||||
|
||||
void CheckStreamError(uint16_t expected_error_code, const char* const response,
|
||||
size_t response_length) {
|
||||
EXPECT_LE(sizeof(kTestStreamErrorResponse), response_length);
|
||||
// Message version and message type
|
||||
EXPECT_EQ(0, memcmp(kTestStreamErrorResponse, response, 3));
|
||||
// Parameter_type - error_status, and its length
|
||||
EXPECT_EQ(0, memcmp(&kTestStreamErrorResponse[17], response + 17, 4));
|
||||
// Error status code.
|
||||
EXPECT_EQ(expected_error_code >> 8, response[21]);
|
||||
EXPECT_EQ(expected_error_code & 0xff, response[22]);
|
||||
}
|
||||
|
||||
EcmgConfig config_;
|
||||
std::unique_ptr<EcmgClientHandler> client_handler_;
|
||||
std::unique_ptr<EcmgClientHandler> handler_;
|
||||
char response_[kBufferSize];
|
||||
size_t response_len_ = 0;
|
||||
char request_[kBufferSize];
|
||||
size_t request_len_ = 0;
|
||||
};
|
||||
|
||||
// TODO(user): Add unit tests for error cases.
|
||||
TEST_F(EcmgClientHandlerTest, SuccessSequenceWithAccessCriteria) {
|
||||
handler_->HandleRequest(kTestEcmgChannelSetup, response_, &response_len_);
|
||||
EXPECT_EQ(62, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessSequence) {
|
||||
char response[BUFFER_SIZE];
|
||||
size_t response_length;
|
||||
handler_->HandleRequest(kTestEcmgChannelTest, response_, &response_len_);
|
||||
EXPECT_EQ(62, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
client_handler_->HandleRequest(kTestEcmgChannelSetup, response,
|
||||
&response_length);
|
||||
EXPECT_EQ(62, response_length);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response, response_length));
|
||||
handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_);
|
||||
EXPECT_EQ(28, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
|
||||
|
||||
client_handler_->HandleRequest(kTestEcmgStreamSetup, response,
|
||||
&response_length);
|
||||
EXPECT_EQ(28, response_length);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response, response_length));
|
||||
handler_->HandleRequest(kTestEcmgStreamTest, response_, &response_len_);
|
||||
EXPECT_EQ(28, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
|
||||
|
||||
client_handler_->HandleRequest(kTestEcmgCwProvision, response,
|
||||
&response_length);
|
||||
EXPECT_EQ(215, response_length);
|
||||
// Only comparing the bytes in front of the ECM_datagram, because
|
||||
// random wrapping IV is generated each time causing the ECM_datagram
|
||||
// to be non-deterministic.
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response, 27));
|
||||
handler_->HandleRequest(kTestEcmgCwProvisionWithAccessCriteria, response_,
|
||||
&response_len_);
|
||||
EXPECT_EQ(215, response_len_);
|
||||
// Only comparing the bytes in front of the ECM_datagram, including TS header,
|
||||
// ECM header and front of first key data. This is because random wrapping IV
|
||||
// is generated each time causing the ECM_datagram to be non-deterministic.
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 72));
|
||||
|
||||
client_handler_->HandleRequest(kTestEcmgStreamCloseRequest, response,
|
||||
&response_length);
|
||||
EXPECT_EQ(17, response_length);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamCloseResponse, response, response_length));
|
||||
// Cw provision request again.
|
||||
handler_->HandleRequest(kTestEcmgCwProvisionWithAccessCriteria, response_,
|
||||
&response_len_);
|
||||
EXPECT_EQ(215, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 72));
|
||||
|
||||
client_handler_->HandleRequest(kTestEcmgChannelClose, response,
|
||||
&response_length);
|
||||
EXPECT_EQ(0, response_length);
|
||||
handler_->HandleRequest(kTestEcmgStreamClose, response_, &response_len_);
|
||||
EXPECT_EQ(17, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamCloseResponse, response_, response_len_));
|
||||
|
||||
handler_->HandleRequest(kTestEcmgChannelClose, response_, &response_len_);
|
||||
EXPECT_EQ(0, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessSequenceWithPrivateParameters) {
|
||||
handler_->HandleRequest(kTestEcmgChannelSetupWithPrivateParameters, response_,
|
||||
&response_len_);
|
||||
EXPECT_EQ(62, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
handler_->HandleRequest(kTestEcmgChannelTest, response_, &response_len_);
|
||||
EXPECT_EQ(62, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
handler_->HandleRequest(kTestEcmgStreamSetupWithPrivateParameters, response_,
|
||||
&response_len_);
|
||||
EXPECT_EQ(28, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
|
||||
|
||||
handler_->HandleRequest(kTestEcmgStreamTest, response_, &response_len_);
|
||||
EXPECT_EQ(28, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
|
||||
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
EXPECT_EQ(215, response_len_);
|
||||
// Only comparing the bytes in front of the ECM_datagram, including TS header,
|
||||
// ECM header and front of first key data. This is because random wrapping IV
|
||||
// is generated each time causing the ECM_datagram to be non-deterministic.
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 72));
|
||||
|
||||
// Cw provision request again.
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
EXPECT_EQ(215, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 72));
|
||||
|
||||
handler_->HandleRequest(kTestEcmgStreamClose, response_, &response_len_);
|
||||
EXPECT_EQ(17, response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamCloseResponse, response_, response_len_));
|
||||
|
||||
handler_->HandleRequest(kTestEcmgChannelClose, response_, &response_len_);
|
||||
EXPECT_EQ(0, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessSequenceGeneratedRequests) {
|
||||
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
|
||||
kCryptoMode, {kTrackTypesHD, kTrackTypesSD},
|
||||
kContentId, kContentProvider, /*entitlements*/ {},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
kTrackTypesSD, {kContentKeyEven, kContentKeyEven},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
|
||||
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27));
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
|
||||
BuildChannelSetupRequest(
|
||||
kChannelId, kSuperCasId, kAgeRestriction, kCryptoMode,
|
||||
{kTrackTypesHD, kTrackTypesSD},
|
||||
/*ContentId*/ "", /*ContentProvider*/ "",
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd),
|
||||
absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
kTrackTypesSD, {kContentKeyEven, kContentKeyEven},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
|
||||
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27));
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongSequenceInvalidChannel) {
|
||||
// ChannelTest without a valid channel (error).
|
||||
handler_->HandleRequest(kTestEcmgChannelTest, response_, &response_len_);
|
||||
CheckChannelError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_);
|
||||
|
||||
// ChannelClose without a valid channel (error).
|
||||
handler_->HandleRequest(kTestEcmgChannelClose, response_, &response_len_);
|
||||
CheckChannelError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_);
|
||||
|
||||
// StreamSetup without a valid channel (error).
|
||||
handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_);
|
||||
CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_);
|
||||
|
||||
// StreamClose without a valid channel (error).
|
||||
handler_->HandleRequest(kTestEcmgStreamClose, response_, &response_len_);
|
||||
CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_);
|
||||
|
||||
// StreamTest without a valid channel (error).
|
||||
handler_->HandleRequest(kTestEcmgStreamTest, response_, &response_len_);
|
||||
CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_);
|
||||
|
||||
// Cw provision without a valid channel (error).
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongSequenceMultipleChannel) {
|
||||
SetupValidChannel();
|
||||
|
||||
// Setup channels muiltiple times (error).
|
||||
handler_->HandleRequest(kTestEcmgChannelSetup, response_, &response_len_);
|
||||
CheckChannelError(INVALID_MESSAGE, response_, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongSequenceInvalidStream) {
|
||||
SetupValidChannel();
|
||||
|
||||
// Cw provision without a valid stream (error).
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
CheckStreamError(UNKNOWN_ECM_STREAM_ID_VALUE, response_, response_len_);
|
||||
|
||||
// StreamClose without a valid stream (error).
|
||||
handler_->HandleRequest(kTestEcmgStreamClose, response_, &response_len_);
|
||||
CheckStreamError(UNKNOWN_ECM_STREAM_ID_VALUE, response_, response_len_);
|
||||
|
||||
// StreamTest without a valid channel (error).
|
||||
handler_->HandleRequest(kTestEcmgStreamTest, response_, &response_len_);
|
||||
CheckStreamError(UNKNOWN_ECM_STREAM_ID_VALUE, response_, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongSequenceMultipleSameIdStream) {
|
||||
SetupValidChannelStream();
|
||||
// Setup streams with the same stream_id muiltiple times (error).
|
||||
handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_);
|
||||
CheckStreamError(ECM_STREAM_ID_VALUE_ALREADY_IN_USE, response_,
|
||||
response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongParameterInsufficientKey) {
|
||||
SetupValidChannelStream();
|
||||
// Cw provision with only one key while expecting two (error).
|
||||
handler_->HandleRequest(kTestEcmgCwProvisionSingleKey, response_,
|
||||
&response_len_);
|
||||
CheckStreamError(NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM, response_,
|
||||
response_len_);
|
||||
|
||||
// Cw provision with no key while expecting two (error).
|
||||
std::vector<EcmgCpCwCombination> cp_cw_combination = {};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckStreamError(NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM, response_,
|
||||
response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongParameterSuperCasId) {
|
||||
// Setup a channel with an unexpected super cas id (expecting kSuperCasId).
|
||||
BuildChannelSetupRequest(kChannelId, 0, kAgeRestriction, kCryptoMode,
|
||||
{kTrackTypesHD, kTrackTypesSD}, kContentId,
|
||||
kContentProvider, /*entitlements*/ {}, request_,
|
||||
&request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckChannelError(UNKNOWN_SUPER_CAS_ID_VALUE, response_, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongParameterChannelId) {
|
||||
SetupValidChannel();
|
||||
// Setup a stream with an unexpected channel id (expecting kChannelId).
|
||||
BuildStreamSetupRequest(0, kStreamId, kEcmId, kNominalCpDuration,
|
||||
kTrackTypesSD, {kContentKeyEven, kContentKeyEven},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -60,8 +60,18 @@
|
||||
#define ERROR_STATUS (0x7000)
|
||||
#define ERROR_INFORMATION (0x7001)
|
||||
|
||||
// User defined ECMG parameter type values - 0x8000 to 0xFFFF.
|
||||
#define AGE_RESTRICTION (0x8000)
|
||||
#define CRYPTO_MODE (0x8001)
|
||||
#define CONTENT_ID (0x8002)
|
||||
#define CONTENT_PROVIDER (0x8003)
|
||||
#define TRACK_TYPES (0x8004)
|
||||
#define STREAM_TRACK_TYPE (0x8005)
|
||||
#define CONTENT_IV (0x8006)
|
||||
#define ENTITLEMENT_ID_KEY_COMBINATION (0x8007)
|
||||
|
||||
// ECMG protocol error values.
|
||||
#define INVAID_MESSAGE (0x0001)
|
||||
#define INVALID_MESSAGE (0x0001)
|
||||
#define UNSUPPORTED_PROTOCOL_VERSION (0x0002)
|
||||
#define UNKNOWN_MESSAGE_TYPE_VALUE (0x0003)
|
||||
#define MESSAGE_TOO_LONG (0x0004)
|
||||
@@ -94,5 +104,6 @@
|
||||
#define CP_NUMBER_SIZE (2)
|
||||
#define CP_DURATION_SIZE (2)
|
||||
#define CP_SIZE (2)
|
||||
#define AGE_RESTRICTION_SIZE (1)
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CONSTANTS_H_
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
#include "media_cas_packager_sdk/internal/emmg.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "example/test_emmg_messages.h"
|
||||
#include "media_cas_packager_sdk/internal/emmg_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
|
||||
@@ -28,7 +30,7 @@ namespace {
|
||||
|
||||
void Print(const char* const msg, size_t msg_size) {
|
||||
for (size_t i = 0; i < msg_size; i++) {
|
||||
printf("'\\x%02x', ", static_cast<uint16_t>(*(msg + i)));
|
||||
absl::PrintF("'\\x%02x', ", static_cast<uint16_t>(*(msg + i)));
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "example/test_emmg_messages.h"
|
||||
|
||||
namespace widevine {
|
||||
@@ -44,13 +45,13 @@ class EmmgTest : public ::testing::Test {
|
||||
// Helper function for debugging the tests.
|
||||
void PrintMessage(const std::string& description, const char* const message,
|
||||
size_t length) {
|
||||
printf("%s ", description.c_str());
|
||||
absl::PrintF("%s ", description);
|
||||
fflush(stdout);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
printf("'\\x%02x', ", static_cast<uint16_t>(*(message + i)));
|
||||
absl::PrintF("'\\x%02x', ", static_cast<uint16_t>(*(message + i)));
|
||||
fflush(stdout);
|
||||
}
|
||||
printf("\n");
|
||||
absl::PrintF("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
Status FixedKeyFetcher::RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) {
|
||||
Status FixedKeyFetcher::RequestEntitlementKey(
|
||||
const std::string& request_string,
|
||||
std::string* signed_response_string) const {
|
||||
CasEncryptionRequest request;
|
||||
request.ParseFromString(request_string);
|
||||
|
||||
CasEncryptionResponse response;
|
||||
response.set_status(CasEncryptionResponse_Status_OK);
|
||||
response.set_status(CasEncryptionResponse::OK);
|
||||
response.set_content_id(request.content_id());
|
||||
for (const auto& track_type : request.track_types()) {
|
||||
if (request.key_rotation()) {
|
||||
@@ -29,19 +30,19 @@ Status FixedKeyFetcher::RequestEntitlementKey(const std::string& request_string,
|
||||
key->set_key_id(even_entitlement_key_id_);
|
||||
key->set_key(even_entitlement_key_);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
|
||||
// Add the Odd key.
|
||||
key = response.add_entitlement_keys();
|
||||
key->set_key_id(odd_entitlement_key_id_);
|
||||
key->set_key(odd_entitlement_key_);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_ODD);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
|
||||
} else {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id(even_entitlement_key_id_);
|
||||
key->set_key(even_entitlement_key_);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
|
||||
}
|
||||
}
|
||||
std::string response_string;
|
||||
|
||||
@@ -26,7 +26,7 @@ class FixedKeyFetcher : public KeyFetcher {
|
||||
even_entitlement_key_("fakefakefakefakefakefakefake1..."),
|
||||
odd_entitlement_key_id_("fake_key_id2...."),
|
||||
odd_entitlement_key_("fakefakefakefakefakefakefake2...") {}
|
||||
// Explictly provide the key_id and entitlement keys rather than using the
|
||||
// Explicitly provide the key_id and entitlement keys rather than using the
|
||||
// hardcoded default.
|
||||
FixedKeyFetcher(const std::string& even_entitlement_key_id,
|
||||
const std::string& even_entitlement_key,
|
||||
@@ -49,8 +49,9 @@ class FixedKeyFetcher : public KeyFetcher {
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// WvCasEcm::ProcessCasEncryptionResponse().
|
||||
Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) override;
|
||||
Status RequestEntitlementKey(
|
||||
const std::string& request_string,
|
||||
std::string* signed_response_string) const override;
|
||||
|
||||
private:
|
||||
std::string even_entitlement_key_id_;
|
||||
|
||||
@@ -33,8 +33,9 @@ class KeyFetcher {
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// WvCasEcm::ProcessCasEncryptionResponse().
|
||||
virtual Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) = 0;
|
||||
virtual Status RequestEntitlementKey(
|
||||
const std::string& request_string,
|
||||
std::string* signed_response_string) const = 0;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -75,7 +75,7 @@ void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message,
|
||||
*message_length += param_length;
|
||||
}
|
||||
|
||||
void AddParam(uint16_t param_type, uint8_t* param_value, uint16_t param_length,
|
||||
void AddParam(uint16_t param_type, const uint8_t* param_value, uint16_t param_length,
|
||||
char* message, size_t* message_length) {
|
||||
DCHECK(param_value);
|
||||
DCHECK(message);
|
||||
|
||||
@@ -49,7 +49,7 @@ void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message,
|
||||
size_t* message_length);
|
||||
|
||||
// Add a param that is |param_length| bytes long.
|
||||
void AddParam(uint16_t param_type, uint8_t* param_value, uint16_t param_length,
|
||||
void AddParam(uint16_t param_type, const uint8_t* param_value, uint16_t param_length,
|
||||
char* message, size_t* message_length);
|
||||
|
||||
} // namespace simulcrypt_util
|
||||
|
||||
@@ -64,8 +64,8 @@ void Host32ToBigEndian(void* destination, const uint32_t* source) {
|
||||
memcpy(destination, &big_endian_number, 4);
|
||||
}
|
||||
|
||||
Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid, uint8_t table_id,
|
||||
ContinuityCounter* cc, uint8_t* buffer,
|
||||
Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
|
||||
uint8_t table_id, ContinuityCounter* cc, uint8_t* buffer,
|
||||
ssize_t* bytes_modified) {
|
||||
DCHECK(cc);
|
||||
DCHECK(buffer);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -41,7 +42,7 @@ void Host32ToBigEndian(void* destination, const uint32_t* source);
|
||||
// - |pid| is the PID used for ECMs in the TS header.
|
||||
// - |table_id| is the table ID byte put in the section header, it should be
|
||||
// either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or
|
||||
// 0x81 to 0x80 is used to singal to the client that the key contained
|
||||
// 0x81 to 0x80 is used to signal to the client that the key contained
|
||||
// in the ECM has changed. In other words, if you are building an ECM
|
||||
// with a new key that was not in any previous ECM, you should flip the
|
||||
// table ID so the client knows this is an important ECM it should process.
|
||||
@@ -53,8 +54,8 @@ void Host32ToBigEndian(void* destination, const uint32_t* source);
|
||||
// the |buffer| and is used as an offset.
|
||||
// |bytes_modified| will be incremented by 188 if insertion of ECM into
|
||||
// |buffer| is successful.
|
||||
Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid, uint8_t table_id,
|
||||
ContinuityCounter* cc, uint8_t* buffer,
|
||||
Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
|
||||
uint8_t table_id, ContinuityCounter* cc, uint8_t* buffer,
|
||||
ssize_t* bytes_modified);
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -46,10 +46,9 @@ cc_library(
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
"//media_cas_packager_sdk/internal:ecm_generator",
|
||||
"//media_cas_packager_sdk/internal:key_fetcher",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
"//protos/public:media_cas_proto",
|
||||
"//protos/public:media_cas_cc_proto",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -65,8 +64,11 @@ cc_library(
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:status",
|
||||
"//common:string_util",
|
||||
"//protos/public:media_cas_proto",
|
||||
"//protos/public:media_cas_cc_proto",
|
||||
],
|
||||
# Make sure libmedia_cas_packager_sdk links in symbols defined in this
|
||||
# target.
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_test(
|
||||
@@ -77,7 +79,7 @@ cc_test(
|
||||
":wv_cas_ca_descriptor",
|
||||
":wv_cas_types",
|
||||
"//testing:gunit_main",
|
||||
"//protos/public:media_cas_proto",
|
||||
"//protos/public:media_cas_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -96,11 +98,13 @@ cc_library(
|
||||
"//common:status",
|
||||
"//example:constants",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
"//media_cas_packager_sdk/internal:ecm_generator",
|
||||
"//media_cas_packager_sdk/internal:fixed_key_fetcher",
|
||||
"//media_cas_packager_sdk/internal:mpeg2ts",
|
||||
"//media_cas_packager_sdk/internal:util",
|
||||
],
|
||||
# Make sure libmedia_cas_packager_sdk links in symbols defined in this
|
||||
# target.
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_test(
|
||||
@@ -126,6 +130,7 @@ cc_library(
|
||||
],
|
||||
deps = [
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@curl_repo//:curl",
|
||||
@@ -133,7 +138,7 @@ cc_library(
|
||||
"//common:signature_util",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/internal:key_fetcher",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -149,7 +154,7 @@ cc_test(
|
||||
"//external:protobuf",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -160,8 +165,12 @@ cc_library(
|
||||
copts = PUBLIC_COPTS,
|
||||
deps = [
|
||||
"//base",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
"//external:protobuf",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
# Make sure libmedia_cas_packager_sdk links in symbols defined in this
|
||||
# target.
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_test(
|
||||
@@ -178,6 +187,7 @@ cc_binary(
|
||||
name = "wv_ecmg",
|
||||
srcs = ["wv_ecmg.cc"],
|
||||
deps = [
|
||||
":wv_cas_types",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
|
||||
@@ -45,8 +45,8 @@ static constexpr uint32_t kCaDescriptorTag = 9;
|
||||
// https://en.wikipedia.org/wiki/Conditional_access
|
||||
static constexpr uint32_t kWidevineCaSystemId = 0x4AD4;
|
||||
|
||||
// Value for CA descriptor reserved field.
|
||||
static constexpr uint32_t kUnusedZero = 0;
|
||||
// Value for CA descriptor reserved field should be set to 1.
|
||||
static constexpr uint32_t kReservedBit = 0x0007;
|
||||
|
||||
// The range of valid PIDs, from section 2.4.3.3, and table 2-3.
|
||||
static constexpr uint32_t kMinValidPID = 0x0010;
|
||||
@@ -83,7 +83,7 @@ WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor(
|
||||
// including private bytes.
|
||||
std::bitset<kNumBitsCaDescriptorLengthField> length(descriptor_length);
|
||||
std::bitset<kNumBitsCaSystemIdField> ca_system_id(kWidevineCaSystemId);
|
||||
std::bitset<kNumBitsCaDescriptorReservedField> reserved(kUnusedZero);
|
||||
std::bitset<kNumBitsCaDescriptorReservedField> reserved(kReservedBit);
|
||||
std::bitset<kNumBitsCaDescriptorPidField> pid(ca_pid);
|
||||
|
||||
// Converts bitset to a std::string where each char represents a bit.
|
||||
@@ -110,8 +110,8 @@ size_t WvCasCaDescriptor::CaDescriptorBaseSize() const {
|
||||
return kCaDescriptorBaseSize;
|
||||
}
|
||||
|
||||
std::string WvCasCaDescriptor::GeneratePrivateData(const std::string& provider,
|
||||
const std::string& content_id) const {
|
||||
std::string WvCasCaDescriptor::GeneratePrivateData(
|
||||
const std::string& provider, const std::string& content_id) const {
|
||||
CaDescriptorPrivateData private_data;
|
||||
private_data.set_provider(provider);
|
||||
private_data.set_content_id(content_id);
|
||||
|
||||
@@ -53,10 +53,9 @@ class WvCasCaDescriptor {
|
||||
// section (for an EMM stream) or into a TS Program Map Table section (for an
|
||||
// ECM stream). The descriptor will be 6 bytes plus any bytes added as
|
||||
// (user-defined) private data.
|
||||
virtual WvCasStatus GenerateCaDescriptor(uint16_t ca_pid,
|
||||
const std::string& provider,
|
||||
const std::string& content_id,
|
||||
std::string* serialized_ca_desc) const;
|
||||
virtual WvCasStatus GenerateCaDescriptor(
|
||||
uint16_t ca_pid, const std::string& provider, const std::string& content_id,
|
||||
std::string* serialized_ca_desc) const;
|
||||
|
||||
// Return the base size (before private data is added) of the CA
|
||||
// descriptor. The user can call this to plan the layout of the Table section
|
||||
@@ -65,7 +64,7 @@ class WvCasCaDescriptor {
|
||||
|
||||
// Return private data in the CA descriptor.
|
||||
virtual std::string GeneratePrivateData(const std::string& provider,
|
||||
const std::string& content_id) const;
|
||||
const std::string& content_id) const;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -42,7 +42,7 @@ TEST_F(WvCasCaDescriptorTest, BaseSize) {
|
||||
TEST_F(WvCasCaDescriptorTest, BasicGoodGen) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x00\x32", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x32", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ TEST_F(WvCasCaDescriptorTest, PidMinOK) {
|
||||
const uint32_t min_pid = 0x10;
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(min_pid, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x00\x10", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x10", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ TEST_F(WvCasCaDescriptorTest, PidMaxOK) {
|
||||
const uint32_t max_pid = 0x1FFE;
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(max_pid, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x1f\xfe");
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\xfe");
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
@@ -82,77 +82,77 @@ TEST_F(WvCasCaDescriptorTest, PidTooHighFail) {
|
||||
TEST_F(WvCasCaDescriptorTest, PidOneByte) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(255, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x00\xff", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\xff", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidSecondByte) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x1F00, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x1f\x00", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidTwelveBits) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0xFFF, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x0f\xff");
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xef\xff");
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x1000, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x10\x00", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xf0\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidTwelthBit) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x800, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x08\x00", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe8\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidElevenththBit) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x400, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x04\x00", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe4\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidTenthBit) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x200, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x02\x00", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe2\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidNinthBit) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x100, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x01\x00", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe1\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PrivateDataOnlyProviderIgnored) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x00\x32", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PrivateDataOnlyContentIdIgnored) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, "", kContentId,
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x00\x32", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PrivateData) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x19\x4a\xd4\x00\x32", 6);
|
||||
const std::string expected_ca_descriptor("\x09\x19\x4a\xd4\xe0\x32", 6);
|
||||
CaDescriptorPrivateData private_data;
|
||||
private_data.set_provider(kProvider);
|
||||
private_data.set_content_id(kContentId);
|
||||
@@ -162,10 +162,13 @@ TEST_F(WvCasCaDescriptorTest, PrivateData) {
|
||||
|
||||
class FakePrivateDataCaDescriptor : public WvCasCaDescriptor {
|
||||
public:
|
||||
void set_private_data(std::string private_data) { private_data_ = private_data; }
|
||||
void set_private_data(std::string private_data) {
|
||||
private_data_ = private_data;
|
||||
}
|
||||
|
||||
std::string GeneratePrivateData(const std::string& provider,
|
||||
const std::string& content_id) const override {
|
||||
std::string GeneratePrivateData(
|
||||
const std::string& provider,
|
||||
const std::string& content_id) const override {
|
||||
return private_data_;
|
||||
}
|
||||
|
||||
@@ -178,7 +181,7 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataOneByte) {
|
||||
fake_descriptor.set_private_data("X");
|
||||
EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x05\x4a\xd4\x00\x32X", 7);
|
||||
const std::string expected_ca_descriptor("\x09\x05\x4a\xd4\xe0\x32X", 7);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
@@ -188,7 +191,7 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMultipleBytes) {
|
||||
fake_descriptor.set_private_data(private_data_bytes);
|
||||
EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x0e\x4a\xd4\x00\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_);
|
||||
}
|
||||
|
||||
@@ -198,7 +201,7 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMaxNumberBytes) {
|
||||
fake_descriptor.set_private_data(private_data_bytes);
|
||||
EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\xff\x4a\xd4\x00\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_);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "common/status.h"
|
||||
#include "example/constants.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_generator.h"
|
||||
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
@@ -115,9 +114,9 @@ WvCasStatus WvCasEcm::GenerateEcm(
|
||||
}
|
||||
std::string even_content_iv_str(even_content_iv, content_iv_size_);
|
||||
std::string even_entitlement_key_id_str(even_entitlement_key_id,
|
||||
kEntitlementKeyIdSizeBytes);
|
||||
kEntitlementKeyIdSizeBytes);
|
||||
std::string even_entitlement_key_str(even_entitlement_key,
|
||||
kEntitlementKeySizeBytes);
|
||||
kEntitlementKeySizeBytes);
|
||||
std::string odd_key_str;
|
||||
if (crypto_mode_ == CryptoMode::kDvbCsa2) {
|
||||
odd_key_str = std::string(odd_key, kCsaContentKeySizeBytes);
|
||||
@@ -127,8 +126,9 @@ WvCasStatus WvCasEcm::GenerateEcm(
|
||||
}
|
||||
std::string odd_content_iv_str(odd_content_iv, content_iv_size_);
|
||||
std::string odd_entitlement_key_id_str(odd_entitlement_key_id,
|
||||
kEntitlementKeyIdSizeBytes);
|
||||
std::string odd_entitlement_key_str(odd_entitlement_key, kEntitlementKeySizeBytes);
|
||||
kEntitlementKeyIdSizeBytes);
|
||||
std::string odd_entitlement_key_str(odd_entitlement_key,
|
||||
kEntitlementKeySizeBytes);
|
||||
|
||||
// Double check some input sizes.
|
||||
if (even_key_str.size() != kContentKeySizeBytes ||
|
||||
@@ -143,7 +143,7 @@ WvCasStatus WvCasEcm::GenerateEcm(
|
||||
}
|
||||
|
||||
// Create an instance of Ecm in order to set the entitlement keys.
|
||||
std::unique_ptr<Ecm> cas_ecm = absl::make_unique<Ecm>();
|
||||
auto cas_ecm = absl::make_unique<Ecm>();
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
EcmInitParameters ecm_init_params = CreateEcmInitParameters(
|
||||
@@ -179,23 +179,21 @@ WvCasStatus WvCasEcm::GenerateEcm(
|
||||
}
|
||||
|
||||
// Generate ECM.
|
||||
EcmGenerator ecm_generator;
|
||||
ecm_generator.set_ecm(std::move(cas_ecm));
|
||||
EcmParameters ecm_param;
|
||||
ecm_param.rotation_enabled = key_rotation_enabled_;
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
keys.reserve(2);
|
||||
// Add even entitlement key.
|
||||
ecm_param.key_params.emplace_back();
|
||||
ecm_param.key_params[0].key_data = even_key_str;
|
||||
ecm_param.key_params[0].wrapped_key_iv = crypto_util::DeriveIv(even_key_str);
|
||||
ecm_param.key_params[0].key_id = crypto_util::DeriveKeyId(even_key_str);
|
||||
ecm_param.key_params[0].content_ivs.push_back(even_content_iv_str);
|
||||
keys.emplace_back();
|
||||
keys[0].key_value = even_key_str;
|
||||
keys[0].wrapped_key_iv = crypto_util::DeriveIv(even_key_str);
|
||||
keys[0].key_id = crypto_util::DeriveKeyId(even_key_str);
|
||||
keys[0].content_iv = even_content_iv_str;
|
||||
// Add odd entitlement key.
|
||||
ecm_param.key_params.emplace_back();
|
||||
ecm_param.key_params[1].key_data = odd_key_str;
|
||||
ecm_param.key_params[1].wrapped_key_iv = crypto_util::DeriveIv(odd_key_str);
|
||||
ecm_param.key_params[1].key_id = crypto_util::DeriveKeyId(odd_key_str);
|
||||
ecm_param.key_params[1].content_ivs.push_back(odd_content_iv_str);
|
||||
*ecm = ecm_generator.GenerateEcm(ecm_param);
|
||||
keys.emplace_back();
|
||||
keys[1].key_value = odd_key_str;
|
||||
keys[1].wrapped_key_iv = crypto_util::DeriveIv(odd_key_str);
|
||||
keys[1].key_id = crypto_util::DeriveKeyId(odd_key_str);
|
||||
keys[1].content_iv = odd_content_iv_str;
|
||||
status = cas_ecm->GenerateEcm(&keys[0], &keys[1], kDefaultTrackTypeSd, ecm);
|
||||
|
||||
size_t expected_ecm_size = content_iv_size_ == 8
|
||||
? kEcmWith8BytesContentIvSizeBytes
|
||||
@@ -239,9 +237,9 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm(
|
||||
}
|
||||
std::string even_content_iv_str(even_content_iv, content_iv_size_);
|
||||
std::string even_entitlement_key_id_str(even_entitlement_key_id,
|
||||
kEntitlementKeyIdSizeBytes);
|
||||
kEntitlementKeyIdSizeBytes);
|
||||
std::string even_entitlement_key_str(even_entitlement_key,
|
||||
kEntitlementKeySizeBytes);
|
||||
kEntitlementKeySizeBytes);
|
||||
|
||||
// Double check some input sizes.
|
||||
if (even_key_str.size() != kContentKeySizeBytes) {
|
||||
@@ -254,7 +252,7 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm(
|
||||
}
|
||||
|
||||
// Create an instance of Ecm in order to set the entitlement keys.
|
||||
std::unique_ptr<Ecm> cas_ecm = absl::make_unique<Ecm>();
|
||||
auto cas_ecm = absl::make_unique<Ecm>();
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
EcmInitParameters ecm_init_params = CreateEcmInitParameters(
|
||||
@@ -290,17 +288,12 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm(
|
||||
}
|
||||
|
||||
// Generate ECM.
|
||||
EcmGenerator ecm_generator;
|
||||
ecm_generator.set_ecm(std::move(cas_ecm));
|
||||
EcmParameters ecm_param;
|
||||
ecm_param.rotation_enabled = key_rotation_enabled_;
|
||||
// Add even entitlement key.
|
||||
ecm_param.key_params.emplace_back();
|
||||
ecm_param.key_params[0].key_data = even_key_str;
|
||||
ecm_param.key_params[0].wrapped_key_iv = crypto_util::DeriveIv(even_key_str);
|
||||
ecm_param.key_params[0].key_id = crypto_util::DeriveKeyId(even_key_str);
|
||||
ecm_param.key_params[0].content_ivs.push_back(even_content_iv_str);
|
||||
*ecm = ecm_generator.GenerateEcm(ecm_param);
|
||||
EntitledKeyInfo key;
|
||||
key.key_value = even_key_str;
|
||||
key.wrapped_key_iv = crypto_util::DeriveIv(even_key_str);
|
||||
key.key_id = crypto_util::DeriveKeyId(key.key_value);
|
||||
key.content_iv = even_content_iv_str;
|
||||
status = cas_ecm->GenerateSingleKeyEcm(&key, kDefaultTrackTypeSd, ecm);
|
||||
|
||||
size_t expected_ecm_size = content_iv_size_ == 8
|
||||
? kSingleKeyEcmWith8BytesContentIvSizeBytes
|
||||
@@ -317,7 +310,7 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm(
|
||||
WvCasStatus WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id,
|
||||
uint8_t* continuity_counter,
|
||||
uint8_t* packet) {
|
||||
uint8_t* packet) const {
|
||||
ssize_t bytes_modified = 0;
|
||||
Status status = InsertEcmAsTsPacket(ecm, pid, table_id, continuity_counter,
|
||||
packet, &bytes_modified);
|
||||
|
||||
@@ -118,7 +118,7 @@ class WvCasEcm {
|
||||
// - |pid| program ID for the ECM stream
|
||||
// - |table_id| is the table ID byte put in the section header, it should be
|
||||
// either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or
|
||||
// 0x81 to 0x80 is used to singal to the client that the key contained
|
||||
// 0x81 to 0x80 is used to signal to the client that the key contained
|
||||
// in the ECM has changed. In other words, if you are building an ECM
|
||||
// with a new key that was not in any previous ECM, you should flip the
|
||||
// table ID so the client knows this is an important ECM it should process.
|
||||
@@ -132,7 +132,7 @@ class WvCasEcm {
|
||||
virtual WvCasStatus GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id,
|
||||
uint8_t* continuity_counter,
|
||||
uint8_t* packet);
|
||||
uint8_t* packet) const;
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
|
||||
@@ -138,7 +138,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Ctr_Success) {
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40103806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"4ad40203806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e6f64645f656e745f6b65795f69642e2e34cd74b6b998889aad0e71b44bdd8c"
|
||||
"0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3ebba4a0bd876f6464"
|
||||
@@ -160,7 +160,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_8BytesContentIv_Ctr_Success) {
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40102806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"4ad40202806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
@@ -186,7 +186,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_16BytesContentIv_Ctr_Success) {
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40103c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"4ad40203c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e2e2e2e2e2e2e2e2e6f64645f656e745f6b65795f69642e2e34cd74b6b99888"
|
||||
"9aad0e71b44bdd8c0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3e"
|
||||
@@ -209,7 +209,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_16BytesContentIv_Ctr_Success) {
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40102c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"4ad40202c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e2e2e2e2e2e2e2e2e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
@@ -235,7 +235,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_16BytesContentIv_Cbc_Success) {
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40101c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"4ad40201c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e2e2e2e2e2e2e2e2e6f64645f656e745f6b65795f69642e2e34cd74b6b99888"
|
||||
"9aad0e71b44bdd8c0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3e"
|
||||
@@ -258,7 +258,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_16BytesContentIv_Cbc_Success) {
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40100c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"4ad40200c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e2e2e2e2e2e2e2e2e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
@@ -284,7 +284,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_Success) {
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40105806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
|
||||
"4ad40205806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
|
||||
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
|
||||
"6e5f69762e6f64645f656e745f6b65795f69642e2e7f655fe61e99e89e03ac23df98cc02"
|
||||
"1cf21dfe9637c72c3480727ab18332d4ee219e81b8f34c9df2704b0595501832736f6464"
|
||||
@@ -307,7 +307,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_8BytesContentIv_Csa_Success) {
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40104806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
|
||||
"4ad40204806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
|
||||
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
|
||||
"6e5f69762e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
@@ -333,7 +333,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_NulCharInKey_Success) {
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40105806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
|
||||
"4ad40205806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
|
||||
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
|
||||
"6e5f69762e6f64645f656e745f6b65795f69642e2e874aab870ffba640875a4521d3cd57"
|
||||
"02f26d0f9c7e9c69d7059c9ad42b091ec1f151aaa190536f4f330edebe84fe5a786f6464"
|
||||
@@ -357,7 +357,7 @@ TEST_F(WvCasEcmTest,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40104806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
|
||||
"4ad40204806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
|
||||
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
|
||||
"6e5f69762e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
|
||||
@@ -40,8 +40,9 @@ DEFINE_string(signing_iv, "",
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
Status WvCasKeyFetcher::RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) {
|
||||
Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
const std::string& request_string,
|
||||
std::string* signed_response_string) const {
|
||||
if (FLAGS_signing_provider.empty() || FLAGS_signing_key.empty() ||
|
||||
FLAGS_signing_iv.empty()) {
|
||||
return Status(
|
||||
@@ -115,7 +116,8 @@ Status WvCasKeyFetcher::RequestEntitlementKey(const std::string& request_string,
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
size_t AppendToString(void* ptr, size_t size, size_t count, std::string* output) {
|
||||
size_t AppendToString(void* ptr, size_t size, size_t count,
|
||||
std::string* output) {
|
||||
const absl::string_view data(static_cast<char*>(ptr), size * count);
|
||||
absl::StrAppend(output, data);
|
||||
return data.size();
|
||||
@@ -140,8 +142,9 @@ Status WvCasKeyFetcher::MakeHttpRequest(const std::string& signed_request_json,
|
||||
(int64_t)strlen(signed_request_json.c_str()));
|
||||
curl_code = curl_easy_perform(curl);
|
||||
if (curl_code != CURLE_OK) {
|
||||
return Status(error::INTERNAL, "curl_easy_perform() failed: " +
|
||||
std::string(curl_easy_strerror(curl_code)));
|
||||
return Status(error::INTERNAL,
|
||||
"curl_easy_perform() failed: " +
|
||||
std::string(curl_easy_strerror(curl_code)));
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
} else {
|
||||
|
||||
@@ -29,7 +29,7 @@ class WvCasKeyFetcher : public KeyFetcher {
|
||||
WvCasKeyFetcher() = default;
|
||||
WvCasKeyFetcher(const WvCasKeyFetcher&) = delete;
|
||||
WvCasKeyFetcher& operator=(const WvCasKeyFetcher&) = delete;
|
||||
virtual ~WvCasKeyFetcher() = default;
|
||||
~WvCasKeyFetcher() override = default;
|
||||
|
||||
// Get entitlement keys from the license server. Send a
|
||||
// SignedCasEncryptionRequest message to the license server, receive a
|
||||
@@ -40,8 +40,9 @@ class WvCasKeyFetcher : public KeyFetcher {
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// widevine::cas::Ecm::ProcessCasEncryptionResponse().
|
||||
Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) override;
|
||||
Status RequestEntitlementKey(
|
||||
const std::string& request_string,
|
||||
std::string* signed_response_string) const override;
|
||||
|
||||
protected:
|
||||
// Makes a HTTP request to License Server for entitlement key(s).
|
||||
|
||||
@@ -54,8 +54,9 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher {
|
||||
public:
|
||||
MockWvCasKeyFetcher() : WvCasKeyFetcher() {}
|
||||
~MockWvCasKeyFetcher() override {}
|
||||
MOCK_CONST_METHOD2(MakeHttpRequest, Status(const std::string& signed_request_json,
|
||||
std::string* http_response_json));
|
||||
MOCK_CONST_METHOD2(MakeHttpRequest,
|
||||
Status(const std::string& signed_request_json,
|
||||
std::string* http_response_json));
|
||||
};
|
||||
|
||||
class WvCasKeyFetcherTest : public ::testing::Test {
|
||||
|
||||
@@ -46,9 +46,13 @@ std::string GetWvCasStatusMessage(WvCasStatus status) {
|
||||
|
||||
// Numeric value of crypto mode is the index into strings array.
|
||||
static const char* kCrypoModeStrings[] = {
|
||||
"AesCbc",
|
||||
"AesCtr",
|
||||
"DvbCsa2",
|
||||
"AesCbc", "AesCtr", "DvbCsa2", "DvbCsa3", "AesOfb", "AesScte",
|
||||
};
|
||||
|
||||
// Numeric value of scrambling level is the index into strings array.
|
||||
static const char* kScramblingLevelStrings[] = {
|
||||
"PES",
|
||||
"TS",
|
||||
};
|
||||
|
||||
bool CryptoModeToString(CryptoMode mode, std::string* str) {
|
||||
@@ -69,7 +73,7 @@ bool StringToCryptoMode(const std::string& str, CryptoMode* mode) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < arraysize(kCrypoModeStrings); ++i) {
|
||||
if (str.compare(kCrypoModeStrings[i]) == 0) {
|
||||
if (str == kCrypoModeStrings[i]) {
|
||||
*mode = static_cast<CryptoMode>(i);
|
||||
return true;
|
||||
}
|
||||
@@ -78,6 +82,33 @@ bool StringToCryptoMode(const std::string& str, CryptoMode* mode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ScramblingLevelToString(ScramblingLevel mode, std::string* str) {
|
||||
if (str == nullptr) {
|
||||
return false;
|
||||
}
|
||||
int mode_idx = static_cast<int>(mode);
|
||||
if (mode_idx >= 0 && mode_idx < arraysize(kScramblingLevelStrings)) {
|
||||
*str = kScramblingLevelStrings[mode_idx];
|
||||
return true;
|
||||
}
|
||||
LOG(ERROR) << "Invalid scrambling mode: " << mode_idx;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StringToScramblingLevel(const std::string& str, ScramblingLevel* mode) {
|
||||
if (mode == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < arraysize(kScramblingLevelStrings); ++i) {
|
||||
if (str == kScramblingLevelStrings[i]) {
|
||||
*mode = static_cast<ScramblingLevel>(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LOG(ERROR) << "Invalid scrambling mode: " << str;
|
||||
return false;
|
||||
}
|
||||
|
||||
WvCasStatus CreateWvCasEncryptionRequestJson(
|
||||
const WvCasEncryptionRequest& request, std::string* request_json) {
|
||||
CHECK(request_json);
|
||||
|
||||
@@ -52,19 +52,31 @@ enum WvCasStatus {
|
||||
std::string GetWvCasStatusMessage(WvCasStatus status);
|
||||
|
||||
// Crypto mode for encryption / decryption. ENUM value should be consistent with
|
||||
// https://docs.google.com/document/d/1A5vflf8tbKyUheV-xsvfxFqB6YyNLNdsGXYx8ZnhjfY/edit#heading=h.ej4ts3lifoio
|
||||
// ECM V2 definition. Largest supported value for this CryptoMode ENUM is 15.
|
||||
enum class CryptoMode : int {
|
||||
kInvalid = -1,
|
||||
kAesCbc = 0,
|
||||
kAesCtr = 1,
|
||||
kDvbCsa2 = 2,
|
||||
kDvbCsa3 = 3,
|
||||
kAesOfb = 4,
|
||||
kAesScte = 5,
|
||||
};
|
||||
|
||||
enum class ScramblingLevel : int { kPES = 0, kTS = 1 };
|
||||
|
||||
// Returns false if mode is not a valid CryptoMode.
|
||||
bool CryptoModeToString(CryptoMode mode, std::string* str);
|
||||
|
||||
// Returns false if str is not a valid CryptoMode.
|
||||
bool StringToCryptoMode(const std::string& str, CryptoMode* mode);
|
||||
|
||||
// Returns false if mode is not a valid ScramblingLevel.
|
||||
bool ScramblingLevelToString(ScramblingLevel mode, std::string* str);
|
||||
|
||||
// Returns false if str is not a valid ScramblingLevel.
|
||||
bool StringToScramblingLevel(const std::string& str, ScramblingLevel* mode);
|
||||
|
||||
struct WvCasEncryptionRequest {
|
||||
std::string content_id;
|
||||
std::string provider;
|
||||
@@ -111,7 +123,7 @@ struct WvCasEncryptionResponse {
|
||||
// This request JSON can be later put into the 'request' field of a signed
|
||||
// request JSON message.
|
||||
// And that signed JSON message can be sent to Widevine license server for
|
||||
// aquiring entitlement keys.
|
||||
// acquiring entitlement keys.
|
||||
WvCasStatus CreateWvCasEncryptionRequestJson(
|
||||
const WvCasEncryptionRequest& request, std::string* request_json);
|
||||
|
||||
|
||||
@@ -33,6 +33,12 @@ TEST(WvCasTypesTest, CryptoModeToString) {
|
||||
EXPECT_EQ("AesCbc", crypto_mode);
|
||||
ASSERT_TRUE(CryptoModeToString(CryptoMode::kDvbCsa2, &crypto_mode));
|
||||
EXPECT_EQ("DvbCsa2", crypto_mode);
|
||||
ASSERT_TRUE(CryptoModeToString(CryptoMode::kDvbCsa3, &crypto_mode));
|
||||
EXPECT_EQ("DvbCsa3", crypto_mode);
|
||||
ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesOfb, &crypto_mode));
|
||||
EXPECT_EQ("AesOfb", crypto_mode);
|
||||
ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesScte, &crypto_mode));
|
||||
EXPECT_EQ("AesScte", crypto_mode);
|
||||
EXPECT_FALSE(CryptoModeToString(static_cast<CryptoMode>(-1), &crypto_mode));
|
||||
}
|
||||
|
||||
@@ -44,9 +50,34 @@ TEST(WvCasTypesTest, StringToCryptoMode) {
|
||||
EXPECT_EQ(CryptoMode::kAesCbc, crypto_mode);
|
||||
ASSERT_TRUE(StringToCryptoMode("DvbCsa2", &crypto_mode));
|
||||
EXPECT_EQ(CryptoMode::kDvbCsa2, crypto_mode);
|
||||
ASSERT_TRUE(StringToCryptoMode("DvbCsa3", &crypto_mode));
|
||||
EXPECT_EQ(CryptoMode::kDvbCsa3, crypto_mode);
|
||||
ASSERT_TRUE(StringToCryptoMode("AesScte", &crypto_mode));
|
||||
EXPECT_EQ(CryptoMode::kAesScte, crypto_mode);
|
||||
EXPECT_FALSE(StringToCryptoMode("invalid crypto mode", &crypto_mode));
|
||||
}
|
||||
|
||||
TEST(WvCasTypesTest, ScramblingLevelToString) {
|
||||
std::string scrambling_level;
|
||||
ASSERT_TRUE(
|
||||
ScramblingLevelToString(ScramblingLevel::kPES, &scrambling_level));
|
||||
EXPECT_EQ("PES", scrambling_level);
|
||||
ASSERT_TRUE(ScramblingLevelToString(ScramblingLevel::kTS, &scrambling_level));
|
||||
EXPECT_EQ("TS", scrambling_level);
|
||||
EXPECT_FALSE(ScramblingLevelToString(static_cast<ScramblingLevel>(-1),
|
||||
&scrambling_level));
|
||||
}
|
||||
|
||||
TEST(WvCasTypeStatus, StringToScramblingLevel) {
|
||||
ScramblingLevel scrambling_level;
|
||||
ASSERT_TRUE(StringToScramblingLevel("PES", &scrambling_level));
|
||||
EXPECT_EQ(ScramblingLevel::kPES, scrambling_level);
|
||||
ASSERT_TRUE(StringToScramblingLevel("TS", &scrambling_level));
|
||||
EXPECT_EQ(ScramblingLevel::kTS, scrambling_level);
|
||||
EXPECT_FALSE(
|
||||
StringToScramblingLevel("invalid scrambling level", &scrambling_level));
|
||||
}
|
||||
|
||||
TEST(WvCasTypesTest, CreateWvCasEncryptionRequestJson) {
|
||||
WvCasEncryptionRequest request;
|
||||
request.content_id = "test_content_id";
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
@@ -22,43 +23,50 @@
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
static constexpr int32_t kDefaultDelayStart = 200;
|
||||
static constexpr int32_t kDefaultDelayStop = 200;
|
||||
static constexpr int32_t kDefaultEcmRepPeriod = 100;
|
||||
static constexpr int32_t kDefaultMaxCompTime = 100;
|
||||
static constexpr int32_t kAccessCriteriaTransferMode = 1;
|
||||
static constexpr int32_t kAccessCriteriaTransferMode = 0;
|
||||
static constexpr int32_t kNumberOfContentKeys = 2;
|
||||
static constexpr char kDefaultCryptoMode[] = "AesCtr";
|
||||
static constexpr bool kDefaultUseFixedFetcher = false;
|
||||
|
||||
DEFINE_int32(port, 0, "Server port number");
|
||||
|
||||
// ECMG related flags.
|
||||
// TODO(user): Consider adding flags 'ac_delay_start', 'ac_delay_stop',
|
||||
// 'transition_delay_start', 'transition_delay_stop'.
|
||||
DEFINE_int32(delay_start, kDefaultDelayStart,
|
||||
absl::StrCat("This flag sets the DVB SimulCrypt delay_start "
|
||||
"parameter, in milliseconds. Default: ",
|
||||
kDefaultDelayStart, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(delay_stop, kDefaultDelayStop,
|
||||
absl::StrCat("This flag sets the DVB SimulCrypt delay_stop "
|
||||
"parameter, in milliseconds. Default: ",
|
||||
kDefaultDelayStop, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(ecm_rep_period, kDefaultEcmRepPeriod,
|
||||
absl::StrCat("It sets the DVB SimulCrypt parameter "
|
||||
"ECM_rep_period, in milliseconds. Default: ",
|
||||
kDefaultEcmRepPeriod, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(max_comp_time, kDefaultMaxCompTime,
|
||||
absl::StrCat("It sets the DVB SimulCrypt parameter max_comp_time, "
|
||||
"in milliseconds. Default: ",
|
||||
kDefaultMaxCompTime, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(access_criteria_transfer_mode, kAccessCriteriaTransferMode,
|
||||
absl::StrCat("It sets the DVB SimulCrypt parameter "
|
||||
"access_criteria_transfer_mode. Default: ",
|
||||
kAccessCriteriaTransferMode)
|
||||
.c_str());
|
||||
DEFINE_int32(
|
||||
delay_start, kDefaultDelayStart,
|
||||
"This flag sets the DVB SimulCrypt delay_start parameter, in milliseconds");
|
||||
DEFINE_int32(
|
||||
delay_stop, kDefaultDelayStop,
|
||||
"This flag sets the DVB SimulCrypt delay_stop parameter, in milliseconds");
|
||||
DEFINE_int32(
|
||||
ecm_rep_period, kDefaultEcmRepPeriod,
|
||||
"It sets the DVB SimulCrypt parameter ECM_rep_period, in milliseconds");
|
||||
DEFINE_int32(
|
||||
max_comp_time, kDefaultMaxCompTime,
|
||||
"It sets the DVB SimulCrypt parameter max_comp_time, in milliseconds.");
|
||||
DEFINE_int32(
|
||||
access_criteria_transfer_mode, kAccessCriteriaTransferMode,
|
||||
"If it equals 0, it indicates that the access_criteria parameter is "
|
||||
"required in the CW_provision message only when the contents of this "
|
||||
"parameter change. If it equals 1, it indicates that the ECMG requires the "
|
||||
"access_criteria parameter be present in each CW_provision message.");
|
||||
DEFINE_int32(number_of_content_keys, kNumberOfContentKeys,
|
||||
"It sets the number of content keys (CwPerMsg). Must be 1 (single "
|
||||
"key) or 2 (key rotation enabled). It also sets LeadCw as "
|
||||
"number_of_content_keys - 1.");
|
||||
DEFINE_string(crypto_mode, kDefaultCryptoMode,
|
||||
"Encryption mode. Choices are \"AesCtr\", \"AesCbc\", "
|
||||
"\"DvbCsa2\", \"DvbCsa3\", \"AesOfb\", \"AesScte\".");
|
||||
DEFINE_bool(use_fixed_fetcher, kDefaultUseFixedFetcher,
|
||||
"Use fixed fetcher to fetch mocked entitlement licenses for "
|
||||
"testing purposes.");
|
||||
|
||||
#define LISTEN_QUEUE_SIZE (20)
|
||||
#define BUFFER_SIZE (1024)
|
||||
@@ -73,6 +81,10 @@ void BuildEcmgConfig(EcmgConfig* config) {
|
||||
config->ecm_rep_period = FLAGS_ecm_rep_period;
|
||||
config->max_comp_time = FLAGS_max_comp_time;
|
||||
config->access_criteria_transfer_mode = FLAGS_access_criteria_transfer_mode;
|
||||
config->number_of_content_keys = FLAGS_number_of_content_keys;
|
||||
CHECK(StringToCryptoMode(FLAGS_crypto_mode, &config->crypto_mode))
|
||||
<< "Unknown crypto mode.";
|
||||
config->use_fixed_fetcher = FLAGS_use_fixed_fetcher;
|
||||
}
|
||||
|
||||
void PrintMessage(const std::string& description, const char* const message,
|
||||
@@ -116,6 +128,8 @@ void ServeClient(int socket_fd, EcmgClientHandler* ecmg) {
|
||||
int main(int argc, char** argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
CHECK(FLAGS_port != 0) << "need --port";
|
||||
CHECK(FLAGS_number_of_content_keys == 1 || FLAGS_number_of_content_keys == 2)
|
||||
<< "--number_of_content_keys must be 1 or 2.";
|
||||
|
||||
EcmgConfig ecmg_config;
|
||||
BuildEcmgConfig(&ecmg_config);
|
||||
|
||||
Reference in New Issue
Block a user