Add EMMG to carry fingerprinting and service blocking info

This commit is contained in:
Lu Chen
2020-09-15 09:16:59 -07:00
parent 3d8f585313
commit 1ce468e5ba
143 changed files with 2316 additions and 17450 deletions

View File

@@ -68,6 +68,7 @@ cc_test(
"//external:protobuf",
"//testing:gunit_main",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"//common:aes_cbc_util",
"//protos/public:media_cas_encryption_cc_proto",
],
@@ -87,7 +88,7 @@ cc_library(
":simulcrypt_util",
":util",
"//base",
"@abseil_repo//absl/container:node_hash_map",
"@abseil_repo//absl/container:flat_hash_map",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/types:optional",
@@ -123,10 +124,12 @@ cc_library(
"simulcrypt_constants.h",
],
deps = [
":emm",
":simulcrypt_util",
":ts_packet",
":util",
"//base",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/strings:str_format",
"@abseil_repo//absl/time",
@@ -226,6 +229,7 @@ cc_library(
":mpeg2ts",
":ts_packet",
"//base",
"@abseil_repo//absl/strings",
"//common:status",
],
)
@@ -249,8 +253,12 @@ cc_library(
srcs = ["emm.cc"],
hdrs = ["emm.h"],
deps = [
":util",
"//base",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/time",
"//common:ec_key",
"//common:hash_algorithm",
"//common:status",
"//common:string_util",
"//protos/public:media_cas_cc_proto",
@@ -262,6 +270,14 @@ cc_test(
srcs = ["emm_test.cc"],
deps = [
":emm",
":util",
"//testing:gunit_main",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/time",
"//common:ec_key",
"//common:ec_test_keys",
"//common:hash_algorithm",
"//common:status",
],
)

View File

@@ -13,6 +13,7 @@
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "common/aes_cbc_util.h"
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
#include "protos/public/media_cas_encryption.pb.h"
@@ -50,9 +51,24 @@ constexpr char kEcmContentIvOdd[] = "AaCbEcGd";
constexpr char kFakeCasEncryptionResponseKeyId[] = "fake_key_id.....";
constexpr char kFakeCasEncryptionResponseKeyData[] =
"fakefakefakefakefakefakefakefake";
constexpr char kFakeCasEncryptionResponseGroupKeyId[] = "fake_key_id12345";
constexpr char kFakeCasEncryptionResponseGroupKeyData[] =
"fakefakefakefakefakefake12345678";
constexpr char kTrackType[] = "SD";
void SetKeyInfoInTestResponse(
widevine::CasEncryptionResponse_KeyInfo* key, bool group_id_exists) {
ASSERT_TRUE(key != nullptr);
if (!group_id_exists) {
key->set_key_id(kFakeCasEncryptionResponseKeyId);
key->set_key(kFakeCasEncryptionResponseKeyData);
} else {
key->set_key_id(kFakeCasEncryptionResponseGroupKeyId);
key->set_key(kFakeCasEncryptionResponseGroupKeyData);
}
}
Status HandleCasEncryptionRequest(const std::string& signed_request_json,
std::string* http_response_json) {
SignedCasEncryptionRequest signed_request;
@@ -72,24 +88,22 @@ Status HandleCasEncryptionRequest(const std::string& signed_request_json,
CasEncryptionResponse response;
response.set_status(CasEncryptionResponse::OK);
response.set_content_id(request.content_id());
response.set_group_id(request.group_id());
for (const auto& track_type : request.track_types()) {
if (request.key_rotation()) {
// Add the Even key.
auto key = response.add_entitlement_keys();
key->set_key_id(kFakeCasEncryptionResponseKeyId);
key->set_key(kFakeCasEncryptionResponseKeyData);
SetKeyInfoInTestResponse(key, request.has_group_id());
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
// Add the Odd key.
key = response.add_entitlement_keys();
key->set_key_id(kFakeCasEncryptionResponseKeyId);
key->set_key(kFakeCasEncryptionResponseKeyData);
SetKeyInfoInTestResponse(key, request.has_group_id());
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
} else {
auto key = response.add_entitlement_keys();
key->set_key_id(kFakeCasEncryptionResponseKeyId);
key->set_key(kFakeCasEncryptionResponseKeyData);
SetKeyInfoInTestResponse(key, request.has_group_id());
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
}
@@ -146,7 +160,8 @@ class EcmGeneratorTest : public testing::Test {
}
// Call this to setup the guts (Ecm) of the ECM Generator.
void PrepareEcmGenerator(bool key_rotation_enabled) {
void PrepareEcmGenerator(bool key_rotation_enabled,
const std::string& group_id) {
std::string entitlement_request;
std::string entitlement_response;
ecm_init_params_.key_rotation_enabled = key_rotation_enabled;
@@ -154,6 +169,7 @@ class EcmGeneratorTest : public testing::Test {
EntitlementRequestParams request_params;
request_params.content_id = kContentId;
request_params.content_provider = kProvider;
request_params.group_id = group_id;
request_params.track_types = {kTrackType};
request_params.key_rotation = key_rotation_enabled;
ASSERT_OK(key_fetcher.CreateEntitlementRequest(request_params,
@@ -169,6 +185,78 @@ class EcmGeneratorTest : public testing::Test {
ecm_gen_.set_ecm(std::move(ecm_));
}
void CheckECMOutput(absl::string_view ecm_string,
absl::string_view response_entitlement_key_id,
absl::string_view response_entitltement_key_data,
bool key_rotation_enabled) {
// Expected size (bytes):
// CA system ID: 2 bytes
// version: 1 byte
// flags: 1 byte
// flags: 1 byte
// entitlement key ID: 16 bytes
// Single key: ID (16), Data (16), IV (16), IV (8) = 56
// total = 77 (ECM message complete if no key retotation)
// second entitlement key ID: 16 bytes
// Second key: ID (16), Data (16), IV (16), IV (8) = 56
// total = 149
uint32_t ecm_string_size = key_rotation_enabled ? 149 : 77;
ASSERT_EQ(ecm_string_size, ecm_string.size());
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
EXPECT_EQ('\x02', ecm_string[2]); // ECM version
if (key_rotation_enabled) {
EXPECT_EQ('\x03', ecm_string[3]); // flags
} else {
EXPECT_EQ('\x02', ecm_string[3]); // flags
}
EXPECT_EQ('\x80', ecm_string[4]); // flags
EXPECT_EQ(response_entitlement_key_id, ecm_string.substr(5, 16));
if (!key_rotation_enabled) {
EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16));
// Key data has been wrapped (encrypted).
EXPECT_NE(kEcmKeyDataSingle, ecm_string.substr(37, 16));
EXPECT_EQ(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16));
// Unwrap key and compare with original.
absl::string_view wrapping_key = response_entitltement_key_data;
absl::string_view wrapping_iv = ecm_string.substr(53, 16);
absl::string_view wrapped_key = ecm_string.substr(37, 16);
std::string unwrapped_key = crypto_util::DecryptAesCbcNoPad(
std::string(wrapping_key), std::string(wrapping_iv),
std::string(wrapped_key));
EXPECT_EQ(kEcmKeyDataSingle, unwrapped_key);
EXPECT_EQ(kEcmContentIvSingle, ecm_string.substr(69, 8));
} else {
EXPECT_EQ(kEcmKeyIdEven, ecm_string.substr(21, 16));
// Key data has been wrapped (encrypted).
EXPECT_NE(kEcmKeyDataEven, ecm_string.substr(37, 16));
EXPECT_EQ(kEcmWrappedKeyIvEven, ecm_string.substr(53, 16));
EXPECT_EQ(kEcmContentIvEven, ecm_string.substr(69, 8));
EXPECT_EQ(response_entitlement_key_id, ecm_string.substr(77, 16));
EXPECT_EQ(kEcmKeyIdOdd, ecm_string.substr(93, 16));
// Key data has been wrapped (encrypted).
EXPECT_NE(kEcmKeyDataOdd, ecm_string.substr(109, 16));
EXPECT_EQ(kEcmWrappedKeyIvOdd, ecm_string.substr(125, 16));
EXPECT_EQ(kEcmContentIvOdd, ecm_string.substr(141, 8));
// Unwrap even key and compare with original.
absl::string_view wrapping_key_even = response_entitltement_key_data;
absl::string_view wrapping_iv_even = ecm_string.substr(53, 16);
absl::string_view wrapped_key_even = ecm_string.substr(37, 16);
std::string unwrapped_key_even = crypto_util::DecryptAesCbcNoPad(
std::string(wrapping_key_even), std::string(wrapping_iv_even),
std::string(wrapped_key_even));
EXPECT_EQ(kEcmKeyDataEven, unwrapped_key_even);
// Unwrap odd key and compare with original.
absl::string_view wrapping_key_odd = response_entitltement_key_data;
absl::string_view wrapping_iv_odd = ecm_string.substr(125, 16);
absl::string_view wrapped_key_odd = ecm_string.substr(109, 16);
std::string unwrapped_key_odd = crypto_util::DecryptAesCbcNoPad(
std::string(wrapping_key_odd), std::string(wrapping_iv_odd),
std::string(wrapped_key_odd));
EXPECT_EQ(kEcmKeyDataOdd, unwrapped_key_odd);
}
}
const KeyParameters kKeyParamsSingle{kEcmKeyIdSingle,
kEcmKeyDataSingle,
kEcmWrappedKeyIvSingle,
@@ -187,101 +275,55 @@ class EcmGeneratorTest : public testing::Test {
TEST_F(EcmGeneratorTest, InitializeNoRotation) {
EXPECT_FALSE(ecm_gen_.initialized());
PrepareEcmGenerator(false);
PrepareEcmGenerator(false, /*group_id=*/"");
EcmParameters ecm_params;
std::vector<EntitledKeyInfo> keys;
SetTestConfig1(&ecm_params);
Status status = ProcessEcmParameters(ecm_params, &keys);
ASSERT_OK(status);
ASSERT_EQ(EntitlementKeySize(ecm_params), 16);
ASSERT_TRUE(ecm_gen_.initialized());
EXPECT_FALSE(ecm_gen_.rotation_enabled());
}
TEST_F(EcmGeneratorTest, GenerateNoRotation) {
PrepareEcmGenerator(false);
PrepareEcmGenerator(false, /*group_id=*/"");
EcmParameters ecm_params;
SetTestConfig1(&ecm_params);
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
// Expected size (bytes):
// CA system ID: 2 bytes
// version: 1 byte
// flags: 1 byte
// flags: 1 byte
// entitlement key ID: 16 bytes
// Single key: ID (16), Data (16), IV (16), IV (8) = 56
// total = 77
ASSERT_EQ(77, ecm_string.size());
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
EXPECT_EQ('\x02', ecm_string[2]); // ECM version
EXPECT_EQ('\x02', ecm_string[3]); // flags
EXPECT_EQ('\x80', ecm_string[4]); // flags
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16));
// Key data has been wrapped (encrypted).
EXPECT_NE(kEcmKeyDataSingle, ecm_string.substr(37, 16));
EXPECT_EQ(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16));
// Unwrap key and compare with original.
std::string wrapping_key = kFakeCasEncryptionResponseKeyData;
std::string wrapping_iv = ecm_string.substr(53, 16);
std::string wrapped_key = ecm_string.substr(37, 16);
std::string unwrapped_key =
crypto_util::DecryptAesCbcNoPad(wrapping_key, wrapping_iv, wrapped_key);
EXPECT_EQ(kEcmKeyDataSingle, unwrapped_key);
EXPECT_EQ(kEcmContentIvSingle, ecm_string.substr(69, 8));
CheckECMOutput(ecm_string, kFakeCasEncryptionResponseKeyId,
kFakeCasEncryptionResponseKeyData, false);
}
TEST_F(EcmGeneratorTest, GenerateNoRotationWithGroupId) {
PrepareEcmGenerator(false, "groupId_1");
EcmParameters ecm_params;
SetTestConfig1(&ecm_params);
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
CheckECMOutput(ecm_string, kFakeCasEncryptionResponseGroupKeyId,
kFakeCasEncryptionResponseGroupKeyData, false);
}
TEST_F(EcmGeneratorTest, Generate2NoRotation) {
PrepareEcmGenerator(false);
PrepareEcmGenerator(false, /*group_id=*/"");
EcmParameters ecm_params;
SetTestConfig1(&ecm_params);
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
ecm_string = ecm_gen_.GenerateEcm(ecm_params);
ASSERT_EQ(77, ecm_string.size());
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
EXPECT_EQ('\x02', ecm_string[2]); // ECM version
EXPECT_EQ('\x02', ecm_string[3]); // flags
EXPECT_EQ('\x80', ecm_string[4]); // flags
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16));
// Key data has been wrapped (encrypted).
EXPECT_NE(kEcmKeyDataSingle, ecm_string.substr(37, 16));
EXPECT_EQ(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16));
// Unwrap key and compare with original.
std::string wrapping_key = kFakeCasEncryptionResponseKeyData;
std::string wrapping_iv = ecm_string.substr(53, 16);
std::string wrapped_key = ecm_string.substr(37, 16);
std::string unwrapped_key =
crypto_util::DecryptAesCbcNoPad(wrapping_key, wrapping_iv, wrapped_key);
EXPECT_EQ(kEcmKeyDataSingle, unwrapped_key);
EXPECT_EQ(kEcmContentIvSingle, ecm_string.substr(69, 8));
CheckECMOutput(ecm_string, kFakeCasEncryptionResponseKeyId,
kFakeCasEncryptionResponseKeyData, false);
}
TEST_F(EcmGeneratorTest, InitializeSimpleRotation) {
EXPECT_FALSE(ecm_gen_.initialized());
EcmParameters ecm_params;
std::vector<EntitledKeyInfo> keys;
PrepareEcmGenerator(true);
PrepareEcmGenerator(true, /*group_id=*/"");
SetTestConfig2(&ecm_params);
ecm_init_params_.key_rotation_enabled = true;
Status status = ProcessEcmParameters(ecm_params, &keys);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(ecm_gen_.initialized());
EXPECT_TRUE(ecm_gen_.rotation_enabled());
@@ -289,50 +331,20 @@ TEST_F(EcmGeneratorTest, InitializeSimpleRotation) {
TEST_F(EcmGeneratorTest, GenerateSimpleRotation) {
EcmParameters ecm_params;
PrepareEcmGenerator(true);
PrepareEcmGenerator(true, /*group_id=*/"");
SetTestConfig2(&ecm_params);
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
CheckECMOutput(ecm_string, kFakeCasEncryptionResponseKeyId,
kFakeCasEncryptionResponseKeyData, true);
}
// Expected size (bytes):
// same as no rotation case = 77
// second entitlement key ID: 16 bytes
// Second key: ID (16), Data (16), IV (16), IV (8) = 56
// total = 149
ASSERT_EQ(149, ecm_string.size());
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
EXPECT_EQ('\x02', ecm_string[2]); // ECM version
EXPECT_EQ('\x03', ecm_string[3]); // flags
EXPECT_EQ('\x80', ecm_string[4]); // flags
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
EXPECT_EQ(kEcmKeyIdEven, ecm_string.substr(21, 16));
// Key data has been wrapped (encrypted).
EXPECT_NE(kEcmKeyDataEven, ecm_string.substr(37, 16));
EXPECT_EQ(kEcmWrappedKeyIvEven, ecm_string.substr(53, 16));
EXPECT_EQ(kEcmContentIvEven, ecm_string.substr(69, 8));
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(77, 16));
EXPECT_EQ(kEcmKeyIdOdd, ecm_string.substr(93, 16));
// Key data has been wrapped (encrypted).
EXPECT_NE(kEcmKeyDataOdd, ecm_string.substr(109, 16));
EXPECT_EQ(kEcmWrappedKeyIvOdd, ecm_string.substr(125, 16));
EXPECT_EQ(kEcmContentIvOdd, ecm_string.substr(141, 8));
// Unwrap even key and compare with original.
std::string wrapping_key_even = kFakeCasEncryptionResponseKeyData;
std::string wrapping_iv_even = ecm_string.substr(53, 16);
std::string wrapped_key_even = ecm_string.substr(37, 16);
std::string unwrapped_key_even = crypto_util::DecryptAesCbcNoPad(
wrapping_key_even, wrapping_iv_even, wrapped_key_even);
EXPECT_EQ(kEcmKeyDataEven, unwrapped_key_even);
// Unwrap odd key and compare with original.
std::string wrapping_key_odd = kFakeCasEncryptionResponseKeyData;
std::string wrapping_iv_odd = ecm_string.substr(125, 16);
std::string wrapped_key_odd = ecm_string.substr(109, 16);
std::string unwrapped_key_odd = crypto_util::DecryptAesCbcNoPad(
wrapping_key_odd, wrapping_iv_odd, wrapped_key_odd);
EXPECT_EQ(kEcmKeyDataOdd, unwrapped_key_odd);
TEST_F(EcmGeneratorTest, GenerateSimpleRotationWithGroupId) {
EcmParameters ecm_params;
PrepareEcmGenerator(true, "groupId_1");
SetTestConfig2(&ecm_params);
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
CheckECMOutput(ecm_string, kFakeCasEncryptionResponseGroupKeyId,
kFakeCasEncryptionResponseGroupKeyData, true);
}
} // namespace cas

View File

@@ -24,6 +24,9 @@
// CA System ID for Widevine.
static constexpr uint16_t kWidevineSystemId = 0x4AD4;
// New CA System ID range for Widevine, all inclusive.
static constexpr uint16_t kWidevineNewSystemIdLowerBound = 0x56C0;
static constexpr uint16_t kWidevineNewSystemIdUpperBound = 0x56C9;
// 'section_TSpkt_flag' defines the format of the ECM.
// We only support MPEG-2 transport stream packet format for now.
// We do NOT support MPEG-2 section format yet.
@@ -500,7 +503,10 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
}
// The super_cas_id is a 32-bit identifier formed by the concatenation of the
// CA_system_id (16 bit) and the CA_subsystem_id (16 bit).
if ((params.super_cas_id >> 16) != kWidevineSystemId) {
uint16_t cas_id = params.super_cas_id >> 16;
if (cas_id != kWidevineSystemId &&
(cas_id < kWidevineNewSystemIdLowerBound ||
cas_id > kWidevineNewSystemIdUpperBound)) {
BuildChannelError(params.ecm_channel_id, UNKNOWN_SUPER_CAS_ID_VALUE, "",
response, response_length);
return;

View File

@@ -15,7 +15,7 @@
#include <vector>
#include <cstdint>
#include "absl/container/node_hash_map.h"
#include "absl/container/flat_hash_map.h"
#include "absl/types/optional.h"
#include "common/status.h"
#include "media_cas_packager_sdk/internal/ecm.h"
@@ -141,7 +141,7 @@ class EcmgClientHandler {
std::vector<std::string> content_ivs_;
// Map from ECM_stream_id to EcmgStreamInfo.
absl::node_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_;
absl::flat_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_;
};
} // namespace cas

View File

@@ -34,7 +34,10 @@ using simulcrypt_util::AddUint8Param;
using simulcrypt_util::BuildMessageHeader;
constexpr size_t kBufferSize = 1024;
constexpr size_t kSuperCasId = 0x4AD40000;
constexpr uint16_t kWidevineSystemId = 0x4AD4;
constexpr uint32_t kSuperCasId = kWidevineSystemId << 16;
constexpr uint16_t kWidevineNewSystemIdLowerBound = 0x56C0;
constexpr uint16_t kWidevineNewSystemIdUpperBound = 0x56C9;
constexpr size_t kChannelId = 1;
constexpr size_t kStreamId = 1;
constexpr size_t kEcmId = 2;
@@ -605,6 +608,38 @@ TEST_F(EcmgClientHandlerTest, BuildEcmDatagramSequenceOfEvenOdd) {
EXPECT_NE(std::string(response_, response_len_), first_response);
}
class CasIdTest
: public EcmgClientHandlerTest,
public ::testing::WithParamInterface<::testing::tuple<int, bool>> {};
TEST_P(CasIdTest, ValidateCasIds) {
uint32_t super_cas_id = ::testing::get<0>(GetParam()) << 16;
bool is_valid_cas_id = ::testing::get<1>(GetParam());
BuildChannelSetupRequest(kChannelId, super_cas_id, kAgeRestriction,
kCryptoMode, request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
if (is_valid_cas_id) {
EXPECT_EQ(response_len_, sizeof(kTestEcmgChannelStatus));
} else {
EXPECT_EQ(response_len_, sizeof(kTestEcmgChannelError));
}
}
INSTANTIATE_TEST_SUITE_P(
ChannelSetupWithNewCasId, CasIdTest,
::testing::Combine(
::testing::Range(static_cast<int>(kWidevineNewSystemIdLowerBound),
static_cast<int>(kWidevineNewSystemIdUpperBound) + 1),
::testing::Values(true)));
INSTANTIATE_TEST_SUITE_P(
ChannelSetupWithInvalidCasId, CasIdTest,
::testing::Combine(::testing::Values(0, kWidevineSystemId - 1,
kWidevineSystemId + 1,
kWidevineNewSystemIdLowerBound - 1,
kWidevineNewSystemIdUpperBound + 1),
::testing::Values(false)));
} // namespace
} // namespace cas
} // namespace widevine

View File

@@ -12,14 +12,18 @@
#include "glog/logging.h"
#include "absl/strings/str_cat.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "common/ec_key.h"
#include "common/hash_algorithm.h"
#include "common/status.h"
#include "common/string_util.h"
#include "media_cas_packager_sdk/internal/util.h"
namespace widevine {
namespace cas {
namespace {
constexpr int kNumBitsVersionField = 8;
constexpr int kNumBitsHeaderLengthField = 8;
constexpr int kNumBitsTimestampLengthField = 64;
@@ -29,13 +33,27 @@ constexpr int kNumBitsPayloadLengthField = 16;
constexpr uint8_t kEmmVersion = 1;
} // namespace
Status Emm::SetPrivateSigningKey(const std::string& private_signing_key) {
if (private_signing_key.empty()) {
return {error::INVALID_ARGUMENT, "private_signing_key must not be empty."};
}
private_signing_key_ = private_signing_key;
return OkStatus();
}
Status Emm::SetFingerprinting(
const std::vector<FingerprintingInitParameters>& fingerprintings) {
// First clear all current fingerprinting payload.
emm_payload_.clear_fingerprinting();
// TODO(b/161149665): validate passed in data.
Status status;
for (const auto& fingerprinting : fingerprintings) {
status = ValidateFingerprintingInitParameters(fingerprinting);
if (!status.ok()) {
return status;
}
}
for (const auto& fingerprinting_param : fingerprintings) {
Fingerprinting* fingerprinting_payload = emm_payload_.add_fingerprinting();
for (const auto& channel : fingerprinting_param.channels) {
@@ -51,8 +69,14 @@ Status Emm::SetServiceBlocking(
// First clear all current service blocking payload.
emm_payload_.clear_service_blocking();
// TODO(b/161149665): validate passed in data.
Status status;
for (const auto& service_blocking : service_blockings) {
status = ValidateServiceBlockingInitParameters(service_blocking);
if (!status.ok()) {
return status;
}
}
for (const auto& service_blocking_param : service_blockings) {
ServiceBlocking* service_blocking_payload =
emm_payload_.add_service_blocking();
@@ -78,7 +102,7 @@ Status Emm::GenerateEmm(std::string* serialized_emm) const {
EmmSerializingParameters serializing_params;
serializing_params.payload = emm_payload_.SerializeAsString();
serializing_params.timestamp = GenerateTimestamp();
serializing_params.timestamp = GenerateTimestampEpochSeconds();
// Generate serialized emm (without signature yet).
Status status =
@@ -88,7 +112,47 @@ Status Emm::GenerateEmm(std::string* serialized_emm) const {
}
// Calculate and append signature.
absl::StrAppend(serialized_emm, GenerateSignature(*serialized_emm));
std::string signature;
status = GenerateSignature(*serialized_emm, &signature);
if (!status.ok()) {
return status;
}
absl::StrAppend(serialized_emm, signature);
return OkStatus();
}
Status Emm::GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter,
uint8_t* packet, ssize_t* packet_size) const {
if (continuity_counter == nullptr || packet == nullptr ||
packet_size == nullptr) {
return {error::INVALID_ARGUMENT,
"continuity_counter, packet and packet_size must not be null"};
}
std::string serialized_emm;
Status status = GenerateEmm(&serialized_emm);
if (!status.ok()) {
return status;
}
if (*packet_size < static_cast<ssize_t>(CalculateTsBufferSizeForEcmEmm(
serialized_emm.size()))) {
return {error::INVALID_ARGUMENT, "packet buffer is too small"};
}
ssize_t bytes_modified = 0;
status = InsertEmmAsTsPacket(serialized_emm, pid, continuity_counter, packet,
&bytes_modified);
if (!status.ok() || bytes_modified <= 0 ||
bytes_modified % kTsPacketSize != 0) {
LOG(ERROR) << "Failed to generate TS packet: "
<< (status.ok()
? absl::StrCat("Unexpected bytes_modified value ",
bytes_modified)
: status.error_message());
return {error::INTERNAL, "Failed to generate TS packet"};
}
*packet_size = bytes_modified;
return OkStatus();
}
@@ -121,15 +185,76 @@ Status Emm::GenerateSerializedEmmNoSignature(
return OkStatus();
}
int64_t Emm::GenerateTimestamp() const {
// TODO(b/161252065): Generate timestamp.
return 0;
int64_t Emm::GenerateTimestampEpochSeconds() const {
return absl::ToUnixSeconds(absl::Now());
}
std::string Emm::GenerateSignature(const std::string& content) const {
// TODO(b/161252442): Calculate signature.
std::string signature(32, 'x'); // A fake 32 bytes signature.
return signature;
Status Emm::GenerateSignature(const std::string& message,
std::string* signature) const {
DCHECK(signature);
if (message.empty()) {
return {error::INVALID_ARGUMENT, "Input message must not be empty."};
}
if (private_signing_key_.empty()) {
return {error::NOT_FOUND, "Private signing key is not set."};
}
std::unique_ptr<ECPrivateKey> ec_private_key(
ECPrivateKey::Create(private_signing_key_));
if (ec_private_key == nullptr) {
return Status(error::INTERNAL, "Failed to construct a ECPrivateKey.");
}
if (!ec_private_key->GenerateSignature(message, HashAlgorithm::kSha256,
signature)) {
return Status(error::INTERNAL, "Failed to generate signature.");
}
if (signature->empty()) {
return Status(error::INTERNAL, "Computed signature is empty.");
}
return OkStatus();
}
Status Emm::ValidateFingerprintingInitParameters(
const FingerprintingInitParameters& fingerprinting) const {
if (fingerprinting.channels.empty()) {
return {error::INVALID_ARGUMENT, "channels are missing."};
}
if (fingerprinting.control.empty()) {
return {error::INVALID_ARGUMENT, "control is missing."};
}
for (const auto& channel : fingerprinting.channels) {
if (channel.empty()) {
return {error::INVALID_ARGUMENT, "empty channel is observed."};
}
}
return OkStatus();
}
Status Emm::ValidateServiceBlockingInitParameters(
const ServiceBlockingInitParameters& service_blocking) const {
if (service_blocking.channels.empty()) {
return {error::INVALID_ARGUMENT, "channels are missing."};
}
if (service_blocking.device_groups.empty()) {
return {error::INVALID_ARGUMENT, "device_groups are missing."};
}
for (const auto& channel : service_blocking.channels) {
if (channel.empty()) {
return {error::INVALID_ARGUMENT, "empty channel is observed."};
}
}
for (const auto& device_group : service_blocking.device_groups) {
if (device_group.empty()) {
return {error::INVALID_ARGUMENT, "empty device_group is observed."};
}
}
if (service_blocking.end_time == 0) {
return {error::INVALID_ARGUMENT, "end_time must be specified"};
}
if (service_blocking.end_time <= service_blocking.start_time) {
return {error::INVALID_ARGUMENT, "end_time must be after start_time"};
}
return OkStatus();
}
} // namespace cas

View File

@@ -27,14 +27,15 @@ struct FingerprintingInitParameters {
struct ServiceBlockingInitParameters {
std::vector<std::string> channels;
std::vector<std::string> device_groups;
// Value 0 in start_time means immediate.
// Epoch time in seconds. Value 0 in start_time means immediate.
int64_t start_time = 0;
int64_t end_time;
// Epoch time in seconds.
int64_t end_time = 0;
};
// Generator for producing Widevine CAS-compliant EMMs. Used to construct the
// Transport Stream packet payload of an EMM containing messages including
// fingerprinting and service blocking.
// Generator for producing Widevine CAS-compliant EMMs (Entitlement Management
// Messages). Used to construct the TS (Transport Stream) packet payload of an
// EMM containing messages including fingerprinting and service blocking.
// Class Emm is not thread safe.
class Emm {
public:
@@ -43,16 +44,34 @@ class Emm {
Emm& operator=(const Emm&) = delete;
virtual ~Emm() = default;
// Sets the signing key used to generate signature field in EMM.
// Must be called before calling GenerateEmm() or GenerateEmmTsPackets().
virtual Status SetPrivateSigningKey(const std::string& private_signing_key);
// Replaces current fingerprinting info with |fingerprintings|.
Status SetFingerprinting(
virtual Status SetFingerprinting(
const std::vector<FingerprintingInitParameters>& fingerprintings);
// Replaces current service blocking info with |service_blockings|.
Status SetServiceBlocking(
virtual Status SetServiceBlocking(
const std::vector<ServiceBlockingInitParameters>& service_blockings);
// Generates serialized EMM to |serialized_emm|.
Status GenerateEmm(std::string* serialized_emm) const;
virtual Status GenerateEmm(std::string* serialized_emm) const;
// Generates serialized EMM and wraps it in TS (transport stream) packets.
// Args (all pointer parameters must be not nullptr):
// - |pid| program ID for the EMM stream
// - |continuity_counter| continuity_counter for the EMM packet,
// it will be incremented, only last 4 bits are used
// - |packet| a buffer to be used to return the generated TS packet. Must be
// able to hold the maximum possible size of genereated Ts packets
// (kMaxPossibleTsPacketsSizeBytes = 1128 bytes).
// - |packet_size| is the size of the allocated |packet|. It will be updated
// as the number of bytes actually used.
virtual Status GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter,
uint8_t* packet,
ssize_t* packet_size) const;
private:
struct EmmSerializingParameters {
@@ -63,10 +82,17 @@ class Emm {
Status GenerateSerializedEmmNoSignature(
const EmmSerializingParameters& params,
std::string* serialized_emm) const;
int64_t GenerateTimestamp() const;
std::string GenerateSignature(const std::string& content) const;
virtual int64_t GenerateTimestampEpochSeconds() const;
virtual Status GenerateSignature(const std::string& message,
std::string* signature) const;
Status ValidateFingerprintingInitParameters(
const FingerprintingInitParameters& fingerprinting) const;
Status ValidateServiceBlockingInitParameters(
const ServiceBlockingInitParameters& service_blocking) const;
EmmPayload emm_payload_;
std::string private_signing_key_;
};
} // namespace cas

View File

@@ -8,9 +8,26 @@
#include "media_cas_packager_sdk/internal/emm.h"
#include <string>
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/memory/memory.h"
#include "absl/strings/escaping.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "common/ec_key.h"
#include "common/ec_test_keys.h"
#include "common/hash_algorithm.h"
#include "common/status.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
using ::testing::DoAll;
using ::testing::ElementsAreArray;
using ::testing::HasSubstr;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace widevine {
namespace cas {
@@ -22,19 +39,91 @@ constexpr char kChannelThree[] = "CH3";
constexpr char kFingerprintingControl[] = "controls";
constexpr char kDeviceGroupOne[] = "Group1";
constexpr char kDeviceGroupTwo[] = "Group2";
constexpr int64_t kServiceBockingStartTime = 100;
constexpr int64_t kServiceBockingEndTime = 1000;
constexpr int64_t kServiceBockingStartTime = 1597342393;
constexpr int64_t kServiceBockingEndTime = 1597342394;
constexpr uint16_t kTestPid = 1;
constexpr int64_t kTestTimestamp = 1597882875;
// Hex std::string of kTestTimestamp.
constexpr char kTestTimestampHexString[] = "000000005f3dc1fb";
// Length in bytes when there is no payload.
constexpr uint8_t kExpectedNoPayloadLengthBytes = 44;
constexpr uint8_t kExpectedEmmVersion = 1;
constexpr uint8_t kExpectedHeaderLength = 8;
constexpr uint8_t kVersionStartIndex = 0;
constexpr uint8_t kHeaderLengthStartIndex = 1;
constexpr uint8_t kPayloadLengthStartIndex = 10;
constexpr uint8_t kPayloadStartIndex = 12;
constexpr uint8_t kSignatureLength = 32;
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',
// Section header.
'\x00', '\x82', '\x70', '\x07',
// Mocked EMM payload ("abcdefg").
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67',
// 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', '\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'};
FingerprintingInitParameters GetValidFingerprintingParams() {
FingerprintingInitParameters fingerprinting_params;
@@ -68,25 +157,86 @@ void LoadExpectedServiceBlockingProto(
service_blocking_payload->set_end_time_sec(kServiceBockingEndTime);
}
TEST(EmmTest, GenerateEmmSinglePayloadSuccess) {
Emm emm_gen;
class FakeEmmWithFakeSignature : public Emm {
private:
// Generates a fake signature.
Status GenerateSignature(const std::string& message,
std::string* signature) const override {
signature->assign(kSignatureLength, kFakeSignatureFiller);
return OkStatus();
}
};
// Verifies a timestamp can be generated and inserted into EMM.
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));
}
class FakeEmm : public Emm {
private:
// Generates a fake timestamp.
int64_t GenerateTimestampEpochSeconds() const override {
return kTestTimestamp;
}
// Generates a fake signature.
Status GenerateSignature(const std::string& message,
std::string* signature) const override {
signature->assign(kSignatureLength, kFakeSignatureFiller);
return OkStatus();
}
};
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_;
};
// Verifies GenerateEmm is successful with single fingerprinting and
// service_blocking information in the payload.
TEST_F(EmmTest, GenerateEmmSinglePayloadSuccess) {
FingerprintingInitParameters fingerprinting = GetValidFingerprintingParams();
ServiceBlockingInitParameters service_blocking =
GetValidServiceBlockingParams();
EXPECT_EQ(emm_gen.SetFingerprinting({fingerprinting}), OkStatus());
EXPECT_EQ(emm_gen.SetServiceBlocking({service_blocking}), OkStatus());
EXPECT_OK(emm_->SetFingerprinting({fingerprinting}));
EXPECT_OK(emm_->SetServiceBlocking({service_blocking}));
std::string result;
EXPECT_EQ(emm_gen.GenerateEmm(&result), OkStatus());
EXPECT_OK(emm_->GenerateEmm(&result));
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
VerifiyEmmHeader(result);
EXPECT_EQ(static_cast<uint8_t>(result[kVersionStartIndex]),
kExpectedEmmVersion);
EXPECT_EQ(static_cast<uint8_t>(result[kHeaderLengthStartIndex]),
kExpectedHeaderLength);
int payload_lengh = static_cast<uint16_t>(result[kPayloadLengthStartIndex])
<< 8 |
static_cast<uint8_t>(result[kPayloadLengthStartIndex + 1]);
int payload_lengh = GetPayloadLength(result);
ASSERT_GT(payload_lengh, 0);
ASSERT_EQ(result.length(),
kPayloadStartIndex + payload_lengh + kSignatureLength);
@@ -103,37 +253,33 @@ TEST(EmmTest, GenerateEmmSinglePayloadSuccess) {
std::string serialized_expected_payload;
expected_payload.SerializeToString(&serialized_expected_payload);
EXPECT_EQ(payload_section, serialized_expected_payload);
VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh));
}
TEST(EmmTest, GenerateEmmMultiplePayloadSuccess) {
Emm emm_gen;
// Verifies GenerateEmm is successful with multiple fingerprinting and
// service_blocking information in the payload.
TEST_F(EmmTest, GenerateEmmMultiplePayloadSuccess) {
FingerprintingInitParameters fingerprinting_params;
fingerprinting_params.channels = {kChannelThree};
fingerprinting_params.control = kFingerprintingControl;
EXPECT_EQ(emm_gen.SetFingerprinting(
{GetValidFingerprintingParams(), fingerprinting_params}),
OkStatus());
EXPECT_OK(emm_->SetFingerprinting(
{GetValidFingerprintingParams(), fingerprinting_params}));
ServiceBlockingInitParameters service_blocking_params;
service_blocking_params.channels = {kChannelThree};
service_blocking_params.device_groups = {kDeviceGroupOne, kDeviceGroupTwo};
service_blocking_params.start_time = kServiceBockingStartTime;
service_blocking_params.end_time = kServiceBockingEndTime;
EXPECT_EQ(emm_gen.SetServiceBlocking(
{GetValidServiceBlockingParams(), service_blocking_params}),
OkStatus());
EXPECT_OK(emm_->SetServiceBlocking(
{GetValidServiceBlockingParams(), service_blocking_params}));
std::string result;
EXPECT_EQ(emm_gen.GenerateEmm(&result), OkStatus());
EXPECT_OK(emm_->GenerateEmm(&result));
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
VerifiyEmmHeader(result);
EXPECT_EQ(static_cast<uint8_t>(result[kVersionStartIndex]),
kExpectedEmmVersion);
EXPECT_EQ(static_cast<uint8_t>(result[kHeaderLengthStartIndex]),
kExpectedHeaderLength);
int payload_lengh = static_cast<uint16_t>(result[kPayloadLengthStartIndex])
<< 8 |
static_cast<uint8_t>(result[kPayloadLengthStartIndex + 1]);
int payload_lengh = GetPayloadLength(result);
ASSERT_GT(payload_lengh, 0);
ASSERT_EQ(result.length(),
kPayloadStartIndex + payload_lengh + kSignatureLength);
@@ -145,26 +291,24 @@ TEST(EmmTest, GenerateEmmMultiplePayloadSuccess) {
ASSERT_TRUE(emm_payload.ParseFromString(payload_section));
EXPECT_EQ(emm_payload.fingerprinting_size(), 2);
EXPECT_EQ(emm_payload.service_blocking_size(), 2);
VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh));
}
TEST(EmmTest, GenerateEmmFingerprintingOnlySuccess) {
Emm emm_gen;
// Verifies GenerateEmm is successful with only fingerprinting information in
// the payload.
TEST_F(EmmTest, GenerateEmmFingerprintingOnlySuccess) {
FingerprintingInitParameters fingerprinting = GetValidFingerprintingParams();
EXPECT_EQ(emm_gen.SetFingerprinting({fingerprinting}), OkStatus());
EXPECT_OK(emm_->SetFingerprinting({fingerprinting}));
// OK to be called again.
EXPECT_EQ(emm_gen.SetFingerprinting({fingerprinting}), OkStatus());
EXPECT_OK(emm_->SetFingerprinting({fingerprinting}));
std::string result;
EXPECT_EQ(emm_gen.GenerateEmm(&result), OkStatus());
EXPECT_OK(emm_->GenerateEmm(&result));
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
VerifiyEmmHeader(result);
EXPECT_EQ(static_cast<uint8_t>(result[kVersionStartIndex]),
kExpectedEmmVersion);
EXPECT_EQ(static_cast<uint8_t>(result[kHeaderLengthStartIndex]),
kExpectedHeaderLength);
int payload_lengh = static_cast<uint16_t>(result[kPayloadLengthStartIndex])
<< 8 |
static_cast<uint8_t>(result[kPayloadLengthStartIndex + 1]);
int payload_lengh = GetPayloadLength(result);
ASSERT_GT(payload_lengh, 0);
ASSERT_EQ(result.length(),
kPayloadStartIndex + payload_lengh + kSignatureLength);
@@ -179,25 +323,23 @@ TEST(EmmTest, GenerateEmmFingerprintingOnlySuccess) {
std::string serialized_expected_payload;
expected_payload.SerializeToString(&serialized_expected_payload);
EXPECT_EQ(payload_section, serialized_expected_payload);
VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh));
}
TEST(EmmTest, GenerateEmmServiceBlockingOnlySuccess) {
Emm emm_gen;
// Verifies GenerateEmm is successful with only service_blocking information in
// the payload.
TEST_F(EmmTest, GenerateEmmServiceBlockingOnlySuccess) {
ServiceBlockingInitParameters service_blocking =
GetValidServiceBlockingParams();
EXPECT_EQ(emm_gen.SetServiceBlocking({service_blocking}), OkStatus());
EXPECT_OK(emm_->SetServiceBlocking({service_blocking}));
std::string result;
EXPECT_EQ(emm_gen.GenerateEmm(&result), OkStatus());
EXPECT_OK(emm_->GenerateEmm(&result));
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
VerifiyEmmHeader(result);
EXPECT_EQ(static_cast<uint8_t>(result[kVersionStartIndex]),
kExpectedEmmVersion);
EXPECT_EQ(static_cast<uint8_t>(result[kHeaderLengthStartIndex]),
kExpectedHeaderLength);
int payload_lengh = static_cast<uint16_t>(result[kPayloadLengthStartIndex])
<< 8 |
static_cast<uint8_t>(result[kPayloadLengthStartIndex + 1]);
int payload_lengh = GetPayloadLength(result);
ASSERT_GT(payload_lengh, 0);
ASSERT_EQ(result.length(),
kPayloadStartIndex + payload_lengh + kSignatureLength);
@@ -212,23 +354,246 @@ TEST(EmmTest, GenerateEmmServiceBlockingOnlySuccess) {
std::string serialized_expected_payload;
expected_payload.SerializeToString(&serialized_expected_payload);
EXPECT_EQ(payload_section, serialized_expected_payload);
VerifiySignature(result.substr(kPayloadStartIndex + payload_lengh));
}
TEST(EmmTest, GenerateEmmNoPayloadSuccess) {
Emm emm_gen;
// Verifies GenerateEmm is successful with empty payload.
TEST_F(EmmTest, GenerateEmmNoPayloadSuccess) {
std::string result;
EXPECT_EQ(emm_gen.GenerateEmm(&result), OkStatus());
EXPECT_OK(emm_->GenerateEmm(&result));
EXPECT_EQ(result.length(), kExpectedNoPayloadLengthBytes);
VerifiyEmmHeader(result);
EXPECT_EQ(static_cast<uint8_t>(result[kVersionStartIndex]),
kExpectedEmmVersion);
EXPECT_EQ(static_cast<uint8_t>(result[kHeaderLengthStartIndex]),
kExpectedHeaderLength);
int payload_lengh = static_cast<uint16_t>(result[kPayloadLengthStartIndex])
<< 8 |
static_cast<uint8_t>(result[kPayloadLengthStartIndex + 1]);
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 packet_size = kTsPacketSize;
EXPECT_OK(
emm_->GenerateEmmTsPackets(kTestPid, &counter, packet, &packet_size));
EXPECT_THAT(packet, ElementsAreArray(kExpectedEmptyEmmPacket));
EXPECT_EQ(packet_size, kTsPacketSize);
}
// Verifies GenerateSignature is successful with empty payload.
TEST(GenerateSignatureTest, GenerateSignatureSuccess) {
Emm emm;
std::string emm_generated;
ECTestKeys test_keys;
// Generates signature for empty EMM payload.
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());
// 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));
}
// Verifies GenerateEmm fails with no singing key set.
TEST(GenerateSignatureTest, SignningKeyNotSetFail) {
Emm emm;
std::string emm_generated;
Status status = emm.GenerateEmm(&emm_generated);
EXPECT_EQ(status.error_code(), error::NOT_FOUND);
EXPECT_THAT(status.error_message(), HasSubstr("signing key is not set"));
}
// Verifies GenerateEmm fails with empty singing key set.
TEST(GenerateSignatureTest, SetPrivateSigningKeyEmptyKeyFail) {
Emm emm;
EXPECT_EQ(emm.SetPrivateSigningKey("").error_code(), error::INVALID_ARGUMENT);
}
// Verifies GenerateEmm fails with invalid singing key set.
TEST(GenerateSignatureTest, InvalidSigningKeyFail) {
Emm emm;
EXPECT_OK(emm.SetPrivateSigningKey("signing key"));
std::string emm_generated;
Status status = emm.GenerateEmm(&emm_generated);
EXPECT_EQ(status.error_code(), error::INTERNAL);
EXPECT_THAT(status.error_message(),
HasSubstr("Failed to construct a ECPrivateKey"));
}
// Overrides GenerateEmm() with mocked method for easier test of
// GenerateEmmTsPacketsTest.
class MockEmmGenerate : public Emm {
public:
MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm),
(const, override));
};
// Verifies GenerateEmmTsPackets is successful with mocked EMM.
TEST(GenerateEmmTsPacketsTest, MockPayloadSuccess) {
MockEmmGenerate emm_gen;
std::string serialized_emm = "abcdefg";
EXPECT_CALL(emm_gen, GenerateEmm(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(serialized_emm), Return(OkStatus())));
uint8_t counter = 0;
uint8_t packet[kTsPacketSize];
ssize_t packet_size = kTsPacketSize;
EXPECT_OK(
emm_gen.GenerateEmmTsPackets(kTestPid, &counter, packet, &packet_size));
EXPECT_THAT(packet, ElementsAreArray(kExpectedMockEmmPacket));
EXPECT_EQ(packet_size, kTsPacketSize);
}
// Verifies GenerateEmmTsPackets fails if the given continuity counter is null.
TEST(GenerateEmmTsPacketsTest, NullContinuityCounterPointerFail) {
Emm emm_gen;
uint8_t packet[kTsPacketSize];
ssize_t packet_size = kTsPacketSize;
EXPECT_EQ(
emm_gen.GenerateEmmTsPackets(kTestPid, nullptr, packet, &packet_size)
.error_code(),
error::INVALID_ARGUMENT);
}
// Verifies GenerateEmmTsPackets fails if the given packet buffer is null.
TEST(GenerateEmmTsPacketsTest, NullPacketBufferFail) {
Emm emm_gen;
uint8_t counter = 0;
uint8_t packet[kTsPacketSize];
ssize_t packet_size = kTsPacketSize;
EXPECT_EQ(
emm_gen.GenerateEmmTsPackets(kTestPid, &counter, nullptr, &packet_size)
.error_code(),
error::INVALID_ARGUMENT);
EXPECT_EQ(emm_gen.GenerateEmmTsPackets(kTestPid, &counter, packet, nullptr)
.error_code(),
error::INVALID_ARGUMENT);
}
// Verifies GenerateEmmTsPackets fails if the given buffer is too small.
TEST(GenerateEmmTsPacketsTest, PacketBufferTooSmallFail) {
MockEmmGenerate emm_gen;
std::string serialized_emm = "abcdefg";
EXPECT_CALL(emm_gen, GenerateEmm(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(serialized_emm), Return(OkStatus())));
uint8_t counter = 0;
uint8_t packet[kTsPacketSize];
ssize_t packet_size = 1;
Status status =
emm_gen.GenerateEmmTsPackets(kTestPid, &counter, packet, &packet_size);
EXPECT_EQ(status.error_code(), error::INVALID_ARGUMENT);
EXPECT_THAT(status.error_message(), HasSubstr("buffer is too small"));
}
TEST(SetFingerprinting, ValidParametersSuccess) {
Emm emm_gen;
EXPECT_OK(emm_gen.SetFingerprinting({GetValidFingerprintingParams()}));
}
TEST(SetFingerprinting, EmptyParamsFail) {
Emm emm_gen;
FingerprintingInitParameters params;
EXPECT_EQ(emm_gen.SetFingerprinting({params}).error_code(),
error::INVALID_ARGUMENT);
}
TEST(SetFingerprinting, EmptyChannelsFail) {
Emm emm_gen;
FingerprintingInitParameters params = GetValidFingerprintingParams();
params.channels = {};
EXPECT_EQ(emm_gen.SetFingerprinting({params}).error_code(),
error::INVALID_ARGUMENT);
}
TEST(SetFingerprinting, EmptyChannelValueFail) {
Emm emm_gen;
FingerprintingInitParameters params = GetValidFingerprintingParams();
params.channels = {kChannelOne, ""};
EXPECT_EQ(emm_gen.SetFingerprinting({params}).error_code(),
error::INVALID_ARGUMENT);
}
TEST(SetFingerprinting, EmptyControlsFail) {
Emm emm_gen;
FingerprintingInitParameters params = GetValidFingerprintingParams();
params.control = "";
EXPECT_EQ(emm_gen.SetFingerprinting({params}).error_code(),
error::INVALID_ARGUMENT);
}
TEST(SetServiceBlocking, ValidParametersSuccess) {
Emm emm_gen;
EXPECT_OK(emm_gen.SetServiceBlocking({GetValidServiceBlockingParams()}));
}
TEST(SetServiceBlocking, EmptyParamsFail) {
Emm emm_gen;
ServiceBlockingInitParameters params;
EXPECT_EQ(emm_gen.SetServiceBlocking({params}).error_code(),
error::INVALID_ARGUMENT);
}
TEST(SetServiceBlocking, EmptyChannelsFail) {
Emm emm_gen;
ServiceBlockingInitParameters params = GetValidServiceBlockingParams();
params.channels = {};
EXPECT_EQ(emm_gen.SetServiceBlocking({params}).error_code(),
error::INVALID_ARGUMENT);
}
TEST(SetServiceBlocking, EmptyChannelValueFail) {
Emm emm_gen;
ServiceBlockingInitParameters params = GetValidServiceBlockingParams();
params.channels = {kChannelOne, ""};
EXPECT_EQ(emm_gen.SetServiceBlocking({params}).error_code(),
error::INVALID_ARGUMENT);
}
TEST(SetServiceBlocking, EmptyDeviceGroupsFail) {
Emm emm_gen;
ServiceBlockingInitParameters params = GetValidServiceBlockingParams();
params.device_groups = {};
EXPECT_EQ(emm_gen.SetServiceBlocking({params}).error_code(),
error::INVALID_ARGUMENT);
}
TEST(SetServiceBlocking, EmptyDeviceGroupValueFail) {
Emm emm_gen;
ServiceBlockingInitParameters params = GetValidServiceBlockingParams();
params.device_groups = {"", kDeviceGroupTwo};
EXPECT_EQ(emm_gen.SetServiceBlocking({params}).error_code(),
error::INVALID_ARGUMENT);
}
TEST(SetServiceBlocking, InvalidEndTimeFail) {
Emm emm_gen;
ServiceBlockingInitParameters params = GetValidServiceBlockingParams();
params.end_time = 0;
EXPECT_EQ(emm_gen.SetServiceBlocking({params}).error_code(),
error::INVALID_ARGUMENT);
}
TEST(SetServiceBlocking, EndTimeBeforeStartTimeFail) {
Emm emm_gen;
ServiceBlockingInitParameters params = GetValidServiceBlockingParams();
params.start_time = 10;
params.end_time = 9;
EXPECT_EQ(emm_gen.SetServiceBlocking({params}).error_code(),
error::INVALID_ARGUMENT);
}
} // namespace

View File

@@ -14,6 +14,8 @@
#include <iostream>
#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/time/clock.h"
@@ -26,17 +28,16 @@
#include "media_cas_packager_sdk/internal/util.h"
#include "protos/public/media_cas.pb.h"
// Minimum sending interval in milliseconds.
static constexpr uint16_t KMinSendIntervalMs = 2;
// Maximum number of entitlement key ids shown in private data.
static constexpr uint16_t kMaxNumOfEntitlementKeyIds = 2;
// Entitlement key id length is fixed to 16 bytes.
static constexpr uint16_t kEntitlementKeyIdLength = 16;
namespace widevine {
namespace cas {
namespace {
// Minimum sending interval in milliseconds.
constexpr uint16_t KMinSendIntervalMillis = 2;
// Data type is Emm.
constexpr uint8_t kEmmDataType = 0;
// Data type is private data.
constexpr uint8_t kPrivateDataDataType = 1;
void Print(const char* const msg, size_t msg_size) {
for (size_t i = 0; i < msg_size; i++) {
@@ -142,7 +143,8 @@ Status HandleParameters(const char* const response, size_t response_length,
} // namespace
Emmg::Emmg(EmmgConfig* emmg_config, int server_socket_fd) {
Emmg::Emmg(EmmgConfig* emmg_config, int server_socket_fd)
: emm_impl_(absl::make_unique<Emm>()) {
CHECK(emmg_config);
emmg_config_ = emmg_config;
server_socket_fd_ = server_socket_fd;
@@ -157,7 +159,7 @@ void Emmg::Start() {
for (size_t i = 0; i < emmg_config_->max_num_message; i++) {
SendDataProvision();
absl::SleepFor(std::max(absl::Milliseconds(KMinSendIntervalMs),
absl::SleepFor(std::max(absl::Milliseconds(KMinSendIntervalMillis),
absl::Milliseconds(send_interval_ms_)));
}
@@ -226,28 +228,11 @@ void Emmg::BuildStreamBwRequest() {
Status Emmg::GeneratePrivateData(
const std::string& content_provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids, uint8_t* buffer) {
DCHECK(buffer);
const std::vector<std::string>& entitlement_key_ids) {
// Generate payload.
CaDescriptorPrivateData private_data;
private_data.set_provider(content_provider);
private_data.set_content_id(content_id);
if (entitlement_key_ids.size() > kMaxNumOfEntitlementKeyIds) {
return Status(
error::INVALID_ARGUMENT,
absl::StrCat("Number of entitlement key ids shouldn't exceed ",
kMaxNumOfEntitlementKeyIds));
}
for (const auto& entitlement_key_id : entitlement_key_ids) {
if (entitlement_key_id.size() != kEntitlementKeyIdLength) {
return Status(
error::INVALID_ARGUMENT,
absl::StrCat("Entitlement key id length must be ",
kEntitlementKeyIdLength, ". The offending key id is ",
entitlement_key_id));
}
}
for (const auto& entitlement_key_id : entitlement_key_ids) {
private_data.add_entitlement_key_ids(entitlement_key_id);
}
@@ -270,7 +255,45 @@ Status Emmg::GeneratePrivateData(
LOG(ERROR) << status.ToString();
return status;
}
memcpy(buffer, ecm_ts_packet.data(), ecm_ts_packet.size());
uint8_t datagram[kTsPacketSize];
memcpy(datagram, ecm_ts_packet.data(), ecm_ts_packet.size());
simulcrypt_util::AddParam(EMMG_DATAGRAM, datagram, kTsPacketSize, request_,
&request_length_);
return OkStatus();
}
Status Emmg::GenerateEmmData() {
Status status;
if (!has_configured_emm_impl_) {
status = emm_impl_->SetPrivateSigningKey(
absl::HexStringToBytes(emmg_config_->ecc_signing_key));
if (!status.ok()) {
return status;
}
status = emm_impl_->SetFingerprinting(emmg_config_->fingerprinting);
if (!status.ok()) {
return status;
}
status = emm_impl_->SetServiceBlocking(emmg_config_->service_blocking);
if (!status.ok()) {
return status;
}
has_configured_emm_impl_ = true;
}
uint8_t datagram[kMaxPossibleTsPacketsSizeBytes];
ssize_t datagram_size = kMaxPossibleTsPacketsSizeBytes;
status = emm_impl_->GenerateEmmTsPackets(
/*pid=*/0, &continuity_counter_, datagram, &datagram_size);
if (!status.ok()) {
return status;
}
if (datagram_size <= 0 || datagram_size % kTsPacketSize != 0) {
return {error::INTERNAL, "Failed to generate EMM TS packet"};
}
simulcrypt_util::AddParam(EMMG_DATAGRAM, datagram, datagram_size, request_,
&request_length_);
return OkStatus();
}
@@ -291,16 +314,26 @@ void Emmg::BuildDataProvision() {
simulcrypt_util::AddUint16Param(EMMG_DATA_ID, emmg_config_->data_id, request_,
&request_length_);
uint8_t datagram[kTsPacketSize];
Status status = GeneratePrivateData(
emmg_config_->content_provider, emmg_config_->content_id,
emmg_config_->entitlement_key_ids, datagram);
Status status;
// Generate and load datagram to |request_| message based on specified
// data_type.
switch (emmg_config_->data_type) {
case kEmmDataType:
status = GenerateEmmData();
break;
case kPrivateDataDataType:
status = GeneratePrivateData(emmg_config_->content_provider,
emmg_config_->content_id,
emmg_config_->entitlement_key_ids);
break;
default:
LOG(ERROR) << "Unexpected data type: " << emmg_config_->data_type;
return;
}
if (!status.ok()) {
LOG(ERROR) << "Fail to generate private data. " << status.ToString();
LOG(ERROR) << "Fail to generate datagram. " << status.ToString();
return;
}
simulcrypt_util::AddParam(EMMG_DATAGRAM, datagram, kTsPacketSize, request_,
&request_length_);
uint16_t total_param_length = request_length_ - 5;
Host16ToBigEndian(request_ + 3, &total_param_length);
@@ -413,7 +446,7 @@ void Emmg::UpdateSendInterval(uint16_t bandwidth_kbps) {
return;
}
send_interval_ms_ =
std::max(KMinSendIntervalMs,
std::max(KMinSendIntervalMillis,
static_cast<uint16_t>((kTsPacketSize * 8) / bandwidth_kbps));
LOG(INFO) << "Send interval set to " << send_interval_ms_ << " ms.";
}

View File

@@ -15,8 +15,9 @@
#include <cstdint>
#include "common/status.h"
#include "media_cas_packager_sdk/internal/emm.h"
#define BUFFER_SIZE (1024)
#define BUFFER_SIZE (2048)
namespace widevine {
namespace cas {
@@ -34,6 +35,9 @@ struct EmmgConfig {
std::vector<std::string> entitlement_key_ids;
uint16_t bandwidth;
uint32_t max_num_message;
std::string ecc_signing_key;
std::vector<FingerprintingInitParameters> fingerprinting;
std::vector<ServiceBlockingInitParameters> service_blocking;
};
// A struct that is used to hold all possible params for a EMMG request.
@@ -67,10 +71,11 @@ class Emmg {
size_t request_length_ = 0;
char request_[BUFFER_SIZE];
// Sending interval in milliseconds. Stream specific parameter but currently
// only one stream will be setup.
uint16_t send_interval_ms_ = 0;
// Internal implementation of Emm.
std::unique_ptr<Emm> emm_impl_;
private:
void SendChannelSetup();
@@ -86,7 +91,9 @@ class Emmg {
Status GeneratePrivateData(
const std::string& content_provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids, uint8_t* buffer);
const std::vector<std::string>& entitlement_key_ids);
Status GenerateEmmData();
void ReceiveResponseAndVerify(uint16_t expected_type);
void Send(uint16_t message_type);
@@ -98,7 +105,9 @@ class Emmg {
// with the MUX server.
int server_socket_fd_;
// |continuity_counter| is incremented each time private data is generated.
int continuity_counter_ = 0;
// Only the lower 4 bits are used.
uint8_t continuity_counter_ = 0;
bool has_configured_emm_impl_ = false;
};
} // namespace cas

View File

@@ -23,10 +23,30 @@ namespace widevine {
namespace cas {
namespace {
constexpr int64_t kTestTimestamp = 1597882875;
constexpr int kSignatureLength = 71;
constexpr char kFakeSignatureFiller = 'x'; // 0x78
class FakeEmm : public Emm {
private:
// Generates a fake timestamp.
int64_t GenerateTimestampEpochSeconds() const override {
return kTestTimestamp;
}
// Generates a fake signature.
Status GenerateSignature(const std::string& message,
std::string* signature) const override {
signature->assign(kSignatureLength, kFakeSignatureFiller);
return OkStatus();
}
};
class TestableEmmg : public Emmg {
public:
explicit TestableEmmg(EmmgConfig* emmg_config)
: Emmg(emmg_config, /* server_socket_fd= */ 1) {}
: Emmg(emmg_config, /* server_socket_fd= */ 1) {
emm_impl_ = absl::make_unique<FakeEmm>();
}
void PublicBuildChannelSetup() { BuildChannelSetup(); }
void PublicBuildStreamSetup() { BuildStreamSetup(); }
void PublicBuildStreamBwRequest() { BuildStreamBwRequest(); }
@@ -48,13 +68,46 @@ class EmmgTest : public ::testing::Test {
config_.data_channel_id = 0x0001;
config_.data_stream_id = 0x0001;
config_.data_id = 0x0001;
config_.bandwidth = 100;
config_.max_num_message = 100;
config_.data_type = 0x01;
}
void InitEmmg() { emmg_ = absl::make_unique<TestableEmmg>(&config_); }
void LoadPrivateDataConfigs() {
config_.data_type = 0x01;
config_.content_provider = "widevine_test";
config_.content_id = "CasTsFake";
config_.entitlement_key_ids = {"fakeKeyId1KeyId1", "fakeKeyId2KeyId2"};
config_.bandwidth = 100;
config_.max_num_message = 100;
emmg_ = absl::make_unique<TestableEmmg>(&config_);
}
void LoadEmmDataConfigs() {
config_.data_type = 0x00;
config_.ecc_signing_key = "signing_key";
FingerprintingInitParameters fingerprinting_1;
fingerprinting_1.channels = {"CH1", "CH2"};
fingerprinting_1.control = "controls";
config_.fingerprinting.push_back(std::move(fingerprinting_1));
FingerprintingInitParameters fingerprinting_2;
fingerprinting_2.channels = {"CH3"};
fingerprinting_2.control = "another control";
config_.fingerprinting.push_back(std::move(fingerprinting_2));
ServiceBlockingInitParameters service_blocking_1;
service_blocking_1.channels = {"CH1", "CH2"};
service_blocking_1.device_groups = {"Group1"};
service_blocking_1.start_time = 0;
service_blocking_1.end_time = 1234567890;
config_.service_blocking.push_back(std::move(service_blocking_1));
ServiceBlockingInitParameters service_blocking_2;
service_blocking_2.channels = {"CH3"};
service_blocking_2.device_groups = {"Group2", "Group3"};
service_blocking_2.start_time = 1234567890;
service_blocking_2.end_time = 1234567891;
config_.service_blocking.push_back(std::move(service_blocking_2));
}
protected:
@@ -76,63 +129,76 @@ class EmmgTest : public ::testing::Test {
};
TEST_F(EmmgTest, BuildChannelSetup) {
InitEmmg();
emmg_->PublicBuildChannelSetup();
EXPECT_EQ(0, memcmp(kTestEmmgChannelSetup, emmg_->GetRequest(),
sizeof(kTestEmmgChannelSetup)));
EXPECT_EQ(memcmp(kTestEmmgChannelSetup, emmg_->GetRequest(),
sizeof(kTestEmmgChannelSetup)),
0);
}
TEST_F(EmmgTest, BuildStreamSetup) {
TEST_F(EmmgTest, BuildStreamSetupPrivateData) {
InitEmmg();
emmg_->PublicBuildStreamSetup();
EXPECT_EQ(0, memcmp(kTestEmmgStreamSetup, emmg_->GetRequest(),
sizeof(kTestEmmgStreamSetup)));
EXPECT_EQ(memcmp(kTestEmmgStreamSetupForPrivateData, emmg_->GetRequest(),
sizeof(kTestEmmgStreamSetupForPrivateData)),
0);
}
TEST_F(EmmgTest, BuildStreamSetupEmm) {
config_.data_type = 0x00; // EMM data type.
InitEmmg();
emmg_->PublicBuildStreamSetup();
EXPECT_EQ(memcmp(kTestEmmgStreamSetupForEmm, emmg_->GetRequest(),
sizeof(kTestEmmgStreamSetupForEmm)),
0);
}
TEST_F(EmmgTest, BuildStreamBwRequest) {
InitEmmg();
emmg_->PublicBuildStreamBwRequest();
EXPECT_EQ(0, memcmp(kTestEmmgStreamBwRequest, emmg_->GetRequest(),
sizeof(kTestEmmgStreamBwRequest)));
EXPECT_EQ(memcmp(kTestEmmgStreamBwRequest, emmg_->GetRequest(),
sizeof(kTestEmmgStreamBwRequest)),
0);
}
TEST_F(EmmgTest, HandleStreamBwAllocation) {
InitEmmg();
emmg_->PublicHandleResponse(kTestEmmgStreamBwAllocation);
EXPECT_EQ(30, emmg_->GetSendInterval());
EXPECT_EQ(emmg_->GetSendInterval(), 30);
}
TEST_F(EmmgTest, BuildDataProvision) {
TEST_F(EmmgTest, BuildDataProvisionPrivateData) {
LoadPrivateDataConfigs();
InitEmmg();
emmg_->PublicBuildDataProvision();
EXPECT_EQ(0, memcmp(kTestEmmgDataProvision, emmg_->GetRequest(),
sizeof(kTestEmmgDataProvision)));
EXPECT_EQ(memcmp(kTestEmmgPrivateDataProvision, emmg_->GetRequest(),
sizeof(kTestEmmgPrivateDataProvision)),
0);
}
TEST_F(EmmgTest,
BuildDataProvisionFailedWhenNumOfEntitlementKeyIdsExceedLimit) {
config_.entitlement_key_ids = {"fakeKeyId1KeyId1", "fakeKeyId2KeyId2",
"fakeKeyId3KeyId3"};
emmg_ = absl::make_unique<TestableEmmg>(&config_);
TEST_F(EmmgTest, BuildDataProvisionEmmData) {
LoadEmmDataConfigs();
InitEmmg();
emmg_->PublicBuildDataProvision();
EXPECT_EQ(0, memcmp(kTestEmptyEmmgDataProvision, emmg_->GetRequest(),
sizeof(kTestEmptyEmmgDataProvision)));
}
TEST_F(EmmgTest,
BuildDataProvisionFailedWhenEntitlementKeyIdLengthExceedLimit) {
config_.entitlement_key_ids = {"fakeKeyId1KeyId1", "fakeKeyId2KeyId2KeyId2"};
emmg_ = absl::make_unique<TestableEmmg>(&config_);
emmg_->PublicBuildDataProvision();
EXPECT_EQ(0, memcmp(kTestEmptyEmmgDataProvision, emmg_->GetRequest(),
sizeof(kTestEmptyEmmgDataProvision)));
EXPECT_EQ(memcmp(kTestEmmgEmmDataProvision, emmg_->GetRequest(),
sizeof(kTestEmmgEmmDataProvision)),
0);
}
TEST_F(EmmgTest, BuildStreamCloseRequest) {
InitEmmg();
emmg_->PublicBuildStreamCloseRequest();
EXPECT_EQ(0, memcmp(kTestEmmgStreamCloseRequest, emmg_->GetRequest(),
sizeof(kTestEmmgStreamCloseRequest)));
EXPECT_EQ(memcmp(kTestEmmgStreamCloseRequest, emmg_->GetRequest(),
sizeof(kTestEmmgStreamCloseRequest)),
0);
}
TEST_F(EmmgTest, BuildChannelClose) {
InitEmmg();
emmg_->PublicBuildChannelClose();
EXPECT_EQ(0, memcmp(kTestEmmgChannelClose, emmg_->GetRequest(),
sizeof(kTestEmmgChannelClose)));
EXPECT_EQ(memcmp(kTestEmmgChannelClose, emmg_->GetRequest(),
sizeof(kTestEmmgChannelClose)),
0);
}
} // namespace

View File

@@ -37,6 +37,9 @@ Status FixedKeyFetcher::MakeHttpRequest(const std::string& signed_request_json,
CasEncryptionResponse response;
response.set_status(CasEncryptionResponse::OK);
response.set_content_id(request.content_id());
if (request.has_group_id()) {
response.set_group_id(request.group_id());
}
for (const auto& track_type : request.track_types()) {
if (request.key_rotation()) {
// Add the Even key.

View File

@@ -27,6 +27,10 @@ constexpr ProgramId kInvalidPid = 0x2000;
constexpr size_t kTsPacketSize = 188;
constexpr size_t kTsPacketHeaderSize = 4;
constexpr size_t kMaxTsPayloadSize = kTsPacketSize - kTsPacketHeaderSize;
constexpr size_t kMaxSectionSize = 1021;
constexpr size_t kMaxPossibleTsPacketsCount = 6;
constexpr size_t kMaxPossibleTsPacketsSizeBytes =
kMaxPossibleTsPacketsCount * kTsPacketSize;
constexpr uint8_t kTsPacketSyncByte = 0x47;

View File

@@ -10,22 +10,113 @@
#include <netinet/in.h>
#include <cmath>
#include <cstddef>
#include <cstring>
#include "glog/logging.h"
#include "absl/strings/str_cat.h"
#include "common/status.h"
#include "media_cas_packager_sdk/internal/ts_packet.h"
namespace widevine {
namespace cas {
namespace {
// Reference https://en.wikipedia.org/wiki/Program-specific_information
constexpr size_t kSectionHeaderSize = 4;
constexpr size_t kTsFillValue = 0xFF;
// ETSI ETR 289 specifies table ids 0x82 to 0x8F are for CA System private
// usage, which are typically used by EMM, with one table id for each EMM type.
constexpr uint8_t kEmmTableId = 0x82;
// Present at the start of the TS packet payload signaled by the
// payload_unit_start_indicator bit in the TS header. Used to set packet
// alignment bytes or content before the start of tabled payload data.
constexpr uint8_t kPointerFieldZero = '\x00';
// Here are the 8 bits covers multiple fields from
// "section syntax indicator" to the first two bits of "section length".
// Section syntax indicator (1 bit): Set to 0 as this is not PAT, PMT or CAT.
// Private bit (1 bit): Set to 1 as this is not PAT, PMT or CAT.
// Reserved bits (2 bits): Set to 0x03 (all bits on).
// Section length unused bits (2 bits): Set to 0 (all bits off).
// Section length higher 2 bits (2 bits): 0.
constexpr uint8_t kSyntaxIndicatorToLengthByte = '\x70';
ContinuityCounter Increment(ContinuityCounter continuity_counter) {
return ++continuity_counter & 0xf;
}
Status InsertDataAsTsPacket(const std::string& insert_data, ProgramId pid,
uint8_t table_id, ContinuityCounter* cc,
uint8_t* buffer, ssize_t* bytes_modified) {
if (insert_data.empty()) {
return {error::INVALID_ARGUMENT, "insert_data must not be empty"};
}
if (cc == nullptr || buffer == nullptr || bytes_modified == nullptr) {
return {error::INVALID_ARGUMENT,
"cc, buffer and bytes_modified must not be null"};
}
if (*bytes_modified % kTsPacketSize != 0) {
return {error::INVALID_ARGUMENT,
"bytes_modified must be a multiple of TS packet size (188 bytes)"};
}
if (insert_data.size() > kMaxSectionSize) {
return {error::INVALID_ARGUMENT,
absl::StrCat("Data is too large. Max: ", kMaxSectionSize)};
}
int reading_index = 0;
while (reading_index < insert_data.size()) {
bool is_first_packet = reading_index == 0;
// Create a TS payload of 184 bytes (Max TS size - ts header length).
std::string payload(kMaxTsPayloadSize, kTsFillValue);
// Section header if this is the first TS packet.
if (is_first_packet) {
uint8_t syntax_indicator_to_length_byte =
kSyntaxIndicatorToLengthByte | ((insert_data.size() >> 8) & 0x3);
uint8_t emm_size_lower_bits = insert_data.size() & 0xFF;
memcpy(&payload.at(0), &kPointerFieldZero, 1);
memcpy(&payload.at(1), &table_id, 1);
memcpy(&payload.at(2), &syntax_indicator_to_length_byte, 1);
memcpy(&payload.at(3), &emm_size_lower_bits, 1);
}
// Payload data.
int payload_index = is_first_packet ? kSectionHeaderSize : 0;
// Min of {remaining bytes in TS payload, remaining bytes in insert data}.
int used_bytes = std::min(kMaxTsPayloadSize - payload_index,
insert_data.size() - reading_index);
memcpy(&payload.at(payload_index), insert_data.substr(reading_index).data(),
used_bytes);
reading_index += used_bytes;
// Wrap the data with a TS header.
TsPacket ts_packet;
ts_packet.set_payload_unit_start_indicator(is_first_packet);
ts_packet.set_pid(pid);
ts_packet.set_payload(payload);
ts_packet.set_adaptation_field_control(TsPacket::kPayloadOnly);
ts_packet.set_continuity_counter(*cc);
*cc = Increment(*cc);
// And write the packet to the buffer.
std::string serialized_ts_packet;
Status status = ts_packet.Write(&serialized_ts_packet);
if (!status.ok()) {
return status;
}
if (serialized_ts_packet.size() != kTsPacketSize) {
return {error::INTERNAL, "Unexpected TS packet size generated."};
}
memcpy(buffer + *bytes_modified, serialized_ts_packet.data(),
serialized_ts_packet.size());
*bytes_modified += serialized_ts_packet.size();
}
return OkStatus();
}
} // namespace
void BigEndianToHost16(uint16_t* destination, const void* source) {
@@ -68,52 +159,20 @@ void Host32ToBigEndian(void* destination, const uint32_t* source) {
Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
uint8_t table_id, ContinuityCounter* cc, uint8_t* buffer,
ssize_t* bytes_modified) {
DCHECK(cc);
DCHECK(buffer);
DCHECK(bytes_modified);
DCHECK_EQ(*bytes_modified % kTsPacketSize, 0);
return InsertDataAsTsPacket(ecm, pid, table_id, cc, buffer, bytes_modified);
}
// Create a TS payload of 184 bytes (Max TS size - ts header length).
CHECK_LE(ecm.size(), kMaxTsPayloadSize);
std::string payload(kMaxTsPayloadSize, kTsFillValue);
// Reference https://en.wikipedia.org/wiki/Program-specific_information
static constexpr uint8_t kPointerField = '\x00';
// Here are the 8 bits covers multiple fields from
// "section syntax indicator" to the first two bits of "section length".
// We always set the first two bits of "section length" to 0 because
// the data follows (i.e. the ECM) cannot be more than 180 bytes, so
// the remaining 8 bits in "section length" is sufficient to store its
// length.
static constexpr uint8_t kSyntaxtIndicatorToLength = '\x70';
uint8_t ecm_length = ecm.size();
// Section header.
memcpy(&payload.at(0), &kPointerField, 1);
memcpy(&payload.at(1), &table_id, 1);
memcpy(&payload.at(2), &kSyntaxtIndicatorToLength, 1);
memcpy(&payload.at(3), &ecm_length, 1);
// ECM follows the header.
memcpy(&payload.at(kSectionHeaderSize), ecm.data(), ecm.size());
Status InsertEmmAsTsPacket(const std::string& emm, ProgramId pid,
ContinuityCounter* cc, uint8_t* buffer,
ssize_t* bytes_modified) {
return InsertDataAsTsPacket(emm, pid, kEmmTableId, cc, buffer,
bytes_modified);
}
// Wrap the data with a TS header.
TsPacket ecm_packet;
ecm_packet.set_payload_unit_start_indicator(true);
ecm_packet.set_pid(pid);
ecm_packet.set_payload(payload);
ecm_packet.set_adaptation_field_control(0x1);
ecm_packet.set_continuity_counter(*cc);
*cc = Increment(*cc);
// And write the packet.
std::string ecm_ts_packet;
Status status = ecm_packet.Write(&ecm_ts_packet);
if (!status.ok()) {
return status;
}
memcpy(buffer + *bytes_modified, ecm_ts_packet.data(), ecm_ts_packet.size());
*bytes_modified += ecm_ts_packet.size();
return OkStatus();
uint16_t CalculateTsBufferSizeForEcmEmm(uint16_t ecm_emm_size) {
return kTsPacketSize *
std::ceil(static_cast<float>(kSectionHeaderSize + ecm_emm_size) /
kMaxTsPayloadSize);
}
} // namespace cas

View File

@@ -49,15 +49,39 @@ void Host32ToBigEndian(void* destination, const uint32_t* source);
// - |cc| is the continuity counter to use in the header, which will also be
// incremented by this function.
// - |buffer| is the output buffer. Must be big enough to hold an additional
// 188 bytes.
// 188 bytes * number of TS packets needed to put |ecm|.
// - |bytes_modified| the number of bytes which have already been modified in
// the |buffer| and is used as an offset.
// |bytes_modified| will be incremented by 188 if insertion of ECM into
// |buffer| is successful.
// |bytes_modified| will be incremented by a multiple of 188 if insertion of
// ECM into |buffer| is successful.
Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
uint8_t table_id, ContinuityCounter* cc, uint8_t* buffer,
ssize_t* bytes_modified);
// Packages an EMM as a TS packet and inserts it into a buffer.
// Args:
// - |emm| is the serialized EMM.
// - |pid| is the PID used for ECMs in the TS header.
// - |cc| is the continuity counter to use in the header, which will also be
// incremented by this function.
// - |buffer| is the output buffer. Must be big enough to hold an additional
// 188 bytes * number of TS packets needed to put |emm|.
// - |bytes_modified| the number of bytes which have already been modified in
// the |buffer| and is used as an offset.
// |bytes_modified| will be incremented by a multiple of 188 if insertion of
// ECM into |buffer| is successful.
Status InsertEmmAsTsPacket(const std::string& emm, ProgramId pid,
ContinuityCounter* cc, uint8_t* buffer,
ssize_t* bytes_modified);
// Calculates the buffer size needed to hold TS packets generated by inserting
// ECM/EMM calls InsertEcmAsTsPacket() and InsertEmmAsTsPacket(). The returned
// buffer size will always be a multiple of kTsPacketSize (188 bytes).
// Args:
// - |ecm_emm_size| is the size of the serialized ECM or EMM that will be
// inserted to TS packets.
uint16_t CalculateTsBufferSizeForEcmEmm(uint16_t ecm_emm_size);
} // namespace cas
} // namespace widevine

View File

@@ -13,8 +13,11 @@
#include "testing/gmock.h"
#include "testing/gunit.h"
namespace widevine {
namespace cas {
namespace {
constexpr ProgramId kTestPid = 0x1FFD;
// ECM payload data taken from a CETS encrypted file at Google Fiber
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
@@ -53,28 +56,209 @@ constexpr char kExpectedEcmPacket[] = {
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF'};
} // namespace
namespace widevine {
namespace cas {
// 300 bytes payload.
constexpr char kLongPayload[] = {
'\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x09',
'\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11', '\x12',
'\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b',
'\x1c', '\x1d', '\x1e', '\x1f', '\x20', '\x21', '\x22', '\x23', '\x24',
'\x25', '\x26', '\x27', '\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d',
'\x2e', '\x2f', '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36',
'\x37', '\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
'\x40', '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', '\x48',
'\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', '\x50', '\x51',
'\x52', '\x53', '\x54', '\x55', '\x56', '\x57', '\x58', '\x59', '\x5a',
'\x5b', '\x5c', '\x5d', '\x5e', '\x5f', '\x60', '\x61', '\x62', '\x63',
'\x64', '\x65', '\x66', '\x67', '\x68', '\x69', '\x6a', '\x6b', '\x6c',
'\x6d', '\x6e', '\x6f', '\x70', '\x71', '\x72', '\x73', '\x74', '\x75',
'\x76', '\x77', '\x78', '\x79', '\x7a', '\x7b', '\x7c', '\x7d', '\x7e',
'\x7f', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87',
'\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f', '\x90',
'\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', '\x99',
'\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f', '\xa0', '\xa1', '\xa2',
'\xa3', '\xa4', '\xa5', '\xa6', '\xa7', '\xa8', '\xa9', '\xaa', '\xab',
'\xac', '\xad', '\xae', '\xaf', '\xb0', '\xb1', '\xb2', '\xb3', '\xb4',
'\xb5', '\xb6', '\xb7', '\xb8', '\xb9', '\xba', '\xbb', '\xbc', '\xbd',
'\xbe', '\xbf', '\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', '\xc6',
'\xc7', '\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', '\xcf',
'\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd7', '\xd8',
'\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf', '\xe0', '\xe1',
'\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8', '\xe9', '\xea',
'\xeb', '\xec', '\xed', '\xee', '\xef', '\xf0', '\xf1', '\xf2', '\xf3',
'\xf4', '\xf5', '\xf6', '\xf7', '\xf8', '\xf9', '\xfa', '\xfb', '\xfc',
'\xfd', '\xfe', '\xff', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06',
'\x07', '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18',
'\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', '\x20', '\x21',
'\x22', '\x23', '\x24', '\x25', '\x26', '\x27', '\x28', '\x29', '\x2a',
'\x2b', '\x2c', '\x2d'};
constexpr char kExpectedLongTs[] = {
// The 1st TS header.
'\x47', '\x5f', '\xfd', '\x10',
// Section header.
'\x00', '\x80', '\x71', '\x2c',
// Payload.
'\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x09',
'\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11', '\x12',
'\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b',
'\x1c', '\x1d', '\x1e', '\x1f', '\x20', '\x21', '\x22', '\x23', '\x24',
'\x25', '\x26', '\x27', '\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d',
'\x2e', '\x2f', '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36',
'\x37', '\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
'\x40', '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', '\x48',
'\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', '\x50', '\x51',
'\x52', '\x53', '\x54', '\x55', '\x56', '\x57', '\x58', '\x59', '\x5a',
'\x5b', '\x5c', '\x5d', '\x5e', '\x5f', '\x60', '\x61', '\x62', '\x63',
'\x64', '\x65', '\x66', '\x67', '\x68', '\x69', '\x6a', '\x6b', '\x6c',
'\x6d', '\x6e', '\x6f', '\x70', '\x71', '\x72', '\x73', '\x74', '\x75',
'\x76', '\x77', '\x78', '\x79', '\x7a', '\x7b', '\x7c', '\x7d', '\x7e',
'\x7f', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87',
'\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f', '\x90',
'\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', '\x99',
'\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f', '\xa0', '\xa1', '\xa2',
'\xa3', '\xa4', '\xa5', '\xa6', '\xa7', '\xa8', '\xa9', '\xaa', '\xab',
'\xac', '\xad', '\xae', '\xaf', '\xb0', '\xb1', '\xb2', '\xb3', '\xb4',
// The 2nd TS header.
'\x47', '\x1f', '\xfd', '\x11',
// Payload (continued).
'\xb5', '\xb6', '\xb7', '\xb8', '\xb9', '\xba', '\xbb', '\xbc', '\xbd',
'\xbe', '\xbf', '\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', '\xc6',
'\xc7', '\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', '\xcf',
'\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd7', '\xd8',
'\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf', '\xe0', '\xe1',
'\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8', '\xe9', '\xea',
'\xeb', '\xec', '\xed', '\xee', '\xef', '\xf0', '\xf1', '\xf2', '\xf3',
'\xf4', '\xf5', '\xf6', '\xf7', '\xf8', '\xf9', '\xfa', '\xfb', '\xfc',
'\xfd', '\xfe', '\xff', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06',
'\x07', '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18',
'\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', '\x20', '\x21',
'\x22', '\x23', '\x24', '\x25', '\x26', '\x27', '\x28', '\x29', '\x2a',
'\x2b', '\x2c', '\x2d',
// 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'};
// TODO(user): Add unit tests for BigEndianToHost16, BigEndianToHost32 and
// Host16ToBigEndian.
TEST(InsertEcmAsTsPacketTest, BasicHappyPath) {
TEST(InsertEcmAsTsPacketTest, SingleTsPacket) {
// declare variables used by InsertEcmAsTsPacket()
ContinuityCounter ecm_cc_ = 0;
ProgramId video_ecm_pid_ = 0x1FFD;
uint8_t buffer[188]; // output ts packet size
ContinuityCounter ecm_cc = 0;
uint8_t buffer[kTsPacketSize]; // output ts packet size
ssize_t output_bytes_modified = 0;
// convert kEcmPayload[] into a std::string to be accepted by the method
std::string ecm_data(kEcmPayload);
EXPECT_OK(InsertEcmAsTsPacket(ecm_data, video_ecm_pid_, kTsPacketTableId80,
&ecm_cc_, buffer, &output_bytes_modified));
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, buffer, sizeof(buffer)));
EXPECT_EQ(1, ecm_cc_);
EXPECT_EQ(188, output_bytes_modified);
EXPECT_OK(InsertEcmAsTsPacket(ecm_data, kTestPid, kTsPacketTableId80, &ecm_cc,
buffer, &output_bytes_modified));
EXPECT_EQ(memcmp(kExpectedEcmPacket, buffer, sizeof(buffer)), 0);
EXPECT_EQ(ecm_cc, 1);
EXPECT_EQ(output_bytes_modified, kTsPacketSize);
}
TEST(InsertEcmAsTsPacketTest, MultipleTsPackets) {
ContinuityCounter ecm_cc = 0;
uint8_t buffer[kTsPacketSize * 2];
ssize_t output_bytes_modified = 0;
std::string long_payload(kLongPayload);
EXPECT_OK(InsertEcmAsTsPacket(long_payload, kTestPid, kTsPacketTableId80,
&ecm_cc, buffer, &output_bytes_modified));
EXPECT_EQ(memcmp(kExpectedLongTs, buffer, sizeof(buffer)), 0);
EXPECT_EQ(ecm_cc, 2);
EXPECT_EQ(output_bytes_modified, kTsPacketSize * 2);
}
TEST(InsertEmmAsTsPacketTest, SingleTsPacket) {
ContinuityCounter counter = 0;
uint8_t buffer[kTsPacketSize];
ssize_t output_bytes_modified = 0;
std::string payload(kEcmPayload);
EXPECT_OK(InsertEmmAsTsPacket(payload, kTestPid, &counter, buffer,
&output_bytes_modified));
char expectedEmmPacket[sizeof(kExpectedEcmPacket)];
memcpy(expectedEmmPacket, kExpectedEcmPacket, sizeof(kExpectedEcmPacket));
expectedEmmPacket[5] = 0x82; // Expected table id for emm
EXPECT_EQ(memcmp(expectedEmmPacket, buffer, sizeof(buffer)), 0);
EXPECT_EQ(counter, 1);
EXPECT_EQ(output_bytes_modified, kTsPacketSize);
}
TEST(InsertEmmAsTsPacketTest, MultipleTsPackets) {
ContinuityCounter counter = 0;
uint8_t buffer[kTsPacketSize * 2];
ssize_t output_bytes_modified = 0;
std::string long_payload(kLongPayload);
EXPECT_OK(InsertEmmAsTsPacket(long_payload, kTestPid, &counter, buffer,
&output_bytes_modified));
char expectedEmmPacket[sizeof(kExpectedLongTs)];
memcpy(expectedEmmPacket, kExpectedLongTs, sizeof(kExpectedLongTs));
expectedEmmPacket[5] = 0x82; // Expected table id for emm
EXPECT_EQ(memcmp(expectedEmmPacket, buffer, sizeof(buffer)), 0);
EXPECT_EQ(counter, 2);
EXPECT_EQ(output_bytes_modified, kTsPacketSize * 2);
}
TEST(InsertEcmAsTsPacketTest, InvalidArgument) {
ContinuityCounter counter = 0;
uint8_t buffer[kTsPacketSize];
ssize_t output_bytes_modified = 0;
std::string payload(kEcmPayload);
// Continuity counter is null.
EXPECT_EQ(InsertEcmAsTsPacket(payload, kTestPid, kTsPacketTableId80, nullptr,
buffer, &output_bytes_modified)
.error_code(),
error::INVALID_ARGUMENT);
// Buffer is null.
EXPECT_EQ(InsertEcmAsTsPacket(payload, kTestPid, kTsPacketTableId80, &counter,
nullptr, &output_bytes_modified)
.error_code(),
error::INVALID_ARGUMENT);
// Output bytes modified is null.
EXPECT_EQ(InsertEcmAsTsPacket(payload, kTestPid, kTsPacketTableId80, &counter,
buffer, nullptr)
.error_code(),
error::INVALID_ARGUMENT);
// Payload is empty.
EXPECT_EQ(InsertEcmAsTsPacket("", kTestPid, kTsPacketTableId80, &counter,
buffer, &output_bytes_modified)
.error_code(),
error::INVALID_ARGUMENT);
// Payload is too large.
std::string too_large_payload(kMaxSectionSize + 1, '\x11');
EXPECT_EQ(InsertEcmAsTsPacket(too_large_payload, kTestPid, kTsPacketTableId80,
&counter, buffer, &output_bytes_modified)
.error_code(),
error::INVALID_ARGUMENT);
}
TEST(CalculateTsBufferSizeForEcmEmm, VariousSize) {
EXPECT_EQ(CalculateTsBufferSizeForEcmEmm(1), kTsPacketSize);
EXPECT_EQ(CalculateTsBufferSizeForEcmEmm(180), kTsPacketSize);
EXPECT_EQ(CalculateTsBufferSizeForEcmEmm(181), 2 * kTsPacketSize);
EXPECT_EQ(CalculateTsBufferSizeForEcmEmm(300), 2 * kTsPacketSize);
EXPECT_EQ(CalculateTsBufferSizeForEcmEmm(364), 2 * kTsPacketSize);
EXPECT_EQ(CalculateTsBufferSizeForEcmEmm(365), 3 * kTsPacketSize);
EXPECT_EQ(CalculateTsBufferSizeForEcmEmm(500), 3 * kTsPacketSize);
EXPECT_EQ(CalculateTsBufferSizeForEcmEmm(1021), 6 * kTsPacketSize);
}
} // namespace
} // namespace cas
} // namespace widevine

View File

@@ -177,6 +177,18 @@ cc_library(
alwayslink = 1,
)
cc_library(
name = "wv_cas_emm",
srcs = ["wv_cas_emm.cc"],
hdrs = ["wv_cas_emm.h"],
deps = [
"//base",
"@abseil_repo//absl/memory",
"//common:status",
"//media_cas_packager_sdk/internal:emm",
],
)
cc_test(
name = "wv_cas_types_test",
size = "small",
@@ -187,6 +199,17 @@ cc_test(
],
)
cc_test(
name = "wv_cas_emm_test",
srcs = ["wv_cas_emm_test.cc"],
deps = [
":wv_cas_emm",
"//testing:gunit_main",
"//media_cas_packager_sdk/internal:emm",
"//media_cas_packager_sdk/internal:util",
],
)
cc_binary(
name = "wv_ecmg",
srcs = ["wv_ecmg.cc"],
@@ -206,6 +229,7 @@ cc_binary(
"//base",
"@abseil_repo//absl/flags:flag",
"@abseil_repo//absl/flags:parse",
"@abseil_repo//absl/strings",
"//media_cas_packager_sdk/internal:emmg",
],
)

View File

@@ -0,0 +1,101 @@
////////////////////////////////////////////////////////////////////////////////
// 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/public/wv_cas_emm.h"
#include "glog/logging.h"
#include "absl/memory/memory.h"
#include "media_cas_packager_sdk/internal/emm.h"
namespace widevine {
namespace cas {
namespace {
std::vector<FingerprintingInitParameters> ConvertFingerprintingInitVector(
const std::vector<WvCasFingerprintingInitParameters>& params) {
std::vector<FingerprintingInitParameters> converted_vector;
converted_vector.reserve(params.size());
for (const auto& param : params) {
FingerprintingInitParameters converted;
converted.channels.assign(param.channels.begin(), param.channels.end());
converted.control = param.control;
converted_vector.push_back(converted);
}
return converted_vector;
}
std::vector<ServiceBlockingInitParameters> ConvertServiceBlockingInitVector(
const std::vector<WvCasServiceBlockingInitParameters>& params) {
std::vector<ServiceBlockingInitParameters> converted_vector;
converted_vector.reserve(params.size());
for (const auto& param : params) {
ServiceBlockingInitParameters converted;
converted.channels.assign(param.channels.begin(), param.channels.end());
converted.device_groups.assign(param.device_groups.begin(),
param.device_groups.end());
converted.start_time = param.start_time;
converted.end_time = param.end_time;
converted_vector.push_back(converted);
}
return converted_vector;
}
} // namespace
WvCasEmm::WvCasEmm(std::unique_ptr<Emm> emm) : emm_(std::move(emm)) {}
WvCasEmm::~WvCasEmm() = default;
std::unique_ptr<WvCasEmm> WvCasEmm::Create(
const std::string& private_ecc_signing_key) {
if (private_ecc_signing_key.empty()) {
LOG(ERROR) << "private_ecc_signing_key must not be empty.";
return nullptr;
}
auto emm = absl::make_unique<Emm>();
Status status = emm->SetPrivateSigningKey(private_ecc_signing_key);
if (!status.ok()) {
LOG(ERROR) << "Failed to set private signing key: " << status.ToString();
return nullptr;
}
return absl::WrapUnique(new WvCasEmm(std::move(emm)));
}
Status WvCasEmm::SetFingerprinting(
const std::vector<WvCasFingerprintingInitParameters>& fingerprintings) {
return emm_->SetFingerprinting(
ConvertFingerprintingInitVector(fingerprintings));
}
Status WvCasEmm::SetServiceBlocking(
const std::vector<WvCasServiceBlockingInitParameters>& service_blockings) {
return emm_->SetServiceBlocking(
ConvertServiceBlockingInitVector(service_blockings));
}
Status WvCasEmm::GenerateEmm(std::string* serialized_emm) const {
if (serialized_emm == nullptr) {
return {error::INVALID_ARGUMENT, "serialized_emm must not be null"};
}
return emm_->GenerateEmm(serialized_emm);
}
Status WvCasEmm::GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter,
uint8_t* packet,
ssize_t* packet_size) const {
if (continuity_counter == nullptr || packet == nullptr ||
packet_size == nullptr) {
return {error::INVALID_ARGUMENT,
"continuity_counter, packet and packet_size must not be null"};
}
return emm_->GenerateEmmTsPackets(pid, continuity_counter, packet,
packet_size);
}
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,91 @@
////////////////////////////////////////////////////////////////////////////////
// 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.
////////////////////////////////////////////////////////////////////////////////
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_EMM_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_EMM_H_
#include <memory>
#include <string>
#include <vector>
#include <cstdint>
#include "common/status.h"
namespace widevine {
namespace cas {
class Emm;
struct WvCasFingerprintingInitParameters {
std::vector<std::string> channels;
std::string control;
};
struct WvCasServiceBlockingInitParameters {
std::vector<std::string> channels;
std::vector<std::string> device_groups;
// Value 0 in start_time means immediate.
int64_t start_time = 0;
int64_t end_time;
};
// Class for generating Widevine CAS EMMs. Widevine CAS EMMs are not used for
// carrying entitlement information. Instead, it is used as an information
// channel carrying information for fingerprinting, service blocking, etc.
// Class WvCasEmm is NOT thread-safe.
class WvCasEmm {
public:
WvCasEmm(const WvCasEmm&) = delete;
WvCasEmm& operator=(const WvCasEmm&) = delete;
virtual ~WvCasEmm();
// Creates and returns a WvCasEmm with the given private ECC signing key.
// Returns a null value if the creation is unsuccessful.
static std::unique_ptr<WvCasEmm> Create(
const std::string& private_ecc_signing_key);
// Replaces current fingerprinting info with |fingerprintings|.
virtual Status SetFingerprinting(
const std::vector<WvCasFingerprintingInitParameters>& fingerprintings);
// Replaces current service blocking info with |service_blockings|.
virtual Status SetServiceBlocking(
const std::vector<WvCasServiceBlockingInitParameters>& service_blockings);
// Constructs an EMM payload with fingerprinting info set by calling
// SetFingerprinting(), and service blocking info set by calling
// SetServiceBlocking().
// Args (all pointer parameters must not be nullptr):
// - |serialized_emm| caller-supplied std::string pointer to receive the EMM.
virtual Status GenerateEmm(std::string* serialized_emm) const;
// Generates EMM that are wrapped in TS packets.
// Args (all pointer parameters must not be nullptr):
// - |pid| program ID for the EMM stream.
// - |continuity_counter| continuity_counter for the TS packet. It will be
// automatically incremented. Only the last 4 bits are used.
// - |packet| a buffer to be used to return the generated TS packet. Must be
// able to hold the maximum possible size of genereated Ts packets
// (kMaxPossibleTsPacketsSizeBytes = 1128 bytes).
// - |packet_size| is the size of the allocated |packet|. It will be updated
// as the number of bytes actually used.
virtual Status GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter,
uint8_t* packet,
ssize_t* packet_size) const;
protected:
explicit WvCasEmm(std::unique_ptr<Emm> emm);
private:
std::unique_ptr<Emm> emm_;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_EMM_H_

View File

@@ -0,0 +1,260 @@
////////////////////////////////////////////////////////////////////////////////
// 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/public/wv_cas_emm.h"
#include <memory>
#include <vector>
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "media_cas_packager_sdk/internal/emm.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
using ::testing::DoAll;
using ::testing::ElementsAreArray;
using ::testing::IsEmpty;
using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::Pointwise;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::SetArrayArgument;
namespace widevine {
namespace cas {
namespace {
constexpr int kTestPid = 1;
constexpr char kChannelOne[] = "CH1";
constexpr char kChannelTwo[] = "CH2";
constexpr char kChannelThree[] = "CH3";
constexpr char kFingerprintingControl[] = "controls";
constexpr char kFingerprintingControlTwo[] = "another control";
constexpr char kDeviceGroupOne[] = "Group1";
constexpr char kDeviceGroupTwo[] = "Group2";
constexpr int64_t kServiceBockingStartTime = 100;
constexpr int64_t kServiceBockingEndTime = 1000;
TEST(WvCasEmmFactoryCeateTest, FactoryCreateSuccess) {
EXPECT_THAT(WvCasEmm::Create("Signing key"), NotNull());
}
TEST(WvCasEmmFactoryCeateTest, EmptySigningKeyFail) {
EXPECT_THAT(WvCasEmm::Create(/*private_ecc_signing_key=*/""), IsNull());
}
class MockEmm : public Emm {
public:
MOCK_METHOD(
Status, SetFingerprinting,
(const std::vector<FingerprintingInitParameters>& fingerprintings),
(override));
MOCK_METHOD(
Status, SetServiceBlocking,
(const std::vector<ServiceBlockingInitParameters>& service_blockings),
(override));
MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm),
(const, override));
MOCK_METHOD(Status, GenerateEmmTsPackets,
(uint16_t pid, uint8_t* continuity_counter, uint8_t* packet,
ssize_t* packet_size),
(const, override));
};
class MockWvCasEmm : public WvCasEmm {
public:
explicit MockWvCasEmm(Emm* emm) : WvCasEmm(std::unique_ptr<Emm>(emm)) {}
};
class WvCasEmmTest : public ::testing::Test {
protected:
WvCasEmmTest()
: mock_internal_emm_(new MockEmm()),
cas_emm_(new MockWvCasEmm(mock_internal_emm_)) {}
MockEmm* const mock_internal_emm_;
std::unique_ptr<MockWvCasEmm> cas_emm_;
};
MATCHER(FingerprintingEq, "") {
const auto& actual = std::get<0>(arg);
const auto& expected = std::get<1>(arg);
return (actual.channels == expected.channels) &&
(actual.control == expected.control);
}
MATCHER(ServiceBlockingEq, "") {
const auto& actual = std::get<0>(arg);
const auto& expected = std::get<1>(arg);
return (actual.channels == expected.channels) &&
(actual.device_groups == expected.device_groups) &&
(actual.start_time == expected.start_time) &&
(actual.end_time == expected.end_time);
}
TEST_F(WvCasEmmTest, SetFingerprintingSuccess) {
EXPECT_CALL(*mock_internal_emm_, SetFingerprinting(IsEmpty()))
.WillOnce(Return(OkStatus()));
EXPECT_OK(cas_emm_->SetFingerprinting({}));
}
TEST_F(WvCasEmmTest, SetFingerprintingConvertSuccess) {
std::vector<WvCasFingerprintingInitParameters> param_vector;
param_vector.reserve(2);
param_vector.emplace_back();
param_vector.back().channels = {kChannelOne, kChannelTwo};
param_vector.back().control = kFingerprintingControl;
param_vector.emplace_back();
param_vector.back().channels = {kChannelThree};
param_vector.back().control = kFingerprintingControlTwo;
std::vector<FingerprintingInitParameters> expected_vector;
expected_vector.reserve(2);
expected_vector.emplace_back();
expected_vector.back().channels = {kChannelOne, kChannelTwo};
expected_vector.back().control = kFingerprintingControl;
expected_vector.emplace_back();
expected_vector.back().channels = {kChannelThree};
expected_vector.back().control = kFingerprintingControlTwo;
EXPECT_CALL(*mock_internal_emm_,
SetFingerprinting(Pointwise(FingerprintingEq(), expected_vector)))
.WillOnce(Return(OkStatus()));
EXPECT_OK(cas_emm_->SetFingerprinting(param_vector));
}
TEST_F(WvCasEmmTest, SetFingerprintingFail) {
EXPECT_CALL(*mock_internal_emm_, SetFingerprinting(IsEmpty()))
.WillOnce(Return(Status(error::INVALID_ARGUMENT)));
EXPECT_EQ(cas_emm_->SetFingerprinting({}).error_code(),
error::INVALID_ARGUMENT);
}
TEST_F(WvCasEmmTest, SetServiceBlockingSuccess) {
EXPECT_CALL(*mock_internal_emm_, SetServiceBlocking(IsEmpty()))
.WillOnce(Return(OkStatus()));
EXPECT_OK(cas_emm_->SetServiceBlocking({}));
}
TEST_F(WvCasEmmTest, SetServiceBlockingConvertSuccess) {
std::vector<WvCasServiceBlockingInitParameters> param_vector;
param_vector.reserve(2);
param_vector.emplace_back();
param_vector.back().channels = {kChannelOne, kChannelTwo};
param_vector.back().device_groups = {kDeviceGroupOne, kDeviceGroupTwo};
param_vector.back().start_time = kServiceBockingStartTime;
param_vector.back().end_time = kServiceBockingEndTime;
param_vector.emplace_back();
param_vector.back().channels = {kChannelThree};
std::vector<ServiceBlockingInitParameters> expected_vector;
expected_vector.reserve(2);
expected_vector.emplace_back();
expected_vector.back().channels = {kChannelOne, kChannelTwo};
expected_vector.back().device_groups = {kDeviceGroupOne, kDeviceGroupTwo};
expected_vector.back().start_time = kServiceBockingStartTime;
expected_vector.back().end_time = kServiceBockingEndTime;
expected_vector.emplace_back();
expected_vector.back().channels = {kChannelThree};
EXPECT_CALL(*mock_internal_emm_, SetServiceBlocking(Pointwise(
ServiceBlockingEq(), expected_vector)))
.WillOnce(Return(OkStatus()));
EXPECT_OK(cas_emm_->SetServiceBlocking(param_vector));
}
TEST_F(WvCasEmmTest, SetServiceBlockingFail) {
EXPECT_CALL(*mock_internal_emm_, SetServiceBlocking(IsEmpty()))
.WillOnce(Return(Status(error::INVALID_ARGUMENT)));
EXPECT_EQ(cas_emm_->SetServiceBlocking({}).error_code(),
error::INVALID_ARGUMENT);
}
TEST_F(WvCasEmmTest, GenerateEmmSuccess) {
std::string expected_emm = "emm";
EXPECT_CALL(*mock_internal_emm_, GenerateEmm(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(expected_emm), Return(OkStatus())));
std::string generated_emm;
EXPECT_OK(cas_emm_->GenerateEmm(&generated_emm));
EXPECT_EQ(generated_emm, expected_emm);
}
TEST_F(WvCasEmmTest, GenerateEmmFail) {
EXPECT_CALL(*mock_internal_emm_, GenerateEmm(NotNull()))
.WillOnce(Return(Status(error::INVALID_ARGUMENT)));
std::string generated_emm;
EXPECT_EQ(cas_emm_->GenerateEmm(&generated_emm).error_code(),
error::INVALID_ARGUMENT);
}
TEST_F(WvCasEmmTest, GenerateEmmNullBuffer) {
EXPECT_EQ(cas_emm_->GenerateEmm(nullptr).error_code(),
error::INVALID_ARGUMENT);
}
TEST_F(WvCasEmmTest, GenerateEmmTsPacketsSuccess) {
uint8_t expected_continuity_counter = 2;
uint8_t expected_packet[] = {1, 2, 3, 4, 5};
EXPECT_CALL(*mock_internal_emm_,
GenerateEmmTsPackets(kTestPid, NotNull(), NotNull(), NotNull()))
.WillOnce(
DoAll(SetArgPointee<1>(expected_continuity_counter),
SetArrayArgument<2>(expected_packet,
expected_packet + sizeof(expected_packet)),
SetArgPointee<3>(sizeof(expected_packet)), Return(OkStatus())));
uint8_t continuity_counter = 1;
uint8_t packet[sizeof(expected_packet)];
ssize_t packet_size = kTsPacketSize;
EXPECT_OK(cas_emm_->GenerateEmmTsPackets(kTestPid, &continuity_counter,
packet, &packet_size));
EXPECT_EQ(continuity_counter, expected_continuity_counter);
EXPECT_THAT(packet, ElementsAreArray(expected_packet));
}
TEST_F(WvCasEmmTest, GenerateEmmTsPacketsFail) {
EXPECT_CALL(*mock_internal_emm_,
GenerateEmmTsPackets(kTestPid, NotNull(), NotNull(), NotNull()))
.WillOnce(Return(Status(error::INVALID_ARGUMENT)));
uint8_t continuity_counter = 1;
uint8_t packet[kTsPacketSize];
ssize_t packet_size = kTsPacketSize;
EXPECT_EQ(cas_emm_
->GenerateEmmTsPackets(kTestPid, &continuity_counter, packet,
&packet_size)
.error_code(),
error::INVALID_ARGUMENT);
}
TEST_F(WvCasEmmTest, GenerateEmmTsPacketsNullPointer) {
uint8_t continuity_counter = 1;
uint8_t packet[kTsPacketSize];
ssize_t packet_size = kTsPacketSize;
EXPECT_EQ(
cas_emm_->GenerateEmmTsPackets(kTestPid, nullptr, packet, &packet_size)
.error_code(),
error::INVALID_ARGUMENT);
EXPECT_EQ(cas_emm_
->GenerateEmmTsPackets(kTestPid, &continuity_counter, nullptr,
&packet_size)
.error_code(),
error::INVALID_ARGUMENT);
EXPECT_EQ(
cas_emm_
->GenerateEmmTsPackets(kTestPid, &continuity_counter, packet, nullptr)
.error_code(),
error::INVALID_ARGUMENT);
}
} // namespace
} // namespace cas
} // namespace widevine

View File

@@ -61,6 +61,9 @@ Status WvCasKeyFetcher::CreateEntitlementRequest(
request.set_content_id(request_params.content_id);
request.set_provider(request_params.content_provider);
request.set_key_rotation(request_params.key_rotation);
if (!request_params.group_id.empty()) {
request.set_group_id(request_params.group_id);
}
// Add labels for tracks.
for (const auto& track_type : request_params.track_types) {
request.add_track_types(track_type);

View File

@@ -26,11 +26,15 @@ namespace cas {
// server.
// |key_rotation| the encryption uses two keys if set. Two entitlement keys
// are requested for each track type.
// |group_id| optional field indicates if this is a key used for a group of
// contents. If this field is not empty, entitlement key would be generated
// for the group instead of the single content.
struct EntitlementRequestParams {
std::string content_id;
std::string content_provider;
std::vector<std::string> track_types;
bool key_rotation;
std::string group_id;
};
// WV CAS KeyFetcher. Performs the communication with the Widevine License

View File

@@ -13,6 +13,7 @@
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/strings/escaping.h"
#include "absl/strings/string_view.h"
#include "protos/public/media_cas_encryption.pb.h"
using testing::_;
@@ -37,6 +38,12 @@ const char kSignedCasEncryptionRequest[] =
"RyYWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2V9\",\"signature\":"
"\"JyTnKEy1w98HRP1lL78+OIEiqtIXyfCN8iudvNXIWhw=\",\"signer\":\"widevine_"
"test\"}";
absl::string_view kSignedCasEncryptionRequestForGroupKey =
"{\"request\":"
"\"eyJjb250ZW50X2lkIjoiTWpFeE5EQTRORFE9IiwicHJvdmlkZXIiOiJ3aWRldmluZSIsInRy"
"YWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2UsImdyb3VwX2lkIjoiWjNKdm"
"RYQXgifQ==\",\"signature\":\"iDaAr74ldEV6X1X9kwyLoZ/"
"hfP5RJOkXEzucq6IXyfQ=\",\"signer\":\"widevine_test\"}";
const char kHttpResponse[] =
"{\"response\":"
"\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu"
@@ -47,6 +54,9 @@ const char kTrackTypeSD[] = "SD";
const char kTrackTypeHD[] = "HD";
const char kProvider[] = "widevine";
const char kContentId[] = "21140844";
const char kGroupId[] = "group1";
const char kEntitlementKeyId[] = "fake_key_id.....";
const char kEntitlementKey[] = "fakefakefakefakefakefakefakefake";
} // namespace
namespace widevine {
@@ -89,6 +99,12 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher {
LOG(ERROR) << "Unable to understand signed_request_json.";
return Status(error::INTERNAL);
}
// Validate the parameters shown in the request.
EXPECT_EQ(request.content_id(), kContentId);
EXPECT_EQ(request.provider(), kProvider);
if (request.has_group_id()) {
EXPECT_EQ(request.group_id(), kGroupId);
}
CasEncryptionResponse response;
if (!report_status_ok_) {
@@ -96,24 +112,27 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher {
} else {
response.set_status(CasEncryptionResponse::OK);
response.set_content_id(request.content_id());
if (request.has_group_id()) {
response.set_group_id(request.group_id());
}
for (const auto& track_type : request.track_types()) {
if (request.key_rotation()) {
// Add the Even key.
auto key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_key_id(kEntitlementKeyId);
key->set_key(kEntitlementKey);
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
// Add the Odd key.
key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_key_id(kEntitlementKeyId);
key->set_key(kEntitlementKey);
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
} else {
auto key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_key_id(kEntitlementKeyId);
key->set_key(kEntitlementKey);
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
}
@@ -148,10 +167,12 @@ class WvCasKeyFetcherTest : public ::testing::Test {
protected:
EntitlementRequestParams CreateRequestParamsStruct(
const std::string& content_id, const std::string& content_provider,
const std::vector<std::string>& track_types, bool key_rotation) {
const std::string& group_id, const std::vector<std::string>& track_types,
bool key_rotation) {
EntitlementRequestParams request_params;
request_params.content_id = content_id;
request_params.content_provider = content_provider;
request_params.group_id = group_id;
request_params.track_types.assign(track_types.begin(), track_types.end());
request_params.key_rotation = key_rotation;
return request_params;
@@ -161,8 +182,9 @@ class WvCasKeyFetcherTest : public ::testing::Test {
TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
HardcodedWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation=*/false);
EntitlementRequestParams request_params =
CreateRequestParamsStruct(kContentId, kProvider, /*group_id=*/"",
{kTrackTypeSD}, /*key_rotation=*/false);
std::string signed_request_json;
mock_key_fetcher.CreateEntitlementRequest(request_params,
&signed_request_json);
@@ -180,7 +202,7 @@ TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
&entitlements));
ASSERT_EQ(entitlements.size(), 1);
const EntitlementKeyInfo& entitlement = entitlements.at(0);
const EntitlementKeyInfo& entitlement = entitlements[0];
EXPECT_EQ(entitlement.track_type, "SD");
EXPECT_EQ(entitlement.is_even_key, true);
std::string expected_key_id;
@@ -192,12 +214,62 @@ TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
EXPECT_EQ(entitlement.key_value, expected_key_value);
}
TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, kGroupId, {kTrackTypeSD}, /*key_rotation=*/false);
std::string signed_request_json;
ASSERT_OK(mock_key_fetcher.CreateEntitlementRequest(request_params,
&signed_request_json));
EXPECT_EQ(signed_request_json, kSignedCasEncryptionRequestForGroupKey);
// Parse the signed request json std::string and validate the request proto.
SignedCasEncryptionRequest signed_request;
ASSERT_OK(
google::protobuf::util::JsonStringToMessage(signed_request_json, &signed_request));
CasEncryptionRequest request;
ASSERT_OK(
google::protobuf::util::JsonStringToMessage(signed_request.request(), &request));
EXPECT_EQ(request.content_id(), kContentId);
EXPECT_EQ(request.provider(), kProvider);
EXPECT_EQ(request.group_id(), kGroupId);
EXPECT_EQ(request.track_types_size(), 1);
EXPECT_EQ(request.track_types(0), kTrackTypeSD);
EXPECT_FALSE(request.key_rotation());
std::string actual_signed_response;
EXPECT_OK(mock_key_fetcher.MakeHttpRequest(signed_request_json,
&actual_signed_response));
// Parse the signed response json std::string and validate the response proto.
SignedCasEncryptionResponse signed_response;
ASSERT_OK(google::protobuf::util::JsonStringToMessage(actual_signed_response,
&signed_response));
CasEncryptionResponse response;
ASSERT_OK(
google::protobuf::util::JsonStringToMessage(signed_response.response(), &response));
EXPECT_EQ(response.content_id(), kContentId);
EXPECT_EQ(response.status(), CasEncryptionResponse::OK);
EXPECT_EQ(response.group_id(), kGroupId);
std::vector<EntitlementKeyInfo> entitlements;
EXPECT_OK(mock_key_fetcher.ParseEntitlementResponse(actual_signed_response,
&entitlements));
// Validate the entitlement key info in the response.
ASSERT_EQ(entitlements.size(), 1);
const EntitlementKeyInfo& entitlement = entitlements[0];
EXPECT_EQ(entitlement.track_type, "SD");
EXPECT_EQ(entitlement.is_even_key, true);
EXPECT_EQ(entitlement.key_id, kEntitlementKeyId);
EXPECT_EQ(entitlement.key_value, kEntitlementKey);
}
TEST_F(WvCasKeyFetcherTest, OneKeyOK) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
std::string request;
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ false);
EntitlementRequestParams request_params =
CreateRequestParamsStruct(kContentId, kProvider, /*group_id=*/"",
{kTrackTypeSD}, /*key_rotation=*/false);
ASSERT_OK(
mock_key_fetcher.CreateEntitlementRequest(request_params, &request));
@@ -217,8 +289,9 @@ TEST_F(WvCasKeyFetcherTest, OneKeyOK) {
TEST_F(WvCasKeyFetcherTest, OneKeyConvenientOK) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ false);
EntitlementRequestParams request_params =
CreateRequestParamsStruct(kContentId, kProvider, /*group_id=*/"",
{kTrackTypeSD}, /*key_rotation=*/false);
std::vector<EntitlementKeyInfo> entitlements;
ASSERT_OK(mock_key_fetcher.FetchEntitlements(request_params, &entitlements));
@@ -233,8 +306,9 @@ TEST_F(WvCasKeyFetcherTest, OneKeyConvenientOK) {
TEST_F(WvCasKeyFetcherTest, TwoKeysOK) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ true);
EntitlementRequestParams request_params =
CreateRequestParamsStruct(kContentId, kProvider, /*group_id=*/"",
{kTrackTypeSD}, /*key_rotation=*/true);
std::vector<EntitlementKeyInfo> entitlements;
ASSERT_OK(mock_key_fetcher.FetchEntitlements(request_params, &entitlements));
@@ -247,8 +321,8 @@ TEST_F(WvCasKeyFetcherTest, TwoTracksOK) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD, kTrackTypeHD},
/*key_rotation*/ true);
kContentId, kProvider, /*group_id=*/"", {kTrackTypeSD, kTrackTypeHD},
/*key_rotation=*/true);
std::vector<EntitlementKeyInfo> entitlements;
ASSERT_OK(mock_key_fetcher.FetchEntitlements(request_params, &entitlements));
ASSERT_EQ(entitlements.size(), 4);
@@ -258,8 +332,9 @@ TEST_F(WvCasKeyFetcherTest, BadResponseFail) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
std::string request;
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ true);
EntitlementRequestParams request_params =
CreateRequestParamsStruct(kContentId, kProvider, /*group_id=*/"",
{kTrackTypeSD}, /*key_rotation=*/true);
ASSERT_OK(
mock_key_fetcher.CreateEntitlementRequest(request_params, &request));
std::string response;

View File

@@ -92,6 +92,9 @@ Status CreateWvCasEncryptionRequestJson(const WvCasEncryptionRequest& request,
CasEncryptionRequest request_proto;
request_proto.set_content_id(request.content_id);
request_proto.set_provider(request.provider);
if (!request.group_id.empty()) {
request_proto.set_group_id(request.group_id);
}
for (const std::string& track_type : request.track_types) {
request_proto.add_track_types(track_type);
}
@@ -129,6 +132,7 @@ Status ParseWvCasEncryptionResponseJson(const std::string& response_json,
static_cast<WvCasEncryptionResponse::Status>(response_proto.status());
response->status_message = response_proto.status_message();
response->content_id = response_proto.content_id();
response->group_id = response_proto.group_id();
for (const auto& key_info_proto : response_proto.entitlement_keys()) {
WvCasEncryptionResponse::KeyInfo key_info;
key_info.key_id = key_info_proto.key_id();

View File

@@ -68,6 +68,7 @@ struct EntitlementKeyInfo {
struct WvCasEncryptionRequest {
std::string content_id;
std::string provider;
std::string group_id;
// Track types such as "AUDIO", SD" or "HD".
std::vector<std::string> track_types;
// Indicates if the client is using key rotation. If true, the server will
@@ -104,6 +105,7 @@ struct WvCasEncryptionResponse {
Status status;
std::string status_message;
std::string content_id;
std::string group_id;
std::vector<KeyInfo> entitlement_keys;
};

View File

@@ -83,6 +83,17 @@ TEST(WvCasTypesTest, CreateWvCasEncryptionRequestJson) {
"{\"content_id\":\"dGVzdF9jb250ZW50X2lk\",\"provider\":\"test_provider\","
"\"track_types\":[\"SD\",\"AUDIO\"],\"key_rotation\":true}";
EXPECT_EQ(expected_request_json, actual_request_json);
// Set group_id in the request.
request.group_id = "test_group_id";
actual_request_json.clear();
EXPECT_OK(CreateWvCasEncryptionRequestJson(request, &actual_request_json));
// Content_id and group_id have been base64 encoded.
std::string expected_request_json_with_group_id =
"{\"content_id\":\"dGVzdF9jb250ZW50X2lk\",\"provider\":\"test_provider\","
"\"track_types\":[\"SD\",\"AUDIO\"],\"key_rotation\":true,\"group_id\":"
"\"dGVzdF9ncm91cF9pZA==\"}";
EXPECT_EQ(actual_request_json, expected_request_json_with_group_id);
}
TEST(WvCasTypesTest, ParseWvCasEncryptionResponseJson) {
@@ -93,7 +104,7 @@ TEST(WvCasTypesTest, ParseWvCasEncryptionResponseJson) {
"\"track_type\":\"SD\",\"key_slot\":\"EVEN\"},{\"key_id\":"
"\"ZmFrZV9rZXlfaWQyLi4uLg==\",\"key\":"
"\"ZmFrZWZha2VmYWtlZmFrZWZha2VmYWtlZmFrZTIuLi4=\",\"track_type\":\"SD\","
"\"key_slot\":\"ODD\"}]}";
"\"key_slot\":\"ODD\"}],\"group_id\":\"Z3JvdXAx\"}";
WvCasEncryptionResponse actual_response;
EXPECT_OK(ParseWvCasEncryptionResponseJson(response_json, &actual_response));
@@ -116,6 +127,8 @@ TEST(WvCasTypesTest, ParseWvCasEncryptionResponseJson) {
EXPECT_EQ("SD", actual_response.entitlement_keys.at(1).track_type);
EXPECT_EQ(WvCasEncryptionResponse::KeyInfo::KeySlot::ODD,
actual_response.entitlement_keys.at(1).key_slot);
// "group1" is base64 decode of "Z3JvdXAx".
EXPECT_EQ(actual_response.group_id, "group1");
}
} // namespace cas

View File

@@ -20,8 +20,19 @@
#include "glog/logging.h"
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "media_cas_packager_sdk/internal/emmg.h"
namespace {
// Maximum number of entitlement key ids shown in private data.
constexpr uint16_t kMaxNumOfEntitlementKeyIds = 2;
// Entitlement key id length is fixed to 16 bytes.
constexpr uint16_t kEntitlementKeyIdLengthBytes = 16;
constexpr int32_t kEmmDataType = 0;
constexpr int32_t kPrivateDataType = 1;
} // namespace
ABSL_FLAG(std::string, mux_address, "", "Mux server address.");
ABSL_FLAG(int32_t, mux_port, 0, "Mux server port number.");
// EMMG specific configs.
@@ -40,43 +51,167 @@ ABSL_FLAG(int32_t, data_id, 0, "EMMG data_id.");
// 0x01: private data;
// 0x02: DVB reserved (ECM);
// other values: DVB reserved.
ABSL_FLAG(int32_t, data_type, 1, "EMMG data_type");
ABSL_FLAG(std::string, content_provider, "", "Content provider");
ABSL_FLAG(std::string, content_id, "", "Content id");
ABSL_FLAG(int32_t, data_type, kPrivateDataType,
"EMMG data_type, must be either 0 (EMM) or 1 (Private data).");
ABSL_FLAG(std::string, content_provider, "",
"Content provider to put into priavte data.");
ABSL_FLAG(std::string, content_id, "", "Content id to put into priavte data");
ABSL_FLAG(
std::vector<std::string>, entitlement_key_ids, {},
"Comma-separated list of entitlement_key_ids to put into private data");
ABSL_FLAG(int32_t, bandwidth, 100, "Requested bandwidth in kbps");
ABSL_FLAG(int32_t, max_num_message, 100,
"Maximum number of messages that can be sent");
ABSL_FLAG(std::string, ecc_signing_key, "",
"Signing key used to sign EMM data. Must be elliptic-curve "
"cryptography key.");
ABSL_FLAG(std::string, fingerprinting, "",
"Fingerprinting info to put into EMM data. Must follow this format:\n"
"1. Multiple fingerprintings are separated by \'||\';\n"
"2. Different fields (e.g., channels) within a fingerprinting are "
"separated by \';\'. All fields must present (channels;control);\n"
"3. Value list within a field is separated by \',\'.\n"
"Example: channel1,channel2;control||channel3;control2");
ABSL_FLAG(
std::string, service_blocking, "",
"Service blocking info to put into EMM data. Must follow this format:\n"
"1. Multiple service_blockings are separated by \'||\';\n"
"2. Different fields (e.g., channels) within a service_blocking are "
"separated by \';\'. All fields must present (channels;device_groups;"
"start_time;end_time). The start/end time are Epoch time in seconds with "
"start_time = 0 means immediate;\n"
"3. Value list within a field is separated by \',\'.\n"
"Example: channel1,channel2;device_group1,device_group2;0;1597186636"
"||channel3;device_group3;1597186636,1597186637");
#define BUFFER_SIZE (1024)
void CheckEmmgUsage(const widevine::cas::EmmgConfig& config) {
// Emmg is configured as EMM generator.
if (config.data_type == kEmmDataType) {
LOG_IF(WARNING, !config.content_provider.empty())
<< "content_provider is set but ignored as data_type is set to 0 (EMM "
"data).";
LOG_IF(WARNING, !config.content_id.empty())
<< "content_id is set but ignored as data_type is set to 0 (EMM data).";
LOG_IF(WARNING, !config.entitlement_key_ids.empty())
<< "entitlement_key_ids is set but ignored as data_type is set to 0 "
"(EMM data).";
}
void BuildEmmgConfig(widevine::cas::EmmgConfig *config) {
// Emmg is configured as private data generator (PDG).
if (config.data_type == kPrivateDataType) {
LOG_IF(WARNING, !config.ecc_signing_key.empty())
<< "ecc_signing_key is set but ignored as data_type is set to 1 "
"(Private data).";
LOG_IF(WARNING, !config.fingerprinting.empty())
<< "fingerprinting is set but ignored as data_type is set to 1 "
"(Private data).";
LOG_IF(WARNING, !config.service_blocking.empty())
<< "service_blocking is set but ignored as data_type is set to 1 "
"(Private data).";
}
}
void BuildEmmgConfig(widevine::cas::EmmgConfig* config) {
CHECK(config);
config->client_id = absl::GetFlag(FLAGS_client_id);
config->section_tspkt_flag = absl::GetFlag(FLAGS_section_tspkt_flag);
config->data_channel_id = absl::GetFlag(FLAGS_data_channel_id);
config->data_stream_id = absl::GetFlag(FLAGS_data_stream_id);
config->data_id = absl::GetFlag(FLAGS_data_id);
config->data_type = absl::GetFlag(FLAGS_data_type);
config->content_provider = absl::GetFlag(FLAGS_content_provider);
config->content_id = absl::GetFlag(FLAGS_content_id);
config->entitlement_key_ids = absl::GetFlag(FLAGS_entitlement_key_ids);
config->bandwidth = absl::GetFlag(FLAGS_bandwidth);
config->max_num_message = absl::GetFlag(FLAGS_max_num_message);
// Check and set data_type.
config->data_type = absl::GetFlag(FLAGS_data_type);
CHECK(config->data_type == kEmmDataType ||
config->data_type == kPrivateDataType)
<< "Data type must be either 0 (EMM) or 1 (Private data).";
// Below are private data specific configurations.
config->content_provider = absl::GetFlag(FLAGS_content_provider);
config->content_id = absl::GetFlag(FLAGS_content_id);
// Check and set entitlement_key_ids.
config->entitlement_key_ids = absl::GetFlag(FLAGS_entitlement_key_ids);
CHECK_LE(config->entitlement_key_ids.size(), kMaxNumOfEntitlementKeyIds)
<< absl::StrCat("Number of entitlement key ids shouldn't exceed ",
kMaxNumOfEntitlementKeyIds);
for (const auto& entitlement_key_id : config->entitlement_key_ids) {
CHECK_EQ(entitlement_key_id.size(), kEntitlementKeyIdLengthBytes)
<< absl::StrCat("Entitlement key id length must be ",
kEntitlementKeyIdLengthBytes,
". The offending key id is ", entitlement_key_id);
}
// Below are EMM data specific configurations.
config->ecc_signing_key = absl::GetFlag(FLAGS_ecc_signing_key);
if (config->data_type == kEmmDataType) {
CHECK(!config->ecc_signing_key.empty())
<< "ECC signing key is required to generate EMM data.";
}
// Check and set fingerprinting.
std::vector<std::string> fingerprintings = absl::StrSplit(
absl::GetFlag(FLAGS_fingerprinting), "||", absl::SkipWhitespace());
config->fingerprinting.reserve(fingerprintings.size());
for (const auto& fingerprinting : fingerprintings) {
config->fingerprinting.emplace_back();
widevine::cas::FingerprintingInitParameters& fingerprinting_inserted =
config->fingerprinting.back();
std::vector<std::string> fields = absl::StrSplit(fingerprinting, ';');
CHECK_EQ(fields.size(), 2)
<< "Each fingerprinting must contain exactly 2 fields (channels;"
"control) separated by \';\'.";
fingerprinting_inserted.channels = absl::StrSplit(fields[0], ',');
CHECK(!fingerprinting_inserted.channels.empty())
<< "Channels in fingerprinting must be specified.";
fingerprinting_inserted.control = fields[1];
CHECK(!fingerprinting_inserted.control.empty())
<< "Conrtol in fingerprinting must be specified.";
}
// Check and set service_blocking.
std::vector<std::string> service_blockings = absl::StrSplit(
absl::GetFlag(FLAGS_service_blocking), "||", absl::SkipWhitespace());
config->service_blocking.reserve(service_blockings.size());
for (const auto& service_blocking : service_blockings) {
config->service_blocking.emplace_back();
widevine::cas::ServiceBlockingInitParameters&
service_blocking_inserted = config->service_blocking.back();
std::vector<std::string> fields = absl::StrSplit(service_blocking, ';');
CHECK_EQ(fields.size(), 4)
<< "Each service blocking must contain exactly 4 fields (channels;"
"device_groups;start_time;end_time) separated by \';\'.";
service_blocking_inserted.channels = absl::StrSplit(fields[0], ',');
CHECK(!service_blocking_inserted.channels.empty())
<< "Channels in service blocking must be specified.";
service_blocking_inserted.device_groups = absl::StrSplit(fields[1], ',');
CHECK(!service_blocking_inserted.device_groups.empty())
<< "Device groups in service blocking must be specified.";
service_blocking_inserted.start_time = std::stoll(fields[2]);
service_blocking_inserted.end_time = std::stoll(fields[3]);
CHECK_NE(service_blocking_inserted.end_time, 0)
<< "End time in service blocking must be non zero.";
}
}
int main(int argc, char **argv) {
int main(int argc, char** argv) {
absl::ParseCommandLine(argc, argv);
CHECK(!absl::GetFlag(FLAGS_mux_address).empty())
<< "flag --mux_address is required";
CHECK(absl::GetFlag(FLAGS_mux_port) != 0) << "flag --mux_port is required";
widevine::cas::EmmgConfig emmg_config;
BuildEmmgConfig(&emmg_config);
CheckEmmgUsage(emmg_config);
// Create server address.
struct hostent ret;
struct hostent *server;
char buffer[BUFFER_SIZE];
struct hostent* server;
char buffer[1024];
int h_errnop = 0;
int return_val =
gethostbyname_r(absl::GetFlag(FLAGS_mux_address).c_str(), &ret, buffer,
@@ -86,10 +221,10 @@ int main(int argc, char **argv) {
<< absl::GetFlag(FLAGS_mux_address);
}
struct sockaddr_in server_address;
bzero(reinterpret_cast<char *>(&server_address), sizeof(server_address));
bzero(reinterpret_cast<char*>(&server_address), sizeof(server_address));
server_address.sin_family = AF_INET;
bcopy(server->h_addr,
reinterpret_cast<char *>(&server_address.sin_addr.s_addr),
reinterpret_cast<char*>(&server_address.sin_addr.s_addr),
server->h_length);
server_address.sin_port = htons(absl::GetFlag(FLAGS_mux_port));
@@ -99,14 +234,12 @@ int main(int argc, char **argv) {
LOG(FATAL) << "Failed to create socket.";
}
if (connect(server_socket_fd,
reinterpret_cast<struct sockaddr *>(&server_address),
reinterpret_cast<struct sockaddr*>(&server_address),
sizeof(server_address)) < 0) {
LOG(FATAL) << "Failed to connect to server.";
}
// Send EMMG messages.
widevine::cas::EmmgConfig emmg_config;
BuildEmmgConfig(&emmg_config);
widevine::cas::Emmg emmg(&emmg_config, server_socket_fd);
emmg.Start();