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(),