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:
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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, ¶m_type);
|
||||
*message_length += 2;
|
||||
uint16_t param_length = 4;
|
||||
Host16ToBigEndian(message + *message_length, ¶m_length);
|
||||
*message_length += 2;
|
||||
Host32ToBigEndian(message + *message_length, ¶m_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_, ¶m_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, ¶m_type);
|
||||
*message_length += 2;
|
||||
uint16_t param_length = 2;
|
||||
Host16ToBigEndian(message + *message_length, ¶m_length);
|
||||
*message_length += 2;
|
||||
Host16ToBigEndian(message + *message_length, ¶m_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_, ¶m_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, ¶m_type);
|
||||
*message_length += 2;
|
||||
uint16_t param_length = 1;
|
||||
Host16ToBigEndian(message + *message_length, ¶m_length);
|
||||
*message_length += 2;
|
||||
memcpy(message + *message_length, ¶m_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, ¶m_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, ¶m_type);
|
||||
*message_length += 2;
|
||||
Host16ToBigEndian(message + *message_length, ¶m_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_, ¶m_type);
|
||||
*message_length_ += PARAMETER_TYPE_SIZE;
|
||||
// Add parameter length.
|
||||
Host16ToBigEndian(message_.data() + *message_length_, ¶m_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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
154
media_cas_packager_sdk/internal/simulcrypt_util_test.cc
Normal file
154
media_cas_packager_sdk/internal/simulcrypt_util_test.cc
Normal 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
|
||||
Reference in New Issue
Block a user