Support for group license
Content keys in ECM v3 can now additionally be encrypted by group entitlement keys.
This commit is contained in:
@@ -55,34 +55,6 @@ cc_test(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ecm_generator",
|
||||
srcs = ["ecm_generator.cc"],
|
||||
hdrs = ["ecm_generator.h"],
|
||||
deps = [
|
||||
":ecm",
|
||||
"//base",
|
||||
"//common:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ecm_generator_test",
|
||||
size = "small",
|
||||
srcs = ["ecm_generator_test.cc"],
|
||||
deps = [
|
||||
":ecm_generator",
|
||||
":fixed_key_fetcher",
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:aes_cbc_util",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ecmg_client_handler",
|
||||
srcs = ["ecmg_client_handler.cc"],
|
||||
@@ -98,6 +70,7 @@ cc_library(
|
||||
":util",
|
||||
"//base",
|
||||
"@abseil_repo//absl/container:flat_hash_map",
|
||||
"@abseil_repo//absl/container:flat_hash_set",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/types:optional",
|
||||
@@ -123,6 +96,7 @@ cc_test(
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/strings:str_format",
|
||||
"@abseil_repo//absl/types:span",
|
||||
"//common:status",
|
||||
"//example:test_ecmg_messages",
|
||||
],
|
||||
)
|
||||
@@ -309,6 +283,7 @@ cc_library(
|
||||
deps = [
|
||||
":ecm_serializer",
|
||||
"//base",
|
||||
"@abseil_repo//absl/container:flat_hash_set",
|
||||
"@abseil_repo//absl/status",
|
||||
"@abseil_repo//absl/status:statusor",
|
||||
"@abseil_repo//absl/strings",
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include <bitset>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/memory/memory.h"
|
||||
@@ -76,10 +79,14 @@ Status Ecm::Initialize(
|
||||
return {error::INVALID_ARGUMENT, "Invalid CA system ID."};
|
||||
}
|
||||
|
||||
ClearEntitlementKeys();
|
||||
entitlement_keys_.clear();
|
||||
for (const auto& entitlement : injected_entitlements) {
|
||||
PushEntitlementKey(entitlement.track_type, entitlement.is_even_key,
|
||||
{entitlement.key_id, entitlement.key_value});
|
||||
std::vector<EntitlementKeyIdValue>& map_entry =
|
||||
entitlement_keys_[std::make_pair(entitlement.track_type,
|
||||
entitlement.group_id)];
|
||||
map_entry.insert(
|
||||
entitlement.is_even_key ? map_entry.begin() : map_entry.end(),
|
||||
{entitlement.key_id, entitlement.key_value});
|
||||
}
|
||||
|
||||
if (!CheckEntitlementKeys()) {
|
||||
@@ -114,13 +121,14 @@ void Ecm::SetServiceBlocking(const EcmServiceBlockingParams* service_blocking) {
|
||||
|
||||
Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type,
|
||||
const std::vector<std::string>& group_ids,
|
||||
std::string* serialized_ecm) const {
|
||||
absl::ReaderMutexLock lock(&ecm_params_mutex_);
|
||||
|
||||
if (!initialized_) {
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (!HaveEntitlementKeys()) {
|
||||
if (entitlement_keys_.empty()) {
|
||||
return {error::INTERNAL, "Need entitlement key."};
|
||||
}
|
||||
if (even_key == nullptr) {
|
||||
@@ -134,31 +142,45 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"odd_key can not be null as key rotation is enabled."};
|
||||
}
|
||||
|
||||
if (serialized_ecm == nullptr) {
|
||||
return {error::INVALID_ARGUMENT, "No return ecm std::string pointer."};
|
||||
}
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys = {even_key};
|
||||
if (odd_key != nullptr) {
|
||||
keys.push_back(odd_key);
|
||||
}
|
||||
Status status = WrapEntitledKeys(track_type, keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
const bool has_odd_key = odd_key != nullptr;
|
||||
|
||||
EcmSerializerParams serializer_params;
|
||||
serializer_params.crypto_mode = crypto_mode_;
|
||||
serializer_params.age_restriction = age_restriction_;
|
||||
serializer_params.cas_id = cas_id_;
|
||||
serializer_params.even_key = {even_key->entitlement_key_id, even_key->key_id,
|
||||
even_key->wrapped_key_value,
|
||||
even_key->wrapped_key_iv, even_key->content_iv};
|
||||
if (odd_key != nullptr) {
|
||||
serializer_params.odd_key = {odd_key->entitlement_key_id, odd_key->key_id,
|
||||
odd_key->wrapped_key_value,
|
||||
odd_key->wrapped_key_iv, odd_key->content_iv};
|
||||
|
||||
// Process normal content keys.
|
||||
std::vector<EntitledKeyInfo> content_keys = {*even_key};
|
||||
std::vector<EcmSerializerKeyInfo*> serializer_keys = {
|
||||
&serializer_params.even_key};
|
||||
if (has_odd_key) {
|
||||
content_keys.push_back(*odd_key);
|
||||
serializer_keys.push_back(&serializer_params.odd_key);
|
||||
}
|
||||
Status status = WrapEntitledKeys(track_type, /*group_id=*/"", content_keys,
|
||||
serializer_keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Process group keys.
|
||||
for (const auto& group_id : group_ids) {
|
||||
EcmSerializerGroupKeyInfo group_key_info;
|
||||
group_key_info.group_id = group_id;
|
||||
std::vector<EcmSerializerKeyInfo*> group_serializer_keys = {
|
||||
&group_key_info.even_key};
|
||||
if (has_odd_key) {
|
||||
group_serializer_keys.push_back(&group_key_info.odd_key);
|
||||
}
|
||||
Status status = WrapEntitledKeys(track_type, group_id, content_keys,
|
||||
group_serializer_keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
serializer_params.group_keys.push_back(group_key_info);
|
||||
}
|
||||
|
||||
serializer_params.fingerprinting = fingerprinting_;
|
||||
@@ -175,12 +197,13 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
|
||||
|
||||
Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
const std::string& track_type,
|
||||
const std::vector<std::string>& group_ids,
|
||||
std::string* serialized_ecm) const {
|
||||
return GenerateEcm(key, nullptr, track_type, serialized_ecm);
|
||||
return GenerateEcm(key, nullptr, track_type, group_ids, serialized_ecm);
|
||||
}
|
||||
|
||||
Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid, uint8_t table_id,
|
||||
uint8_t* continuity_counter,
|
||||
Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id, uint8_t* continuity_counter,
|
||||
absl::Span<uint8_t> packet,
|
||||
ssize_t* bytes_modified) {
|
||||
if (ecm.empty()) {
|
||||
@@ -207,38 +230,49 @@ Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid, uint8_t table
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::WrapEntitledKeys(const std::string& track_type,
|
||||
const std::vector<EntitledKeyInfo*>& keys) const {
|
||||
if (!initialized_) {
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (keys.empty()) {
|
||||
Status Ecm::WrapEntitledKeys(
|
||||
const std::string& track_type, const std::string& group_id,
|
||||
const std::vector<EntitledKeyInfo>& content_keys,
|
||||
std::vector<EcmSerializerKeyInfo*> serializer_keys) const {
|
||||
DCHECK(initialized_);
|
||||
if (content_keys.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Vector of EntitledKeyInfo is empty."};
|
||||
}
|
||||
|
||||
auto ekey_map_entry = entitlement_keys_.find(track_type);
|
||||
auto ekey_map_entry =
|
||||
entitlement_keys_.find(std::make_pair(track_type, group_id));
|
||||
if (ekey_map_entry == entitlement_keys_.end()) {
|
||||
return {error::INTERNAL, "No Entitlement Key found for given track_type."};
|
||||
}
|
||||
const std::vector<EntitlementKeyIdValue>& entitlement_keys =
|
||||
ekey_map_entry->second;
|
||||
|
||||
const auto& ekey_list = ekey_map_entry->second;
|
||||
if (ekey_list.size() != keys.size()) {
|
||||
if (entitlement_keys.size() != content_keys.size()) {
|
||||
return {error::INTERNAL,
|
||||
"Number of Entitled keys and Entitlement keys must match."};
|
||||
"Number of content keys and entitlement keys must match."};
|
||||
}
|
||||
if (content_keys.size() != serializer_keys.size()) {
|
||||
return {error::INTERNAL,
|
||||
"Number of content keys and serializer keys must match."};
|
||||
}
|
||||
|
||||
auto entitlement_key = ekey_list.begin();
|
||||
for (auto entitled_key : keys) {
|
||||
entitled_key->entitlement_key_id = entitlement_key->key_id;
|
||||
// Wrap key using entitlement key.
|
||||
Status status =
|
||||
WrapKey(entitlement_key->key_value, entitled_key->wrapped_key_iv,
|
||||
entitled_key->key_value, &entitled_key->wrapped_key_value);
|
||||
for (int i = 0; i < entitlement_keys.size(); ++i) {
|
||||
// Fill these fields only if this is NOT a group key.
|
||||
if (group_id.empty()) {
|
||||
serializer_keys[i]->wrapped_key_iv = content_keys[i].wrapped_key_iv;
|
||||
serializer_keys[i]->content_iv = content_keys[i].content_iv;
|
||||
serializer_keys[i]->content_key_id = content_keys[i].key_id;
|
||||
}
|
||||
|
||||
serializer_keys[i]->entitlement_key_id = entitlement_keys[i].key_id;
|
||||
Status status = WrapKey(
|
||||
entitlement_keys[i].key_value, content_keys[i].wrapped_key_iv,
|
||||
content_keys[i].key_value, &serializer_keys[i]->wrapped_key_value);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
entitlement_key++;
|
||||
}
|
||||
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
@@ -291,20 +325,12 @@ Status Ecm::ValidateIv(const std::string& iv, size_t size) const {
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
size_t Ecm::CountEntitlementKeys() const {
|
||||
size_t count = 0;
|
||||
for (const auto& track : entitlement_keys_) {
|
||||
count += track.second.size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool Ecm::CheckEntitlementKeys() const {
|
||||
for (const auto& track : entitlement_keys_) {
|
||||
if (track.second.size() < (paired_keys_required_ ? 2 : 1)) {
|
||||
LOG(ERROR) << " Wrong number of entitlement keys for track "
|
||||
<< track.first << ": " << (paired_keys_required_ ? 2 : 1)
|
||||
<< " expected, " << track.second.size() << " received.";
|
||||
if (track.second.size() != (paired_keys_required_ ? 2 : 1)) {
|
||||
LOG(ERROR) << " Wrong number of entitlement keys: "
|
||||
<< (paired_keys_required_ ? 2 : 1) << " expected, "
|
||||
<< track.second.size() << " received.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -111,6 +112,8 @@ class Ecm {
|
||||
// |even_key| information for even key to be encoded into ECM.
|
||||
// |odd_key| information for odd key to be encoded into ECM.
|
||||
// |track_type| the track that the keys are being used to encrypt.
|
||||
// |group_ids| If specified, the content keys will be additionally encrypted
|
||||
// by group entitlement keys with specified |group_ids|.
|
||||
// |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
|
||||
// The |even_key| and |odd_key| contents (specifically IV sizes) must be
|
||||
// consistent with the initialized settings.
|
||||
@@ -119,6 +122,7 @@ class Ecm {
|
||||
virtual Status GenerateEcm(EntitledKeyInfo* even_key,
|
||||
EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type,
|
||||
const std::vector<std::string>& group_ids,
|
||||
std::string* serialized_ecm) const;
|
||||
|
||||
// Accept a key and IV and construct an ECM that will fit into a Transport
|
||||
@@ -127,11 +131,14 @@ class Ecm {
|
||||
// Args:
|
||||
// |key| information for key to be encoded into ECM.
|
||||
// |track_type| the track that the key is being used to encrypt.
|
||||
// |group_ids| If specified, the content keys will be additionally encrypted
|
||||
// by group entitlement keys with specified |group_ids|.
|
||||
// |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
|
||||
// The |key| contents (specifically IV sizes) must be consistent
|
||||
// with the initialized settings.
|
||||
virtual Status GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
const std::string& track_type,
|
||||
const std::vector<std::string>& group_ids,
|
||||
std::string* serialized_ecm) const;
|
||||
|
||||
// Generate a TS packet with the given |ecm| as payload.
|
||||
@@ -165,16 +172,6 @@ class Ecm {
|
||||
ecm_serializer_ = std::move(ecm_serializer);
|
||||
}
|
||||
|
||||
// Apply the Entitlement key with the given track type and polarity
|
||||
// (even vs. odd key) to wrap the given Entitled key.
|
||||
// Args:
|
||||
// |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) const;
|
||||
|
||||
private:
|
||||
// Entitlement key - |key_value| is used to wrap the content key, and |key_id|
|
||||
// is added to the ECM. |track_type| allows the entitlement key to be
|
||||
@@ -184,32 +181,23 @@ class Ecm {
|
||||
std::string key_value;
|
||||
};
|
||||
|
||||
// Check whether we've acquired entitlement keys.
|
||||
bool HaveEntitlementKeys() const { return !entitlement_keys_.empty(); }
|
||||
|
||||
// Count the different tracks for which we have entitlement keys.
|
||||
size_t CountEntitlementTracks() const { return entitlement_keys_.size(); }
|
||||
|
||||
// Count the total number of entitlement keys in our current state.
|
||||
size_t CountEntitlementKeys() const;
|
||||
|
||||
// Verify that each track has the right number of keys. Complain if not.
|
||||
bool CheckEntitlementKeys() const;
|
||||
|
||||
// Remove all current entitlement keys.
|
||||
void ClearEntitlementKeys() { entitlement_keys_.clear(); }
|
||||
|
||||
// Add an entitlement key to our current state. Even key is placed first.
|
||||
void PushEntitlementKey(const std::string& track_type, bool is_even_key,
|
||||
const EntitlementKeyIdValue& key) {
|
||||
auto emplaced = entitlement_keys_.emplace(
|
||||
track_type, std::list<EntitlementKeyIdValue>{});
|
||||
if (is_even_key) {
|
||||
emplaced.first->second.push_front(key);
|
||||
} else {
|
||||
emplaced.first->second.push_back(key);
|
||||
}
|
||||
}
|
||||
// Apply the entitlement keys with the given track type (and group_ids if
|
||||
// specified) to wrap the given |keys|.
|
||||
// Args:
|
||||
// |track_type| the track type for the entitlement keys that should be used.
|
||||
// |group_id| group id of the entitlement keys to use. Can be empty.
|
||||
// |keys| the content keys to be wrapped.
|
||||
// |serializer_keys| is the output vector. It must have the same size as
|
||||
// |keys|. Fields in EcmSerializerKeyInfo will be updated. If group_id is
|
||||
// non-empty, only fields entitlement_key_id and wrapped_key_value will be
|
||||
// updated.
|
||||
Status WrapEntitledKeys(
|
||||
const std::string& track_type, const std::string& group_id,
|
||||
const std::vector<EntitledKeyInfo>& content_keys,
|
||||
std::vector<EcmSerializerKeyInfo*> serializer_keys) const;
|
||||
|
||||
// Wrap |key_value| using |wrapping_key| (entitlement key) and |wrapping_iv|.
|
||||
// Returns the resulting wrapped key in |wrapped_key|.
|
||||
@@ -236,8 +224,11 @@ class Ecm {
|
||||
std::string ecc_private_signing_key_;
|
||||
// Entitlement keys needed for ECM generation.
|
||||
// The keys are added when the CasEncryptionResponse is processed.
|
||||
// Maps from track_type to one/two EntitlementKeyIdValue with even key first.
|
||||
std::map<std::string, std::list<EntitlementKeyIdValue>> entitlement_keys_;
|
||||
// Maps from {track_type, group_id} to one/two EntitlementKeyIdValue with even
|
||||
// key first.
|
||||
std::map<std::pair<std::string, std::string>,
|
||||
std::vector<EntitlementKeyIdValue>>
|
||||
entitlement_keys_;
|
||||
std::unique_ptr<EcmSerializer> ecm_serializer_;
|
||||
|
||||
mutable absl::Mutex ecm_params_mutex_;
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "media_cas_packager_sdk/internal/ecm_generator.h"
|
||||
|
||||
#include "glog/logging.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
static constexpr int kKeyDataSize = 16;
|
||||
static constexpr int kKeyIvSize8 = 8;
|
||||
static constexpr int kKeyIvSize16 = 16;
|
||||
static constexpr int kMaxBytesKeyIdField = 16;
|
||||
|
||||
std::string EcmGenerator::GenerateEcm(const EcmParameters& params) {
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
Status status = ProcessEcmParameters(params, &keys);
|
||||
if (!status.ok() || !initialized_) {
|
||||
LOG(ERROR) << " EcmParameters is not set up properly: " << status;
|
||||
return "";
|
||||
}
|
||||
std::string serialized_ecm;
|
||||
std::string track_type = params.track_type;
|
||||
if (params.rotation_enabled) {
|
||||
status = ecm_->GenerateEcm(&keys[0], &keys[1], track_type, &serialized_ecm);
|
||||
} else {
|
||||
status = ecm_->GenerateSingleKeyEcm(&keys[0], track_type, &serialized_ecm);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << " Generate ECM call failed: " << status;
|
||||
return "";
|
||||
}
|
||||
return serialized_ecm;
|
||||
}
|
||||
|
||||
Status EcmGenerator::ProcessEcmParameters(const EcmParameters& ecm_params,
|
||||
std::vector<EntitledKeyInfo>* keys) {
|
||||
initialized_ = false;
|
||||
rotation_enabled_ = ecm_params.rotation_enabled;
|
||||
|
||||
// Validate and add key data
|
||||
keys->clear();
|
||||
uint32_t keys_needed = ecm_params.rotation_enabled ? 2 : 1;
|
||||
if (ecm_params.key_params.size() < keys_needed) {
|
||||
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()) {
|
||||
return status;
|
||||
}
|
||||
keys->emplace_back(EntitledKeyInfo());
|
||||
EntitledKeyInfo& key = keys->back();
|
||||
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_iv;
|
||||
}
|
||||
current_key_index_ = 0;
|
||||
current_key_even_ = true;
|
||||
initialized_ = true;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status EcmGenerator::ValidateKeyId(const std::string& id) const {
|
||||
if (id.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Key id is empty."};
|
||||
}
|
||||
if (id.size() > kMaxBytesKeyIdField) {
|
||||
return {error::INVALID_ARGUMENT, "Key id is too long."};
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status EcmGenerator::ValidateKeyData(const std::string& key_data) const {
|
||||
if (key_data.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Key data is empty."};
|
||||
}
|
||||
if (key_data.size() != kKeyDataSize) {
|
||||
return {error::INVALID_ARGUMENT, "Key data is wrong size."};
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status EcmGenerator::ValidateIv(const std::string& iv,
|
||||
size_t required_size) const {
|
||||
if (iv.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "IV is empty."};
|
||||
}
|
||||
if (required_size != 8 && required_size != 16) {
|
||||
return {error::INTERNAL, "IV size has not been set up correctly."};
|
||||
}
|
||||
|
||||
if (iv.size() != required_size) {
|
||||
return {error::INVALID_ARGUMENT, "IV has wrong or inconsistent size."};
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status EcmGenerator::ValidateWrappedKeyIv(const std::string& iv) const {
|
||||
// All wrapped key IVs must be 16 bytes.
|
||||
Status status = ValidateIv(iv, kIvSize16);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << " Wrapped key IV is not valid: " << status;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status EcmGenerator::ValidateKeyParameters(
|
||||
const KeyParameters& key_params) const {
|
||||
Status status = ValidateKeyId(key_params.key_id);
|
||||
if (!status.ok()) return status;
|
||||
|
||||
return ValidateContentIv(key_params.content_iv);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
@@ -1,92 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// KeyParameters carries key information for a single encryption key.
|
||||
// Instances of this struct are owned by an EcmParameters struct.
|
||||
struct KeyParameters {
|
||||
std::string key_id;
|
||||
std::string key_data;
|
||||
std::string wrapped_key_iv;
|
||||
std::string content_iv;
|
||||
};
|
||||
|
||||
// EcmParameters holds information that is needed by the EcmGenerator. It is
|
||||
// partially set up with data from a KeyGenerator (obtained via GenerateKey()).
|
||||
// IVs are added later.
|
||||
struct EcmParameters {
|
||||
// TODO(user): entitlement_key_id does not seem to be used, but assumed
|
||||
// to exist by unit tests.
|
||||
std::string entitlement_key_id;
|
||||
bool rotation_enabled = true;
|
||||
// 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;
|
||||
};
|
||||
|
||||
// ECM Generator for Widevine/MediaCAS entitled keys.
|
||||
class EcmGenerator {
|
||||
public:
|
||||
EcmGenerator() = default;
|
||||
EcmGenerator(const EcmGenerator&) = delete;
|
||||
EcmGenerator& operator=(const EcmGenerator&) = delete;
|
||||
virtual ~EcmGenerator() = default;
|
||||
|
||||
virtual std::string GenerateEcm(const EcmParameters& params);
|
||||
|
||||
// Query the state of this ECM Generator
|
||||
bool initialized() const { return initialized_; }
|
||||
bool rotation_enabled() const { return rotation_enabled_; }
|
||||
|
||||
void set_ecm(std::unique_ptr<Ecm> ecm) { ecm_ = std::move(ecm); }
|
||||
|
||||
private:
|
||||
friend class EcmGeneratorTest;
|
||||
|
||||
Status ProcessEcmParameters(const EcmParameters& ecm_params,
|
||||
std::vector<EntitledKeyInfo>* keys);
|
||||
|
||||
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;
|
||||
bool rotation_enabled_ = false;
|
||||
uint32_t current_key_index_ = 0;
|
||||
bool current_key_even_ = true;
|
||||
uint32_t content_iv_size_ = 0;
|
||||
std::unique_ptr<Ecm> ecm_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_
|
||||
@@ -1,351 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "media_cas_packager_sdk/internal/ecm_generator.h"
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "google/protobuf/util/json_util.h"
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "common/aes_cbc_util.h"
|
||||
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kCADescriptor[] = "TestCa";
|
||||
constexpr char kEcm[] = "TestEcm";
|
||||
constexpr char kProvider[] = "Gfiber";
|
||||
constexpr char kContentId[] = "TestContent";
|
||||
constexpr int kDefaultKeyRotationPeriodMilliseconds = 30 * 60 * 1000;
|
||||
constexpr char kPsshData[] = "TestPsshData";
|
||||
|
||||
constexpr char kEntitlementKeySingle[] = "testEKId12345678";
|
||||
constexpr char kEntitlementKeyDouble[] = "testEKIdabcdefgh";
|
||||
|
||||
constexpr char kEcmKeyIdSingle[] = "key-id-One123456";
|
||||
constexpr char kEcmKeyDataSingle[] = "0123456701234567";
|
||||
constexpr char kEcmWrappedKeyIvSingle[] = "abcdefghacbdefgh";
|
||||
constexpr char kEcmContentIvSingle[] = "ABCDEFGH";
|
||||
|
||||
constexpr char kEcmKeyIdEven[] = "key-Id-One123456";
|
||||
constexpr char kEcmKeyDataEven[] = "KKEEYYDDAATTAAee";
|
||||
constexpr char kEcmWrappedKeyIvEven[] = "abcdefghacbdefgh";
|
||||
constexpr char kEcmContentIvEven[] = "ABCDEFGH";
|
||||
|
||||
constexpr char kEcmKeyIdOdd[] = "key-Id-Two456789";
|
||||
constexpr char kEcmKeyDataOdd[] = "kkeeyyddaattaaOO";
|
||||
constexpr char kEcmWrappedKeyIvOdd[] = "a1c2e3g4a1c2e3g4";
|
||||
constexpr char kEcmContentIvOdd[] = "AaCbEcGd";
|
||||
|
||||
constexpr char kFakeCasEncryptionResponseKeyId[] = "fake_key_id.....";
|
||||
constexpr char kFakeCasEncryptionResponseKeyData[] =
|
||||
"fakefakefakefakefakefakefakefake";
|
||||
constexpr char kFakeCasEncryptionResponseGroupKeyId[] = "fake_key_id12345";
|
||||
constexpr char kFakeCasEncryptionResponseGroupKeyData[] =
|
||||
"fakefakefakefakefakefake12345678";
|
||||
|
||||
constexpr char kTrackType[] = "SD";
|
||||
|
||||
void SetKeyInfoInTestResponse(
|
||||
widevine::CasEncryptionResponse_KeyInfo* key, bool group_id_exists) {
|
||||
ASSERT_TRUE(key != nullptr);
|
||||
if (!group_id_exists) {
|
||||
key->set_key_id(kFakeCasEncryptionResponseKeyId);
|
||||
key->set_key(kFakeCasEncryptionResponseKeyData);
|
||||
} else {
|
||||
key->set_key_id(kFakeCasEncryptionResponseGroupKeyId);
|
||||
key->set_key(kFakeCasEncryptionResponseGroupKeyData);
|
||||
}
|
||||
}
|
||||
|
||||
Status HandleCasEncryptionRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) {
|
||||
SignedCasEncryptionRequest signed_request;
|
||||
if (!google::protobuf::util::JsonStringToMessage(signed_request_json, &signed_request)
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Unable to parse signed_request_json.";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
CasEncryptionRequest request;
|
||||
if (!google::protobuf::util::JsonStringToMessage(signed_request.request(), &request)
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Unable to understand signed_request_json.";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
CasEncryptionResponse response;
|
||||
response.set_status(CasEncryptionResponse::OK);
|
||||
response.set_content_id(request.content_id());
|
||||
response.set_group_id(request.group_id());
|
||||
for (const auto& track_type : request.track_types()) {
|
||||
if (request.key_rotation()) {
|
||||
// Add the Even key.
|
||||
auto key = response.add_entitlement_keys();
|
||||
SetKeyInfoInTestResponse(key, request.has_group_id());
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
|
||||
// Add the Odd key.
|
||||
key = response.add_entitlement_keys();
|
||||
SetKeyInfoInTestResponse(key, request.has_group_id());
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
|
||||
} else {
|
||||
auto key = response.add_entitlement_keys();
|
||||
SetKeyInfoInTestResponse(key, request.has_group_id());
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
|
||||
}
|
||||
}
|
||||
std::string response_string;
|
||||
if (!google::protobuf::util::MessageToJsonString(response, &response_string).ok()) {
|
||||
LOG(ERROR) << "MessageToJsonString(response, &response_string)";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response_string);
|
||||
|
||||
if (!google::protobuf::util::MessageToJsonString(signed_response, http_response_json)
|
||||
.ok()) {
|
||||
LOG(ERROR) << "MessageToJsonString(signed_response, http_response_json)";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class EcmGeneratorTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {}
|
||||
|
||||
Status ProcessEcmParameters(const EcmParameters& params,
|
||||
std::vector<EntitledKeyInfo>* keys) {
|
||||
return ecm_gen_.ProcessEcmParameters(params, keys);
|
||||
}
|
||||
|
||||
int EntitlementKeySize(const EcmParameters& params) const {
|
||||
return params.entitlement_key_id.size();
|
||||
}
|
||||
|
||||
void SetTestConfig1(EcmParameters* params) {
|
||||
params->entitlement_key_id = kEntitlementKeySingle;
|
||||
params->rotation_enabled = false;
|
||||
params->key_params.push_back(kKeyParamsSingle);
|
||||
params->track_type = kTrackType;
|
||||
}
|
||||
|
||||
void SetTestConfig2(EcmParameters* params) {
|
||||
KeyParameters even_key_params;
|
||||
KeyParameters odd_key_params;
|
||||
params->entitlement_key_id = kEntitlementKeyDouble;
|
||||
params->rotation_enabled = true;
|
||||
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.
|
||||
void PrepareEcmGenerator(bool key_rotation_enabled,
|
||||
const std::string& group_id) {
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
ecm_init_params_.key_rotation_enabled = key_rotation_enabled;
|
||||
FixedKeyFetcher key_fetcher;
|
||||
EntitlementRequestParams request_params;
|
||||
request_params.content_id = kContentId;
|
||||
request_params.content_provider = kProvider;
|
||||
request_params.group_id = group_id;
|
||||
request_params.track_types = {kTrackType};
|
||||
request_params.key_rotation = key_rotation_enabled;
|
||||
ASSERT_OK(key_fetcher.CreateEntitlementRequest(request_params,
|
||||
&entitlement_request));
|
||||
ASSERT_OK(
|
||||
HandleCasEncryptionRequest(entitlement_request, &entitlement_response));
|
||||
std::vector<EntitlementKeyInfo> entitlements;
|
||||
ASSERT_OK(key_fetcher.ParseEntitlementResponse(entitlement_response,
|
||||
&entitlements));
|
||||
|
||||
ecm_ = absl::make_unique<Ecm>();
|
||||
ASSERT_OK(ecm_->Initialize(ecm_init_params_, entitlements));
|
||||
ecm_gen_.set_ecm(std::move(ecm_));
|
||||
}
|
||||
|
||||
void CheckECMOutput(absl::string_view ecm_string,
|
||||
absl::string_view response_entitlement_key_id,
|
||||
absl::string_view response_entitltement_key_data,
|
||||
bool key_rotation_enabled) {
|
||||
// Expected size (bytes):
|
||||
// CA system ID: 2 bytes
|
||||
// version: 1 byte
|
||||
// flags: 1 byte
|
||||
// flags: 1 byte
|
||||
// entitlement key ID: 16 bytes
|
||||
// Single key: ID (16), Data (16), IV (16), IV (8) = 56
|
||||
// total = 77 (ECM message complete if no key retotation)
|
||||
// second entitlement key ID: 16 bytes
|
||||
// Second key: ID (16), Data (16), IV (16), IV (8) = 56
|
||||
// total = 149
|
||||
uint32_t ecm_string_size = key_rotation_enabled ? 149 : 77;
|
||||
ASSERT_EQ(ecm_string_size, 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('\x02', ecm_string[2]); // ECM version
|
||||
if (key_rotation_enabled) {
|
||||
EXPECT_EQ('\x03', ecm_string[3]); // flags
|
||||
} else {
|
||||
EXPECT_EQ('\x02', ecm_string[3]); // flags
|
||||
}
|
||||
EXPECT_EQ('\x80', ecm_string[4]); // flags
|
||||
EXPECT_EQ(response_entitlement_key_id, ecm_string.substr(5, 16));
|
||||
if (!key_rotation_enabled) {
|
||||
EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 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.
|
||||
absl::string_view wrapping_key = response_entitltement_key_data;
|
||||
absl::string_view wrapping_iv = ecm_string.substr(53, 16);
|
||||
absl::string_view wrapped_key = ecm_string.substr(37, 16);
|
||||
std::string unwrapped_key = crypto_util::DecryptAesCbcNoPad(
|
||||
std::string(wrapping_key), std::string(wrapping_iv),
|
||||
std::string(wrapped_key));
|
||||
EXPECT_EQ(kEcmKeyDataSingle, unwrapped_key);
|
||||
EXPECT_EQ(kEcmContentIvSingle, ecm_string.substr(69, 8));
|
||||
} else {
|
||||
EXPECT_EQ(kEcmKeyIdEven, ecm_string.substr(21, 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(response_entitlement_key_id, ecm_string.substr(77, 16));
|
||||
EXPECT_EQ(kEcmKeyIdOdd, ecm_string.substr(93, 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.
|
||||
absl::string_view wrapping_key_even = response_entitltement_key_data;
|
||||
absl::string_view wrapping_iv_even = ecm_string.substr(53, 16);
|
||||
absl::string_view wrapped_key_even = ecm_string.substr(37, 16);
|
||||
std::string unwrapped_key_even = crypto_util::DecryptAesCbcNoPad(
|
||||
std::string(wrapping_key_even), std::string(wrapping_iv_even),
|
||||
std::string(wrapped_key_even));
|
||||
EXPECT_EQ(kEcmKeyDataEven, unwrapped_key_even);
|
||||
// Unwrap odd key and compare with original.
|
||||
absl::string_view wrapping_key_odd = response_entitltement_key_data;
|
||||
absl::string_view wrapping_iv_odd = ecm_string.substr(125, 16);
|
||||
absl::string_view wrapped_key_odd = ecm_string.substr(109, 16);
|
||||
std::string unwrapped_key_odd = crypto_util::DecryptAesCbcNoPad(
|
||||
std::string(wrapping_key_odd), std::string(wrapping_iv_odd),
|
||||
std::string(wrapped_key_odd));
|
||||
EXPECT_EQ(kEcmKeyDataOdd, unwrapped_key_odd);
|
||||
}
|
||||
}
|
||||
|
||||
const KeyParameters kKeyParamsSingle{kEcmKeyIdSingle,
|
||||
kEcmKeyDataSingle,
|
||||
kEcmWrappedKeyIvSingle,
|
||||
{kEcmContentIvSingle}};
|
||||
const KeyParameters kKeyParamsEven{kEcmKeyIdEven,
|
||||
kEcmKeyDataEven,
|
||||
kEcmWrappedKeyIvEven,
|
||||
{kEcmContentIvEven}};
|
||||
const KeyParameters kKeyParamsOdd{
|
||||
kEcmKeyIdOdd, kEcmKeyDataOdd, kEcmWrappedKeyIvOdd, {kEcmContentIvOdd}};
|
||||
|
||||
std::unique_ptr<Ecm> ecm_;
|
||||
EcmInitParameters ecm_init_params_;
|
||||
EcmGenerator ecm_gen_;
|
||||
};
|
||||
|
||||
TEST_F(EcmGeneratorTest, InitializeNoRotation) {
|
||||
EXPECT_FALSE(ecm_gen_.initialized());
|
||||
PrepareEcmGenerator(false, /*group_id=*/"");
|
||||
EcmParameters ecm_params;
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
SetTestConfig1(&ecm_params);
|
||||
|
||||
Status status = ProcessEcmParameters(ecm_params, &keys);
|
||||
ASSERT_OK(status);
|
||||
ASSERT_EQ(EntitlementKeySize(ecm_params), 16);
|
||||
ASSERT_TRUE(ecm_gen_.initialized());
|
||||
EXPECT_FALSE(ecm_gen_.rotation_enabled());
|
||||
}
|
||||
|
||||
TEST_F(EcmGeneratorTest, GenerateNoRotation) {
|
||||
PrepareEcmGenerator(false, /*group_id=*/"");
|
||||
EcmParameters ecm_params;
|
||||
SetTestConfig1(&ecm_params);
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
|
||||
CheckECMOutput(ecm_string, kFakeCasEncryptionResponseKeyId,
|
||||
kFakeCasEncryptionResponseKeyData, false);
|
||||
}
|
||||
|
||||
TEST_F(EcmGeneratorTest, GenerateNoRotationWithGroupId) {
|
||||
PrepareEcmGenerator(false, "groupId_1");
|
||||
EcmParameters ecm_params;
|
||||
SetTestConfig1(&ecm_params);
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
CheckECMOutput(ecm_string, kFakeCasEncryptionResponseGroupKeyId,
|
||||
kFakeCasEncryptionResponseGroupKeyData, false);
|
||||
}
|
||||
|
||||
TEST_F(EcmGeneratorTest, Generate2NoRotation) {
|
||||
PrepareEcmGenerator(false, /*group_id=*/"");
|
||||
EcmParameters ecm_params;
|
||||
SetTestConfig1(&ecm_params);
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
CheckECMOutput(ecm_string, kFakeCasEncryptionResponseKeyId,
|
||||
kFakeCasEncryptionResponseKeyData, false);
|
||||
}
|
||||
|
||||
TEST_F(EcmGeneratorTest, InitializeSimpleRotation) {
|
||||
EXPECT_FALSE(ecm_gen_.initialized());
|
||||
EcmParameters ecm_params;
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
PrepareEcmGenerator(true, /*group_id=*/"");
|
||||
SetTestConfig2(&ecm_params);
|
||||
ecm_init_params_.key_rotation_enabled = true;
|
||||
Status status = ProcessEcmParameters(ecm_params, &keys);
|
||||
EXPECT_TRUE(status.ok());
|
||||
EXPECT_TRUE(ecm_gen_.initialized());
|
||||
EXPECT_TRUE(ecm_gen_.rotation_enabled());
|
||||
}
|
||||
|
||||
TEST_F(EcmGeneratorTest, GenerateSimpleRotation) {
|
||||
EcmParameters ecm_params;
|
||||
PrepareEcmGenerator(true, /*group_id=*/"");
|
||||
SetTestConfig2(&ecm_params);
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
CheckECMOutput(ecm_string, kFakeCasEncryptionResponseKeyId,
|
||||
kFakeCasEncryptionResponseKeyData, true);
|
||||
}
|
||||
|
||||
TEST_F(EcmGeneratorTest, GenerateSimpleRotationWithGroupId) {
|
||||
EcmParameters ecm_params;
|
||||
PrepareEcmGenerator(true, "groupId_1");
|
||||
SetTestConfig2(&ecm_params);
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
CheckECMOutput(ecm_string, kFakeCasEncryptionResponseGroupKeyId,
|
||||
kFakeCasEncryptionResponseGroupKeyData, true);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
@@ -11,6 +11,7 @@
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -32,6 +33,13 @@ struct EcmSerializerKeyInfo {
|
||||
std::string content_iv;
|
||||
};
|
||||
|
||||
// Group key information used to generate serialized ecm.
|
||||
struct EcmSerializerGroupKeyInfo {
|
||||
std::string group_id;
|
||||
EcmSerializerKeyInfo even_key;
|
||||
EcmSerializerKeyInfo odd_key;
|
||||
};
|
||||
|
||||
// Parameters passed into ECM serializer to generate ECM.
|
||||
struct EcmSerializerParams {
|
||||
uint16_t cas_id;
|
||||
@@ -44,6 +52,7 @@ struct EcmSerializerParams {
|
||||
// Private signing key used to sign ECM data. Must be an elliptic-curve
|
||||
// cryptography key.
|
||||
std::string ecc_private_signing_key;
|
||||
std::vector<EcmSerializerGroupKeyInfo> group_keys;
|
||||
};
|
||||
|
||||
enum class EcmSerializerVersion { kV2 = 0, kV3 };
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer_v2.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer_v3.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
@@ -67,6 +70,57 @@ Status ValidateKeyInfo(const EcmSerializerKeyInfo& key_info, bool is_even_key) {
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
// Fields entitlement_key_id and wrapped_key_value must exist;
|
||||
// wrapped_key_iv is optional; content_iv is ignored as it should be the
|
||||
// same as the one specified in normal key.
|
||||
Status ValidateGroupSerializerKeyInfo(EcmSerializerKeyInfo key_info) {
|
||||
if (key_info.entitlement_key_id.size() != kKeyIdSizeBytes) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Unexpected group entitlement key ID size: ",
|
||||
key_info.entitlement_key_id.size())};
|
||||
}
|
||||
if (key_info.wrapped_key_value.size() != kKeyDataSizeBytes) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Unexpected group wrapped key size: ",
|
||||
key_info.wrapped_key_value.size())};
|
||||
}
|
||||
// Wrapped key IV may be empty.
|
||||
if (!key_info.wrapped_key_iv.empty() &&
|
||||
key_info.wrapped_key_iv.size() != kWrappedKeyIvSizeBytes) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Unexpected group wrapped key iv size: ",
|
||||
key_info.wrapped_key_iv.size())};
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status ValidateGroupKey(
|
||||
const std::vector<EcmSerializerGroupKeyInfo>& group_keys,
|
||||
bool has_odd_key) {
|
||||
// Used to make sure group ids are unique.
|
||||
absl::flat_hash_set<std::string> group_ids;
|
||||
for (const auto& group_key : group_keys) {
|
||||
if (group_key.group_id.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Missing group id."};
|
||||
}
|
||||
if (!group_ids.insert(group_key.group_id).second) {
|
||||
return {error::INVALID_ARGUMENT, "Group ids are not unique."};
|
||||
}
|
||||
|
||||
Status status = ValidateGroupSerializerKeyInfo(group_key.even_key);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (has_odd_key) {
|
||||
status = ValidateGroupSerializerKeyInfo(group_key.odd_key);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
EcmMetaData::CipherMode ConvertCryptoModeToProtoCipherMode(
|
||||
CryptoMode crypto_mode) {
|
||||
switch (crypto_mode) {
|
||||
@@ -114,6 +168,11 @@ Status EcmSerializerV3::SerializeEcm(const EcmSerializerParams& params,
|
||||
return {error::INVALID_ARGUMENT, "Content IV size must match."};
|
||||
}
|
||||
}
|
||||
status = ValidateGroupKey(params.group_keys, has_odd_key);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Invalid group key info";
|
||||
return status;
|
||||
}
|
||||
|
||||
EcmPayload ecm_payload;
|
||||
EcmMetaData* meta_data = ecm_payload.mutable_meta_data();
|
||||
@@ -162,6 +221,27 @@ Status EcmSerializerV3::SerializeEcm(const EcmSerializerParams& params,
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& group_key : params.group_keys) {
|
||||
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||
group_key_data->set_group_id(group_key.group_id);
|
||||
EcmKeyData* even_key_data = group_key_data->mutable_even_key_data();
|
||||
even_key_data->set_entitlement_key_id(
|
||||
group_key.even_key.entitlement_key_id);
|
||||
even_key_data->set_wrapped_key_data(group_key.even_key.wrapped_key_value);
|
||||
if (!group_key.even_key.wrapped_key_iv.empty()) {
|
||||
even_key_data->set_wrapped_key_iv(group_key.even_key.wrapped_key_iv);
|
||||
}
|
||||
if (has_odd_key) {
|
||||
EcmKeyData* odd_key_data = group_key_data->mutable_odd_key_data();
|
||||
odd_key_data->set_entitlement_key_id(
|
||||
group_key.odd_key.entitlement_key_id);
|
||||
odd_key_data->set_wrapped_key_data(group_key.odd_key.wrapped_key_value);
|
||||
if (!group_key.odd_key.wrapped_key_iv.empty()) {
|
||||
odd_key_data->set_wrapped_key_iv(group_key.odd_key.wrapped_key_iv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::string serialized_ecm_payload = ecm_payload.SerializeAsString();
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(serialized_ecm_payload);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer_v3.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -86,6 +87,35 @@ class EcmSerializerV3Test : public testing::Test {
|
||||
EXPECT_EQ(ecm_key_data.wrapped_key_iv(), key_info.wrapped_key_iv);
|
||||
}
|
||||
|
||||
void AddGroupKey(const std::string& group_id,
|
||||
EcmGroupKeyData* expected_group_key_data) {
|
||||
EcmSerializerGroupKeyInfo group_key;
|
||||
group_key.group_id = group_id;
|
||||
group_key.even_key.entitlement_key_id = kEntitlementId;
|
||||
group_key.even_key.wrapped_key_value = kWrappedContentKey;
|
||||
group_key.even_key.wrapped_key_iv = kWrappedKeyIv;
|
||||
group_key.odd_key.entitlement_key_id = kEntitlementId2;
|
||||
group_key.odd_key.wrapped_key_value = kWrappedContentKey2;
|
||||
group_key.odd_key.wrapped_key_iv = kWrappedKeyIv2;
|
||||
serializer_params_.group_keys.push_back(group_key);
|
||||
|
||||
if (expected_group_key_data != nullptr) {
|
||||
expected_group_key_data->set_group_id(group_id);
|
||||
expected_group_key_data->mutable_even_key_data()->set_entitlement_key_id(
|
||||
kEntitlementId);
|
||||
expected_group_key_data->mutable_even_key_data()->set_wrapped_key_data(
|
||||
kWrappedContentKey);
|
||||
expected_group_key_data->mutable_even_key_data()->set_wrapped_key_iv(
|
||||
kWrappedKeyIv);
|
||||
expected_group_key_data->mutable_odd_key_data()->set_entitlement_key_id(
|
||||
kEntitlementId2);
|
||||
expected_group_key_data->mutable_odd_key_data()->set_wrapped_key_data(
|
||||
kWrappedContentKey2);
|
||||
expected_group_key_data->mutable_odd_key_data()->set_wrapped_key_iv(
|
||||
kWrappedKeyIv2);
|
||||
}
|
||||
}
|
||||
|
||||
EcmSerializerParams serializer_params_;
|
||||
};
|
||||
|
||||
@@ -407,6 +437,142 @@ TEST_F(EcmSerializerV3Test, GenerateSignatureSuccess) {
|
||||
signed_payload.signature()));
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, GroupKeySuccess) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
EcmGroupKeyData expected_group_key_data_1;
|
||||
EcmGroupKeyData expected_group_key_data_2;
|
||||
AddGroupKey("group_id_1", &expected_group_key_data_1);
|
||||
AddGroupKey("group_id_2", &expected_group_key_data_2);
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
SignedEcmPayload signed_payload;
|
||||
ASSERT_TRUE(signed_payload.ParseFromString(
|
||||
serialized_ecm.substr(kEcmProtoStartOffset)));
|
||||
EcmPayload payload;
|
||||
ASSERT_TRUE(payload.ParseFromString(signed_payload.serialized_payload()));
|
||||
ASSERT_EQ(payload.group_key_data_size(), 2);
|
||||
EXPECT_EQ(payload.group_key_data(0).SerializeAsString(),
|
||||
expected_group_key_data_1.SerializeAsString());
|
||||
EXPECT_EQ(payload.group_key_data(1).SerializeAsString(),
|
||||
expected_group_key_data_2.SerializeAsString());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, GroupKeyEvenKeyOnlySuccess) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
EcmSerializerKeyInfo empty_odd_key;
|
||||
serializer_params_.odd_key = empty_odd_key;
|
||||
EcmSerializerGroupKeyInfo group_key;
|
||||
group_key.group_id = "group_id";
|
||||
group_key.even_key.entitlement_key_id = kEntitlementId;
|
||||
group_key.even_key.wrapped_key_value = kWrappedContentKey;
|
||||
group_key.even_key.wrapped_key_iv = kWrappedKeyIv;
|
||||
serializer_params_.group_keys.push_back(group_key);
|
||||
EcmGroupKeyData expected_group_key_data;
|
||||
expected_group_key_data.set_group_id("group_id");
|
||||
expected_group_key_data.mutable_even_key_data()->set_entitlement_key_id(
|
||||
kEntitlementId);
|
||||
expected_group_key_data.mutable_even_key_data()->set_wrapped_key_data(
|
||||
kWrappedContentKey);
|
||||
expected_group_key_data.mutable_even_key_data()->set_wrapped_key_iv(
|
||||
kWrappedKeyIv);
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
SignedEcmPayload signed_payload;
|
||||
ASSERT_TRUE(signed_payload.ParseFromString(
|
||||
serialized_ecm.substr(kEcmProtoStartOffset)));
|
||||
EcmPayload payload;
|
||||
ASSERT_TRUE(payload.ParseFromString(signed_payload.serialized_payload()));
|
||||
ASSERT_EQ(payload.group_key_data_size(), 1);
|
||||
EXPECT_EQ(payload.group_key_data(0).SerializeAsString(),
|
||||
expected_group_key_data.SerializeAsString());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, GroupKeyMissingOddKeyFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
EcmSerializerGroupKeyInfo group_key;
|
||||
group_key.group_id = "group_id";
|
||||
group_key.even_key.entitlement_key_id = kEntitlementId;
|
||||
group_key.even_key.wrapped_key_value = kWrappedContentKey;
|
||||
group_key.even_key.wrapped_key_iv = kWrappedKeyIv;
|
||||
serializer_params_.group_keys.push_back(group_key);
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, GroupKeyMissingEvenKeyFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
EcmSerializerGroupKeyInfo group_key;
|
||||
group_key.group_id = "group_id";
|
||||
group_key.odd_key.entitlement_key_id = kEntitlementId;
|
||||
group_key.odd_key.wrapped_key_value = kWrappedContentKey;
|
||||
group_key.odd_key.wrapped_key_iv = kWrappedKeyIv;
|
||||
serializer_params_.group_keys.push_back(group_key);
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, GroupKeyRepeatedGroupIdFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
AddGroupKey("group_id", /*expected_group_key_data=*/nullptr);
|
||||
AddGroupKey("group_id", /*expected_group_key_data=*/nullptr);
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_EQ(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm),
|
||||
Status(error::INVALID_ARGUMENT, "Group ids are not unique."));
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, GroupKeyNoGroupIdFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
AddGroupKey("group_id", /*expected_group_key_data=*/nullptr);
|
||||
AddGroupKey(/*group_id=*/"", /*expected_group_key_data=*/nullptr);
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_EQ(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm),
|
||||
Status(error::INVALID_ARGUMENT, "Missing group id."));
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, GroupKeyInvalidEntitlementKeyIdFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
AddGroupKey("group_id", /*expected_group_key_data=*/nullptr);
|
||||
serializer_params_.group_keys[0].even_key.entitlement_key_id = "short_id";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_EQ(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm),
|
||||
Status(error::INVALID_ARGUMENT,
|
||||
"Unexpected group entitlement key ID size: 8"));
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, GroupKeyInvalidWrappedKeyValueFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
AddGroupKey("group_id", /*expected_group_key_data=*/nullptr);
|
||||
serializer_params_.group_keys[0].odd_key.wrapped_key_value = "short_v";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_EQ(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm),
|
||||
Status(error::INVALID_ARGUMENT, "Unexpected group wrapped key size: 7"));
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, GroupKeyInvalidWrappedKeyIvFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
AddGroupKey("group_id", /*expected_group_key_data=*/nullptr);
|
||||
AddGroupKey("group_id2", /*expected_group_key_data=*/nullptr);
|
||||
serializer_params_.group_keys[1].odd_key.wrapped_key_iv = "short_iv";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_EQ(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm),
|
||||
Status(error::INVALID_ARGUMENT,
|
||||
"Unexpected group wrapped key iv size: 8"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/memory/memory.h"
|
||||
@@ -32,6 +36,7 @@ constexpr char kTrackTypeSD[] = "SD";
|
||||
constexpr char kTrackTypeHD[] = "HD";
|
||||
constexpr char kWrappedKeyIv[] = "wrapped_key_iv..";
|
||||
constexpr char kEntitlementKeyId[] = "entitlement_Mock";
|
||||
constexpr char kEntitlementKeyValue[] = "key__value.key__value.key__value";
|
||||
constexpr char kWrappedKeyValue[] = "MockMockMockMock";
|
||||
|
||||
constexpr size_t kEcmVersionIndex = 2;
|
||||
@@ -96,9 +101,11 @@ class EcmTest : public testing::Test {
|
||||
params_one_key_.key_rotation_enabled = false;
|
||||
params_two_keys_.key_rotation_enabled = true;
|
||||
|
||||
injected_entitlement_one_ = {kTrackTypeSD, true, "entitlement_id_1",
|
||||
injected_entitlement_one_ = {kTrackTypeSD, /*group_id=*/"",
|
||||
/*is_even_key=*/true, "entitlement_id_1",
|
||||
"key__value.key__value.key__value"};
|
||||
injected_entitlement_two_ = {kTrackTypeSD, true, "entitlement_id_2",
|
||||
injected_entitlement_two_ = {kTrackTypeSD, /*group_id=*/"",
|
||||
/*is_even_key=*/false, "entitlement_id_2",
|
||||
"key__value.key__value.key__value"};
|
||||
}
|
||||
|
||||
@@ -121,7 +128,9 @@ TEST_F(EcmTest, GenerateEcmNotInitialized) {
|
||||
EntitledKeyInfo key1;
|
||||
std::string ecm_data;
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm_data)
|
||||
ecm_gen
|
||||
.GenerateSingleKeyEcm(&key1, kTrackTypeSD, /*group_ids=*/{},
|
||||
&ecm_data)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
@@ -130,9 +139,10 @@ TEST_F(EcmTest, GenerateEcm2NotInitialized) {
|
||||
EntitledKeyInfo key1;
|
||||
EntitledKeyInfo key2;
|
||||
std::string ecm_data;
|
||||
EXPECT_EQ(
|
||||
error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data).error_code());
|
||||
EXPECT_EQ(error::INTERNAL, ecm_gen
|
||||
.GenerateEcm(&key1, &key2, kTrackTypeSD,
|
||||
/*group_ids=*/{}, &ecm_data)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, InitSucceedInjectedEntitlementSingle) {
|
||||
@@ -179,7 +189,8 @@ TEST_F(EcmTest, InitIvSize16x16OK) {
|
||||
}
|
||||
|
||||
class EcmInitWithValidCasIdTest : public EcmTest,
|
||||
public testing::WithParamInterface<uint16_t> {};
|
||||
public testing::WithParamInterface<uint16_t> {
|
||||
};
|
||||
TEST_P(EcmInitWithValidCasIdTest, InitWithValidCasIdSucceed) {
|
||||
params_simple_.cas_id = GetParam();
|
||||
Ecm ecm_gen;
|
||||
@@ -191,9 +202,9 @@ INSTANTIATE_TEST_SUITE_P(EcmInitWithValidCasIdTest, EcmInitWithValidCasIdTest,
|
||||
kWidevineNewSystemIdLowerBound, 0x56C5,
|
||||
kWidevineNewSystemIdUpperBound));
|
||||
|
||||
class EcmInitWithInvalidCasIdTest : public EcmTest,
|
||||
public testing::WithParamInterface<uint16_t> {
|
||||
};
|
||||
class EcmInitWithInvalidCasIdTest
|
||||
: public EcmTest,
|
||||
public testing::WithParamInterface<uint16_t> {};
|
||||
TEST_P(EcmInitWithInvalidCasIdTest, InitWithInvalidCasIdFail) {
|
||||
params_simple_.cas_id = GetParam();
|
||||
Ecm ecm_gen;
|
||||
@@ -219,8 +230,8 @@ TEST_P(EcmSerializerVersionTest, EcmSerializerOk) {
|
||||
ASSERT_OK(ecm_gen.Initialize(params, {injected_entitlement_one_}));
|
||||
|
||||
std::string ecm;
|
||||
ASSERT_OK(
|
||||
ecm_gen.GenerateSingleKeyEcm(&valid3_iv_16_16_, kTrackTypeSD, &ecm));
|
||||
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&valid3_iv_16_16_, kTrackTypeSD,
|
||||
/*group_ids=*/{}, &ecm));
|
||||
ASSERT_GT(ecm.size(), kEcmVersionIndex);
|
||||
EXPECT_EQ(ecm[kEcmVersionIndex],
|
||||
params.ecm_version == EcmVersion::kV2 ? 2 : 3);
|
||||
@@ -237,8 +248,10 @@ TEST_F(EcmTest, GenerateWithBadTrackType) {
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
std::string ecm;
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeHD, &ecm).error_code());
|
||||
EXPECT_EQ(
|
||||
error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeHD, /*group_ids=*/{}, &ecm)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
class MockEcmSerializer : public EcmSerializer {
|
||||
@@ -252,6 +265,7 @@ class MockEcmSerializer : public EcmSerializer {
|
||||
MOCK_METHOD(EcmSerializerVersion, Version, (), (const, override));
|
||||
};
|
||||
|
||||
// For easier test by setting mock ecm serializer and fixed wrapped key value.
|
||||
class FakeEcm : public Ecm {
|
||||
public:
|
||||
FakeEcm() = default;
|
||||
@@ -262,18 +276,16 @@ class FakeEcm : public Ecm {
|
||||
return Ecm::SetEcmSerializer(std::move(ecm_serializer));
|
||||
}
|
||||
|
||||
Status WrapEntitledKeys(
|
||||
const std::string& track_type,
|
||||
const std::vector<EntitledKeyInfo*>& keys) const override {
|
||||
for (auto entitled_key : keys) {
|
||||
entitled_key->entitlement_key_id = kEntitlementKeyId;
|
||||
entitled_key->wrapped_key_value = kWrappedKeyValue;
|
||||
}
|
||||
Status WrapKey(const std::string& wrapping_key,
|
||||
const std::string& wrapping_iv, const std::string& key_value,
|
||||
std::string* wrapped_key) const override {
|
||||
*wrapped_key = kWrappedKeyValue;
|
||||
return OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
bool IsEcmSerializerKeyInfoEq(EcmSerializerKeyInfo a, EcmSerializerKeyInfo b) {
|
||||
bool IsEcmSerializerKeyInfoEq(const EcmSerializerKeyInfo& a,
|
||||
const EcmSerializerKeyInfo& b) {
|
||||
return (a.entitlement_key_id == b.entitlement_key_id) &&
|
||||
(a.content_key_id == b.content_key_id) &&
|
||||
(a.wrapped_key_value == b.wrapped_key_value) &&
|
||||
@@ -281,12 +293,29 @@ bool IsEcmSerializerKeyInfoEq(EcmSerializerKeyInfo a, EcmSerializerKeyInfo b) {
|
||||
(a.content_iv == b.content_iv);
|
||||
}
|
||||
|
||||
bool IsEcmSerializerGroupKeyInfoEq(
|
||||
const std::vector<EcmSerializerGroupKeyInfo>& a,
|
||||
const std::vector<EcmSerializerGroupKeyInfo>& b) {
|
||||
if (a.size() != b.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < a.size(); ++i) {
|
||||
if (a[i].group_id != b[i].group_id ||
|
||||
!IsEcmSerializerKeyInfoEq(a[i].even_key, b[i].even_key) ||
|
||||
!IsEcmSerializerKeyInfoEq(a[i].odd_key, b[i].odd_key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MATCHER_P(EcmSerializerParamsEq, expected, "") {
|
||||
return (arg.cas_id == expected.cas_id) &&
|
||||
(arg.crypto_mode == expected.crypto_mode) &&
|
||||
(arg.age_restriction == expected.age_restriction) &&
|
||||
IsEcmSerializerKeyInfoEq(arg.even_key, expected.even_key) &&
|
||||
IsEcmSerializerKeyInfoEq(arg.odd_key, expected.odd_key);
|
||||
IsEcmSerializerKeyInfoEq(arg.odd_key, expected.odd_key) &&
|
||||
IsEcmSerializerGroupKeyInfoEq(arg.group_keys, expected.group_keys);
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateEcmTwoKeysOK) {
|
||||
@@ -302,10 +331,12 @@ TEST_F(EcmTest, GenerateEcmTwoKeysOK) {
|
||||
expected_params.cas_id = kWvCasCaSystemId;
|
||||
expected_params.age_restriction = 0;
|
||||
expected_params.crypto_mode = CryptoMode::kAesCtr;
|
||||
expected_params.even_key = {kEntitlementKeyId, key1.key_id, kWrappedKeyValue,
|
||||
key1.wrapped_key_iv, key1.content_iv};
|
||||
expected_params.odd_key = {kEntitlementKeyId, key2.key_id, kWrappedKeyValue,
|
||||
key2.wrapped_key_iv, key2.content_iv};
|
||||
expected_params.even_key = {injected_entitlement_one_.key_id, key1.key_id,
|
||||
kWrappedKeyValue, key1.wrapped_key_iv,
|
||||
key1.content_iv};
|
||||
expected_params.odd_key = {injected_entitlement_two_.key_id, key2.key_id,
|
||||
kWrappedKeyValue, key2.wrapped_key_iv,
|
||||
key2.content_iv};
|
||||
std::string expected_serialized_ecm = "expected_serialized_ecm";
|
||||
EXPECT_CALL(*ecm_serializer_pointer,
|
||||
SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull()))
|
||||
@@ -313,8 +344,8 @@ TEST_F(EcmTest, GenerateEcmTwoKeysOK) {
|
||||
DoAll(SetArgPointee<1>(expected_serialized_ecm), Return(OkStatus())));
|
||||
std::string actual_generated_ecm;
|
||||
|
||||
ASSERT_OK(
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &actual_generated_ecm));
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, /*group_ids=*/{},
|
||||
&actual_generated_ecm));
|
||||
|
||||
EXPECT_EQ(actual_generated_ecm, expected_serialized_ecm);
|
||||
}
|
||||
@@ -340,16 +371,19 @@ TEST_F(EcmTest, GenerateEcmParamsPassedDown) {
|
||||
expected_params.age_restriction = params.age_restriction;
|
||||
expected_params.crypto_mode = params.crypto_mode;
|
||||
expected_params.ecc_private_signing_key = params.ecc_private_signing_key;
|
||||
expected_params.even_key = {kEntitlementKeyId, key1.key_id, kWrappedKeyValue,
|
||||
key1.wrapped_key_iv, key1.content_iv};
|
||||
expected_params.odd_key = {kEntitlementKeyId, key2.key_id, kWrappedKeyValue,
|
||||
key2.wrapped_key_iv, key2.content_iv};
|
||||
expected_params.even_key = {injected_entitlement_one_.key_id, key1.key_id,
|
||||
kWrappedKeyValue, key1.wrapped_key_iv,
|
||||
key1.content_iv};
|
||||
expected_params.odd_key = {injected_entitlement_two_.key_id, key2.key_id,
|
||||
kWrappedKeyValue, key2.wrapped_key_iv,
|
||||
key2.content_iv};
|
||||
EXPECT_CALL(*ecm_serializer_pointer,
|
||||
SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull()))
|
||||
.WillOnce(Return(OkStatus()));
|
||||
std::string generated_ecm;
|
||||
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &generated_ecm));
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, /*group_ids=*/{},
|
||||
&generated_ecm));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateEcmSerializeErrorOK) {
|
||||
@@ -365,10 +399,88 @@ TEST_F(EcmTest, GenerateEcmSerializeErrorOK) {
|
||||
std::string ecm;
|
||||
|
||||
EXPECT_EQ(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_,
|
||||
kTrackTypeSD, &ecm),
|
||||
kTrackTypeSD, /*group_ids=*/{}, &ecm),
|
||||
expected_status);
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateEcmWithGroupKeysSuccess) {
|
||||
FakeEcm ecm_gen;
|
||||
EcmInitParameters params;
|
||||
std::vector<std::string> group_ids = {"group_id_1", "group_id_2"};
|
||||
// Group even and odd entitlement keys for group_id_1.
|
||||
EntitlementKeyInfo entitlement_3 = {
|
||||
kTrackTypeSD, group_ids[0],
|
||||
/*is_even_key=*/true, "entitlement_id_3", kEntitlementKeyValue,
|
||||
};
|
||||
EntitlementKeyInfo entitlement_4 = {
|
||||
kTrackTypeSD, group_ids[0],
|
||||
/*is_even_key=*/false, "entitlement_id_4", kEntitlementKeyValue,
|
||||
};
|
||||
// Group even and odd entitlement keys for group_id_2.
|
||||
EntitlementKeyInfo entitlement_5 = {
|
||||
kTrackTypeSD, group_ids[1],
|
||||
/*is_even_key=*/true, "entitlement_id_5", kEntitlementKeyValue,
|
||||
};
|
||||
EntitlementKeyInfo entitlement_6 = {
|
||||
kTrackTypeSD, group_ids[1],
|
||||
/*is_even_key=*/false, "entitlement_id_6", kEntitlementKeyValue,
|
||||
};
|
||||
// Entitlement key sequence is not required.
|
||||
ASSERT_OK(ecm_gen.Initialize(
|
||||
params, {entitlement_6, injected_entitlement_two_, entitlement_3,
|
||||
entitlement_4, entitlement_5, injected_entitlement_one_}));
|
||||
auto ecm_serializer = absl::make_unique<MockEcmSerializer>();
|
||||
const MockEcmSerializer* ecm_serializer_pointer = ecm_serializer.get();
|
||||
ecm_gen.SetEcmSerializer(std::move(ecm_serializer));
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
EcmSerializerParams expected_params;
|
||||
expected_params.cas_id = params.cas_id;
|
||||
expected_params.age_restriction = params.age_restriction;
|
||||
expected_params.crypto_mode = params.crypto_mode;
|
||||
expected_params.ecc_private_signing_key = params.ecc_private_signing_key;
|
||||
expected_params.even_key = {injected_entitlement_one_.key_id, key1.key_id,
|
||||
kWrappedKeyValue, key1.wrapped_key_iv,
|
||||
key1.content_iv};
|
||||
expected_params.odd_key = {injected_entitlement_two_.key_id, key2.key_id,
|
||||
kWrappedKeyValue, key2.wrapped_key_iv,
|
||||
key2.content_iv};
|
||||
EcmSerializerGroupKeyInfo group_key_1;
|
||||
group_key_1.group_id = entitlement_3.group_id;
|
||||
group_key_1.even_key.entitlement_key_id = entitlement_3.key_id;
|
||||
group_key_1.even_key.wrapped_key_value = kWrappedKeyValue;
|
||||
group_key_1.odd_key.entitlement_key_id = entitlement_4.key_id;
|
||||
group_key_1.odd_key.wrapped_key_value = kWrappedKeyValue;
|
||||
EcmSerializerGroupKeyInfo group_key_2;
|
||||
group_key_2.group_id = entitlement_5.group_id;
|
||||
group_key_2.even_key.entitlement_key_id = entitlement_5.key_id;
|
||||
group_key_2.even_key.wrapped_key_value = kWrappedKeyValue;
|
||||
group_key_2.odd_key.entitlement_key_id = entitlement_6.key_id;
|
||||
group_key_2.odd_key.wrapped_key_value = kWrappedKeyValue;
|
||||
expected_params.group_keys = {group_key_1, group_key_2};
|
||||
EXPECT_CALL(*ecm_serializer_pointer,
|
||||
SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull()))
|
||||
.WillOnce(Return(OkStatus()));
|
||||
std::string generated_ecm;
|
||||
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, group_ids,
|
||||
&generated_ecm));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateEcmWithMissingGroupKeysFail) {
|
||||
FakeEcm ecm_gen;
|
||||
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}));
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
std::string generated_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_gen
|
||||
.GenerateEcm(&key1, &key2, kTrackTypeSD, {"group_id"}, &generated_ecm)
|
||||
.ok());
|
||||
}
|
||||
|
||||
class EcmTsPacketTest : public ::testing::Test {};
|
||||
|
||||
TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) {
|
||||
|
||||
@@ -9,15 +9,19 @@
|
||||
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "common/crypto_util.h"
|
||||
#include "common/random_util.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
|
||||
@@ -94,14 +98,54 @@ Status ProcessPrivateParameters(const char* const request, uint16_t param_type,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
params->entitlement_comb.emplace_back();
|
||||
EntitlementIdKeyComb* combination = ¶ms->entitlement_comb.back();
|
||||
combination->key_id =
|
||||
std::string(request + *offset, kEntitlementKeyIdSizeBytes);
|
||||
*offset += kEntitlementKeyIdSizeBytes;
|
||||
combination->key_value =
|
||||
std::string(request + *offset, kEntitlementKeyValueSizeBytes);
|
||||
*offset += kEntitlementKeyValueSizeBytes;
|
||||
EntitlementKeyInfo entitlement_key;
|
||||
entitlement_key.key_id.assign(request + *offset,
|
||||
kEntitlementKeyIdSizeBytes);
|
||||
entitlement_key.key_value.assign(
|
||||
request + *offset + kEntitlementKeyIdSizeBytes,
|
||||
kEntitlementKeyValueSizeBytes);
|
||||
// If a key has been already received (without group id), then this is an
|
||||
// odd key. Othervise, this is an even key.
|
||||
entitlement_key.is_even_key = true;
|
||||
for (const auto& existing_key : params->entitlement_keys) {
|
||||
if (existing_key.group_id.empty()) {
|
||||
entitlement_key.is_even_key = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
params->entitlement_keys.push_back(entitlement_key);
|
||||
*offset += param_length;
|
||||
break;
|
||||
}
|
||||
case ENTITLEMENT_ID_KEY_GROUP_COMBINATION: {
|
||||
if (param_length <=
|
||||
kEntitlementKeyIdSizeBytes + kEntitlementKeyValueSizeBytes) {
|
||||
return Status(error::FAILED_PRECONDITION,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
EntitlementKeyInfo entitlement_key;
|
||||
entitlement_key.key_id.assign(request + *offset,
|
||||
kEntitlementKeyIdSizeBytes);
|
||||
entitlement_key.key_value.assign(
|
||||
request + *offset + kEntitlementKeyIdSizeBytes,
|
||||
kEntitlementKeyValueSizeBytes);
|
||||
entitlement_key.group_id.assign(
|
||||
request + *offset + kEntitlementKeyIdSizeBytes +
|
||||
kEntitlementKeyValueSizeBytes,
|
||||
param_length - kEntitlementKeyIdSizeBytes -
|
||||
kEntitlementKeyValueSizeBytes);
|
||||
// If a key with the same group id has been already received, then this is
|
||||
// an odd key. Othervise, this is an even key.
|
||||
entitlement_key.is_even_key = true;
|
||||
for (const auto& existing_key : params->entitlement_keys) {
|
||||
if (existing_key.group_id == entitlement_key.group_id) {
|
||||
entitlement_key.is_even_key = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
params->entitlement_keys.push_back(entitlement_key);
|
||||
*offset += param_length;
|
||||
break;
|
||||
}
|
||||
case FINGERPRINTING_CONTROL:
|
||||
@@ -300,6 +344,10 @@ void BuildChannelError(uint16_t channel_id, uint16_t error_status,
|
||||
}
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
|
||||
VLOG(3) << "ChannelError sent. ECM_CHANNEL_ID: " << channel_id
|
||||
<< "; ERROR_STATUS: " << error_status
|
||||
<< "; ERROR_INFORMATION: " << error_info;
|
||||
}
|
||||
|
||||
void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message,
|
||||
@@ -340,11 +388,22 @@ void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message,
|
||||
message_length);
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
|
||||
VLOG(3) << "ChannelStatus sent. ECM_CHANNEL_ID: " << channel_id
|
||||
<< "; SECTION_TSPKT_FLAG: " << kSectionTSpktFlag
|
||||
<< "; DELAY_START: " << config->delay_start
|
||||
<< "; DELAY_STOP: " << config->delay_stop
|
||||
<< "; ECM_REP_PERIOD: " << config->ecm_rep_period
|
||||
<< "; MAX_STREAMS: " << kMaxStream
|
||||
<< "; MIN_CP_DURATION: " << config->max_comp_time
|
||||
<< "; LEAD_CW: " << config->number_of_content_keys - 1
|
||||
<< "; CW_PER_MESSAGE: " << config->number_of_content_keys
|
||||
<< "; MAX_COMP_TIME: " << config->max_comp_time;
|
||||
}
|
||||
|
||||
void BuildStreamError(uint16_t channel_id, uint16_t stream_id, uint16_t error_status,
|
||||
const std::string& error_info, char* message,
|
||||
size_t* message_length) {
|
||||
void BuildStreamError(uint16_t channel_id, uint16_t stream_id,
|
||||
uint16_t error_status, const std::string& error_info,
|
||||
char* message, size_t* message_length) {
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
simulcrypt_util::BuildMessageHeader(
|
||||
@@ -362,6 +421,11 @@ void BuildStreamError(uint16_t channel_id, uint16_t stream_id, uint16_t error_st
|
||||
}
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
|
||||
VLOG(3) << "StreamError sent. ECM_CHANNEL_ID: " << channel_id
|
||||
<< "; ECM_STREAM_ID: " << stream_id
|
||||
<< "; ERROR_STATUS: " << error_status
|
||||
<< "; ERROR_INFORMATION: " << error_info;
|
||||
}
|
||||
|
||||
uint16_t StatusToDvbErrorCode(const Status& status) {
|
||||
@@ -401,6 +465,11 @@ void BuildStreamStatus(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id,
|
||||
message_length);
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
|
||||
VLOG(3) << "StreamStatus sent. ECM_CHANNEL_ID: " << channel_id
|
||||
<< "; ECM_STREAM_ID: " << stream_id << "; ECM_ID: " << ecm_id
|
||||
<< "; ACCESS_CRITERIA_TRANSFER_MODE: "
|
||||
<< access_criteria_transfer_mode;
|
||||
}
|
||||
|
||||
void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id,
|
||||
@@ -416,9 +485,13 @@ void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id,
|
||||
message_length);
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
|
||||
VLOG(3) << "StreamCloseResponse sent. ECM_CHANNEL_ID: " << channel_id
|
||||
<< "; ECM_STREAM_ID: " << stream_id;
|
||||
}
|
||||
|
||||
void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, uint16_t cp_number,
|
||||
void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id,
|
||||
uint16_t cp_number,
|
||||
absl::Span<const uint8_t> ecm_datagram, char* message,
|
||||
size_t* message_length) {
|
||||
DCHECK(message);
|
||||
@@ -435,6 +508,9 @@ void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, uint16_t cp_numbe
|
||||
ecm_datagram.size(), message, message_length);
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
|
||||
VLOG(3) << "EcmResponse sent. ECM_CHANNEL_ID: " << channel_id
|
||||
<< "; ECM_STREAM_ID: " << stream_id << "; CP_NUMBER: " << cp_number;
|
||||
}
|
||||
|
||||
Status CheckReceivedContentIv(const std::vector<std::string>& content_ivs,
|
||||
@@ -460,32 +536,6 @@ Status CheckReceivedContentIv(const std::vector<std::string>& content_ivs,
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
std::vector<EntitlementIdKeyComb> ConvertEntitlementKeyInfo(
|
||||
const std::vector<EntitlementKeyInfo>& fetched_entitlements) {
|
||||
std::vector<EntitlementIdKeyComb> converted;
|
||||
// Only process if the fetched size is expected (1 or 2 entries).
|
||||
if (fetched_entitlements.empty()) {
|
||||
LOG(WARNING) << "EntitlementKeyFetcherFunc returned empty result.";
|
||||
} else if (fetched_entitlements.size() > 2) {
|
||||
LOG(ERROR)
|
||||
<< "EntitlementKeyFetcherFunc should return at most 2 entries, but "
|
||||
<< fetched_entitlements.size() << " entries are returned.";
|
||||
} else {
|
||||
converted.reserve(fetched_entitlements.size());
|
||||
for (auto const& fetched_entitlement : fetched_entitlements) {
|
||||
EntitlementIdKeyComb entitlement_comb;
|
||||
entitlement_comb.key_id = fetched_entitlement.key_id;
|
||||
entitlement_comb.key_value = fetched_entitlement.key_value;
|
||||
// In the case of two entitlement keys, the first in |entitlements| must
|
||||
// be even key followed by the odd key.
|
||||
converted.insert(
|
||||
fetched_entitlement.is_even_key ? converted.begin() : converted.end(),
|
||||
entitlement_comb);
|
||||
}
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config)
|
||||
@@ -496,8 +546,9 @@ EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config)
|
||||
ecmg_config_ = ecmg_config;
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
size_t* response_length) {
|
||||
size_t EcmgClientHandler::HandleRequest(const char* const request,
|
||||
char* response,
|
||||
size_t* response_length) {
|
||||
DCHECK(request);
|
||||
DCHECK(response);
|
||||
|
||||
@@ -507,16 +558,18 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
// 'offset' is used to track where we are within |request|.
|
||||
size_t offset = 0;
|
||||
memcpy(&protocol_version, request, PROTOCOL_VERSION_SIZE);
|
||||
if (protocol_version != ECMG_SCS_PROTOCOL_VERSION) {
|
||||
BuildChannelError(0, UNSUPPORTED_PROTOCOL_VERSION, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
offset += PROTOCOL_VERSION_SIZE;
|
||||
BigEndianToHost16(&request_type, request + offset);
|
||||
offset += MESSAGE_TYPE_SIZE;
|
||||
BigEndianToHost16(&request_length, request + offset);
|
||||
offset += MESSAGE_LENGTH_SIZE;
|
||||
const size_t total_request_length = offset + request_length;
|
||||
if (protocol_version != ECMG_SCS_PROTOCOL_VERSION) {
|
||||
BuildChannelError(0, UNSUPPORTED_PROTOCOL_VERSION, "", response,
|
||||
response_length);
|
||||
return total_request_length;
|
||||
}
|
||||
|
||||
EcmgParameters params;
|
||||
const bool should_sdk_process_access_criteria =
|
||||
custom_ac_processor_ == nullptr;
|
||||
@@ -526,7 +579,7 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
LOG(ERROR) << status.ToString();
|
||||
BuildChannelError(params.ecm_channel_id, StatusToDvbErrorCode(status),
|
||||
status.error_message(), response, response_length);
|
||||
return;
|
||||
return total_request_length;
|
||||
}
|
||||
switch (request_type) {
|
||||
case ECMG_CHANNEL_SETUP:
|
||||
@@ -549,7 +602,14 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
break;
|
||||
case ECMG_CW_PROVISION:
|
||||
if (!should_sdk_process_access_criteria) {
|
||||
UpdateParamsWithCustomAccessCriteriaProcessor(params);
|
||||
status = UpdateParamsWithCustomAccessCriteriaProcessor(params);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << status.ToString();
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
INVALID_MESSAGE, status.ToString(), response,
|
||||
response_length);
|
||||
return total_request_length;
|
||||
}
|
||||
}
|
||||
HandleCwProvision(params, response, response_length);
|
||||
break;
|
||||
@@ -564,6 +624,7 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
response, response_length);
|
||||
break;
|
||||
}
|
||||
return total_request_length;
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
|
||||
@@ -571,6 +632,9 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
VLOG(3) << "ChannelSetup request. ECM_channel_id: " << params.ecm_channel_id
|
||||
<< "; ECM_stream_id: " << params.ecm_stream_id;
|
||||
|
||||
// There is always one (and only one) channel per TCP connection.
|
||||
if (channel_id_set_) {
|
||||
BuildChannelError(params.ecm_channel_id, INVALID_MESSAGE, "", response,
|
||||
@@ -604,6 +668,7 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
|
||||
}
|
||||
|
||||
BuildChannelStatus(channel_id_, ecmg_config_, response, response_length);
|
||||
VLOG(1) << "Channel " << channel_id_ << " opened";
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params,
|
||||
@@ -611,6 +676,8 @@ void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params,
|
||||
size_t* response_length) const {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
VLOG(3) << "ChannelTest request. ECM_channel_id: " << params.ecm_channel_id;
|
||||
|
||||
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
|
||||
response, response_length);
|
||||
@@ -624,6 +691,8 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
VLOG(3) << "ChannelClose request. ECM_channel_id: " << params.ecm_channel_id;
|
||||
|
||||
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
|
||||
response, response_length);
|
||||
@@ -632,11 +701,14 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
|
||||
channel_id_set_ = false;
|
||||
streams_info_.clear();
|
||||
*response_length = 0;
|
||||
VLOG(1) << "Channel " << channel_id_ << " closed";
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleChannelError(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) const {
|
||||
VLOG(3) << "ChannelError request. ECM_channel_id: " << params.ecm_channel_id;
|
||||
|
||||
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
|
||||
response, response_length);
|
||||
@@ -659,6 +731,11 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
VLOG(3) << "StreamSetup request. ECM_channel_id: " << params.ecm_channel_id
|
||||
<< "; ECM_stream_id: " << params.ecm_stream_id
|
||||
<< "; ECM_id: " << params.ecm_id << "; nominal_CP_duration"
|
||||
<< params.nominal_cp_duration;
|
||||
|
||||
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,
|
||||
@@ -689,21 +766,19 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
|
||||
}
|
||||
|
||||
// Try to fetch entitlement keys using custom key fetcher if none is received.
|
||||
std::vector<EntitlementIdKeyComb>& entitlements =
|
||||
streams_info_[params.ecm_stream_id]->entitlement_comb;
|
||||
if (entitlements.empty() && custom_key_fetcher_ != nullptr) {
|
||||
const std::vector<EntitlementKeyInfo>& fetched_entitlements =
|
||||
custom_key_fetcher_(params.ecm_channel_id, params.ecm_stream_id,
|
||||
params.ecm_id);
|
||||
const std::vector<EntitlementIdKeyComb>& converted_entitlements =
|
||||
ConvertEntitlementKeyInfo(fetched_entitlements);
|
||||
entitlements.assign(converted_entitlements.begin(),
|
||||
converted_entitlements.end());
|
||||
if (custom_key_fetcher_ != nullptr) {
|
||||
auto& entitlements = streams_info_[params.ecm_stream_id]->entitlement_keys;
|
||||
if (entitlements.empty()) {
|
||||
entitlements = custom_key_fetcher_(params.ecm_channel_id,
|
||||
params.ecm_stream_id, params.ecm_id);
|
||||
}
|
||||
}
|
||||
|
||||
BuildStreamStatus(params.ecm_channel_id, params.ecm_stream_id, params.ecm_id,
|
||||
ecmg_config_->access_criteria_transfer_mode, response,
|
||||
response_length);
|
||||
VLOG(1) << "Stream " << params.ecm_stream_id
|
||||
<< " opened. Number of streams open: " << streams_info_.size();
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params,
|
||||
@@ -711,6 +786,9 @@ void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params,
|
||||
size_t* response_length) const {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
VLOG(3) << "StreamTest request. ECM_channel_id: " << params.ecm_channel_id
|
||||
<< "; ECM_stream_id: " << params.ecm_stream_id;
|
||||
|
||||
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,
|
||||
@@ -734,6 +812,9 @@ void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
VLOG(3) << "StreamClose request. ECM_channel_id: " << params.ecm_channel_id
|
||||
<< "; ECM_stream_id: " << params.ecm_stream_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,
|
||||
@@ -749,11 +830,16 @@ void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params,
|
||||
streams_info_.erase(params.ecm_stream_id);
|
||||
BuildStreamCloseResponse(params.ecm_channel_id, params.ecm_stream_id,
|
||||
response, response_length);
|
||||
VLOG(1) << "Stream " << params.ecm_stream_id
|
||||
<< " closed. Number of streams open: " << streams_info_.size();
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleStreamError(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) const {
|
||||
VLOG(3) << "StreamError request. ECM_channel_id: " << params.ecm_channel_id
|
||||
<< "; ECM_stream_id: " << params.ecm_stream_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,
|
||||
@@ -778,11 +864,16 @@ void EcmgClientHandler::HandleStreamError(const EcmgParameters& params,
|
||||
*response_length = 0;
|
||||
}
|
||||
|
||||
void EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor(
|
||||
Status EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor(
|
||||
EcmgParameters& params) const {
|
||||
DCHECK(custom_ac_processor_ != nullptr);
|
||||
const EcmgCustomParameters& custom_params = custom_ac_processor_(
|
||||
params.ecm_channel_id, params.ecm_stream_id, params.access_criteria);
|
||||
EcmgCustomParameters custom_params;
|
||||
Status status =
|
||||
custom_ac_processor_(params.ecm_channel_id, params.ecm_stream_id,
|
||||
params.access_criteria, custom_params);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
// Load results to |params|.
|
||||
if (custom_params.age_restriction >= 0) {
|
||||
params.age_restriction = custom_params.age_restriction;
|
||||
@@ -794,10 +885,7 @@ void EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor(
|
||||
params.content_ivs = custom_params.content_ivs;
|
||||
}
|
||||
if (!custom_params.entitlement_keys.empty()) {
|
||||
const std::vector<EntitlementIdKeyComb>& converted_entitlements =
|
||||
ConvertEntitlementKeyInfo(custom_params.entitlement_keys);
|
||||
params.entitlement_comb.assign(converted_entitlements.begin(),
|
||||
converted_entitlements.end());
|
||||
params.entitlement_keys = custom_params.entitlement_keys;
|
||||
}
|
||||
if (!custom_params.fingerprinting_control.empty()) {
|
||||
params.fingerprinting_control = custom_params.fingerprinting_control;
|
||||
@@ -805,11 +893,17 @@ void EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor(
|
||||
if (!custom_params.service_blocking_groups.empty()) {
|
||||
params.service_blocking_groups = custom_params.service_blocking_groups;
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) {
|
||||
VLOG(3) << "CwProvision request. ECM_channel_id: " << params.ecm_channel_id
|
||||
<< "; ECM_stream_id: " << params.ecm_stream_id
|
||||
<< "; CP_number: " << params.cp_number
|
||||
<< "; CP_CW_Combination size: " << params.cp_cw_combinations.size();
|
||||
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
|
||||
@@ -882,12 +976,14 @@ Status EcmgClientHandler::UpdateChannelPrivateParameters(
|
||||
return {error::INVALID_ARGUMENT, "Age restriction too large."};
|
||||
}
|
||||
age_restriction_ = params.age_restriction;
|
||||
VLOG(1) << "Channel age restriction has been set to: " << age_restriction_;
|
||||
}
|
||||
if (!params.crypto_mode.empty()) {
|
||||
if (!StringToCryptoMode(params.crypto_mode, &ecmg_config_->crypto_mode)) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Unknown crypto mode: ", params.crypto_mode, ".")};
|
||||
}
|
||||
VLOG(1) << "Channel crypto mode has been set to " << params.crypto_mode;
|
||||
}
|
||||
if (!params.content_ivs.empty()) {
|
||||
Status status = CheckReceivedContentIv(
|
||||
@@ -896,6 +992,8 @@ Status EcmgClientHandler::UpdateChannelPrivateParameters(
|
||||
return status;
|
||||
}
|
||||
content_ivs_.assign(params.content_ivs.begin(), params.content_ivs.end());
|
||||
VLOG(1) << "Channel content IVs have been set with size "
|
||||
<< content_ivs_.size();
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
@@ -916,6 +1014,9 @@ Status EcmgClientHandler::UpdateStreamPrivateParameters(
|
||||
if (stream_info->age_restriction != params.age_restriction) {
|
||||
stream_info->age_restriction = params.age_restriction;
|
||||
invalidate_ecm_gen = true;
|
||||
VLOG(1) << "Stream " << params.ecm_stream_id
|
||||
<< " age restriction has been set to: "
|
||||
<< stream_info->age_restriction;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -928,6 +1029,8 @@ Status EcmgClientHandler::UpdateStreamPrivateParameters(
|
||||
if (stream_info->crypto_mode != new_crypto_mode) {
|
||||
stream_info->crypto_mode = new_crypto_mode;
|
||||
invalidate_ecm_gen = true;
|
||||
VLOG(1) << "Stream " << params.ecm_stream_id
|
||||
<< " crypto mode has been set to: " << params.crypto_mode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -943,13 +1046,18 @@ Status EcmgClientHandler::UpdateStreamPrivateParameters(
|
||||
}
|
||||
stream_info->content_ivs.assign(params.content_ivs.begin(),
|
||||
params.content_ivs.end());
|
||||
VLOG(1) << "Stream " << params.ecm_stream_id
|
||||
<< " content IVs have been set with size "
|
||||
<< stream_info->content_ivs.size();
|
||||
}
|
||||
|
||||
if (!params.entitlement_comb.empty() &&
|
||||
stream_info->entitlement_comb != params.entitlement_comb) {
|
||||
stream_info->entitlement_comb.assign(params.entitlement_comb.begin(),
|
||||
params.entitlement_comb.end());
|
||||
if (!params.entitlement_keys.empty() &&
|
||||
stream_info->entitlement_keys != params.entitlement_keys) {
|
||||
stream_info->entitlement_keys = params.entitlement_keys;
|
||||
invalidate_ecm_gen = true;
|
||||
VLOG(1) << "Stream " << params.ecm_stream_id
|
||||
<< " entitlement keys have been set with size "
|
||||
<< stream_info->entitlement_keys.size();
|
||||
}
|
||||
|
||||
if (invalidate_ecm_gen) {
|
||||
@@ -967,14 +1075,22 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
|
||||
if (stream_info->content_ivs.empty()) {
|
||||
return {error::NOT_FOUND, "Content iv not specified."};
|
||||
}
|
||||
if (stream_info->entitlement_comb.empty()) {
|
||||
|
||||
// Check number of keys for each group id (including empty group id).
|
||||
absl::flat_hash_map<std::string, int> group_id_to_key_count;
|
||||
for (const auto& entitlement_key : stream_info->entitlement_keys) {
|
||||
group_id_to_key_count[entitlement_key.group_id]++;
|
||||
}
|
||||
// Empty group id is the normal entitlement key just for this stream.
|
||||
if (!group_id_to_key_count.contains("")) {
|
||||
return {error::NOT_FOUND, "Entitlement key id comb not specified."};
|
||||
}
|
||||
if (stream_info->entitlement_comb.size() !=
|
||||
ecmg_config_->number_of_content_keys) {
|
||||
return {error::NOT_FOUND,
|
||||
"Number of injected entitlement keys must equal to number of "
|
||||
"content keys per ecm."};
|
||||
for (const auto& group_id_count : group_id_to_key_count) {
|
||||
if (group_id_count.second != ecmg_config_->number_of_content_keys) {
|
||||
return {error::NOT_FOUND,
|
||||
"Number of injected entitlement keys must equal to number of "
|
||||
"content keys per ecm."};
|
||||
}
|
||||
}
|
||||
|
||||
bool key_rotation = ecmg_config_->number_of_content_keys > 1;
|
||||
@@ -989,19 +1105,19 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
|
||||
ecm_init_params.ecc_private_signing_key =
|
||||
ecmg_config_->ecc_private_signing_key;
|
||||
|
||||
std::vector<EntitlementKeyInfo> entitlements;
|
||||
entitlements.reserve(stream_info->entitlement_comb.size());
|
||||
for (size_t i = 0; i < stream_info->entitlement_comb.size(); i++) {
|
||||
entitlements.emplace_back();
|
||||
EntitlementKeyInfo* entitlement = &entitlements.back();
|
||||
entitlement->track_type = kDefaultTrackType;
|
||||
entitlement->is_even_key = key_rotation ? i % 2 == 0 : true;
|
||||
entitlement->key_id = stream_info->entitlement_comb[i].key_id;
|
||||
entitlement->key_value = stream_info->entitlement_comb[i].key_value;
|
||||
// Override track type. This Simulcrypt ECMg implementation assumes only one
|
||||
// track type -- all entitlement keys passed in will be used to generate ECM.
|
||||
// It makes a copy to avoid changing the original one as the original one may
|
||||
// be used to detect if there is change or not.
|
||||
std::vector<EntitlementKeyInfo> modified_entitlement_keys =
|
||||
stream_info->entitlement_keys;
|
||||
for (auto& entitlement_key : modified_entitlement_keys) {
|
||||
entitlement_key.track_type = kDefaultTrackType;
|
||||
}
|
||||
|
||||
stream_info->ecm = CreateEcmInstance();
|
||||
return stream_info->ecm->Initialize(ecm_init_params, entitlements);
|
||||
return stream_info->ecm->Initialize(ecm_init_params,
|
||||
modified_entitlement_keys);
|
||||
}
|
||||
|
||||
Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
@@ -1085,14 +1201,23 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
service_blocking_params.device_groups.empty() ? nullptr
|
||||
: &service_blocking_params);
|
||||
|
||||
// Prepare group ids. All group ids of entitlement keys received will be used.
|
||||
absl::flat_hash_set<std::string> group_id_set;
|
||||
for (const auto& entitlement_key : stream_info->entitlement_keys) {
|
||||
if (!entitlement_key.group_id.empty()) {
|
||||
group_id_set.insert(entitlement_key.group_id);
|
||||
}
|
||||
}
|
||||
std::vector<std::string> group_ids(group_id_set.begin(), group_id_set.end());
|
||||
|
||||
Status status;
|
||||
std::string serialized_ecm;
|
||||
if (key_count > 1) {
|
||||
status = stream_info->ecm->GenerateEcm(&keys[0], &keys[1],
|
||||
kDefaultTrackType, &serialized_ecm);
|
||||
status = stream_info->ecm->GenerateEcm(
|
||||
&keys[0], &keys[1], kDefaultTrackType, group_ids, &serialized_ecm);
|
||||
} else {
|
||||
status = stream_info->ecm->GenerateSingleKeyEcm(&keys[0], kDefaultTrackType,
|
||||
&serialized_ecm);
|
||||
group_ids, &serialized_ecm);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CLIENT_HANDLER_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -27,23 +28,10 @@ namespace cas {
|
||||
|
||||
// A struct that represent a CP_CW_Combination.
|
||||
struct EcmgCpCwCombination {
|
||||
uint16_t cp; // crypto period
|
||||
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
|
||||
|
||||
bool operator==(const EntitlementIdKeyComb& other) const {
|
||||
return (other.key_id == key_id) && (other.key_value == key_value);
|
||||
}
|
||||
bool operator!=(const EntitlementIdKeyComb& other) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
};
|
||||
|
||||
// A struct that is used to hold all possible params for a ECMG request.
|
||||
struct EcmgParameters {
|
||||
std::vector<EcmgCpCwCombination> cp_cw_combinations; // Size is 1 or 2.
|
||||
@@ -61,7 +49,7 @@ struct EcmgParameters {
|
||||
int age_restriction = -1; // Negative value to indidate the field is not set.
|
||||
std::string crypto_mode;
|
||||
std::vector<std::string> content_ivs; // 8 or 16 bytes, one for each key.
|
||||
std::vector<EntitlementIdKeyComb> entitlement_comb;
|
||||
std::vector<EntitlementKeyInfo> entitlement_keys;
|
||||
std::string access_criteria;
|
||||
std::string fingerprinting_control;
|
||||
std::vector<std::string> service_blocking_groups;
|
||||
@@ -73,7 +61,7 @@ struct EcmgStreamInfo {
|
||||
CryptoMode crypto_mode;
|
||||
// 8 or 16 bytes, one for each key.
|
||||
std::vector<std::string> content_ivs;
|
||||
std::vector<EntitlementIdKeyComb> entitlement_comb;
|
||||
std::vector<EntitlementKeyInfo> entitlement_keys;
|
||||
std::unique_ptr<Ecm> ecm;
|
||||
uint8_t age_restriction;
|
||||
};
|
||||
@@ -90,8 +78,9 @@ class EcmgClientHandler {
|
||||
// Handle a |request| from the client.
|
||||
// If any response is generated, it would returned via |response|
|
||||
// and |response_length|.
|
||||
virtual void HandleRequest(const char* const request, char* response,
|
||||
size_t* response_length);
|
||||
// Returns length of |request| that has been processed.
|
||||
virtual size_t HandleRequest(const char* const request, char* response,
|
||||
size_t* response_length);
|
||||
|
||||
// Sets the custom entitlement key fetching function used by ECMG to fetch
|
||||
// entitlement keys. If entitlement key information is not received in
|
||||
@@ -145,7 +134,7 @@ class EcmgClientHandler {
|
||||
size_t* response_length) const;
|
||||
void HandleCwProvision(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
void UpdateParamsWithCustomAccessCriteriaProcessor(
|
||||
Status UpdateParamsWithCustomAccessCriteriaProcessor(
|
||||
EcmgParameters& params) const;
|
||||
|
||||
// Update channel level private parameters using |params|.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "common/status.h"
|
||||
#include "example/test_ecmg_messages.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
||||
@@ -36,8 +38,12 @@ using simulcrypt_util::AddUint16Param;
|
||||
using simulcrypt_util::AddUint32Param;
|
||||
using simulcrypt_util::AddUint8Param;
|
||||
using simulcrypt_util::BuildMessageHeader;
|
||||
using ::testing::_;
|
||||
using ::testing::ByMove;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::ElementsAreArray;
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Return;
|
||||
|
||||
constexpr size_t kBufferSize = 1024;
|
||||
@@ -69,28 +75,29 @@ constexpr int kExpectedEcmResponseLength = sizeof(kTestEcmgEcmResponse);
|
||||
constexpr char kFingerprintingControl[] = "control";
|
||||
constexpr char kServiceBlockingGroup1[] = "group1";
|
||||
constexpr char kServiceBlockingGroup2[] = "group2";
|
||||
constexpr char kEntitlementGroupId[] = "GroupId";
|
||||
|
||||
std::vector<EntitlementKeyInfo> FakeEntitlementKeyFetcherFunc(
|
||||
uint16_t /*channel_id*/, uint16_t /*stream_id*/, uint16_t /*ecm_id*/) {
|
||||
return {{kTrackTypesSD, /*is_even_key*/ false, kEntitlementKeyIdOdd,
|
||||
kEntitlementKeyValueOdd},
|
||||
{kTrackTypesSD, /*is_even_key*/ true, kEntitlementKeyIdEven,
|
||||
kEntitlementKeyValueEven}};
|
||||
return {{kTrackTypesSD, /*group_id=*/"", /*is_even_key=*/false,
|
||||
kEntitlementKeyIdOdd, kEntitlementKeyValueOdd},
|
||||
{kTrackTypesSD, /*group_id=*/"", /*is_even_key=*/true,
|
||||
kEntitlementKeyIdEven, kEntitlementKeyValueEven}};
|
||||
}
|
||||
|
||||
EcmgCustomParameters FakeCustomAcProcessorFunc(
|
||||
uint16_t /*channel_id*/, uint16_t /*stream_id*/,
|
||||
const std::string& access_criteria) {
|
||||
EcmgCustomParameters params;
|
||||
Status FakeCustomAcProcessorFunc(uint16_t /*channel_id*/,
|
||||
uint16_t /*stream_id*/,
|
||||
const std::string& access_criteria,
|
||||
EcmgCustomParameters& params) {
|
||||
params.age_restriction = access_criteria.empty() ? 0 : 3;
|
||||
params.content_ivs = {kContentKeyIvEven, kContentKeyIvOdd};
|
||||
params.crypto_mode = kCryptoMode;
|
||||
params.entitlement_keys = {{kTrackTypesSD, /*is_even_key*/ false,
|
||||
kEntitlementKeyIdOdd, kEntitlementKeyValueOdd},
|
||||
{kTrackTypesSD, /*is_even_key*/ true,
|
||||
kEntitlementKeyIdEven, kEntitlementKeyValueEven}};
|
||||
|
||||
return params;
|
||||
params.entitlement_keys = {
|
||||
{kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ false,
|
||||
kEntitlementKeyIdOdd, kEntitlementKeyValueOdd},
|
||||
{kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ true,
|
||||
kEntitlementKeyIdEven, kEntitlementKeyValueEven}};
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
class MockEcmgClientHandler : public EcmgClientHandler {
|
||||
@@ -178,18 +185,21 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
AddUint8Param(AGE_RESTRICTION, age_restriction, message, message_length);
|
||||
}
|
||||
if (!crypto_mode.empty()) {
|
||||
AddParam(CRYPTO_MODE, reinterpret_cast<const uint8_t*>(crypto_mode.c_str()),
|
||||
AddParam(CRYPTO_MODE,
|
||||
reinterpret_cast<const uint8_t*>(crypto_mode.c_str()),
|
||||
crypto_mode.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::vector<std::string>& entitlements,
|
||||
const std::vector<std::string>& content_ivs,
|
||||
char* message, size_t* message_length) {
|
||||
void BuildStreamSetupRequest(
|
||||
uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id,
|
||||
uint16_t nominal_CP_duration,
|
||||
const std::vector<std::string>& entitlements,
|
||||
const std::vector<std::string>& group_entitlements,
|
||||
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,
|
||||
@@ -207,9 +217,17 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
entitlement.size(), message, message_length);
|
||||
}
|
||||
}
|
||||
if (!group_entitlements.empty()) {
|
||||
for (const auto& entitlement : group_entitlements) {
|
||||
AddParam(ENTITLEMENT_ID_KEY_GROUP_COMBINATION,
|
||||
reinterpret_cast<const uint8_t*>(entitlement.c_str()),
|
||||
entitlement.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()),
|
||||
AddParam(CONTENT_IV,
|
||||
reinterpret_cast<const uint8_t*>(content_iv.c_str()),
|
||||
content_iv.size(), message, message_length);
|
||||
}
|
||||
}
|
||||
@@ -253,8 +271,8 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
void CheckChannelError(uint16_t expected_error_code, const char* const response,
|
||||
size_t response_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));
|
||||
@@ -265,8 +283,8 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
EXPECT_EQ(expected_error_code & 0xff, response[16]);
|
||||
}
|
||||
|
||||
void CheckStreamError(uint16_t expected_error_code, const char* const response,
|
||||
size_t response_length) {
|
||||
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));
|
||||
@@ -374,7 +392,8 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
/*group_entitlements=*/{}, {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_));
|
||||
@@ -383,7 +402,8 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
|
||||
kChannelId, kStreamId + 1, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
/*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_,
|
||||
&request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
|
||||
|
||||
@@ -414,7 +434,7 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceFetchedEntitlements) {
|
||||
|
||||
// No entitlement keys are injected.
|
||||
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
/*entitlements=*/{},
|
||||
/*entitlements=*/{}, /*group_entitlements=*/{},
|
||||
{kContentKeyIvEven, kContentKeyIvOdd}, request_,
|
||||
&request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
@@ -482,7 +502,8 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceCsa2) {
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
/*group_entitlements=*/{}, {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_));
|
||||
@@ -614,7 +635,8 @@ TEST_F(EcmgClientHandlerTest, WrongParameterChannelId) {
|
||||
0, kStreamId, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
/*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_,
|
||||
&request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_);
|
||||
}
|
||||
@@ -642,7 +664,7 @@ TEST_F(EcmgClientHandlerTest, NoInjectedEntitlements) {
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
/*entitlements*/ {},
|
||||
/*entitlements*/ {}, /*group_entitlements=*/{},
|
||||
{kContentKeyEven, kContentKeyEven}, request_,
|
||||
&request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
@@ -668,7 +690,8 @@ TEST_F(EcmgClientHandlerTest, NotEnoughInjectedEntitlements) {
|
||||
BuildStreamSetupRequest(
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
/*group_entitlements=*/{}, {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_));
|
||||
@@ -694,7 +717,8 @@ TEST_F(EcmgClientHandlerTest, TooManyInjectedEntitlements) {
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd),
|
||||
absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
/*group_entitlements=*/{}, {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_));
|
||||
@@ -766,7 +790,7 @@ TEST_F(EcmgClientHandlerTest, NewCasIdEmbeddedInEcmSuccess) {
|
||||
|
||||
ASSERT_EQ(response_len_, sizeof(kTestEcmgEcmResponse));
|
||||
uint16_t actual_cas_id = (static_cast<uint8_t>(response_[35]) << 8) |
|
||||
static_cast<uint8_t>(response_[36]);
|
||||
static_cast<uint8_t>(response_[36]);
|
||||
EXPECT_EQ(actual_cas_id, expected_cas_id);
|
||||
}
|
||||
|
||||
@@ -806,10 +830,20 @@ class MockEcm : public Ecm {
|
||||
public:
|
||||
MockEcm() = default;
|
||||
~MockEcm() override = default;
|
||||
MOCK_METHOD(Status, Initialize,
|
||||
(const EcmInitParameters&,
|
||||
const std::vector<EntitlementKeyInfo>&),
|
||||
(override));
|
||||
MOCK_METHOD(void, SetFingerprinting, (const EcmFingerprintingParams*),
|
||||
(override));
|
||||
MOCK_METHOD(void, SetServiceBlocking, (const EcmServiceBlockingParams*),
|
||||
(override));
|
||||
MOCK_METHOD(Status, GenerateEcm,
|
||||
(EntitledKeyInfo * even_key, EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type,
|
||||
const std::vector<std::string>& group_ids,
|
||||
std::string* serialized_ecm),
|
||||
(const, override));
|
||||
};
|
||||
|
||||
EcmFingerprintingParams FakeFingerprintingSettingFunc(uint16_t channel_id,
|
||||
@@ -895,6 +929,149 @@ TEST_F(EcmgClientHandlerTest, SetServiceBlockingViaAceessCriteriaSuccess) {
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, ProcessedRequestLengthExpected) {
|
||||
EXPECT_EQ(
|
||||
handler_->HandleRequest(kTestEcmgChannelSetup, response_, &response_len_),
|
||||
sizeof(kTestEcmgChannelSetup));
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, ExtraRequestProcessedRequestLengthExpected) {
|
||||
char request[2048];
|
||||
memcpy(request, kTestEcmgChannelSetup, sizeof(kTestEcmgChannelSetup));
|
||||
memcpy(request + sizeof(kTestEcmgChannelSetup), kTestEcmgChannelSetup,
|
||||
sizeof(kTestEcmgChannelSetup));
|
||||
|
||||
EXPECT_EQ(handler_->HandleRequest(request, response_, &response_len_),
|
||||
sizeof(kTestEcmgChannelSetup));
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, NoGroupEntitlementKeysSuccess) {
|
||||
auto ecm = absl::make_unique<MockEcm>();
|
||||
MockEcm* ecm_ptr = ecm.get();
|
||||
EXPECT_CALL(*handler_.get(), CreateEcmInstance)
|
||||
.Times(1)
|
||||
.WillOnce(Return(ByMove(std::move(ecm))));
|
||||
EXPECT_CALL(*ecm_ptr, Initialize).Times(1);
|
||||
EXPECT_CALL(*ecm_ptr, GenerateEcm(NotNull(), NotNull(), _,
|
||||
/*group_ids=*/IsEmpty(), NotNull()))
|
||||
.Times(1);
|
||||
|
||||
// Set up channel.
|
||||
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
|
||||
kCryptoMode, request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
ASSERT_EQ(response_len_, sizeof(kTestEcmgChannelStatus));
|
||||
// Set up stream with group enetitlements.
|
||||
BuildStreamSetupRequest(
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
/*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_,
|
||||
&request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
ASSERT_EQ(response_len_, sizeof(kTestEcmgStreamStatus));
|
||||
// Do provisioing.
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
}
|
||||
|
||||
MATCHER(IsExpectedEntitlements, "") {
|
||||
const std::vector<EntitlementKeyInfo> expected = {
|
||||
{"track_type", /*group_id=*/"", /*is_even_key=*/true,
|
||||
kEntitlementKeyIdEven, kEntitlementKeyValueEven},
|
||||
{"track_type", /*group_id=*/"", /*is_even_key=*/false,
|
||||
kEntitlementKeyIdOdd, kEntitlementKeyValueOdd},
|
||||
{"track_type", kEntitlementGroupId, /*is_even_key=*/true,
|
||||
kEntitlementKeyIdEven, kEntitlementKeyValueEven},
|
||||
{"track_type", kEntitlementGroupId, /*is_even_key=*/false,
|
||||
kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}};
|
||||
return arg == expected;
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, GroupEntitlementKeysViaStreamSetupSuccess) {
|
||||
auto ecm = absl::make_unique<MockEcm>();
|
||||
MockEcm* ecm_ptr = ecm.get();
|
||||
EXPECT_CALL(*handler_.get(), CreateEcmInstance)
|
||||
.Times(1)
|
||||
.WillOnce(Return(ByMove(std::move(ecm))));
|
||||
EXPECT_CALL(*ecm_ptr, Initialize(_, IsExpectedEntitlements())).Times(1);
|
||||
EXPECT_CALL(*ecm_ptr,
|
||||
GenerateEcm(NotNull(), NotNull(), _,
|
||||
ElementsAre(kEntitlementGroupId), NotNull()))
|
||||
.Times(1);
|
||||
|
||||
// Set up channel.
|
||||
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
|
||||
kCryptoMode, request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
ASSERT_EQ(response_len_, sizeof(kTestEcmgChannelStatus));
|
||||
// Set up stream with group enetitlements.
|
||||
BuildStreamSetupRequest(
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven,
|
||||
kEntitlementGroupId),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd,
|
||||
kEntitlementGroupId)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
ASSERT_EQ(response_len_, sizeof(kTestEcmgStreamStatus));
|
||||
// Do provisioing.
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
}
|
||||
|
||||
Status FakeCustomAcProcessorFuncWithGroupKeys(
|
||||
uint16_t /*channel_id*/, uint16_t /*stream_id*/,
|
||||
const std::string& access_criteria, EcmgCustomParameters& params) {
|
||||
params.age_restriction = 0;
|
||||
params.content_ivs = {kContentKeyIvEven, kContentKeyIvOdd};
|
||||
params.crypto_mode = kCryptoMode;
|
||||
params.entitlement_keys = {
|
||||
{kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ true,
|
||||
kEntitlementKeyIdEven, kEntitlementKeyValueEven},
|
||||
{kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ false,
|
||||
kEntitlementKeyIdOdd, kEntitlementKeyValueOdd},
|
||||
{kTrackTypesSD, kEntitlementGroupId, /*is_even_key=*/true,
|
||||
kEntitlementKeyIdEven, kEntitlementKeyValueEven},
|
||||
{kTrackTypesSD, kEntitlementGroupId, /*is_even_key=*/false,
|
||||
kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}};
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, GroupEntitlementKeysViaCallbackSuccess) {
|
||||
auto ecm = absl::make_unique<MockEcm>();
|
||||
MockEcm* ecm_ptr = ecm.get();
|
||||
EXPECT_CALL(*handler_.get(), CreateEcmInstance)
|
||||
.Times(1)
|
||||
.WillOnce(Return(ByMove(std::move(ecm))));
|
||||
EXPECT_CALL(*ecm_ptr, Initialize(_, IsExpectedEntitlements())).Times(1);
|
||||
EXPECT_CALL(*ecm_ptr,
|
||||
GenerateEcm(NotNull(), NotNull(), _,
|
||||
ElementsAre(kEntitlementGroupId), NotNull()))
|
||||
.Times(1);
|
||||
|
||||
handler_->SetCustomAccessCriteriaProcessFunc(
|
||||
FakeCustomAcProcessorFuncWithGroupKeys);
|
||||
SetupValidStandardChannelStream();
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
}
|
||||
|
||||
Status FakeCustomAcProcessorFuncReturningError(
|
||||
uint16_t /*channel_id*/, uint16_t /*stream_id*/,
|
||||
const std::string& /*access_criteria*/, EcmgCustomParameters& /*params*/) {
|
||||
return Status(error::FAILED_PRECONDITION, "Invalid");
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, CustomAccessCriteriaProcessorReturnsError) {
|
||||
handler_->SetCustomAccessCriteriaProcessFunc(
|
||||
FakeCustomAcProcessorFuncReturningError);
|
||||
SetupValidStandardChannelStream();
|
||||
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
|
||||
CheckStreamError(INVALID_MESSAGE, response_, response_len_);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
#define ENTITLEMENT_ID_KEY_COMBINATION (0x8004)
|
||||
#define FINGERPRINTING_CONTROL (0x8005)
|
||||
#define SERVICE_BLOCKING_GROUP (0x8006)
|
||||
#define ENTITLEMENT_ID_KEY_GROUP_COMBINATION (0x8007)
|
||||
|
||||
// ECMG protocol error values.
|
||||
#define INVALID_MESSAGE (0x0001)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "media_cas_packager_sdk/internal/emm.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <cstdint>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMM_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMM_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "media_cas_packager_sdk/internal/emm.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
@@ -215,7 +216,8 @@ class EmmTest : public ::testing::Test {
|
||||
EXPECT_EQ(signature, expected_signature);
|
||||
}
|
||||
int GetPayloadLength(const std::string& serialized_emm) {
|
||||
return static_cast<uint16_t>(serialized_emm[kPayloadLengthStartIndex]) << 8 |
|
||||
return static_cast<uint16_t>(serialized_emm[kPayloadLengthStartIndex])
|
||||
<< 8 |
|
||||
static_cast<uint8_t>(serialized_emm[kPayloadLengthStartIndex + 1]);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "testing/gunit.h"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_util.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "glog/logging.h"
|
||||
@@ -74,8 +75,8 @@ void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message,
|
||||
*message_length += param_length;
|
||||
}
|
||||
|
||||
void AddParam(uint16_t param_type, const uint8_t* param_value, uint16_t param_length,
|
||||
char* message, size_t* message_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);
|
||||
DCHECK(message_length);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_UTIL_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -44,8 +45,8 @@ 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, const uint8_t* param_value, uint16_t param_length,
|
||||
char* message, size_t* message_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
|
||||
} // namespace cas
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "media_cas_packager_sdk/internal/ts_packet.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <cstdint>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -78,7 +79,9 @@ class TsPacket {
|
||||
uint32_t transport_scrambling_control() const {
|
||||
return transport_scrambling_control_;
|
||||
}
|
||||
uint32_t adaptation_field_control() const { return adaptation_field_control_; }
|
||||
uint32_t adaptation_field_control() const {
|
||||
return adaptation_field_control_;
|
||||
}
|
||||
uint32_t continuity_counter() const { return continuity_counter_; }
|
||||
const std::string& payload() const { return payload_; }
|
||||
|
||||
@@ -94,7 +97,9 @@ class TsPacket {
|
||||
void set_transport_scrambling_control(uint32_t v) {
|
||||
transport_scrambling_control_ = v;
|
||||
}
|
||||
void set_adaptation_field_control(uint32_t v) { adaptation_field_control_ = v; }
|
||||
void set_adaptation_field_control(uint32_t v) {
|
||||
adaptation_field_control_ = v;
|
||||
}
|
||||
void set_continuity_counter(uint32_t v) { continuity_counter_ = v; }
|
||||
void set_payload(const std::string& p) { payload_ = p; }
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "glog/logging.h"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
|
||||
Reference in New Issue
Block a user