Add EMMG to carry fingerprinting and service blocking info
This commit is contained in:
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
101
media_cas_packager_sdk/public/wv_cas_emm.cc
Normal file
101
media_cas_packager_sdk/public/wv_cas_emm.cc
Normal 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
|
||||
91
media_cas_packager_sdk/public/wv_cas_emm.h
Normal file
91
media_cas_packager_sdk/public/wv_cas_emm.h
Normal 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_
|
||||
260
media_cas_packager_sdk/public/wv_cas_emm_test.cc
Normal file
260
media_cas_packager_sdk/public/wv_cas_emm_test.cc
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user