Replace hardcoded parameters

This commit is contained in:
Lu Chen
2020-01-27 16:05:15 -08:00
parent cdd4d97e0f
commit 5c42bf9b7f
134 changed files with 9510 additions and 1938 deletions

View File

@@ -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",
],
)

View File

@@ -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);
}
}

View File

@@ -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_;

View File

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

View File

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

View File

@@ -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.

View File

@@ -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(&params_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

View File

@@ -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 = &params->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(&param_type, request + offset);
offset += PARAMETER_TYPE_SIZE;
BigEndianToHost16(&param_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(&param_type, request + offset);
offset += PARAMETER_TYPE_SIZE;
BigEndianToHost16(&param_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 =
&params->cp_cw_combinations[current_cp_cw_combination_index++];
params->cp_cw_combinations.emplace_back();
EcmgCpCwCombination* combination = &params->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(&params->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(&params->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(&params->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(&params->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(&params->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(&params->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(&params->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, &params);
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

View File

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

View File

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

View File

@@ -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_

View File

@@ -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;
}

View File

@@ -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);
}

View File

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

View File

@@ -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_;

View File

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

View File

@@ -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);

View File

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

View File

@@ -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);

View File

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

View File

@@ -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",

View File

@@ -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);

View File

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

View File

@@ -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_);
}

View File

@@ -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);

View File

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

View File

@@ -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));

View File

@@ -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 {

View File

@@ -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).

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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);

View File

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

View File

@@ -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);