Entitlement rotation support

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

View File

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

View File

@@ -13,6 +13,7 @@
#include <bitset>
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include "glog/logging.h"
@@ -38,46 +39,29 @@ constexpr uint16_t kWidevineSystemId = 0x4AD4;
// New CA System ID range for Widevine, all inclusive.
constexpr uint16_t kWidevineNewSystemIdLowerBound = 0x56C0;
constexpr uint16_t kWidevineNewSystemIdUpperBound = 0x56C9;
bool ConvertIvSizeParam(EcmIvSize param, size_t* size) {
if (param == kIvSize8) {
*size = 8;
} else if (param == kIvSize16) {
*size = 16;
} else {
return false;
}
return true;
}
} // namespace
Status Ecm::Initialize(
const EcmInitParameters& ecm_init_parameters,
const std::vector<EntitlementKeyInfo>& injected_entitlements) {
if (initialized_) {
return {error::INTERNAL, "Already initialized."};
LOG(INFO) << "Reinitializing ECM.";
}
if (injected_entitlements.empty()) {
return {error::NOT_FOUND, "Empty injected entitlements."};
}
if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size,
&content_iv_size_)) {
return {error::INVALID_ARGUMENT,
"Parameter content_iv_size must be kIvSize8 or kIvSize16."};
}
crypto_mode_ = ecm_init_parameters.crypto_mode;
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
age_restriction_ = ecm_init_parameters.age_restriction;
ecc_private_signing_key_ = ecm_init_parameters.ecc_private_signing_key;
cas_id_ = ecm_init_parameters.cas_id;
if (cas_id_ != kWidevineSystemId &&
(cas_id_ < kWidevineNewSystemIdLowerBound ||
cas_id_ > kWidevineNewSystemIdUpperBound)) {
if (ecm_init_parameters.cas_id != kWidevineSystemId &&
(ecm_init_parameters.cas_id < kWidevineNewSystemIdLowerBound ||
ecm_init_parameters.cas_id > kWidevineNewSystemIdUpperBound)) {
return {error::INVALID_ARGUMENT, "Invalid CA system ID."};
}
if (ecm_init_parameters.entitlement_rotation.rotation_enabled &&
ecm_init_parameters.entitlement_rotation.rotation_window_left == 0) {
return {error::INVALID_ARGUMENT,
"Entitlement rotation window left must be non-zero when "
"entitlement rotation is enabled."};
}
init_params_ = ecm_init_parameters;
entitlement_keys_.clear();
for (const auto& entitlement : injected_entitlements) {
@@ -119,10 +103,29 @@ void Ecm::SetServiceBlocking(const EcmServiceBlockingParams* service_blocking) {
: absl::make_optional(*service_blocking);
}
Status Ecm::SetEntitlementRotationWindowLeft(
uint32_t entitlement_rotation_window_left) {
if (!init_params_.entitlement_rotation.rotation_enabled) {
return Status(error::FAILED_PRECONDITION,
"Entitlement key rotation is not enabled.");
}
if (entitlement_rotation_window_left == 0) {
return Status(error::INVALID_ARGUMENT,
"window_left must be greater than 0.");
}
init_params_.entitlement_rotation.rotation_window_left =
entitlement_rotation_window_left;
return OkStatus();
}
uint32_t Ecm::GetEntitlementRotationWindowLeft() const {
return init_params_.entitlement_rotation.rotation_window_left;
}
Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const {
std::string* serialized_ecm) {
absl::ReaderMutexLock lock(&ecm_params_mutex_);
if (!initialized_) {
@@ -134,11 +137,11 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
if (even_key == nullptr) {
return {error::INVALID_ARGUMENT, "even_key must not be null."};
}
if (!paired_keys_required_ && odd_key != nullptr) {
if (!init_params_.key_rotation_enabled && odd_key != nullptr) {
return {error::INVALID_ARGUMENT,
"odd_key should be null as key rotation is disabled."};
}
if (paired_keys_required_ && odd_key == nullptr) {
if (init_params_.key_rotation_enabled && odd_key == nullptr) {
return {error::INVALID_ARGUMENT,
"odd_key can not be null as key rotation is enabled."};
}
@@ -148,9 +151,17 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
const bool has_odd_key = odd_key != nullptr;
EcmSerializerParams serializer_params;
serializer_params.crypto_mode = crypto_mode_;
serializer_params.age_restriction = age_restriction_;
serializer_params.cas_id = cas_id_;
serializer_params.crypto_mode = init_params_.crypto_mode;
serializer_params.age_restriction = init_params_.age_restriction;
serializer_params.cas_id = init_params_.cas_id;
serializer_params.entitlement_rotation.rotation_enabled =
init_params_.entitlement_rotation.rotation_enabled;
if (serializer_params.entitlement_rotation.rotation_enabled) {
serializer_params.entitlement_rotation.period_index =
init_params_.entitlement_rotation.period_index;
serializer_params.entitlement_rotation.rotation_window_left =
init_params_.entitlement_rotation.rotation_window_left;
}
// Process normal content keys.
std::vector<EntitledKeyInfo> content_keys = {*even_key};
@@ -185,20 +196,26 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
serializer_params.fingerprinting = fingerprinting_;
serializer_params.service_blocking = service_blocking_;
serializer_params.ecc_private_signing_key = ecc_private_signing_key_;
serializer_params.ecc_private_signing_key =
init_params_.ecc_private_signing_key;
status = ecm_serializer_->SerializeEcm(serializer_params, serialized_ecm);
if (!status.ok()) {
return status;
}
// Automatically decrease rotation_window_left by 1 on each successful call to
// GenerateEcm or GenerateSingleKeyEcm, until the value reaches 1.
if (init_params_.entitlement_rotation.rotation_window_left > 1) {
--init_params_.entitlement_rotation.rotation_window_left;
}
return OkStatus();
}
Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key,
const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const {
std::string* serialized_ecm) {
return GenerateEcm(key, nullptr, track_type, group_ids, serialized_ecm);
}
@@ -326,11 +343,11 @@ Status Ecm::ValidateIv(const std::string& iv, size_t size) const {
}
bool Ecm::CheckEntitlementKeys() const {
int expected_num_keys = init_params_.key_rotation_enabled ? 2 : 1;
for (const auto& track : entitlement_keys_) {
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.";
if (track.second.size() != expected_num_keys) {
LOG(ERROR) << " Wrong number of entitlement keys: " << expected_num_keys
<< " expected, " << track.second.size() << " received.";
return false;
}
}

View File

@@ -61,6 +61,7 @@ struct EcmInitParameters {
// Private signing key used to sign ECM data. Must be an elliptic-curve
// cryptography key.
std::string ecc_private_signing_key;
EntitlementKeyRotationInfo entitlement_rotation;
};
// Generator for producing Widevine CAS-compliant ECMs. Used to construct the
@@ -106,6 +107,20 @@ class Ecm {
virtual void SetServiceBlocking(
const EcmServiceBlockingParams* service_blocking);
// Sets the current value of the entitlement key rotation window left to
// |entitlement_rotation_window_left|. The value will be used in
// GenerateEcm/GenerateSingleKeyEcm if entitlement rotation is enabled as
// specified at initializing, and is automatically decreased by 1 on each call
// to GenerateEcm/GenerateSingleKeyEcm, until it reaches 1.
virtual Status SetEntitlementRotationWindowLeft(
uint32_t entitlement_rotation_window_left);
// Gets the current value of the entitlement key rotation window left. The
// value is used in GenerateEcm/GenerateSingleKeyEcm, and is automatically
// decreased by 1 on each call to GenerateEcm/GenerateSingleKeyEcm, until it
// reaches 1.
virtual uint32_t GetEntitlementRotationWindowLeft() const;
// Accept keys and IVs and construct an ECM that will fit into a Transport
// Stream packet payload.
// Args:
@@ -119,11 +134,14 @@ class Ecm {
// consistent with the initialized settings.
// The even_key and odd_key will be wrapped using the appropriate
// entitlement key. Wrapping modifies the original structure.
// If entitlement rotation is enabled as specified at initializing, the
// entitlement key rotation window left value will be automatically decreased
// by 1, until it reaches 1.
virtual Status GenerateEcm(EntitledKeyInfo* even_key,
EntitledKeyInfo* odd_key,
const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const;
std::string* serialized_ecm);
// Accept a key and IV and construct an ECM that will fit into a Transport
// Stream packet payload. This call is specifically for the case where key
@@ -136,10 +154,13 @@ class Ecm {
// |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
// The |key| contents (specifically IV sizes) must be consistent
// with the initialized settings.
// If entitlement rotation is enabled as specified at initializing, the
// entitlement key rotation window left value will be automatically decreased
// by 1, until it reaches 1.
virtual Status GenerateSingleKeyEcm(EntitledKeyInfo* key,
const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const;
std::string* serialized_ecm);
// Generate a TS packet with the given |ecm| as payload.
//
@@ -214,14 +235,8 @@ class Ecm {
// Set to true when the object has been properly initialized.
bool initialized_ = false;
// Content IV size may be 8 or 16. Size is set once during Initialize().
size_t content_iv_size_ = 8;
// Remember if a pair of keys is required (for key rotation).
bool paired_keys_required_ = false;
CryptoMode crypto_mode_ = CryptoMode::kAesCtr;
uint8_t age_restriction_ = 0;
uint16_t cas_id_ = 0;
std::string ecc_private_signing_key_;
EcmInitParameters init_params_;
// Entitlement keys needed for ECM generation.
// The keys are added when the CasEncryptionResponse is processed.
// Maps from {track_type, group_id} to one/two EntitlementKeyIdValue with even

View File

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

View File

@@ -181,6 +181,12 @@ Status EcmSerializerV3::SerializeEcm(const EcmSerializerParams& params,
if (params.age_restriction != 0) {
meta_data->set_age_restriction(params.age_restriction);
}
if (params.entitlement_rotation.rotation_enabled) {
meta_data->set_entitlement_period_index(
params.entitlement_rotation.period_index);
meta_data->set_entitlement_rotation_window_left(
params.entitlement_rotation.rotation_window_left);
}
EcmKeyData* even_key_data = ecm_payload.mutable_even_key_data();
even_key_data->set_entitlement_key_id(params.even_key.entitlement_key_id);

View File

@@ -573,6 +573,42 @@ TEST_F(EcmSerializerV3Test, GroupKeyInvalidWrappedKeyIvFail) {
"Unexpected group wrapped key iv size: 8"));
}
TEST_F(EcmSerializerV3Test, EntitlementRotationEnabled) {
EcmSerializerV3 ecm_serializer;
serializer_params_.entitlement_rotation.rotation_enabled = true;
serializer_params_.entitlement_rotation.period_index = 1;
serializer_params_.entitlement_rotation.rotation_window_left = 2;
std::string serialized_ecm;
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
SignedEcmPayload signed_payload;
ASSERT_TRUE(signed_payload.ParseFromString(
serialized_ecm.substr(kEcmProtoStartOffset)));
EcmPayload payload;
ASSERT_TRUE(payload.ParseFromString(signed_payload.serialized_payload()));
EXPECT_EQ(payload.meta_data().entitlement_period_index(), 1);
EXPECT_EQ(payload.meta_data().entitlement_rotation_window_left(), 2);
}
TEST_F(EcmSerializerV3Test, EntitlementRotationDisabled) {
EcmSerializerV3 ecm_serializer;
serializer_params_.entitlement_rotation.rotation_enabled = false;
serializer_params_.entitlement_rotation.period_index = 1;
serializer_params_.entitlement_rotation.rotation_window_left = 2;
std::string serialized_ecm;
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
SignedEcmPayload signed_payload;
ASSERT_TRUE(signed_payload.ParseFromString(
serialized_ecm.substr(kEcmProtoStartOffset)));
EcmPayload payload;
ASSERT_TRUE(payload.ParseFromString(signed_payload.serialized_payload()));
EXPECT_FALSE(payload.meta_data().has_entitlement_period_index());
EXPECT_FALSE(payload.meta_data().has_entitlement_rotation_window_left());
}
} // namespace
} // namespace cas
} // namespace widevine

View File

@@ -10,6 +10,8 @@
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "testing/gmock.h"
@@ -25,6 +27,7 @@ namespace cas {
namespace {
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SetArgPointee;
@@ -170,15 +173,12 @@ TEST_F(EcmTest, InitFailNotEnoughInjectedEntitlement) {
.error_code());
}
TEST_F(EcmTest, SecondInitFail) {
TEST_F(EcmTest, SecondInitOk) {
Ecm ecm_gen;
ASSERT_OK(ecm_gen.Initialize(
params_simple_, {injected_entitlement_one_, injected_entitlement_two_}));
EXPECT_EQ(error::INTERNAL,
ecm_gen
.Initialize(params_simple_, {injected_entitlement_one_,
injected_entitlement_two_})
.error_code());
ASSERT_OK(ecm_gen.Initialize(
params_simple_, {injected_entitlement_one_, injected_entitlement_two_}));
}
TEST_F(EcmTest, InitIvSize16x16OK) {
@@ -188,6 +188,19 @@ TEST_F(EcmTest, InitIvSize16x16OK) {
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
}
TEST_F(EcmTest, InitWithInvalidRotationWindowFail) {
Ecm ecm_gen;
params_two_keys_.entitlement_rotation.rotation_enabled = true;
params_two_keys_.entitlement_rotation.period_index = 1;
// rotation_window_left should be a positive value.
params_two_keys_.entitlement_rotation.rotation_window_left = 0;
EXPECT_THAT(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}),
Eq(Status(error::INVALID_ARGUMENT,
"Entitlement rotation window left must be non-zero "
"when entitlement rotation is enabled.")));
}
class EcmInitWithValidCasIdTest : public EcmTest,
public testing::WithParamInterface<uint16_t> {
};
@@ -315,7 +328,13 @@ MATCHER_P(EcmSerializerParamsEq, expected, "") {
(arg.age_restriction == expected.age_restriction) &&
IsEcmSerializerKeyInfoEq(arg.even_key, expected.even_key) &&
IsEcmSerializerKeyInfoEq(arg.odd_key, expected.odd_key) &&
IsEcmSerializerGroupKeyInfoEq(arg.group_keys, expected.group_keys);
IsEcmSerializerGroupKeyInfoEq(arg.group_keys, expected.group_keys) &&
arg.entitlement_rotation.rotation_enabled ==
expected.entitlement_rotation.rotation_enabled &&
arg.entitlement_rotation.period_index ==
expected.entitlement_rotation.period_index &&
arg.entitlement_rotation.rotation_window_left ==
expected.entitlement_rotation.rotation_window_left;
}
TEST_F(EcmTest, GenerateEcmTwoKeysOK) {
@@ -350,6 +369,75 @@ TEST_F(EcmTest, GenerateEcmTwoKeysOK) {
EXPECT_EQ(actual_generated_ecm, expected_serialized_ecm);
}
TEST_F(EcmTest, GenerateEcmWithEntitlementRotation) {
FakeEcm ecm_gen;
params_two_keys_.entitlement_rotation.rotation_enabled = true;
params_two_keys_.entitlement_rotation.period_index = 1;
params_two_keys_.entitlement_rotation.rotation_window_left = 2;
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
auto ecm_serializer = absl::make_unique<MockEcmSerializer>();
const MockEcmSerializer* ecm_serializer_pointer = ecm_serializer.get();
ecm_gen.SetEcmSerializer(std::move(ecm_serializer));
EcmSerializerParams passed_params;
EXPECT_CALL(*ecm_serializer_pointer, SerializeEcm(testing::_, NotNull()))
.WillOnce(DoAll(testing::SaveArg<0>(&passed_params),
SetArgPointee<1>("serialized_ecm"), Return(OkStatus())));
std::string generated_ecm;
ASSERT_OK(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_,
kTrackTypeSD, /*group_ids=*/{},
&generated_ecm));
EXPECT_TRUE(passed_params.entitlement_rotation.rotation_enabled);
EXPECT_EQ(passed_params.entitlement_rotation.period_index, 1);
EXPECT_EQ(passed_params.entitlement_rotation.rotation_window_left, 2);
}
TEST_F(EcmTest, GenerateEcmEntitlementRotationWindowDescreases) {
FakeEcm ecm_gen;
params_two_keys_.entitlement_rotation.rotation_enabled = true;
params_two_keys_.entitlement_rotation.period_index = 1;
params_two_keys_.entitlement_rotation.rotation_window_left = 2;
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
auto ecm_serializer = absl::make_unique<MockEcmSerializer>();
const MockEcmSerializer* ecm_serializer_pointer = ecm_serializer.get();
ecm_gen.SetEcmSerializer(std::move(ecm_serializer));
EcmSerializerParams passed_params;
std::string generated_ecm;
// The first call to Generate ECM.
EXPECT_CALL(*ecm_serializer_pointer, SerializeEcm(testing::_, NotNull()))
.WillOnce(DoAll(testing::SaveArg<0>(&passed_params),
SetArgPointee<1>("serialized_ecm"), Return(OkStatus())));
ASSERT_OK(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_,
kTrackTypeSD, /*group_ids=*/{},
&generated_ecm));
EXPECT_TRUE(passed_params.entitlement_rotation.rotation_enabled);
EXPECT_EQ(passed_params.entitlement_rotation.period_index, 1);
EXPECT_EQ(passed_params.entitlement_rotation.rotation_window_left, 2);
// The second call to Generate ECM with descreased rotation_window_left.
EXPECT_CALL(*ecm_serializer_pointer, SerializeEcm(testing::_, NotNull()))
.WillOnce(DoAll(testing::SaveArg<0>(&passed_params),
SetArgPointee<1>("serialized_ecm"), Return(OkStatus())));
ASSERT_OK(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_,
kTrackTypeSD, /*group_ids=*/{},
&generated_ecm));
EXPECT_TRUE(passed_params.entitlement_rotation.rotation_enabled);
EXPECT_EQ(passed_params.entitlement_rotation.period_index, 1);
EXPECT_EQ(passed_params.entitlement_rotation.rotation_window_left, 1);
// The third call to Generate ECM. rotation_window_left remains to be 1.
EXPECT_CALL(*ecm_serializer_pointer, SerializeEcm(testing::_, NotNull()))
.WillOnce(DoAll(testing::SaveArg<0>(&passed_params),
SetArgPointee<1>("serialized_ecm"), Return(OkStatus())));
ASSERT_OK(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_,
kTrackTypeSD, /*group_ids=*/{},
&generated_ecm));
EXPECT_TRUE(passed_params.entitlement_rotation.rotation_enabled);
EXPECT_EQ(passed_params.entitlement_rotation.period_index, 1);
EXPECT_EQ(passed_params.entitlement_rotation.rotation_window_left, 1);
}
TEST_F(EcmTest, GenerateEcmParamsPassedDown) {
FakeEcm ecm_gen;
EcmInitParameters params;
@@ -481,6 +569,59 @@ TEST_F(EcmTest, GenerateEcmWithMissingGroupKeysFail) {
.ok());
}
TEST_F(EcmTest, GetEntitlementRotationWindowLeftSuccess) {
Ecm ecm_gen;
EcmInitParameters params;
uint32_t expected_window_left = 10;
params.entitlement_rotation.rotation_enabled = true;
params.entitlement_rotation.period_index = 1;
params.entitlement_rotation.rotation_window_left = expected_window_left;
ASSERT_OK(ecm_gen.Initialize(
params, {injected_entitlement_one_, injected_entitlement_two_}));
EXPECT_EQ(ecm_gen.GetEntitlementRotationWindowLeft(), expected_window_left);
}
TEST_F(EcmTest, SetEntitlementRotationWindowLeftSuccess) {
Ecm ecm_gen;
EcmInitParameters params;
params.entitlement_rotation.rotation_enabled = true;
params.entitlement_rotation.period_index = 1;
params.entitlement_rotation.rotation_window_left = 10;
ASSERT_OK(ecm_gen.Initialize(
params, {injected_entitlement_one_, injected_entitlement_two_}));
EXPECT_EQ(ecm_gen.GetEntitlementRotationWindowLeft(), 10);
uint32_t expected_window_left = 100;
ASSERT_OK(ecm_gen.SetEntitlementRotationWindowLeft(expected_window_left));
EXPECT_EQ(ecm_gen.GetEntitlementRotationWindowLeft(), expected_window_left);
}
TEST_F(EcmTest, SetEntitlementRotationWindowLeftRotationDisabledFail) {
Ecm ecm_gen;
EcmInitParameters params;
params.entitlement_rotation.rotation_enabled = false;
ASSERT_OK(ecm_gen.Initialize(
params, {injected_entitlement_one_, injected_entitlement_two_}));
EXPECT_EQ(ecm_gen.SetEntitlementRotationWindowLeft(100).error_code(),
error::FAILED_PRECONDITION);
}
TEST_F(EcmTest, SetEntitlementRotationWindowLeftInvalidValueFail) {
Ecm ecm_gen;
EcmInitParameters params;
params.entitlement_rotation.rotation_enabled = true;
params.entitlement_rotation.period_index = 1;
params.entitlement_rotation.rotation_window_left = 10;
ASSERT_OK(ecm_gen.Initialize(
params, {injected_entitlement_one_, injected_entitlement_two_}));
EXPECT_EQ(ecm_gen.SetEntitlementRotationWindowLeft(0).error_code(),
error::INVALID_ARGUMENT);
}
class EcmTsPacketTest : public ::testing::Test {};
TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) {

View File

@@ -327,67 +327,50 @@ Status HandleParameters(const char* const request, size_t request_length,
}
void BuildChannelError(uint16_t channel_id, uint16_t error_status,
const std::string& error_info, char* message,
size_t* message_length) {
DCHECK(message);
const std::string& error_info,
const absl::Span<char> message, size_t* message_length) {
DCHECK(message_length);
simulcrypt_util::BuildMessageHeader(
ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_ERROR, message, message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message,
message_length);
SimulcryptMessage builder(message, message_length);
builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_ERROR);
builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
builder.AddUint16Param(ERROR_STATUS, error_status);
if (!error_info.empty()) {
simulcrypt_util::AddParam(
ERROR_INFORMATION, reinterpret_cast<const uint8_t*>(error_info.c_str()),
error_info.size(), message, message_length);
builder.AddParam(ERROR_INFORMATION,
reinterpret_cast<const uint8_t*>(error_info.c_str()),
error_info.size());
}
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
VLOG(3) << "ChannelError sent. ECM_CHANNEL_ID: " << channel_id
<< "; ERROR_STATUS: " << error_status
<< "; ERROR_INFORMATION: " << error_info;
}
void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message,
void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config,
const absl::Span<char> message,
size_t* message_length) {
DCHECK(config);
DCHECK(message);
DCHECK(message_length);
simulcrypt_util::BuildMessageHeader(
ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_STATUS, message, message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint8Param(SECTION_TSPKT_FLAG, kSectionTSpktFlag, message,
message_length);
SimulcryptMessage builder(message, message_length);
builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_STATUS);
builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
builder.AddUint8Param(SECTION_TSPKT_FLAG, kSectionTSpktFlag);
// No setting AC_delay_start parameter yet.
// No setting AC_delay_stop parameter yet.
simulcrypt_util::AddUint16Param(DELAY_START, config->delay_start, message,
message_length);
simulcrypt_util::AddUint16Param(DELAY_STOP, config->delay_stop, message,
message_length);
builder.AddUint16Param(DELAY_START, config->delay_start);
builder.AddUint16Param(DELAY_STOP, config->delay_stop);
// No setting transition_delay_start parameter yet.
// No setting transition_delay_stop parameter yet.
simulcrypt_util::AddUint16Param(ECM_REP_PERIOD, config->ecm_rep_period,
message, message_length);
simulcrypt_util::AddUint16Param(MAX_STREAMS, kMaxStream, message,
message_length);
builder.AddUint16Param(ECM_REP_PERIOD, config->ecm_rep_period);
builder.AddUint16Param(MAX_STREAMS, kMaxStream);
// min_CP_duration needs to be at least max_comp_time. So we just use
// max_comp_time here for now.
simulcrypt_util::AddUint16Param(MIN_CP_DURATION, config->max_comp_time,
message, message_length);
builder.AddUint16Param(MIN_CP_DURATION, config->max_comp_time);
// LEAD_CW defines the number of control words required in advance to build an
// ECM. When CW_PER_MESSAGE is 2, LEAD_CW should be 1; when CW_PER_MESSAGE is
// 1, LEAD_CW should be 0.
simulcrypt_util::AddUint8Param(LEAD_CW, config->number_of_content_keys - 1,
message, message_length);
simulcrypt_util::AddUint8Param(CW_PER_MESSAGE, config->number_of_content_keys,
message, message_length);
simulcrypt_util::AddUint16Param(MAX_COMP_TIME, config->max_comp_time, message,
message_length);
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
builder.AddUint8Param(LEAD_CW, config->number_of_content_keys - 1);
builder.AddUint8Param(CW_PER_MESSAGE, config->number_of_content_keys);
builder.AddUint16Param(MAX_COMP_TIME, config->max_comp_time);
VLOG(3) << "ChannelStatus sent. ECM_CHANNEL_ID: " << channel_id
<< "; SECTION_TSPKT_FLAG: " << kSectionTSpktFlag
@@ -403,24 +386,18 @@ void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message,
void BuildStreamError(uint16_t channel_id, uint16_t stream_id,
uint16_t error_status, const std::string& error_info,
char* message, size_t* message_length) {
DCHECK(message);
const absl::Span<char> message, size_t* message_length) {
DCHECK(message_length);
simulcrypt_util::BuildMessageHeader(
ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_ERROR, message, message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message,
message_length);
simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message,
message_length);
SimulcryptMessage builder(message, message_length);
builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_ERROR);
builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
builder.AddUint16Param(ECM_STREAM_ID, stream_id);
builder.AddUint16Param(ERROR_STATUS, error_status);
if (!error_info.empty()) {
simulcrypt_util::AddParam(
ERROR_INFORMATION, reinterpret_cast<const uint8_t*>(error_info.c_str()),
error_info.size(), message, message_length);
builder.AddParam(ERROR_INFORMATION,
reinterpret_cast<const uint8_t*>(error_info.c_str()),
error_info.size());
}
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
VLOG(3) << "StreamError sent. ECM_CHANNEL_ID: " << channel_id
<< "; ECM_STREAM_ID: " << stream_id
@@ -449,22 +426,16 @@ uint16_t StatusToDvbErrorCode(const Status& status) {
}
void BuildStreamStatus(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id,
uint8_t access_criteria_transfer_mode, char* message,
size_t* message_length) {
DCHECK(message);
uint8_t access_criteria_transfer_mode,
const absl::Span<char> message, size_t* message_length) {
DCHECK(message_length);
simulcrypt_util::BuildMessageHeader(
ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_STATUS, message, message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_ID, ecm_id, message, message_length);
simulcrypt_util::AddUint8Param(ACCESS_CRITERIA_TRANSFER_MODE,
access_criteria_transfer_mode, message,
message_length);
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
SimulcryptMessage builder(message, message_length);
builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_STATUS);
builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
builder.AddUint16Param(ECM_STREAM_ID, stream_id);
builder.AddUint16Param(ECM_ID, ecm_id);
builder.AddUint8Param(ACCESS_CRITERIA_TRANSFER_MODE,
access_criteria_transfer_mode);
VLOG(3) << "StreamStatus sent. ECM_CHANNEL_ID: " << channel_id
<< "; ECM_STREAM_ID: " << stream_id << "; ECM_ID: " << ecm_id
@@ -473,18 +444,14 @@ void BuildStreamStatus(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id,
}
void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id,
char* message, size_t* message_length) {
DCHECK(message);
const absl::Span<char> message,
size_t* message_length) {
DCHECK(message_length);
simulcrypt_util::BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION,
ECMG_STREAM_CLOSE_RESPONSE, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message,
message_length);
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
SimulcryptMessage builder(message, message_length);
builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION,
ECMG_STREAM_CLOSE_RESPONSE);
builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
builder.AddUint16Param(ECM_STREAM_ID, stream_id);
VLOG(3) << "StreamCloseResponse sent. ECM_CHANNEL_ID: " << channel_id
<< "; ECM_STREAM_ID: " << stream_id;
@@ -492,22 +459,15 @@ void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id,
void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id,
uint16_t cp_number,
absl::Span<const uint8_t> ecm_datagram, char* message,
size_t* message_length) {
DCHECK(message);
absl::Span<const uint8_t> ecm_datagram,
const absl::Span<char> message, size_t* message_length) {
DCHECK(message_length);
simulcrypt_util::BuildMessageHeader(
ECMG_SCS_PROTOCOL_VERSION, ECMG_ECM_RESPONSE, message, message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message,
message_length);
simulcrypt_util::AddUint16Param(CP_NUMBER, cp_number, message,
message_length);
simulcrypt_util::AddParam(ECM_DATAGRAM, ecm_datagram.data(),
ecm_datagram.size(), message, message_length);
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
SimulcryptMessage builder(message, message_length);
builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_ECM_RESPONSE);
builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
builder.AddUint16Param(ECM_STREAM_ID, stream_id);
builder.AddUint16Param(CP_NUMBER, cp_number);
builder.AddParam(ECM_DATAGRAM, ecm_datagram.data(), ecm_datagram.size());
VLOG(3) << "EcmResponse sent. ECM_CHANNEL_ID: " << channel_id
<< "; ECM_STREAM_ID: " << stream_id << "; CP_NUMBER: " << cp_number;
@@ -547,10 +507,9 @@ EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config)
}
size_t EcmgClientHandler::HandleRequest(const char* const request,
char* response,
const absl::Span<char> response,
size_t* response_length) {
DCHECK(request);
DCHECK(response);
uint8_t protocol_version;
uint16_t request_type;
@@ -628,12 +587,12 @@ size_t EcmgClientHandler::HandleRequest(const char* const request,
}
void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
char* response,
const absl::Span<char> response,
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;
absl::WriterMutexLock lock(&channel_mutex_);
// There is always one (and only one) channel per TCP connection.
if (channel_id_set_) {
@@ -672,11 +631,11 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
}
void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params,
char* response,
const absl::Span<char> response,
size_t* response_length) const {
DCHECK(response);
DCHECK(response_length);
VLOG(3) << "ChannelTest request. ECM_channel_id: " << params.ecm_channel_id;
absl::ReaderMutexLock lock(&channel_mutex_);
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
@@ -687,11 +646,12 @@ void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params,
}
void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
char* response,
const absl::Span<char> response,
size_t* response_length) {
DCHECK(response);
DCHECK(response_length);
VLOG(3) << "ChannelClose request. ECM_channel_id: " << params.ecm_channel_id;
absl::WriterMutexLock lock1(&channel_mutex_);
absl::WriterMutexLock lock2(&stream_mutex_);
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
@@ -705,9 +665,10 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
}
void EcmgClientHandler::HandleChannelError(const EcmgParameters& params,
char* response,
const absl::Span<char> response,
size_t* response_length) const {
VLOG(3) << "ChannelError request. ECM_channel_id: " << params.ecm_channel_id;
absl::ReaderMutexLock lock(&channel_mutex_);
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
@@ -727,14 +688,15 @@ void EcmgClientHandler::HandleChannelError(const EcmgParameters& params,
}
void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
char* response,
const absl::Span<char> response,
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;
absl::ReaderMutexLock lock1(&channel_mutex_);
absl::WriterMutexLock lock2(&stream_mutex_);
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
@@ -782,12 +744,13 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
}
void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params,
char* response,
const absl::Span<char> response,
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;
absl::ReaderMutexLock lock1(&channel_mutex_);
absl::ReaderMutexLock lock2(&stream_mutex_);
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
@@ -807,13 +770,14 @@ void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params,
response_length);
}
void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params,
char* response,
size_t* response_length) {
DCHECK(response);
void EcmgClientHandler::HandleStreamCloseRequest(
const EcmgParameters& params, const absl::Span<char> response,
size_t* response_length) {
DCHECK(response_length);
VLOG(3) << "StreamClose request. ECM_channel_id: " << params.ecm_channel_id
<< "; ECM_stream_id: " << params.ecm_stream_id;
absl::ReaderMutexLock lock1(&channel_mutex_);
absl::WriterMutexLock lock2(&stream_mutex_);
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
@@ -835,10 +799,12 @@ void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params,
}
void EcmgClientHandler::HandleStreamError(const EcmgParameters& params,
char* response,
const absl::Span<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;
absl::ReaderMutexLock lock1(&channel_mutex_);
absl::ReaderMutexLock lock2(&stream_mutex_);
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
@@ -893,18 +859,27 @@ Status EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor(
if (!custom_params.service_blocking_groups.empty()) {
params.service_blocking_groups = custom_params.service_blocking_groups;
}
if (custom_params.entitlement_rotation.rotation_enabled) {
params.entitlement_period_index =
custom_params.entitlement_rotation.period_index;
params.has_entitlement_period_index = true;
params.entitlement_rotation_window_left =
custom_params.entitlement_rotation.rotation_window_left;
params.has_entitlement_rotation_window_left = true;
}
return OkStatus();
}
void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
char* response,
const absl::Span<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();
absl::ReaderMutexLock lock1(&channel_mutex_);
absl::WriterMutexLock lock2(&stream_mutex_);
DCHECK(response);
DCHECK(response_length);
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
@@ -1060,6 +1035,29 @@ Status EcmgClientHandler::UpdateStreamPrivateParameters(
<< stream_info->entitlement_keys.size();
}
// A specified entitlement_period_index also indicates entitlement rotation is
// enabled.
if (params.has_entitlement_period_index &&
(!stream_info->entitlement_rotation.rotation_enabled ||
params.entitlement_period_index !=
stream_info->entitlement_rotation.period_index)) {
stream_info->entitlement_rotation.rotation_enabled = true;
stream_info->entitlement_rotation.period_index =
params.entitlement_period_index;
invalidate_ecm_gen = true;
VLOG(1) << "Stream " << params.ecm_stream_id
<< " entitlement key rotation period index has been updated to "
<< stream_info->entitlement_rotation.period_index;
}
if (params.has_entitlement_rotation_window_left) {
stream_info->entitlement_rotation.rotation_window_left =
params.entitlement_rotation_window_left;
VLOG(1) << "Stream " << params.ecm_stream_id
<< " entitlement key rotation window left has been updated to "
<< stream_info->entitlement_rotation.rotation_window_left;
}
if (invalidate_ecm_gen) {
stream_info->ecm.reset();
}
@@ -1104,6 +1102,7 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
ecm_init_params.ecm_version = ecmg_config_->ecm_version;
ecm_init_params.ecc_private_signing_key =
ecmg_config_->ecc_private_signing_key;
ecm_init_params.entitlement_rotation = stream_info->entitlement_rotation;
// Override track type. This Simulcrypt ECMg implementation assumes only one
// track type -- all entitlement keys passed in will be used to generate ECM.
@@ -1210,6 +1209,17 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
}
std::vector<std::string> group_ids(group_id_set.begin(), group_id_set.end());
// Override the entitlement rotation window left if there is a change.
if (stream_info->entitlement_rotation.rotation_enabled &&
stream_info->entitlement_rotation.rotation_window_left !=
stream_info->ecm->GetEntitlementRotationWindowLeft()) {
Status status = stream_info->ecm->SetEntitlementRotationWindowLeft(
stream_info->entitlement_rotation.rotation_window_left);
if (!status.ok()) {
return status;
}
}
Status status;
std::string serialized_ecm;
if (key_count > 1) {
@@ -1224,6 +1234,10 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
}
LOG(INFO) << "ECM generated with size: " << serialized_ecm.size() << ".";
// Record the latest entitlement rotation window left value.
stream_info->entitlement_rotation.rotation_window_left =
stream_info->ecm->GetEntitlementRotationWindowLeft();
// Make a TS packet carrying the serialized ECM.
// According to the standard, it is the head-end's responsibility to fill
// the PID field in TS packet header.
@@ -1249,5 +1263,42 @@ absl::optional<std::string> EcmgClientHandler::GenerateRandomWrappedKeyIv()
return absl::nullopt;
}
WvEcmgChannelStatus EcmgClientHandler::GetChannelStatus() const {
absl::ReaderMutexLock lock1(&channel_mutex_);
absl::ReaderMutexLock lock2(&stream_mutex_);
WvEcmgChannelStatus status;
status.has_setup = channel_id_set_;
status.channel_id = channel_id_;
status.ca_system_id = cas_id_;
status.num_of_open_streams = streams_info_.size();
status.ecm_version = ecmg_config_->ecm_version;
status.section_tspkt_flag = kSectionTSpktFlag;
status.delay_start = ecmg_config_->delay_start;
status.delay_stop = ecmg_config_->delay_stop;
status.ecm_pep_period = ecmg_config_->ecm_rep_period;
status.max_streams = kMaxStream;
status.min_cp_duration = ecmg_config_->max_comp_time;
status.lead_cw = ecmg_config_->number_of_content_keys - 1;
status.cw_per_message = ecmg_config_->number_of_content_keys;
status.max_comp_time = ecmg_config_->max_comp_time;
return status;
}
std::vector<WvEcmgStreamStatus> EcmgClientHandler::GetStreamStatus() const {
absl::ReaderMutexLock lock(&stream_mutex_);
std::vector<WvEcmgStreamStatus> result;
result.reserve(streams_info_.size());
for (const auto& stream_id_info : streams_info_) {
WvEcmgStreamStatus status;
status.stream_id = stream_id_info.first;
status.ecm_id = stream_id_info.second->ecm_id;
status.age_restriction = stream_id_info.second->age_restriction;
status.crypto_mode = stream_id_info.second->crypto_mode;
result.push_back(status);
}
return result;
}
} // namespace cas
} // namespace widevine

View File

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

View File

@@ -33,18 +33,15 @@ namespace widevine {
namespace cas {
namespace {
using simulcrypt_util::AddParam;
using simulcrypt_util::AddUint16Param;
using simulcrypt_util::AddUint32Param;
using simulcrypt_util::AddUint8Param;
using simulcrypt_util::BuildMessageHeader;
using ::testing::_;
using ::testing::ByMove;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::IsEmpty;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SaveArg;
constexpr size_t kBufferSize = 1024;
constexpr uint16_t kWidevineSystemId = 0x4AD4;
@@ -122,6 +119,7 @@ class EcmgClientHandlerTest : public ::testing::Test {
config_.max_comp_time = 100;
config_.access_criteria_transfer_mode = 1;
config_.number_of_content_keys = 2;
config_.crypto_mode = CryptoMode::kAesScte;
handler_ = absl::make_unique<MockEcmgClientHandler>(&config_);
EXPECT_CALL(*handler_.get(), CreateEcmInstance).WillRepeatedly([] {
return absl::make_unique<Ecm>();
@@ -172,25 +170,23 @@ class EcmgClientHandlerTest : public ::testing::Test {
void BuildChannelSetupRequest(uint16_t channel_id, uint32_t super_cas_id,
uint8_t age_restriction,
const std::string& crypto_mode, char* message,
const std::string& crypto_mode,
const absl::Span<char> message,
size_t* message_length) {
EXPECT_TRUE(message != nullptr);
EXPECT_TRUE(message_length != nullptr);
BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_SETUP, message,
message_length);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
AddUint32Param(SUPER_CAS_ID, super_cas_id, message, message_length);
SimulcryptMessage builder(message, message_length);
builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_SETUP);
builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
builder.AddUint32Param(SUPER_CAS_ID, super_cas_id);
if (age_restriction != 0) {
AddUint8Param(AGE_RESTRICTION, age_restriction, message, message_length);
builder.AddUint8Param(AGE_RESTRICTION, age_restriction);
}
if (!crypto_mode.empty()) {
AddParam(CRYPTO_MODE,
reinterpret_cast<const uint8_t*>(crypto_mode.c_str()),
crypto_mode.size(), message, message_length);
builder.AddParam(CRYPTO_MODE,
reinterpret_cast<const uint8_t*>(crypto_mode.c_str()),
crypto_mode.size());
}
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
}
void BuildStreamSetupRequest(
@@ -198,77 +194,66 @@ class EcmgClientHandlerTest : public ::testing::Test {
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);
const std::vector<std::string>& content_ivs,
const absl::Span<char> message, size_t* message_length) {
EXPECT_TRUE(message_length != nullptr);
BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_SETUP, message,
message_length);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
AddUint16Param(ECM_ID, ecm_id, message, message_length);
AddUint16Param(NOMINAL_CP_DURATION, nominal_CP_duration, message,
message_length);
SimulcryptMessage builder(message, message_length);
builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_SETUP);
builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
builder.AddUint16Param(ECM_STREAM_ID, stream_id);
builder.AddUint16Param(ECM_ID, ecm_id);
builder.AddUint16Param(NOMINAL_CP_DURATION, nominal_CP_duration);
if (!entitlements.empty()) {
for (const auto& entitlement : entitlements) {
AddParam(ENTITLEMENT_ID_KEY_COMBINATION,
reinterpret_cast<const uint8_t*>(entitlement.c_str()),
entitlement.size(), message, message_length);
builder.AddParam(ENTITLEMENT_ID_KEY_COMBINATION,
reinterpret_cast<const uint8_t*>(entitlement.c_str()),
entitlement.size());
}
}
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);
builder.AddParam(ENTITLEMENT_ID_KEY_GROUP_COMBINATION,
reinterpret_cast<const uint8_t*>(entitlement.c_str()),
entitlement.size());
}
}
if (!content_ivs.empty()) {
for (auto& content_iv : content_ivs) {
AddParam(CONTENT_IV,
reinterpret_cast<const uint8_t*>(content_iv.c_str()),
content_iv.size(), message, message_length);
builder.AddParam(CONTENT_IV,
reinterpret_cast<const uint8_t*>(content_iv.c_str()),
content_iv.size());
}
}
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
}
void BuildCwProvisionRequest(
uint16_t channel_id, uint16_t stream_id, uint16_t cp_number,
const std::vector<EcmgCpCwCombination>& cp_cw_combination,
const std::string& fingerprinting,
const std::vector<std::string>& service_blockings, char* message,
size_t* message_length) {
EXPECT_TRUE(message != nullptr);
const std::vector<std::string>& service_blockings,
const absl::Span<char> message, size_t* message_length) {
EXPECT_TRUE(message_length != nullptr);
BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CW_PROVISION, message,
message_length);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
AddUint16Param(CP_NUMBER, cp_number, message, message_length);
SimulcryptMessage builder(message, message_length);
builder.BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CW_PROVISION);
builder.AddUint16Param(ECM_CHANNEL_ID, channel_id);
builder.AddUint16Param(ECM_STREAM_ID, stream_id);
builder.AddUint16Param(CP_NUMBER, cp_number);
for (auto& cp_cw : cp_cw_combination) {
uint8_t combined[100];
Host16ToBigEndian(combined, &cp_cw.cp);
memcpy(combined + 2, cp_cw.cw.c_str(), cp_cw.cw.length());
AddParam(CP_CW_COMBINATION, combined, 2 + cp_cw.cw.length(), message,
message_length);
builder.AddParam(CP_CW_COMBINATION, combined, 2 + cp_cw.cw.length());
}
if (!fingerprinting.empty()) {
AddParam(FINGERPRINTING_CONTROL,
reinterpret_cast<const uint8_t*>(fingerprinting.c_str()),
fingerprinting.size(), message, message_length);
builder.AddParam(FINGERPRINTING_CONTROL,
reinterpret_cast<const uint8_t*>(fingerprinting.c_str()),
fingerprinting.size());
}
for (auto const& sb : service_blockings) {
AddParam(SERVICE_BLOCKING_GROUP,
reinterpret_cast<const uint8_t*>(sb.c_str()), sb.size(), message,
message_length);
builder.AddParam(SERVICE_BLOCKING_GROUP,
reinterpret_cast<const uint8_t*>(sb.c_str()), sb.size());
}
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
}
void CheckChannelError(uint16_t expected_error_code,
@@ -838,12 +823,15 @@ class MockEcm : public Ecm {
(override));
MOCK_METHOD(void, SetServiceBlocking, (const EcmServiceBlockingParams*),
(override));
MOCK_METHOD(Status, SetEntitlementRotationWindowLeft, (uint32_t), (override));
MOCK_METHOD(uint32_t, GetEntitlementRotationWindowLeft, (),
(const, 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));
(override));
};
EcmFingerprintingParams FakeFingerprintingSettingFunc(uint16_t channel_id,
@@ -1072,6 +1060,161 @@ TEST_F(EcmgClientHandlerTest, CustomAccessCriteriaProcessorReturnsError) {
CheckStreamError(INVALID_MESSAGE, response_, response_len_);
}
TEST_F(EcmgClientHandlerTest, GetChannelStreamStatusNoChannelSuccess) {
EXPECT_FALSE(handler_->GetChannelStatus().has_setup);
EXPECT_TRUE(handler_->GetStreamStatus().empty());
}
TEST_F(EcmgClientHandlerTest, GetChannelStreamStatusNoStreamSuccess) {
SetupValidStandardChannel();
WvEcmgChannelStatus status = handler_->GetChannelStatus();
EXPECT_TRUE(status.has_setup);
EXPECT_EQ(status.channel_id, kChannelId);
EXPECT_EQ(status.ca_system_id, kWidevineSystemId);
EXPECT_EQ(status.num_of_open_streams, 0);
EXPECT_EQ(status.ecm_version, EcmVersion::kV2);
EXPECT_TRUE(handler_->GetStreamStatus().empty());
}
TEST_F(EcmgClientHandlerTest, GetChannelStreamStatusTwoStreamsSuccess) {
SetupValidStandardChannel();
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
/*entitlements=*/{}, /*group_entitlements=*/{},
{kContentKeyIvEven, kContentKeyIvOdd}, request_,
&request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(response_len_, sizeof(kTestEcmgStreamStatus));
BuildStreamSetupRequest(
kChannelId, kStreamId + 1, kEcmId + 1, kNominalCpDuration,
/*entitlements=*/{}, /*group_entitlements=*/{},
{kContentKeyIvEven, kContentKeyIvOdd}, request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(response_len_, sizeof(kTestEcmgStreamStatus));
WvEcmgChannelStatus channel_status = handler_->GetChannelStatus();
std::vector<WvEcmgStreamStatus> stream_status = handler_->GetStreamStatus();
EXPECT_TRUE(channel_status.has_setup);
EXPECT_EQ(channel_status.channel_id, kChannelId);
EXPECT_EQ(channel_status.ca_system_id, kWidevineSystemId);
EXPECT_EQ(channel_status.num_of_open_streams, 2);
ASSERT_EQ(stream_status.size(), 2);
EXPECT_NE(stream_status[0].stream_id, stream_status[1].stream_id);
for (const auto& status : stream_status) {
EXPECT_EQ(status.age_restriction, 0);
EXPECT_EQ(status.crypto_mode, CryptoMode::kAesScte);
EXPECT_THAT(status.stream_id, testing::AnyOf(kStreamId, kStreamId + 1));
EXPECT_THAT(status.ecm_id, testing::AnyOf(kEcmId, kEcmId + 1));
}
}
// A fake callback function that has entitlement rotation info. It has the
// following behavior: the returned period_index increases by one on each call.
Status FakeCustomAcProcessorWithEntitlementRotationIndexChangeFunc(
uint16_t /*channel_id*/, uint16_t /*stream_id*/,
const std::string& access_criteria, EcmgCustomParameters& params) {
static int period_index = 100;
params.age_restriction = 0;
params.content_ivs = {kContentKeyIvEven, kContentKeyIvOdd};
params.crypto_mode = kCryptoMode;
params.entitlement_keys = {
{kTrackTypesSD, /*group_id=*/"", /*is_even_key=*/false,
kEntitlementKeyIdOdd, kEntitlementKeyValueOdd},
{kTrackTypesSD, /*group_id=*/"", /*is_even_key=*/true,
kEntitlementKeyIdEven, kEntitlementKeyValueEven}};
params.entitlement_rotation.rotation_enabled = true;
params.entitlement_rotation.period_index = period_index;
params.entitlement_rotation.rotation_window_left = 1000;
// The next call to this function will return a different period_index.
++period_index;
return OkStatus();
}
TEST_F(EcmgClientHandlerTest, EntitlementRotationIndexChangeSuccess) {
handler_->SetCustomAccessCriteriaProcessFunc(
FakeCustomAcProcessorWithEntitlementRotationIndexChangeFunc);
SetupValidStandardChannelStream();
auto ecm = absl::make_unique<MockEcm>();
MockEcm* ecm_ptr = ecm.get();
EXPECT_CALL(*handler_, CreateEcmInstance)
.Times(1)
.WillOnce(Return(ByMove(std::move(ecm))));
EcmInitParameters actual_init_params;
EXPECT_CALL(*ecm_ptr, Initialize(_, _))
.Times(1)
.WillOnce(DoAll(SaveArg<0>(&actual_init_params), Return(OkStatus())));
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
EXPECT_TRUE(actual_init_params.entitlement_rotation.rotation_enabled);
EXPECT_EQ(actual_init_params.entitlement_rotation.period_index, 100);
EXPECT_EQ(actual_init_params.entitlement_rotation.rotation_window_left, 1000);
// The second call will reinitialize Ecm with period_index increased by 1.
ecm = absl::make_unique<MockEcm>();
ecm_ptr = ecm.get();
EXPECT_CALL(*handler_, CreateEcmInstance)
.Times(1)
.WillOnce(Return(ByMove(std::move(ecm))));
EXPECT_CALL(*ecm_ptr, Initialize(_, _))
.Times(1)
.WillOnce(DoAll(SaveArg<0>(&actual_init_params), Return(OkStatus())));
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
EXPECT_TRUE(actual_init_params.entitlement_rotation.rotation_enabled);
EXPECT_EQ(actual_init_params.entitlement_rotation.period_index, 101);
EXPECT_EQ(actual_init_params.entitlement_rotation.rotation_window_left, 1000);
}
// A fake callback function that has entitlement rotation info. It has the
// following behavior: the returned period_index and rotation_window_left are
// fixed.
Status FakeCustomAcProcessorWithEntitlementRotationFunc(
uint16_t /*channel_id*/, uint16_t /*stream_id*/,
const std::string& access_criteria, EcmgCustomParameters& params) {
params.age_restriction = 0;
params.content_ivs = {kContentKeyIvEven, kContentKeyIvOdd};
params.crypto_mode = kCryptoMode;
params.entitlement_keys = {
{kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ false,
kEntitlementKeyIdOdd, kEntitlementKeyValueOdd},
{kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ true,
kEntitlementKeyIdEven, kEntitlementKeyValueEven}};
params.entitlement_rotation.rotation_enabled = true;
params.entitlement_rotation.period_index = 100;
params.entitlement_rotation.rotation_window_left = 1000;
return OkStatus();
}
TEST_F(EcmgClientHandlerTest, EntitlementRotationWindowLeftSuccess) {
handler_->SetCustomAccessCriteriaProcessFunc(
FakeCustomAcProcessorWithEntitlementRotationFunc);
SetupValidStandardChannelStream();
auto ecm = absl::make_unique<MockEcm>();
MockEcm* ecm_ptr = ecm.get();
EXPECT_CALL(*handler_, CreateEcmInstance)
.Times(1)
.WillOnce(Return(ByMove(std::move(ecm))));
EcmInitParameters actual_init_params;
EXPECT_CALL(*ecm_ptr, Initialize(_, _))
.Times(1)
.WillOnce(DoAll(SaveArg<0>(&actual_init_params), Return(OkStatus())));
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
EXPECT_TRUE(actual_init_params.entitlement_rotation.rotation_enabled);
EXPECT_EQ(actual_init_params.entitlement_rotation.period_index, 100);
EXPECT_EQ(actual_init_params.entitlement_rotation.rotation_window_left, 1000);
// Calling again will not reinitialize the ECM object. But call to
// SetEntitlementRotationWindowLeft will be made as the rotation_window_left
// specified did not decrease.
EXPECT_CALL(*ecm_ptr, GetEntitlementRotationWindowLeft())
.WillRepeatedly(Return(999));
EXPECT_CALL(*ecm_ptr, SetEntitlementRotationWindowLeft(1000))
.WillOnce(Return(OkStatus()));
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
}
} // namespace
} // namespace cas
} // namespace widevine

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,154 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2020 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/internal/simulcrypt_util.h"
#include <cstdint>
#include "testing/gmock.h"
#include "testing/gunit.h"
namespace widevine {
namespace cas {
namespace {
constexpr size_t kBufferSize = 1024;
constexpr int kMessageHeaderSize = 5;
constexpr int kParameterTypeLengthFieldsSize = 4;
constexpr uint8_t kProtocolVersion = 1;
constexpr uint16_t kMessageType = 0x0123;
constexpr uint16_t kParameterType = 0x4321;
constexpr uint32_t kParameterValue32 = 0x12345678;
constexpr uint16_t kParameterValue16 = 0x1234;
constexpr uint8_t kParameterValue8 = 0x12;
constexpr uint8_t kParameterValueArray[] = {0x12, 0x34};
constexpr uint8_t kExpectedMixedParametersMessage[] = {
// Header
0x01, 0x01, 0x23, 0x00, 0x19,
// Param 1
0x43, 0x21, 0x00, 0x02, 0x12, 0x34,
// Param 2
0x43, 0x21, 0x00, 0x02, 0x12, 0x34,
// Param 3
0x43, 0x21, 0x00, 0x01, 0x12,
// Param 4
0x43, 0x21, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78};
// Verifies that the message header can be built.
TEST(SimulcryptMessageTest, BuildMessageHeader) {
char buffer[kBufferSize];
size_t message_length = 0;
SimulcryptMessage builder(buffer, &message_length);
builder.BuildMessageHeader(kProtocolVersion, kMessageType);
ASSERT_EQ(message_length, kMessageHeaderSize);
EXPECT_THAT(absl::MakeSpan(buffer).subspan(0, kMessageHeaderSize),
::testing::ElementsAre(kProtocolVersion, kMessageType >> 8,
kMessageType & 0xff, 0, 0));
}
// Verifies that a uint32_t parameter can be appended with message length updated.
TEST(SimulcryptMessageTest, AddUint32Param) {
char buffer[kBufferSize];
size_t message_length = 0;
SimulcryptMessage builder(buffer, &message_length);
builder.BuildMessageHeader(kProtocolVersion, kMessageType);
builder.AddUint32Param(kParameterType, kParameterValue32);
uint16_t parameter_length = sizeof(kParameterValue32);
uint16_t expected_payload_length =
kParameterTypeLengthFieldsSize + parameter_length;
ASSERT_EQ(message_length, kMessageHeaderSize + expected_payload_length);
EXPECT_THAT(
absl::MakeSpan(buffer).subspan(0, message_length),
::testing::ElementsAre(
kProtocolVersion, kMessageType >> 8, kMessageType & 0xff,
expected_payload_length >> 8, expected_payload_length & 0xff,
kParameterType >> 8, kParameterType & 0xff, parameter_length >> 8,
parameter_length & 0xff, 0x12, 0x34, 0x56, 0x78));
}
// Verifies that a uint16_t parameter can be appended with message length updated.
TEST(SimulcryptMessageTest, AddUint16Param) {
char buffer[kBufferSize];
size_t message_length = 0;
SimulcryptMessage builder(buffer, &message_length);
builder.BuildMessageHeader(kProtocolVersion, kMessageType);
builder.AddUint16Param(kParameterType, kParameterValue16);
uint16_t parameter_length = sizeof(kParameterValue16);
uint16_t expected_payload_length =
kParameterTypeLengthFieldsSize + parameter_length;
ASSERT_EQ(message_length, kMessageHeaderSize + expected_payload_length);
EXPECT_THAT(absl::MakeSpan(buffer).subspan(0, message_length),
::testing::ElementsAre(
kProtocolVersion, kMessageType >> 8, kMessageType & 0xff,
expected_payload_length >> 8, expected_payload_length & 0xff,
kParameterType >> 8, kParameterType & 0xff,
parameter_length >> 8, parameter_length & 0xff,
kParameterValue16 >> 8, kParameterValue16 & 0xff));
}
// Verifies that a uint8_t parameter can be appended with message length updated.
TEST(SimulcryptMessageTest, AddUint8Param) {
char buffer[kBufferSize];
size_t message_length = 0;
SimulcryptMessage builder(buffer, &message_length);
builder.BuildMessageHeader(kProtocolVersion, kMessageType);
builder.AddUint8Param(kParameterType, kParameterValue8);
uint16_t parameter_length = sizeof(kParameterValue8);
uint16_t expected_payload_length =
kParameterTypeLengthFieldsSize + parameter_length;
ASSERT_EQ(message_length, kMessageHeaderSize + expected_payload_length);
EXPECT_THAT(
absl::MakeSpan(buffer).subspan(0, message_length),
::testing::ElementsAre(
kProtocolVersion, kMessageType >> 8, kMessageType & 0xff,
expected_payload_length >> 8, expected_payload_length & 0xff,
kParameterType >> 8, kParameterType & 0xff, parameter_length >> 8,
parameter_length & 0xff, kParameterValue8));
}
// Verifies that a uint32_t buffer can be appended with message length updated.
TEST(SimulcryptMessageTest, AddParam) {
char buffer[kBufferSize];
size_t message_length = 0;
SimulcryptMessage builder(buffer, &message_length);
builder.BuildMessageHeader(kProtocolVersion, kMessageType);
uint16_t parameter_length = sizeof(kParameterValueArray);
builder.AddParam(kParameterType, kParameterValueArray, parameter_length);
uint16_t expected_payload_length =
kParameterTypeLengthFieldsSize + parameter_length;
ASSERT_EQ(message_length, kMessageHeaderSize + expected_payload_length);
EXPECT_THAT(absl::MakeSpan(buffer).subspan(0, message_length),
::testing::ElementsAre(
kProtocolVersion, kMessageType >> 8, kMessageType & 0xff,
expected_payload_length >> 8, expected_payload_length & 0xff,
kParameterType >> 8, kParameterType & 0xff,
parameter_length >> 8, parameter_length & 0xff,
kParameterValueArray[0], kParameterValueArray[1]));
}
// Verifies that multiple parameters with different types can be appended with
// message length updated.
TEST(SimulcryptMessageTest, MixedParams) {
char buffer[kBufferSize];
size_t message_length = 0;
SimulcryptMessage builder(buffer, &message_length);
builder.BuildMessageHeader(kProtocolVersion, kMessageType);
builder.AddParam(kParameterType, kParameterValueArray,
sizeof(kParameterValueArray));
builder.AddUint16Param(kParameterType, kParameterValue16);
builder.AddUint8Param(kParameterType, kParameterValue8);
builder.AddUint32Param(kParameterType, kParameterValue32);
ASSERT_EQ(message_length, 30);
EXPECT_THAT(absl::MakeSpan(buffer).subspan(0, message_length),
::testing::ElementsAreArray(kExpectedMixedParametersMessage));
}
} // namespace
} // namespace cas
} // namespace widevine