Support for group license

Content keys in ECM v3 can now additionally be encrypted by group
entitlement keys.
This commit is contained in:
Lu Chen
2021-03-04 14:35:08 -08:00
parent 79e39b482d
commit 62777d7d3b
66 changed files with 1275 additions and 954 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/internal/ecm_serializer_v2.h"
#include <cstdint>
#include <string>
#include <vector>

View File

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

View File

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

View File

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

View File

@@ -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 = &params->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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@
#include <sys/socket.h>
#include <cstdint>
#include <cstring>
#include <iostream>

View File

@@ -10,6 +10,7 @@
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_H_
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>

View File

@@ -12,6 +12,7 @@
#include <stdio.h>
#include <string.h>
#include <cstdint>
#include <memory>
#include "testing/gunit.h"

View File

@@ -11,6 +11,7 @@
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_
#include <cstddef>
#include <cstdint>
#include <cstdint>

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,7 @@
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include "glog/logging.h"

View File

@@ -11,6 +11,7 @@
#include <sys/types.h>
#include <cstdint>
#include <string>
#include <cstdint>

View File

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