Update Simulcrypt ECMg

This commit is contained in:
Lu Chen
2020-07-24 18:17:12 -07:00
parent ed5a1d5db1
commit 785df31261
97 changed files with 3671 additions and 987 deletions

View File

@@ -90,6 +90,7 @@ cc_library(
"@abseil_repo//absl/container:node_hash_map",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/types:optional",
"//common:crypto_util",
"//common:random_util",
"//common:status",
@@ -242,3 +243,25 @@ cc_test(
"//common:status",
],
)
cc_library(
name = "emm",
srcs = ["emm.cc"],
hdrs = ["emm.h"],
deps = [
"//base",
"@abseil_repo//absl/strings",
"//common:status",
"//common:string_util",
"//protos/public:media_cas_cc_proto",
],
)
cc_test(
name = "emm_test",
srcs = ["emm_test.cc"],
deps = [
":emm",
"//testing:gunit_main",
],
)

View File

@@ -95,10 +95,10 @@ class MockEcm : public Ecm {
MockEcm() = default;
~MockEcm() override = default;
MOCK_CONST_METHOD0(age_restriction, uint8_t());
MOCK_CONST_METHOD0(crypto_mode, CryptoMode());
MOCK_CONST_METHOD0(paired_keys_required, bool());
MOCK_CONST_METHOD0(content_iv_size, size_t());
MOCK_METHOD(uint8_t, age_restriction, (), (const, override));
MOCK_METHOD(CryptoMode, crypto_mode, (), (const, override));
MOCK_METHOD(bool, paired_keys_required, (), (const, override));
MOCK_METHOD(size_t, content_iv_size, (), (const, override));
std::string CallSerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
return SerializeEcm(keys);

View File

@@ -8,6 +8,7 @@
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
#include <cstddef>
#include <cstring>
#include "glog/logging.h"
@@ -873,32 +874,55 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
EcmgStreamInfo* stream_info = streams_info_.at(params.ecm_stream_id).get();
DCHECK(stream_info->ecm);
size_t key_count = params.cp_cw_combinations.size();
// Number of keys can only be 1 or 2.
if (key_count != ecmg_config_->number_of_content_keys || key_count < 1 ||
key_count > 2) {
return {error::INVALID_ARGUMENT, "Unexpected cp_cw_combinations size."};
}
// Generate serialized ECM.
CryptoMode crypto_mode = stream_info->crypto_mode == CryptoMode::kInvalid
? ecmg_config_->crypto_mode
: stream_info->crypto_mode;
// If two keys are present, the even key should be put first, followed by the
// odd key.
std::vector<EntitledKeyInfo> keys;
keys.reserve(ecmg_config_->number_of_content_keys);
for (size_t i = 0; i < ecmg_config_->number_of_content_keys; i++) {
DCHECK(params.cp_cw_combinations[i].cp == params.cp_number + i);
keys.emplace_back();
keys[i].key_value = params.cp_cw_combinations[i].cw;
keys.reserve(key_count);
for (const auto& cp_cw : params.cp_cw_combinations) {
auto key_info = cp_cw.cp % 2 == 0 ? keys.emplace(keys.begin())
: keys.emplace(keys.end());
key_info->key_value = cp_cw.cw;
// Make content key to 16 bytes if crypto mode is Csa2.
if (crypto_mode == CryptoMode::kDvbCsa2 && keys[i].key_value.size() == 8) {
keys[i].key_value = keys[i].key_value + keys[i].key_value;
if (crypto_mode == CryptoMode::kDvbCsa2 &&
key_info->key_value.size() == 8) {
key_info->key_value =
absl::StrCat(key_info->key_value, key_info->key_value);
}
keys[i].key_id = crypto_util::DeriveKeyId(keys[i].key_value);
keys[i].content_iv = stream_info->content_ivs.empty()
? content_ivs_[i]
: stream_info->content_ivs[i];
if (!RandomBytes(kWrappedKeyIvSizeBytes, &keys[i].wrapped_key_iv)) {
key_info->key_id = crypto_util::DeriveKeyId(key_info->key_value);
auto generated_key_iv = GenerateRandomWrappedKeyIv();
if (!generated_key_iv.has_value() ||
generated_key_iv->size() != kWrappedKeyIvSizeBytes) {
return {error::INTERNAL, "Unable to generate random wrapped key iv."};
}
key_info->wrapped_key_iv = generated_key_iv.value();
}
if (content_ivs_.size() < key_count &&
stream_info->content_ivs.size() < key_count) {
return {error::INVALID_ARGUMENT, "Not enough content iv."};
}
// The first iv received is for even key and second is for odd key.
for (size_t i = 0; i < key_count; ++i) {
keys[i].content_iv = stream_info->content_ivs.size() >= key_count
? stream_info->content_ivs[i]
: content_ivs_[i];
}
Status status;
std::string serialized_ecm;
if (ecmg_config_->number_of_content_keys > 1) {
if (key_count > 1) {
status = stream_info->ecm->GenerateEcm(
&keys[0], &keys[1], stream_info->track_type, &serialized_ecm);
} else {
@@ -926,5 +950,14 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
return OkStatus();
}
absl::optional<std::string> EcmgClientHandler::GenerateRandomWrappedKeyIv()
const {
std::string output_iv;
if (RandomBytes(kWrappedKeyIvSizeBytes, &output_iv)) {
return output_iv;
}
return absl::nullopt;
}
} // namespace cas
} // namespace widevine

View File

@@ -16,6 +16,7 @@
#include <cstdint>
#include "absl/container/node_hash_map.h"
#include "absl/types/optional.h"
#include "common/status.h"
#include "media_cas_packager_sdk/internal/ecm.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
@@ -126,6 +127,10 @@ class EcmgClientHandler {
Status BuildEcmDatagram(const EcmgParameters& params,
uint8_t* ecm_datagram) const;
// Generates a random wrapped key iv string. Returns true on success, false
// otherwise. The main purpose for this function is easier testing.
virtual absl::optional<std::string> GenerateRandomWrappedKeyIv() const;
EcmgConfig* ecmg_config_;
// Per spec, "There is always one (and only one) channel per TCP connection".
bool channel_id_set_;

View File

@@ -12,12 +12,14 @@
#include <stdio.h>
#include <string.h>
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "example/test_ecmg_messages.h"
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/internal/simulcrypt_util.h"
#include "media_cas_packager_sdk/internal/util.h"
@@ -31,28 +33,36 @@ using simulcrypt_util::AddUint32Param;
using simulcrypt_util::AddUint8Param;
using simulcrypt_util::BuildMessageHeader;
static constexpr size_t kBufferSize = 1024;
static constexpr size_t kSuperCasId = 0x4AD40000;
static constexpr size_t kChannelId = 1;
static constexpr size_t kStreamId = 1;
static constexpr size_t kEcmId = 2;
static constexpr size_t kNominalCpDuration = 0x64;
static constexpr size_t kCpNumber = 0;
static constexpr char kContentKeyEven[] = "0123456701234567";
static constexpr char kContentKeyEven8Bytes[] = "01234567";
static constexpr char kContentKeyOdd[] = "abcdefghabcdefgh";
static constexpr char kContentKeyOdd8Bytes[] = "abcdefgh";
static constexpr char kEntitlementKeyIdEven[] = "0123456701234567";
static constexpr char kEntitlementKeyValueEven[] =
"01234567012345670123456701234567";
static constexpr char kEntitlementKeyIdOdd[] = "abcdefghabcdefgh";
static constexpr char kEntitlementKeyValueOdd[] =
"abcdefghabcdefghabcdefghabcdefgh";
static constexpr size_t kAgeRestriction = 3;
static constexpr char kCryptoMode[] = "AesScte";
static constexpr char kCryptoModeCsa2[] = "DvbCsa2";
static constexpr char kTrackTypesSD[] = "SD";
static constexpr char kTrackTypesHD[] = "HD";
constexpr size_t kBufferSize = 1024;
constexpr size_t kSuperCasId = 0x4AD40000;
constexpr size_t kChannelId = 1;
constexpr size_t kStreamId = 1;
constexpr size_t kEcmId = 2;
constexpr size_t kNominalCpDuration = 0x64;
constexpr size_t kCpNumber = 0;
constexpr char kContentKeyEven[] = "0123456701234567";
constexpr char kContentKeyEven8Bytes[] = "01234567";
constexpr char kContentKeyOdd[] = "abcdefghabcdefgh";
constexpr char kContentKeyOdd8Bytes[] = "abcdefgh";
constexpr char kEntitlementKeyIdEven[] = "0123456701234567";
constexpr char kEntitlementKeyValueEven[] = "01234567012345670123456701234567";
constexpr char kEntitlementKeyIdOdd[] = "abcdefghabcdefgh";
constexpr char kEntitlementKeyValueOdd[] = "abcdefghabcdefghabcdefghabcdefgh";
constexpr size_t kAgeRestriction = 3;
constexpr char kCryptoMode[] = "AesScte";
constexpr char kCryptoModeCsa2[] = "DvbCsa2";
constexpr char kTrackTypesSD[] = "SD";
constexpr char kTrackTypesHD[] = "HD";
constexpr absl::string_view kWrappedKeyIv = "0123456701234567";
class MockEcmgClientHandler : public EcmgClientHandler {
public:
explicit MockEcmgClientHandler(EcmgConfig* ecmg_config)
: EcmgClientHandler(ecmg_config) {}
absl::optional<std::string> GenerateRandomWrappedKeyIv() const override {
return std::string(kWrappedKeyIv);
}
};
class EcmgClientHandlerTest : public ::testing::Test {
protected:
@@ -63,7 +73,7 @@ class EcmgClientHandlerTest : public ::testing::Test {
config_.max_comp_time = 100;
config_.access_criteria_transfer_mode = 1;
config_.number_of_content_keys = 2;
handler_ = absl::make_unique<EcmgClientHandler>(&config_);
handler_ = absl::make_unique<MockEcmgClientHandler>(&config_);
}
protected:
@@ -564,6 +574,37 @@ TEST_F(EcmgClientHandlerTest, WrongMessageLength) {
CheckChannelError(UNKNOWN_PARAMETER_TYPE_VALUE, response_, response_len_);
}
TEST_F(EcmgClientHandlerTest, BuildEcmDatagramSequenceOfEvenOdd) {
SetupValidChannelStream();
std::vector<EcmgCpCwCombination> cp_cw_combination = {
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(response_len_, sizeof(kTestEcmgEcmResponse));
std::string first_response(response_, response_len_);
// Change the sequence of cp_cw_combination.
cp_cw_combination = {{kCpNumber + 1, kContentKeyOdd},
{kCpNumber, kContentKeyEven}};
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
// Sequence of cp_cw_combination does not matter as even/odd is based on cp
// number.
EXPECT_EQ(std::string(response_, response_len_), first_response);
// Swap the key value in cp_cw_combination.
cp_cw_combination = {{kCpNumber, kContentKeyOdd},
{kCpNumber + 1, kContentKeyEven}};
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
// Swapping key value changes generated ecm.
EXPECT_NE(std::string(response_, response_len_), first_response);
}
} // namespace
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,136 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2020 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/internal/emm.h"
#include <bitset>
#include "glog/logging.h"
#include "absl/strings/str_cat.h"
#include "common/status.h"
#include "common/string_util.h"
namespace widevine {
namespace cas {
namespace {
constexpr int kNumBitsVersionField = 8;
constexpr int kNumBitsHeaderLengthField = 8;
constexpr int kNumBitsTimestampLengthField = 64;
constexpr int kNumBitsPayloadLengthField = 16;
// Version - this should be incremented if there are changes to the EMM.
constexpr uint8_t kEmmVersion = 1;
} // namespace
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_param : fingerprintings) {
Fingerprinting* fingerprinting_payload = emm_payload_.add_fingerprinting();
for (const auto& channel : fingerprinting_param.channels) {
fingerprinting_payload->add_channels(channel);
}
fingerprinting_payload->set_control(fingerprinting_param.control);
}
return status;
}
Status Emm::SetServiceBlocking(
const std::vector<ServiceBlockingInitParameters>& service_blockings) {
// 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_param : service_blockings) {
ServiceBlocking* service_blocking_payload =
emm_payload_.add_service_blocking();
for (const auto& channel : service_blocking_param.channels) {
service_blocking_payload->add_channels(channel);
}
for (const auto& device_group : service_blocking_param.device_groups) {
service_blocking_payload->add_device_groups(device_group);
}
if (service_blocking_param.start_time != 0) {
service_blocking_payload->set_start_time_sec(
service_blocking_param.start_time);
}
service_blocking_payload->set_end_time_sec(service_blocking_param.end_time);
}
return status;
}
Status Emm::GenerateEmm(std::string* serialized_emm) const {
if (serialized_emm == nullptr) {
return {error::INVALID_ARGUMENT, "No return emm std::string pointer."};
}
EmmSerializingParameters serializing_params;
serializing_params.payload = emm_payload_.SerializeAsString();
serializing_params.timestamp = GenerateTimestamp();
// Generate serialized emm (without signature yet).
Status status =
GenerateSerializedEmmNoSignature(serializing_params, serialized_emm);
if (!status.ok()) {
return status;
}
// Calculate and append signature.
absl::StrAppend(serialized_emm, GenerateSignature(*serialized_emm));
return OkStatus();
}
Status Emm::GenerateSerializedEmmNoSignature(
const EmmSerializingParameters& params, std::string* serialized_emm) const {
if (serialized_emm == nullptr) {
return {error::INVALID_ARGUMENT, "No return emm std::string pointer."};
}
std::bitset<kNumBitsVersionField> version(kEmmVersion);
std::bitset<kNumBitsHeaderLengthField> header_length(
sizeof(params.timestamp));
std::bitset<kNumBitsTimestampLengthField> timestamp(params.timestamp);
std::bitset<kNumBitsPayloadLengthField> payload_length(
params.payload.length());
std::string emm_bitset =
absl::StrCat(version.to_string(), header_length.to_string(),
timestamp.to_string(), payload_length.to_string());
Status status =
string_util::BitsetStringToBinaryString(emm_bitset, serialized_emm);
if (!status.ok() || serialized_emm->empty()) {
LOG(ERROR) << "Failed to convert EMM bitset to std::string";
return {error::INTERNAL, "Failed to convert EMM bitset to std::string"};
}
// Appends payload.
absl::StrAppend(serialized_emm, params.payload);
return OkStatus();
}
int64_t Emm::GenerateTimestamp() const {
// TODO(b/161252065): Generate timestamp.
return 0;
}
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;
}
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,74 @@
////////////////////////////////////////////////////////////////////////////////
// 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_INTERNAL_EMM_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMM_H_
#include <string>
#include <vector>
#include <cstdint>
#include "common/status.h"
#include "protos/public/media_cas.pb.h"
namespace widevine {
namespace cas {
struct FingerprintingInitParameters {
std::vector<std::string> channels;
std::string control;
};
struct ServiceBlockingInitParameters {
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;
};
// 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.
// Class Emm is not thread safe.
class Emm {
public:
Emm() = default;
Emm(const Emm&) = delete;
Emm& operator=(const Emm&) = delete;
virtual ~Emm() = default;
// Replaces current fingerprinting info with |fingerprintings|.
Status SetFingerprinting(
const std::vector<FingerprintingInitParameters>& fingerprintings);
// Replaces current service blocking info with |service_blockings|.
Status SetServiceBlocking(
const std::vector<ServiceBlockingInitParameters>& service_blockings);
// Generates serialized EMM to |serialized_emm|.
Status GenerateEmm(std::string* serialized_emm) const;
private:
struct EmmSerializingParameters {
int64_t timestamp;
std::string payload;
};
Status GenerateSerializedEmmNoSignature(
const EmmSerializingParameters& params,
std::string* serialized_emm) const;
int64_t GenerateTimestamp() const;
std::string GenerateSignature(const std::string& content) const;
EmmPayload emm_payload_;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMM_H_

View File

@@ -0,0 +1,236 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2020 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/internal/emm.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
namespace widevine {
namespace cas {
namespace {
constexpr char kChannelOne[] = "CH1";
constexpr char kChannelTwo[] = "CH2";
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;
// 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;
FingerprintingInitParameters GetValidFingerprintingParams() {
FingerprintingInitParameters fingerprinting_params;
fingerprinting_params.channels = {kChannelOne, kChannelTwo};
fingerprinting_params.control = kFingerprintingControl;
return fingerprinting_params;
}
void LoadExpectedFingerprintingProto(Fingerprinting* fingerprinting_payload) {
fingerprinting_payload->add_channels(kChannelOne);
fingerprinting_payload->add_channels(kChannelTwo);
fingerprinting_payload->set_control(kFingerprintingControl);
}
ServiceBlockingInitParameters GetValidServiceBlockingParams() {
ServiceBlockingInitParameters service_blocking_params;
service_blocking_params.channels = {kChannelOne, kChannelTwo};
service_blocking_params.device_groups = {kDeviceGroupOne, kDeviceGroupTwo};
service_blocking_params.start_time = kServiceBockingStartTime;
service_blocking_params.end_time = kServiceBockingEndTime;
return service_blocking_params;
}
void LoadExpectedServiceBlockingProto(
ServiceBlocking* service_blocking_payload) {
service_blocking_payload->add_channels(kChannelOne);
service_blocking_payload->add_channels(kChannelTwo);
service_blocking_payload->add_device_groups(kDeviceGroupOne);
service_blocking_payload->add_device_groups(kDeviceGroupTwo);
service_blocking_payload->set_start_time_sec(kServiceBockingStartTime);
service_blocking_payload->set_end_time_sec(kServiceBockingEndTime);
}
TEST(EmmTest, GenerateEmmSinglePayloadSuccess) {
Emm emm_gen;
FingerprintingInitParameters fingerprinting = GetValidFingerprintingParams();
ServiceBlockingInitParameters service_blocking =
GetValidServiceBlockingParams();
EXPECT_EQ(emm_gen.SetFingerprinting({fingerprinting}), OkStatus());
EXPECT_EQ(emm_gen.SetServiceBlocking({service_blocking}), OkStatus());
std::string result;
EXPECT_EQ(emm_gen.GenerateEmm(&result), OkStatus());
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
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]);
ASSERT_GT(payload_lengh, 0);
ASSERT_EQ(result.length(),
kPayloadStartIndex + payload_lengh + kSignatureLength);
std::string payload_section =
result.substr(kPayloadStartIndex, payload_lengh);
// Parse the payload and validate fields.
EmmPayload emm_payload;
ASSERT_TRUE(emm_payload.ParseFromString(payload_section));
EmmPayload expected_payload;
LoadExpectedFingerprintingProto(expected_payload.add_fingerprinting());
LoadExpectedServiceBlockingProto(expected_payload.add_service_blocking());
std::string serialized_expected_payload;
expected_payload.SerializeToString(&serialized_expected_payload);
EXPECT_EQ(payload_section, serialized_expected_payload);
}
TEST(EmmTest, GenerateEmmMultiplePayloadSuccess) {
Emm emm_gen;
FingerprintingInitParameters fingerprinting_params;
fingerprinting_params.channels = {kChannelThree};
fingerprinting_params.control = kFingerprintingControl;
EXPECT_EQ(emm_gen.SetFingerprinting(
{GetValidFingerprintingParams(), fingerprinting_params}),
OkStatus());
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());
std::string result;
EXPECT_EQ(emm_gen.GenerateEmm(&result), OkStatus());
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
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]);
ASSERT_GT(payload_lengh, 0);
ASSERT_EQ(result.length(),
kPayloadStartIndex + payload_lengh + kSignatureLength);
std::string payload_section =
result.substr(kPayloadStartIndex, payload_lengh);
// Parse the payload and validate fields.
EmmPayload emm_payload;
ASSERT_TRUE(emm_payload.ParseFromString(payload_section));
EXPECT_EQ(emm_payload.fingerprinting_size(), 2);
EXPECT_EQ(emm_payload.service_blocking_size(), 2);
}
TEST(EmmTest, GenerateEmmFingerprintingOnlySuccess) {
Emm emm_gen;
FingerprintingInitParameters fingerprinting = GetValidFingerprintingParams();
EXPECT_EQ(emm_gen.SetFingerprinting({fingerprinting}), OkStatus());
// OK to be called again.
EXPECT_EQ(emm_gen.SetFingerprinting({fingerprinting}), OkStatus());
std::string result;
EXPECT_EQ(emm_gen.GenerateEmm(&result), OkStatus());
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
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]);
ASSERT_GT(payload_lengh, 0);
ASSERT_EQ(result.length(),
kPayloadStartIndex + payload_lengh + kSignatureLength);
std::string payload_section =
result.substr(kPayloadStartIndex, payload_lengh);
// Parse the payload and validate fields.
EmmPayload emm_payload;
ASSERT_TRUE(emm_payload.ParseFromString(payload_section));
EmmPayload expected_payload;
LoadExpectedFingerprintingProto(expected_payload.add_fingerprinting());
std::string serialized_expected_payload;
expected_payload.SerializeToString(&serialized_expected_payload);
EXPECT_EQ(payload_section, serialized_expected_payload);
}
TEST(EmmTest, GenerateEmmServiceBlockingOnlySuccess) {
Emm emm_gen;
ServiceBlockingInitParameters service_blocking =
GetValidServiceBlockingParams();
EXPECT_EQ(emm_gen.SetServiceBlocking({service_blocking}), OkStatus());
std::string result;
EXPECT_EQ(emm_gen.GenerateEmm(&result), OkStatus());
EXPECT_GT(result.length(), kExpectedNoPayloadLengthBytes);
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]);
ASSERT_GT(payload_lengh, 0);
ASSERT_EQ(result.length(),
kPayloadStartIndex + payload_lengh + kSignatureLength);
std::string payload_section =
result.substr(kPayloadStartIndex, payload_lengh);
// Parse the payload and validate fields.
EmmPayload emm_payload;
ASSERT_TRUE(emm_payload.ParseFromString(payload_section));
EmmPayload expected_payload;
LoadExpectedServiceBlockingProto(expected_payload.add_service_blocking());
std::string serialized_expected_payload;
expected_payload.SerializeToString(&serialized_expected_payload);
EXPECT_EQ(payload_section, serialized_expected_payload);
}
TEST(EmmTest, GenerateEmmNoPayloadSuccess) {
Emm emm_gen;
std::string result;
EXPECT_EQ(emm_gen.GenerateEmm(&result), OkStatus());
EXPECT_EQ(result.length(), kExpectedNoPayloadLengthBytes);
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]);
EXPECT_EQ(payload_lengh, 0);
EXPECT_EQ(result.length(), kPayloadStartIndex + kSignatureLength);
}
} // namespace
} // namespace cas
} // namespace widevine

View File

@@ -28,6 +28,10 @@
// 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 {
@@ -153,8 +157,8 @@ void Emmg::Start() {
for (size_t i = 0; i < emmg_config_->max_num_message; i++) {
SendDataProvision();
absl::SleepFor(
absl::Milliseconds(std::max(KMinSendIntervalMs, send_interval_ms_)));
absl::SleepFor(std::max(absl::Milliseconds(KMinSendIntervalMs),
absl::Milliseconds(send_interval_ms_)));
}
SendStreamCloseRequest();
@@ -220,13 +224,33 @@ void Emmg::BuildStreamBwRequest() {
Host16ToBigEndian(request_ + 3, &total_param_length);
}
Status Emmg::GeneratePrivateData(const std::string& content_provider,
const std::string& content_id, uint8_t* buffer) {
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);
// 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);
}
std::string private_data_str = private_data.SerializeAsString();
std::string payload_filler(kMaxTsPayloadSize - private_data_str.size(), 0);
@@ -268,8 +292,13 @@ void Emmg::BuildDataProvision() {
&request_length_);
uint8_t datagram[kTsPacketSize];
GeneratePrivateData(emmg_config_->content_provider, emmg_config_->content_id,
datagram);
Status status = GeneratePrivateData(
emmg_config_->content_provider, emmg_config_->content_id,
emmg_config_->entitlement_key_ids, datagram);
if (!status.ok()) {
LOG(ERROR) << "Fail to generate private data. " << status.ToString();
return;
}
simulcrypt_util::AddParam(EMMG_DATAGRAM, datagram, kTsPacketSize, request_,
&request_length_);

View File

@@ -11,6 +11,7 @@
#include <cstddef>
#include <string>
#include <vector>
#include <cstdint>
#include "common/status.h"
@@ -30,6 +31,7 @@ struct EmmgConfig {
uint8_t data_type;
std::string content_provider;
std::string content_id;
std::vector<std::string> entitlement_key_ids;
uint16_t bandwidth;
uint32_t max_num_message;
};
@@ -82,8 +84,9 @@ class Emmg {
void UpdateSendInterval(uint16_t bandwidth_kbps);
Status GeneratePrivateData(const std::string& content_provider,
const std::string& content_id, uint8_t* buffer);
Status GeneratePrivateData(
const std::string& content_provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids, uint8_t* buffer);
void ReceiveResponseAndVerify(uint16_t expected_type);
void Send(uint16_t message_type);

View File

@@ -51,6 +51,7 @@ class EmmgTest : public ::testing::Test {
config_.data_type = 0x01;
config_.content_provider = "widevine_test";
config_.content_id = "CasTsFake";
config_.entitlement_key_ids = {"fakeKeyId1KeyId1", "fakeKeyId2KeyId2"};
config_.bandwidth = 100;
config_.max_num_message = 100;
emmg_ = absl::make_unique<TestableEmmg>(&config_);
@@ -103,6 +104,25 @@ TEST_F(EmmgTest, BuildDataProvision) {
sizeof(kTestEmmgDataProvision)));
}
TEST_F(EmmgTest,
BuildDataProvisionFailedWhenNumOfEntitlementKeyIdsExceedLimit) {
config_.entitlement_key_ids = {"fakeKeyId1KeyId1", "fakeKeyId2KeyId2",
"fakeKeyId3KeyId3"};
emmg_ = absl::make_unique<TestableEmmg>(&config_);
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)));
}
TEST_F(EmmgTest, BuildStreamCloseRequest) {
emmg_->PublicBuildStreamCloseRequest();
EXPECT_EQ(0, memcmp(kTestEmmgStreamCloseRequest, emmg_->GetRequest(),

View File

@@ -9,6 +9,7 @@
#include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h"
#include <bitset>
#include <vector>
#include "glog/logging.h"
#include "absl/strings/str_cat.h"
@@ -21,39 +22,44 @@ namespace cas {
namespace {
// Size of fixed portion of CA descriptor (before any private bytes).
static constexpr uint32_t kCaDescriptorBaseSize = 6;
constexpr uint32_t kCaDescriptorBaseSize = 6;
// Size of fixed portion of CA descriptor that follows the length field.
// This and the size of any private bytes must be placed in the length field.
static constexpr uint32_t kCaDescriptorBasePostLengthSize = 4;
constexpr uint32_t kCaDescriptorBasePostLengthSize = 4;
// Bitfield lengths for the CA descriptor fields
static constexpr int kNumBitsCaDescriptorTagField = 8;
static constexpr int kNumBitsCaDescriptorLengthField = 8;
static constexpr int kNumBitsCaSystemIdField = 16;
static constexpr int kNumBitsCaDescriptorReservedField = 3;
static constexpr int kNumBitsCaDescriptorPidField = 13;
constexpr int kNumBitsCaDescriptorTagField = 8;
constexpr int kNumBitsCaDescriptorLengthField = 8;
constexpr int kNumBitsCaSystemIdField = 16;
constexpr int kNumBitsCaDescriptorReservedField = 3;
constexpr int kNumBitsCaDescriptorPidField = 13;
// Bitfield constants for the CA descriptor fields.
// CA descriptor tag value, from table 2-45.
static constexpr uint32_t kCaDescriptorTag = 9;
constexpr uint32_t kCaDescriptorTag = 9;
// CA System ID for Widevine. From table in
// https://en.wikipedia.org/wiki/Conditional_access
static constexpr uint32_t kWidevineCaSystemId = 0x4AD4;
constexpr uint32_t kWidevineCaSystemId = 0x4AD4;
// Value for CA descriptor reserved field should be set to 1.
static constexpr uint32_t kReservedBit = 0x0007;
constexpr uint32_t kReservedBit = 0x0007;
// The range of valid PIDs, from section 2.4.3.3, and table 2-3.
static constexpr uint32_t kMinValidPID = 0x0010;
static constexpr uint32_t kMaxValidPID = 0x1FFE;
constexpr uint32_t kMinValidPID = 0x0010;
constexpr uint32_t kMaxValidPID = 0x1FFE;
// Maximum number of entitlement key ids shown in private data.
constexpr uint32_t kMaxNumOfEntitlementKeyIds = 2;
// Entitlement key id length is fixed to 16 bytes.
constexpr uint16_t kEntitlementKeyIdLength = 16;
} // namespace
Status WvCasCaDescriptor::GenerateCaDescriptor(
uint16_t ca_pid, const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids,
std::string* serialized_ca_desc) const {
if (serialized_ca_desc == nullptr) {
return {error::INVALID_ARGUMENT,
@@ -62,10 +68,25 @@ Status WvCasCaDescriptor::GenerateCaDescriptor(
if (ca_pid < kMinValidPID || ca_pid > kMaxValidPID) {
return {error::INVALID_ARGUMENT, "PID value is out of the valid range."};
}
if (entitlement_key_ids.size() > kMaxNumOfEntitlementKeyIds) {
return {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 {error::INVALID_ARGUMENT,
absl::StrCat("Entitlement key id length must be ",
kEntitlementKeyIdLength,
". The offending key id is ", entitlement_key_id)};
}
}
std::string private_data = "";
// Field of Entitlement_key_ids could be empty.
if (!provider.empty() && !content_id.empty()) {
private_data = GeneratePrivateData(provider, content_id);
private_data =
GeneratePrivateData(provider, content_id, entitlement_key_ids);
}
const size_t descriptor_length =
@@ -107,10 +128,14 @@ size_t WvCasCaDescriptor::CaDescriptorBaseSize() const {
}
std::string WvCasCaDescriptor::GeneratePrivateData(
const std::string& provider, const std::string& content_id) const {
const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids) const {
CaDescriptorPrivateData private_data;
private_data.set_provider(provider);
private_data.set_content_id(content_id);
for (const auto& entitlement_key_id : entitlement_key_ids) {
private_data.add_entitlement_key_ids(entitlement_key_id);
}
return private_data.SerializeAsString();
}

View File

@@ -12,6 +12,7 @@
#include <stddef.h>
#include <string>
#include <vector>
#include <cstdint>
#include "common/status.h"
@@ -48,6 +49,9 @@ class WvCasCaDescriptor {
// |ca_pid| the 13-bit PID of the ECMs
// |provider| provider name, put in private data for client to construct pssh
// |content_id| content ID, put in private data for client to construct pssh
// |entitlement_key_ids| entitlement key ids, put in private data for client
// to select entitlement keys from single fat license. This field is only used
// when client uses single fat license.
// |serialized_ca_desc| a std::string object to receive the encoded descriptor.
//
// Notes:
@@ -55,10 +59,10 @@ class WvCasCaDescriptor {
// section (for an EMM stream) or into a TS Program Map Table section (for an
// ECM stream). The descriptor will be 6 bytes plus any bytes added as
// (user-defined) private data.
virtual Status GenerateCaDescriptor(uint16_t ca_pid,
const std::string& provider,
const std::string& content_id,
std::string* serialized_ca_desc) const;
virtual Status GenerateCaDescriptor(
uint16_t ca_pid, const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids,
std::string* serialized_ca_desc) const;
// Return the base size (before private data is added) of the CA
// descriptor. The user can call this to plan the layout of the Table section
@@ -66,8 +70,9 @@ class WvCasCaDescriptor {
virtual size_t CaDescriptorBaseSize() const;
// Return private data in the CA descriptor.
virtual std::string GeneratePrivateData(const std::string& provider,
const std::string& content_id) const;
virtual std::string GeneratePrivateData(
const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids) const;
};
} // namespace cas

View File

@@ -20,9 +20,11 @@ namespace cas {
namespace {
// Random value for PID
static constexpr int kTestPid = 50;
static constexpr char kProvider[] = "widevine_test";
static constexpr char kContentId[] = "1234";
constexpr int kTestPid = 50;
constexpr char kProvider[] = "widevine_test";
constexpr char kContentId[] = "1234";
const std::vector<std::string>* const kEntitlementKeyIds =
new std::vector<std::string>({"fakekey1fakekey1", "fakekey2fakekey2"});
} // namespace
@@ -31,6 +33,7 @@ class WvCasCaDescriptorTest : public Test {
WvCasCaDescriptorTest() {}
WvCasCaDescriptor ca_descriptor_;
std::string actual_ca_descriptor_;
std::vector<std::string> entitlement_key_ids_;
};
TEST_F(WvCasCaDescriptorTest, BaseSize) {
@@ -38,38 +41,46 @@ TEST_F(WvCasCaDescriptorTest, BaseSize) {
}
TEST_F(WvCasCaDescriptorTest, BasicGoodGen) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
kTestPid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x32", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, NoReturnStringFail) {
EXPECT_EQ(error::INVALID_ARGUMENT,
ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "", nullptr)
ca_descriptor_
.GenerateCaDescriptor(
kTestPid, /*provider=*/"", /*content_id=*/"",
/*entitlement_key_ids=*/{}, /*serialized_ca_desc=*/nullptr)
.error_code());
}
TEST_F(WvCasCaDescriptorTest, PidTooLowFail) {
const uint32_t bad_pid = 0x10 - 1;
EXPECT_EQ(error::INVALID_ARGUMENT,
ca_descriptor_
.GenerateCaDescriptor(bad_pid, "", "", &actual_ca_descriptor_)
.error_code());
EXPECT_EQ(
error::INVALID_ARGUMENT,
ca_descriptor_
.GenerateCaDescriptor(bad_pid, /*provider=*/"", /*content_id=*/"",
entitlement_key_ids_, &actual_ca_descriptor_)
.error_code());
}
TEST_F(WvCasCaDescriptorTest, PidMinOK) {
const uint32_t min_pid = 0x10;
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(min_pid, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
min_pid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x10", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidMaxOK) {
const uint32_t max_pid = 0x1FFE;
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(max_pid, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
max_pid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\xfe");
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
@@ -78,83 +89,93 @@ TEST_F(WvCasCaDescriptorTest, PidTooHighFail) {
const uint32_t bad_pid = 0x1FFF;
EXPECT_EQ(error::INVALID_ARGUMENT,
ca_descriptor_
.GenerateCaDescriptor(bad_pid, "", "", &actual_ca_descriptor_)
.GenerateCaDescriptor(
bad_pid, /*provider=*/"", /*content_id=*/"",
/*entitlement_key_ids=*/{}, &actual_ca_descriptor_)
.error_code());
}
TEST_F(WvCasCaDescriptorTest, PidOneByte) {
EXPECT_OK(
ca_descriptor_.GenerateCaDescriptor(255, "", "", &actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
255, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\xff", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidSecondByte) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x1F00, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x1F00, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidTwelveBits) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0xFFF, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0xFFF, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xef\xff");
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x1000, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x1000, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xf0\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidTwelthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x800, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x800, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe8\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidElevenththBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x400, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x400, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe4\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidTenthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x200, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x200, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe2\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidNinthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x100, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x100, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe1\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PrivateDataOnlyProviderIgnored) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, "",
&actual_ca_descriptor_));
TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoContentIdIgnored) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
kTestPid, kProvider, "", entitlement_key_ids_, &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PrivateDataOnlyContentIdIgnored) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, "", kContentId,
&actual_ca_descriptor_));
TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoProviderIgnored) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
kTestPid, "", kContentId, entitlement_key_ids_, &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PrivateData) {
TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoEntitlementKeyIds) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, kContentId,
&actual_ca_descriptor_));
{}, &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x19\x4a\xd4\xe0\x32", 6);
CaDescriptorPrivateData private_data;
private_data.set_provider(kProvider);
@@ -163,6 +184,44 @@ TEST_F(WvCasCaDescriptorTest, PrivateData) {
actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest,
PrivateDataFailedWhenNumberOfEntitlementKeyIdsExceedLimit) {
const std::vector<std::string> entitlement_key_ids = {
"fakekey1fakekey1", "fakekey2fakekey2", "fakekey3fakekey3"};
Status status = {error::INVALID_ARGUMENT,
"Number of entitlement key ids shouldn't exceed 2"};
EXPECT_EQ(status, ca_descriptor_.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, entitlement_key_ids,
&actual_ca_descriptor_));
}
TEST_F(WvCasCaDescriptorTest,
PrivateDataFailedWhenEntitlementKeyIdLengthExceedLimit) {
const std::vector<std::string> entitlement_key_ids = {
"fakekey1fakekey1", "fakekey2fakekey2fakekey2"};
Status status = {error::INVALID_ARGUMENT,
"Entitlement key id length must be 16. The offending key id "
"is fakekey2fakekey2fakekey2"};
EXPECT_EQ(status, ca_descriptor_.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, entitlement_key_ids,
&actual_ca_descriptor_));
}
TEST_F(WvCasCaDescriptorTest, PrivateData) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, kContentId,
*kEntitlementKeyIds,
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x3d\x4a\xd4\xe0\x32", 6);
CaDescriptorPrivateData private_data;
private_data.set_provider(kProvider);
private_data.set_content_id(kContentId);
for (const auto& entitlementKeyId : *kEntitlementKeyIds) {
private_data.add_entitlement_key_ids(entitlementKeyId);
}
EXPECT_EQ(expected_ca_descriptor + private_data.SerializeAsString(),
actual_ca_descriptor_);
}
class FakePrivateDataCaDescriptor : public WvCasCaDescriptor {
public:
void set_private_data(std::string private_data) {
@@ -170,8 +229,8 @@ class FakePrivateDataCaDescriptor : public WvCasCaDescriptor {
}
std::string GeneratePrivateData(
const std::string& provider,
const std::string& content_id) const override {
const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids) const override {
return private_data_;
}
@@ -183,7 +242,8 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataOneByte) {
FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data("X");
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
kTestPid, kProvider, kContentId, *kEntitlementKeyIds,
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x05\x4a\xd4\xe0\x32X", 7);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
@@ -193,7 +253,8 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMultipleBytes) {
FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data(private_data_bytes);
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
kTestPid, kProvider, kContentId, *kEntitlementKeyIds,
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x0e\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_);
}
@@ -203,7 +264,8 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMaxNumberBytes) {
FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data(private_data_bytes);
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
kTestPid, kProvider, kContentId, *kEntitlementKeyIds,
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\xff\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_);
}
@@ -212,11 +274,12 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataTooManyBytesFail) {
const std::string private_data_bytes(252, 'X');
FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data(private_data_bytes);
EXPECT_EQ(error::INVALID_ARGUMENT,
fake_descriptor
.GenerateCaDescriptor(kTestPid, kProvider, kContentId,
&actual_ca_descriptor_)
.error_code());
EXPECT_EQ(
error::INVALID_ARGUMENT,
fake_descriptor
.GenerateCaDescriptor(kTestPid, kProvider, kContentId,
*kEntitlementKeyIds, &actual_ca_descriptor_)
.error_code());
}
} // namespace cas

View File

@@ -18,7 +18,7 @@
using testing::_;
using testing::DoAll;
using testing::Return;
using testing::SetArgumentPointee;
using testing::SetArgPointee;
namespace {
@@ -59,9 +59,10 @@ class HardcodedWvCasKeyFetcher : public WvCasKeyFetcher {
const std::string& signing_iv)
: WvCasKeyFetcher(signing_provider, signing_key, signing_iv) {}
~HardcodedWvCasKeyFetcher() override {}
MOCK_CONST_METHOD2(MakeHttpRequest,
Status(const std::string& signed_request_json,
std::string* http_response_json));
MOCK_METHOD(Status, MakeHttpRequest,
(const std::string& signed_request_json,
std::string* http_response_json),
(const, override));
};
class MockWvCasKeyFetcher : public WvCasKeyFetcher {
@@ -168,7 +169,7 @@ TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
EXPECT_EQ(signed_request_json, kSignedCasEncryptionRequest);
EXPECT_CALL(mock_key_fetcher, MakeHttpRequest(kSignedCasEncryptionRequest, _))
.WillOnce(DoAll(SetArgumentPointee<1>(std::string(kHttpResponse)),
.WillOnce(DoAll(SetArgPointee<1>(std::string(kHttpResponse)),
Return(OkStatus())));
std::string actual_signed_response;
EXPECT_OK(mock_key_fetcher.MakeHttpRequest(signed_request_json,

View File

@@ -43,6 +43,9 @@ ABSL_FLAG(int32_t, data_id, 0, "EMMG data_id.");
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(
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");
@@ -59,6 +62,7 @@ void BuildEmmgConfig(widevine::cas::EmmgConfig *config) {
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);
}