Decouple key fetcher; Update ECMG API

This commit is contained in:
Lu Chen
2020-02-11 18:08:06 -08:00
parent ac564bb46f
commit 77b2fcc678
41 changed files with 1872 additions and 1905 deletions

View File

@@ -25,13 +25,13 @@ cc_library(
srcs = ["ecm.cc"],
hdrs = ["ecm.h"],
deps = [
":util",
"//base",
"@abseil_repo//absl/strings",
"//common:aes_cbc_util",
"//common:status",
"//common:string_util",
"//media_cas_packager_sdk/public:wv_cas_types",
"//protos/public:media_cas_encryption_cc_proto",
],
)
@@ -41,8 +41,8 @@ cc_test(
srcs = ["ecm_test.cc"],
deps = [
":ecm",
":mpeg2ts",
"//testing:gunit_main",
"//protos/public:media_cas_encryption_cc_proto",
],
)
@@ -63,6 +63,9 @@ cc_test(
srcs = ["ecm_generator_test.cc"],
deps = [
":ecm_generator",
":fixed_key_fetcher",
"//base",
"//external:protobuf",
"//testing:gunit_main",
"@abseil_repo//absl/memory",
"//common:aes_cbc_util",
@@ -80,8 +83,6 @@ cc_library(
],
deps = [
":ecm",
":fixed_key_fetcher",
":key_fetcher",
":mpeg2ts",
":simulcrypt_util",
":util",
@@ -92,8 +93,6 @@ cc_library(
"//common:crypto_util",
"//common:random_util",
"//common:status",
"//media_cas_packager_sdk/public:wv_cas_ecm",
"//media_cas_packager_sdk/public:wv_cas_key_fetcher",
"//media_cas_packager_sdk/public:wv_cas_types",
],
)
@@ -129,6 +128,7 @@ cc_library(
"//base",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/strings:str_format",
"@abseil_repo//absl/time",
"//common:status",
"//protos/public:media_cas_cc_proto",
],
@@ -156,20 +156,14 @@ cc_library(
"fixed_key_fetcher.h",
],
deps = [
":key_fetcher",
"//base",
"//external:protobuf",
"//common:status",
"//media_cas_packager_sdk/public:wv_cas_key_fetcher",
"//protos/public:media_cas_encryption_cc_proto",
],
)
cc_library(
name = "key_fetcher",
hdrs = [
"key_fetcher.h",
],
deps = ["//common:status"],
)
cc_library(
name = "mpeg2ts",
hdrs = [

View File

@@ -16,7 +16,8 @@
#include "absl/strings/str_cat.h"
#include "common/aes_cbc_util.h"
#include "common/string_util.h"
#include "protos/public/media_cas_encryption.pb.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/internal/util.h"
namespace widevine {
namespace cas {
@@ -112,63 +113,14 @@ bool ConvertIvSizeParam(EcmIvSize param, size_t* size) {
} // namespace
Status Ecm::Initialize(const std::string& content_id,
const std::string& content_provider,
const EcmInitParameters& ecm_init_parameters,
std::string* key_request_message) {
if (initialized_) {
return {error::INTERNAL, "Already initialized."};
}
if (content_id.empty()) {
return {error::INVALID_ARGUMENT, "Content ID is empty."};
}
if (content_provider.empty()) {
return {error::INVALID_ARGUMENT, "Content Provider is empty."};
}
if (key_request_message == nullptr) {
return {error::INVALID_ARGUMENT, "key_request_message is null."};
}
if (ecm_init_parameters.track_types.empty()) {
return {error::INVALID_ARGUMENT,
"Parameter track_ids must be set with one or more Track IDs."};
}
if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size,
&content_iv_size_)) {
return {error::INVALID_ARGUMENT,
"Parameter content_iv_size must be kIvSize8 or kIvSize16."};
}
crypto_mode_ = ecm_init_parameters.crypto_mode;
content_id_ = content_id;
content_provider_ = content_provider;
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
track_types_ = ecm_init_parameters.track_types;
age_restriction_ = ecm_init_parameters.age_restriction;
// Construct and return CasEncryptionRequest message for caller to use.
Status status = CreateEntitlementRequest(key_request_message);
if (!status.ok()) {
LOG(ERROR) << "Entitlement request message could not be created.";
return status;
}
// Everything is set up except entitlement keys.
ClearEntitlementKeys();
initialized_ = true;
return OkStatus();
}
Status Ecm::Initialize(
const EcmInitParameters& ecm_init_parameters,
const std::vector<InjectedEntitlementKeyInfo>& injected_entitlements) {
const std::vector<EntitlementKeyInfo>& injected_entitlements) {
if (initialized_) {
return {error::INTERNAL, "Already initialized."};
}
if (ecm_init_parameters.track_types.empty()) {
return {error::INVALID_ARGUMENT,
"Parameter track_ids must be set with one or more Track IDs."};
if (injected_entitlements.empty()) {
return {error::NOT_FOUND, "Empty injected entitlements."};
}
if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size,
&content_iv_size_)) {
@@ -178,7 +130,6 @@ Status Ecm::Initialize(
crypto_mode_ = ecm_init_parameters.crypto_mode;
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
track_types_ = ecm_init_parameters.track_types;
age_restriction_ = ecm_init_parameters.age_restriction;
ClearEntitlementKeys();
@@ -197,16 +148,6 @@ Status Ecm::Initialize(
return OkStatus();
}
Status Ecm::ProcessCasEncryptionResponse(const std::string& response) {
if (!initialized_) {
return {error::INTERNAL, "Not initialized."};
}
if (response.empty()) {
return {error::INVALID_ARGUMENT, "Response std::string is empty."};
}
return ParseEntitlementResponse(response);
}
Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
const std::string& track_type,
std::string* serialized_ecm) const {
@@ -278,6 +219,18 @@ Status Ecm::GenerateEcmCommon(const std::vector<EntitledKeyInfo*>& keys,
return OkStatus();
}
Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid, uint8_t table_id,
uint8_t* continuity_counter, uint8_t* packet) {
ssize_t bytes_modified = 0;
Status status = InsertEcmAsTsPacket(ecm, pid, table_id, continuity_counter,
packet, &bytes_modified);
if (!status.ok() || bytes_modified != kTsPacketSize) {
memset(packet, 0, kTsPacketSize);
return {error::INTERNAL, "Failed to generate TS packet"};
}
return OkStatus();
}
Status Ecm::WrapEntitledKeys(const std::string& track_type,
const std::vector<EntitledKeyInfo*>& keys) const {
if (!initialized_) {
@@ -451,103 +404,6 @@ std::string Ecm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) const {
return serialized_ecm;
}
Status Ecm::CreateEntitlementRequest(std::string* request_string) const {
CasEncryptionRequest request;
request.set_content_id(content_id_);
request.set_provider(content_provider_);
request.set_key_rotation(paired_keys_required_);
// Add labels for tracks.
for (const auto& track_type : track_types_) {
request.add_track_types(track_type);
}
if (!request.SerializeToString(request_string)) {
request_string->clear();
return {error::INTERNAL, "Failure serializing request."};
}
return OkStatus();
}
Status Ecm::ParseEntitlementResponse(const std::string& response_string) {
// TODO(user): parse valid response. NOT Implemented.
ClearEntitlementKeys();
SignedCasEncryptionResponse signed_response;
if (!signed_response.ParseFromString(response_string)) {
return {error::INTERNAL, "Failure parsing signed response."};
}
// TODO(user): Should verify signature.
CasEncryptionResponse response;
if (!response.ParseFromString(signed_response.response())) {
return {error::INTERNAL, "Failure parsing signed response."};
}
if (response.status() != CasEncryptionResponse::OK) {
return Status(error::INTERNAL, absl::StrCat("Failure reported by server: ",
response.status(), " : ",
response.status_message()));
}
if (content_id_ != response.content_id()) {
return Status(
error::INTERNAL,
absl::StrCat("Content ID mismatch in Entitlement Response - expected: ",
content_id_, " received: ", response.content_id()));
}
if (response.entitlement_keys().empty()) {
return {error::INTERNAL, "Failure: no entitlement keys in response."};
}
size_t keys_needed = (paired_keys_required_ ? 2 : 1) * track_types_.size();
if (keys_needed > response.entitlement_keys().size()) {
return Status(
error::INTERNAL,
absl::StrCat(
"Wrong number of keys in Entitlement Response - expected: ",
keys_needed, " got: ", response.entitlement_keys().size()));
}
// Scan available entitlement keys for the ones that are needed.
// For non-key-rotation, this is a key with a track type and not even or odd.
// For key rotation, this is a key with a track type and even or odd.
ClearEntitlementKeys();
for (const auto& key : response.entitlement_keys()) {
if (!key.has_track_type()) {
LOG(WARNING) << "Entitlement key missing track type, skipped.";
// No track ID. Skip it.
continue;
}
EntitlementKeyInfo ekey;
ekey.key_id = key.key_id();
ekey.key_value = key.key();
Status status = ValidateKeyValue(key.key(), kWrappingKeySizeBytes);
if (!status.ok()) {
return status;
}
// Using only keys with correct KeySlot
if (!key.has_key_slot()) {
LOG(WARNING) << "Entitlement key missing key slot, skipped.";
continue;
}
if (paired_keys_required()) {
if (key.key_slot() == CasEncryptionResponse::KeyInfo::EVEN) {
PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey);
} else if (key.key_slot() == CasEncryptionResponse::KeyInfo::ODD) {
PushEntitlementKey(key.track_type(), /* is_even_key= */ false, ekey);
}
} else {
if (key.key_slot() == CasEncryptionResponse::KeyInfo::SINGLE) {
PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey);
}
}
}
if (!CheckEntitlementKeys()) {
LOG(ERROR) << "Could not stage entitlement keys from response:";
response.ShortDebugString();
return Status(error::INTERNAL, "No suitable entitlement key was found.");
}
return OkStatus();
}
size_t Ecm::CountEntitlementKeys() const {
size_t count = 0;
for (const auto& track : entitlement_keys_) {
@@ -557,15 +413,6 @@ size_t Ecm::CountEntitlementKeys() const {
}
bool Ecm::CheckEntitlementKeys() const {
// TODO(user): Cross-check entitlement_keys_ track types with track_types_.
if (track_types_.size() > CountEntitlementTracks()) {
// One or more tracks are missing.
LOG(ERROR)
<< "Entitlement keys for one or more tracks is missing - expected "
<< track_types_.size() << " tracks, received "
<< CountEntitlementTracks() << "tracks.";
return false;
}
for (const auto& track : entitlement_keys_) {
if (track.second.size() < (paired_keys_required() ? 2 : 1)) {
LOG(ERROR) << " Wrong number of entitlement keys for track "

View File

@@ -38,12 +38,6 @@ struct EntitledKeyInfo {
std::string wrapped_key_iv;
};
// Declare Content IV size in an ECM stream.
// Content IVs may be encoded as 8 or 16 random bytes. The receiver is
// responsible for appending 8 zeros to an 8-byte IV. All content IVs, once the
// size is declared, must be the same size. Wrapped key IVs are always 16 bytes.
enum EcmIvSize { kIvSize8 = 0, kIvSize16 = 1 };
// Information needed to start a new ECM stream.
// Fields:
// |content_iv_size| size of all content key IVs in the ECM stream.
@@ -52,25 +46,17 @@ enum EcmIvSize { kIvSize8 = 0, kIvSize16 = 1 };
// |crypto_mode| the encryption mode used for the content stream.
// A constant of type CryptoMode.
// |track_types| a vector of track ID (std::string) that specify the set of track
// types of interest; controls the entitlement keys returned by the server.
// types of interest. |track_types| is deprecated and is ignored by
// Ecm::Initialize().
// |age_restriction| minimum age required; the value represents actual age.
struct EcmInitParameters {
EcmIvSize content_iv_size = kIvSize8;
bool key_rotation_enabled = true;
CryptoMode crypto_mode = CryptoMode::kAesCtr;
std::vector<std::string> track_types;
std::vector<std::string> track_types; // deprecated.
uint8_t age_restriction = 0;
};
// Information needed for the injected entitlement keys. Used for Ecm
// initialization.
struct InjectedEntitlementKeyInfo {
std::string track_type;
bool is_even_key;
std::string key_id; // must be 16 bytes.
std::string key_value; // must be 32 bytes.
};
// Generator for producing Widevine CAS-compliant ECMs. Used to construct the
// Transport Stream packet payload of an ECM containing key information for
// decrypting Widevine CAS encrypted content. The keys in the ECM are wrapped
@@ -90,22 +76,6 @@ class Ecm {
Ecm& operator=(const Ecm&) = delete;
virtual ~Ecm() = default;
// Perform initialization for a new ECM stream.
// Args:
// |content_id| uniquely identifies the content (with |content_provider|)
// |content_provider| unique std::string for provider of the content stream.
// |ecm_init_parameters| encryption-related parameters for configuring
// the ECM stream.
// |key_request_message| pointer to a std::string to receive a CasEncryptionRequest
// message.
// Notes:
// The returned |key_request_message| must be sent to the server and
// the response correctly parsed before ECMs can be generated.
virtual Status Initialize(const std::string& content_id,
const std::string& content_provider,
const EcmInitParameters& ecm_init_parameters,
std::string* key_request_message);
// Perform initialization for a new ECM stream with injected entitlement keys.
// Args:
// |ecm_init_parameters| encryption-related parameters for configuring
@@ -114,15 +84,7 @@ class Ecm {
// fetch keys from server.
virtual Status Initialize(
const EcmInitParameters& ecm_init_parameters,
const std::vector<InjectedEntitlementKeyInfo>& injected_entitlements);
// Parse a CasEncryptionResponse message holding the entitlement keys for
// generating the ECM stream. The entitlement keys are used to encrypt the
// keys conveyed in the ECM. The entitlement key IDs are also part of the ECM.
// Args:
// |response| a serialized CasEncryptionRequest message from the server
// holding entitlement key information (or error information).
virtual Status ProcessCasEncryptionResponse(const std::string& response);
const std::vector<EntitlementKeyInfo>& injected_entitlements);
// Accept keys and IVs and construct an ECM that will fit into a Transport
// Stream packet payload (184 bytes).
@@ -153,6 +115,28 @@ class Ecm {
const std::string& track_type,
std::string* serialized_ecm) const;
// Generate a TS packet with the given |ecm| as payload.
//
// Args (all pointer parameters must be not nullptr):
// - |ecm| serialized ECM, e.g., generated by GenerateEcm() method above
// - |pid| program ID for the ECM stream
// - |table_id| is the table ID byte put in the section header, it should be
// either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or
// 0x81 to 0x80 is used to signal to the client that the key contained
// in the ECM has changed. In other words, if you are building an ECM
// with a new key that was not in any previous ECM, you should flip the
// table ID so the client knows this is an important ECM it should process.
// - |continuity_counter| continuity_counter for the ECM packet,
// it will be incremented, only last 4 bits are used
// - |packet| a buffer of size at least 188 bytes to be used to return
// the generated TS packet
//
// Returns:
// - A status indicating whether there was any error during processing
static Status GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id, uint8_t* continuity_counter,
uint8_t* packet);
protected: // For unit tests.
// Take the input entitled |keys| and our current state, and generate
// the ECM representing the keys.
@@ -173,7 +157,7 @@ class Ecm {
// Entitlement key - |key_value| is used to wrap the content key, and |key_id|
// is added to the ECM. |track_type| allows the entitlement key to be
// associated with the original request. See Initialize() for details.
struct EntitlementKeyInfo {
struct EntitlementKeyIdValue {
std::string key_id;
std::string key_value;
};
@@ -195,9 +179,9 @@ class Ecm {
// Add an entitlement key to our current state. Even key is placed first.
void PushEntitlementKey(const std::string& track_type, bool is_even_key,
const EntitlementKeyInfo& key) {
auto emplaced =
entitlement_keys_.emplace(track_type, std::list<EntitlementKeyInfo>{});
const EntitlementKeyIdValue& key) {
auto emplaced = entitlement_keys_.emplace(
track_type, std::list<EntitlementKeyIdValue>{});
if (is_even_key) {
emplaced.first->second.push_front(key);
} else {
@@ -227,11 +211,6 @@ class Ecm {
size_t key_value_size) const;
Status ValidateIv(const std::string& iv, size_t size) const;
// TODO(user): need unit tests for CreateEntitlementRequest.
virtual Status CreateEntitlementRequest(std::string* request_string) const;
// TODO(user): need unit tests for ParseEntitlementResponse.
virtual Status ParseEntitlementResponse(const std::string& response_string);
virtual CryptoMode crypto_mode() const { return crypto_mode_; }
virtual uint8_t age_restriction() const { return age_restriction_; }
virtual bool paired_keys_required() const { return paired_keys_required_; }
@@ -245,15 +224,14 @@ class Ecm {
std::string content_provider_;
// Content IV size may be 8 or 16. Size is set once during Initialize().
size_t content_iv_size_ = 8;
// The set of tracks that are being encrypted and require ECM streams.
std::vector<std::string> track_types_;
// Remember if a pair of keys is required (for key rotation).
bool paired_keys_required_ = false;
CryptoMode crypto_mode_ = CryptoMode::kAesCtr;
uint8_t age_restriction_ = 0;
// Entitlement keys needed for ECM generation.
// The keys are added when the CasEncryptionResponse is processed.
std::map<std::string, std::list<EntitlementKeyInfo>> entitlement_keys_;
// Maps from track_type to one/two EntitlementKeyIdValue with even key first.
std::map<std::string, std::list<EntitlementKeyIdValue>> entitlement_keys_;
};
} // namespace cas

View File

@@ -8,10 +8,13 @@
#include "media_cas_packager_sdk/internal/ecm_generator.h"
#include "glog/logging.h"
#include "google/protobuf/util/json_util.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/memory/memory.h"
#include "common/aes_cbc_util.h"
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
#include "protos/public/media_cas_encryption.pb.h"
namespace widevine {
@@ -50,10 +53,21 @@ constexpr char kFakeCasEncryptionResponseKeyData[] =
constexpr char kTrackType[] = "SD";
Status HandleCasEncryptionRequest(const std::string& request_string,
std::string* signed_response_string) {
Status HandleCasEncryptionRequest(const std::string& signed_request_json,
std::string* http_response_json) {
SignedCasEncryptionRequest signed_request;
if (!google::protobuf::util::JsonStringToMessage(signed_request_json, &signed_request)
.ok()) {
LOG(ERROR) << "Unable to parse signed_request_json.";
return Status(error::INTERNAL);
}
CasEncryptionRequest request;
request.ParseFromString(request_string);
if (!google::protobuf::util::JsonStringToMessage(signed_request.request(), &request)
.ok()) {
LOG(ERROR) << "Unable to understand signed_request_json.";
return Status(error::INTERNAL);
}
CasEncryptionResponse response;
response.set_status(CasEncryptionResponse::OK);
@@ -81,10 +95,20 @@ Status HandleCasEncryptionRequest(const std::string& request_string,
}
}
std::string response_string;
response.SerializeToString(&response_string);
if (!google::protobuf::util::MessageToJsonString(response, &response_string).ok()) {
LOG(ERROR) << "MessageToJsonString(response, &response_string)";
return Status(error::INTERNAL);
}
SignedCasEncryptionResponse signed_response;
signed_response.set_response(response_string);
signed_response.SerializeToString(signed_response_string);
if (!google::protobuf::util::MessageToJsonString(signed_response, http_response_json)
.ok()) {
LOG(ERROR) << "MessageToJsonString(signed_response, http_response_json)";
return Status(error::INTERNAL);
}
return OkStatus();
}
@@ -123,16 +147,25 @@ class EcmGeneratorTest : public testing::Test {
// Call this to setup the guts (Ecm) of the ECM Generator.
void PrepareEcmGenerator(bool key_rotation_enabled) {
ecm_ = absl::make_unique<Ecm>();
std::string entitlement_request;
std::string entitlement_response;
ecm_init_params_.key_rotation_enabled = key_rotation_enabled;
ecm_init_params_.track_types.push_back(kTrackType);
ASSERT_OK(ecm_->Initialize(kContentId, kProvider, ecm_init_params_,
&entitlement_request));
FixedKeyFetcher key_fetcher;
EntitlementRequestParams request_params;
request_params.content_id = kContentId;
request_params.content_provider = kProvider;
request_params.track_types = {kTrackType};
request_params.key_rotation = key_rotation_enabled;
ASSERT_OK(key_fetcher.CreateEntitlementRequest(request_params,
&entitlement_request));
ASSERT_OK(
HandleCasEncryptionRequest(entitlement_request, &entitlement_response));
ASSERT_OK(ecm_->ProcessCasEncryptionResponse(entitlement_response));
std::vector<EntitlementKeyInfo> entitlements;
ASSERT_OK(key_fetcher.ParseEntitlementResponse(entitlement_response,
&entitlements));
ecm_ = absl::make_unique<Ecm>();
ASSERT_OK(ecm_->Initialize(ecm_init_params_, entitlements));
ecm_gen_.set_ecm(std::move(ecm_));
}
@@ -144,10 +177,8 @@ class EcmGeneratorTest : public testing::Test {
kEcmKeyDataEven,
kEcmWrappedKeyIvEven,
{kEcmContentIvEven}};
const KeyParameters kKeyParamsOdd{kEcmKeyIdOdd,
kEcmKeyDataOdd,
kEcmWrappedKeyIvOdd,
{kEcmContentIvOdd}};
const KeyParameters kKeyParamsOdd{
kEcmKeyIdOdd, kEcmKeyDataOdd, kEcmWrappedKeyIvOdd, {kEcmContentIvOdd}};
std::unique_ptr<Ecm> ecm_;
EcmInitParameters ecm_init_params_;

View File

@@ -10,7 +10,7 @@
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "protos/public/media_cas_encryption.pb.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
using ::testing::Return;
@@ -37,6 +37,45 @@ constexpr size_t kEcmIvSize8 = 8;
constexpr size_t kEcmKeyInfoSize =
kEcmKeyIdSize + kEcmKeyIdSize + kEcmKeyDataSize;
// ECM payload data taken from a CETS encrypted file at Google Fiber
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4',
'\xC6', '\x6D', '\x57', '\xDC'};
// ECM packet for Video PID (TSheader + payload) taken from a CETS encrypted
// file, payload_unit_start = 1, adaptation_field_control = 1, cc = 0
constexpr char kExpectedEcmPacket[] = {
// TS header.
'\x47', '\x5F', '\xFD', '\x10',
// Section header.
'\x00', '\x80', '\x70', '\x1C',
// ECM.
'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4', '\xC6', '\x6D', '\x57',
'\xDC',
// Padding.
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF'};
size_t IvExpectedSize(EcmIvSize iv_size) {
return (iv_size == kIvSize8) ? kEcmIvSize8 : kEcmIvSize16;
}
@@ -103,82 +142,23 @@ class EcmTest : public testing::Test {
params_one_key_.key_rotation_enabled = false;
params_two_keys_.key_rotation_enabled = true;
params_simple_.track_types.push_back(kTrackTypeSD);
injected_entitlement_one_ = {kTrackTypeSD, true, "entitlement_id_1",
"key__value.key__value.key__value"};
injected_entitlement_two_ = {kTrackTypeSD, true, "entitlement_id_2",
"key__value.key__value.key__value"};
}
virtual void InitParams(EcmInitParameters* params,
const std::string& track_type) {
params->track_types.push_back(track_type);
}
virtual void InitParams(EcmInitParameters* params,
const std::string& track_type, EcmIvSize content_iv) {
params->track_types.push_back(track_type);
virtual void InitParams(EcmInitParameters* params, EcmIvSize content_iv) {
params->content_iv_size = content_iv;
}
virtual void ServerCall(const std::string& request_string,
std::string* signed_response_string,
bool report_status_ok, bool generate_valid_ecm) {
CasEncryptionRequest request;
ASSERT_TRUE(request.ParseFromString(request_string));
CasEncryptionResponse response;
if (!report_status_ok) {
response.set_status(CasEncryptionResponse::INTERNAL_ERROR);
} else {
response.set_status(CasEncryptionResponse::OK);
response.set_content_id(request.content_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_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_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
if (!generate_valid_ecm) {
auto key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
key->set_track_type(track_type);
}
} else {
auto key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
if (!generate_valid_ecm) {
auto key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
key->set_track_type(track_type);
}
}
}
}
std::string response_string;
ASSERT_TRUE(response.SerializeToString(&response_string));
SignedCasEncryptionResponse signed_response;
signed_response.set_response(response_string);
ASSERT_TRUE(signed_response.SerializeToString(signed_response_string));
}
const std::string provider_ = "provider";
const std::string content_id_ = "content-id";
EcmInitParameters params_one_key_;
EcmInitParameters params_two_keys_;
EcmInitParameters params_simple_;
EcmInitParameters params_default_;
EntitlementKeyInfo injected_entitlement_one_;
EntitlementKeyInfo injected_entitlement_two_;
EntitledKeyInfo valid1_iv_16_8_;
EntitledKeyInfo valid2_iv_16_8_;
EntitledKeyInfo valid3_iv_16_16_;
@@ -245,152 +225,80 @@ TEST_F(EcmTest, GenerateEcm2NotInitialized) {
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data).error_code());
}
TEST_F(EcmTest, SetResponseNotInitialized) {
TEST_F(EcmTest, InitSucceedInjectedEntitlementSingle) {
Ecm ecm_gen;
const std::string response("anything");
EXPECT_EQ(error::INTERNAL,
ecm_gen.ProcessCasEncryptionResponse(response).error_code());
InitParams(&params_one_key_, kIvSize16);
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
}
TEST_F(EcmTest, InitNoTracksFail) {
TEST_F(EcmTest, InitSucceedInjectedEntitlementDouble) {
Ecm ecm_gen;
std::string request;
EXPECT_EQ(
error::INVALID_ARGUMENT,
ecm_gen.Initialize(content_id_, provider_, params_default_, &request)
.error_code());
ASSERT_OK(ecm_gen.Initialize(
params_simple_, {injected_entitlement_one_, injected_entitlement_two_}));
}
TEST_F(EcmTest, InitSucceed) {
TEST_F(EcmTest, InitFailEmptyInjectedEntitlement) {
Ecm ecm_gen;
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request));
ASSERT_EQ(error::NOT_FOUND,
ecm_gen.Initialize(params_simple_, {}).error_code());
}
TEST_F(EcmTest, InitFailNotEnoughInjectedEntitlement) {
Ecm ecm_gen;
ASSERT_EQ(error::INVALID_ARGUMENT,
ecm_gen.Initialize(params_simple_, {injected_entitlement_one_})
.error_code());
}
TEST_F(EcmTest, SecondInitFail) {
Ecm ecm_gen;
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request));
ASSERT_OK(ecm_gen.Initialize(
params_simple_, {injected_entitlement_one_, injected_entitlement_two_}));
EXPECT_EQ(error::INTERNAL,
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request)
ecm_gen
.Initialize(params_simple_, {injected_entitlement_one_,
injected_entitlement_two_})
.error_code());
}
TEST_F(EcmTest, InitEmptyContentIdFail) {
Ecm ecm_gen;
const std::string empty_content_id;
std::string request;
EXPECT_EQ(
error::INVALID_ARGUMENT,
ecm_gen.Initialize(empty_content_id, provider_, params_simple_, &request)
.error_code());
}
TEST_F(EcmTest, InitEmptyProviderFail) {
Ecm ecm_gen;
const std::string empty_provider;
std::string request;
EXPECT_EQ(
error::INVALID_ARGUMENT,
ecm_gen.Initialize(content_id_, empty_provider, params_simple_, &request)
.error_code());
}
TEST_F(EcmTest, InitIvSize16x16OK) {
Ecm ecm_gen;
InitParams(&params_one_key_, kTrackTypeSD, kIvSize16);
InitParams(&params_one_key_, kIvSize16);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
}
TEST_F(EcmTest, GenerateWithNoEntitlementOneKeyFail) {
Ecm ecm_gen;
EntitledKeyInfo key1;
InitParams(&params_one_key_, kTrackTypeSD);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
std::string ecm;
// This will fail because the entitlement key has not been acquired
// (via server call and ProcessCasEncryptionResponse()).
EXPECT_EQ(
error::INTERNAL,
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm).error_code());
}
TEST_F(EcmTest, GenerateWithNoEntitlementTwoKeysFail) {
Ecm ecm_gen;
EntitledKeyInfo key1;
EntitledKeyInfo key2;
InitParams(&params_two_keys_, kTrackTypeSD);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string ecm;
// This will fail because the entitlement keys have not been acquired
// (via server call and ProcessCasEncryptionResponse()).
EXPECT_EQ(error::INTERNAL,
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
}
TEST_F(EcmTest, RequestNullFail) {
Ecm ecm_gen;
EXPECT_EQ(error::INVALID_ARGUMENT,
ecm_gen.Initialize(content_id_, provider_, params_simple_, nullptr)
.error_code());
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
}
TEST_F(EcmTest, GenerateOneKeyOK) {
Ecm ecm_gen;
InitParams(&params_one_key_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
EntitledKeyInfo key1 = valid1_iv_16_8_;
InitParams(&params_one_key_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
std::string ecm;
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm));
}
TEST_F(EcmTest, BadResponseFail) {
TEST_F(EcmTest, GenerateWithBadTrackType) {
Ecm ecm_gen;
InitParams(&params_two_keys_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
EntitledKeyInfo key1 = valid1_iv_16_8_;
InitParams(&params_one_key_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
std::string response;
ServerCall(request, &response, false, true);
EntitledKeyInfo key2 = valid2_iv_16_8_;
std::string ecm;
EXPECT_EQ(error::INTERNAL,
ecm_gen.ProcessCasEncryptionResponse(response).error_code());
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeHD, &ecm).error_code());
}
TEST_F(EcmTest, GenerateTwoKeysOK) {
Ecm ecm_gen;
InitParams(&params_two_keys_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
EntitledKeyInfo key1 = valid1_iv_16_8_;
EntitledKeyInfo key2 = valid2_iv_16_8_;
InitParams(&params_two_keys_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
std::string ecm;
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
}
@@ -398,15 +306,8 @@ TEST_F(EcmTest, GenerateTwoKeysOK) {
TEST_F(EcmTest, GenerateOneKeyBadKeyIdFail) {
Ecm ecm_gen;
EntitledKeyInfo key1 = invalid1_key_id_iv_16_8_;
InitParams(&params_one_key_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
InitParams(&params_one_key_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
std::string ecm;
EXPECT_EQ(
@@ -417,15 +318,8 @@ TEST_F(EcmTest, GenerateOneKeyBadKeyIdFail) {
TEST_F(EcmTest, GenerateOneKeyWrong) {
Ecm ecm_gen;
EntitledKeyInfo key1 = valid1_iv_16_8_;
InitParams(&params_one_key_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
InitParams(&params_one_key_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
std::string ecm;
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm));
@@ -439,16 +333,10 @@ TEST_F(EcmTest, GenerateTwoKeysIvSizeFail) {
EntitledKeyInfo key1 = valid1_iv_16_8_;
EntitledKeyInfo key2 = valid2_iv_16_8_;
// IV size mismatch.
InitParams(&params_two_keys_, kTrackTypeSD, kIvSize16);
InitParams(&params_two_keys_, kIvSize16);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
std::string ecm;
EXPECT_EQ(error::INVALID_ARGUMENT,
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
@@ -458,15 +346,9 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x8OK) {
Ecm ecm_gen;
EntitledKeyInfo key1 = valid1_iv_16_8_;
EntitledKeyInfo key2 = valid2_iv_16_8_;
InitParams(&params_two_keys_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
InitParams(&params_two_keys_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
std::string ecm;
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
@@ -479,16 +361,9 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x16OK) {
Ecm ecm_gen;
EntitledKeyInfo key1 = valid3_iv_16_16_;
EntitledKeyInfo key2 = valid4_iv_16_16_;
InitParams(&params_two_keys_, kTrackTypeSD, kIvSize16);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
InitParams(&params_two_keys_, kIvSize16);
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
std::string ecm;
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
@@ -497,26 +372,6 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x16OK) {
EXPECT_THAT(165, ecm.size());
}
TEST_F(EcmTest, GenerateThreeKeysIvSize16x16Fail) {
Ecm ecm_gen;
EntitledKeyInfo key1 = valid3_iv_16_16_;
EntitledKeyInfo key2 = valid4_iv_16_16_;
InitParams(&params_two_keys_, kTrackTypeSD, kIvSize16);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string response;
ServerCall(request, &response, true, false);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
std::string ecm;
EXPECT_EQ(error::INTERNAL,
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
}
// TODO(user): Add more unit tests for error paths around SerializeEcm.
TEST_F(EcmSerializeEcmTest, SerializeEcmDoubleKey16ByteIvs) {
MockEcm ecm_gen;
@@ -615,5 +470,30 @@ TEST_F(EcmSerializeEcmTest, SerializeEcmAgeRestriction) {
kIvSize16);
}
class EcmTsPacketTest : public ::testing::Test {};
TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) {
std::string ecm(kEcmPayload);
ProgramId pid = 0x1FFD;
ContinuityCounter cc = 0;
uint8_t packet[188];
EXPECT_OK(Ecm::GenerateTsPacket(ecm, pid, kTsPacketTableId80, &cc, packet));
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, packet, sizeof(packet)));
EXPECT_EQ(1, cc);
}
TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId81) {
std::string ecm(kEcmPayload);
ProgramId pid = 0x1FFD;
ContinuityCounter cc = 0;
uint8_t packet[188];
EXPECT_OK(Ecm::GenerateTsPacket(ecm, pid, kTsPacketTableId81, &cc, packet));
char expected_ecm[188];
memcpy(expected_ecm, kExpectedEcmPacket, 188);
expected_ecm[5] = '\x81';
EXPECT_EQ(0, memcmp(expected_ecm, packet, sizeof(packet)));
EXPECT_EQ(1, cc);
}
} // namespace cas
} // namespace widevine

View File

@@ -16,13 +16,10 @@
#include "common/crypto_util.h"
#include "common/random_util.h"
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
#include "media_cas_packager_sdk/internal/simulcrypt_util.h"
#include "media_cas_packager_sdk/internal/util.h"
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
// CA System ID for Widevine.
static constexpr uint16_t kWidevineSystemId = 0x4AD4;
@@ -248,6 +245,21 @@ Status HandleParameters(const char* const request, size_t request_length,
}
offset += param_length;
break;
case ERROR_STATUS:
if (param_length != ERROR_STATUS_SIZE) {
return Status(error::INVALID_ARGUMENT,
absl::StrCat("Invalid parameter length ", param_length,
" for parameter type ", param_type));
}
params->error_status.emplace_back();
BigEndianToHost16(&params->error_status.back(), request + offset);
offset += param_length;
break;
case ERROR_INFORMATION:
params->error_information.push_back(
std::string(request + offset, param_length));
offset += param_length;
break;
default:
status = ProcessPrivateParameters(request, param_type, param_length,
&offset, params);
@@ -453,39 +465,37 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
return;
}
switch (request_type) {
case ECMG_CHANNEL_SETUP: {
case ECMG_CHANNEL_SETUP:
HandleChannelSetup(params, response, response_length);
break;
}
case ECMG_CHANNEL_TEST: {
case ECMG_CHANNEL_TEST:
HandleChannelTest(params, response, response_length);
break;
}
case ECMG_CHANNEL_CLOSE: {
case ECMG_CHANNEL_CLOSE:
HandleChannelClose(params, response, response_length);
break;
}
case ECMG_STREAM_SETUP: {
case ECMG_STREAM_SETUP:
HandleStreamSetup(params, response, response_length);
break;
}
case ECMG_STREAM_TEST: {
case ECMG_STREAM_TEST:
HandleStreamTest(params, response, response_length);
break;
}
case ECMG_STREAM_CLOSE_REQUEST: {
case ECMG_STREAM_CLOSE_REQUEST:
HandleStreamCloseRequest(params, response, response_length);
break;
}
case ECMG_CW_PROVISION: {
case ECMG_CW_PROVISION:
HandleCwProvision(params, response, response_length);
break;
}
default: {
case ECMG_CHANNEL_ERROR:
HandleChannelError(params, response, response_length);
break;
case ECMG_STREAM_ERROR:
HandleStreamError(params, response, response_length);
break;
default:
BuildChannelError(params.ecm_channel_id, UNKNOWN_MESSAGE_TYPE_VALUE, "",
response, response_length);
break;
}
}
}
@@ -554,6 +564,26 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
*response_length = 0;
}
void EcmgClientHandler::HandleChannelError(const EcmgParameters& params,
char* response,
size_t* response_length) const {
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
response, response_length);
return;
}
LOG(ERROR) << "Channel error received for channel id "
<< params.ecm_channel_id;
for (uint16_t error_code : params.error_status) {
LOG(ERROR) << "Error status received: " << error_code;
}
for (const auto& error_info : params.error_information) {
LOG(ERROR) << "Error info received: " << error_info;
}
*response_length = 0;
}
void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
char* response,
size_t* response_length) {
@@ -634,6 +664,33 @@ void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params,
response, response_length);
}
void EcmgClientHandler::HandleStreamError(const EcmgParameters& params,
char* response,
size_t* response_length) const {
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response,
response_length);
return;
}
if (!streams_info_.contains(params.ecm_stream_id)) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
UNKNOWN_ECM_STREAM_ID_VALUE, "", response,
response_length);
return;
}
LOG(ERROR) << "Stream error received for channel id " << params.ecm_channel_id
<< ", stream id " << params.ecm_stream_id;
for (uint16_t error_code : params.error_status) {
LOG(ERROR) << "Error status received: " << error_code;
}
for (const auto& error_info : params.error_information) {
LOG(ERROR) << "Error info received: " << error_info;
}
*response_length = 0;
}
void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
char* response,
size_t* response_length) {
@@ -794,6 +851,15 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
if (track_types_.empty()) {
return {error::NOT_FOUND, "Track type not specified."};
}
if (entitlement_comb_.empty()) {
return {error::NOT_FOUND, "Entitlement key id comb not specified."};
}
if (entitlement_comb_.size() !=
track_types_.size() * ecmg_config_->number_of_content_keys) {
return {error::NOT_FOUND,
"Number of injected entitlement keys must equal to number of "
"track types * number of content keys per ecm."};
}
bool key_rotation = ecmg_config_->number_of_content_keys > 1;
EcmInitParameters ecm_init_params;
@@ -808,60 +874,20 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
(!content_ivs_.empty() && content_ivs_[0].size() == 16)) {
ecm_init_params.content_iv_size = kIvSize16;
}
ecm_init_params.track_types.assign(track_types_.begin(), track_types_.end());
std::vector<EntitlementKeyInfo> entitlements;
entitlements.reserve(entitlement_comb_.size());
for (size_t i = 0; i < entitlement_comb_.size(); i++) {
entitlements.emplace_back();
EntitlementKeyInfo* entitlement = &entitlements.back();
entitlement->track_type = track_types_.at(key_rotation ? i / 2 : i);
entitlement->is_even_key = key_rotation ? i % 2 == 0 : true;
entitlement->key_id = entitlement_comb_[i].key_id;
entitlement->key_value = entitlement_comb_[i].key_value;
}
ecm_ = absl::make_unique<Ecm>();
if (!entitlement_comb_.empty()) {
// Using injected entitlement keys.
if (entitlement_comb_.size() !=
track_types_.size() * ecmg_config_->number_of_content_keys) {
return {error::NOT_FOUND,
"Number of injected entitlement keys must equal to number of "
"track types * number of content keys per ecm."};
}
std::vector<InjectedEntitlementKeyInfo> entitlements;
entitlements.reserve(entitlement_comb_.size());
for (size_t i = 0; i < entitlement_comb_.size(); i++) {
entitlements.emplace_back();
InjectedEntitlementKeyInfo* entitlement = &entitlements.back();
entitlement->track_type = track_types_.at(key_rotation ? i / 2 : i);
entitlement->is_even_key = key_rotation ? i % 2 == 0 : true;
entitlement->key_id = entitlement_comb_[i].key_id;
entitlement->key_value = entitlement_comb_[i].key_value;
}
return ecm_->Initialize(ecm_init_params, entitlements);
}
// No injected enetitlement keys. Fetching entitlement keys from server.
if (content_id_.empty()) {
return {error::NOT_FOUND, "Content id not specified."};
}
if (content_provider_.empty()) {
return {error::NOT_FOUND, "Content provider not specified."};
}
std::string entitlement_request;
std::string entitlement_response;
Status status = ecm_->Initialize(content_id_, content_provider_,
ecm_init_params, &entitlement_request);
if (!status.ok()) {
return status;
}
if (key_fetcher_ == nullptr) {
if (ecmg_config_->use_fixed_fetcher) {
key_fetcher_ = absl::make_unique<FixedKeyFetcher>();
} else {
key_fetcher_ = absl::make_unique<WvCasKeyFetcher>();
}
}
status = key_fetcher_->RequestEntitlementKey(entitlement_request,
&entitlement_response);
if (!status.ok()) {
return status;
}
return ecm_->ProcessCasEncryptionResponse(entitlement_response);
return ecm_->Initialize(ecm_init_params, entitlements);
}
Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
@@ -905,12 +931,12 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
// the PID field in TS packet header.
uint16_t pid = 0;
uint8_t continuity_counter = params.cp_number & 0xff;
WvCasEcm wv_cas_ecm;
WvCasStatus cas_status = wv_cas_ecm.GenerateTsPacket(
status = Ecm::GenerateTsPacket(
serialized_ecm, pid,
params.cp_number % 2 == 0 ? kTsPacketTableId80 : kTsPacketTableId81,
&continuity_counter, ecm_datagram);
if (cas_status != OK) {
if (!status.ok()) {
LOG(ERROR) << status;
return {error::INTERNAL, "GenerateTsPacket failed."};
}
return OkStatus();

View File

@@ -18,7 +18,6 @@
#include "absl/container/node_hash_map.h"
#include "common/status.h"
#include "media_cas_packager_sdk/internal/ecm.h"
#include "media_cas_packager_sdk/internal/key_fetcher.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
namespace widevine {
@@ -33,7 +32,6 @@ struct EcmgConfig {
uint8_t access_criteria_transfer_mode;
uint8_t number_of_content_keys;
CryptoMode crypto_mode;
bool use_fixed_fetcher = false;
};
// A struct that represent a CP_CW_Combination.
@@ -58,6 +56,8 @@ struct EcmgParameters {
uint16_t ecm_id;
uint16_t nominal_cp_duration;
uint32_t super_cas_id;
std::vector<uint16_t> error_status;
std::vector<std::string> error_information;
// User defined paremeters below.
uint8_t age_restriction = 0xff; // Assume 0xff (255) is an invalid value.
@@ -103,12 +103,16 @@ class EcmgClientHandler {
size_t* response_length) const;
void HandleChannelClose(const EcmgParameters& params, char* response,
size_t* response_length);
void HandleChannelError(const EcmgParameters& params, char* response,
size_t* response_length) const;
void HandleStreamSetup(const EcmgParameters& params, char* response,
size_t* response_length);
void HandleStreamTest(const EcmgParameters& params, char* response,
size_t* response_length) const;
void HandleStreamCloseRequest(const EcmgParameters& params, char* response,
size_t* response_length);
void HandleStreamError(const EcmgParameters& params, char* response,
size_t* response_length) const;
void HandleCwProvision(const EcmgParameters& params, char* response,
size_t* response_length);
@@ -141,7 +145,6 @@ class EcmgClientHandler {
// Map from ECM_stream_id to EcmgStreamInfo.
absl::node_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_;
std::unique_ptr<KeyFetcher> key_fetcher_;
};
} // namespace cas

View File

@@ -50,8 +50,6 @@ static constexpr size_t kAgeRestriction = 3;
static constexpr char kCryptoMode[] = "AesScte";
static constexpr char kTrackTypesSD[] = "SD";
static constexpr char kTrackTypesHD[] = "HD";
static constexpr char kContentId[] = "CasTsFake";
static constexpr char kContentProvider[] = "widevine_test";
class EcmgClientHandlerTest : public ::testing::Test {
protected:
@@ -62,7 +60,6 @@ class EcmgClientHandlerTest : public ::testing::Test {
config_.max_comp_time = 100;
config_.access_criteria_transfer_mode = 1;
config_.number_of_content_keys = 2;
config_.use_fixed_fetcher = true;
handler_ = absl::make_unique<EcmgClientHandler>(&config_);
}
@@ -97,8 +94,6 @@ class EcmgClientHandlerTest : public ::testing::Test {
uint8_t age_restriction,
const std::string& crypto_mode,
const std::vector<std::string>& track_types,
const std::string& content_id,
const std::string& content_provider,
const std::vector<std::string>& entitlements,
char* message, size_t* message_length) {
EXPECT_TRUE(message != nullptr);
@@ -122,15 +117,6 @@ class EcmgClientHandlerTest : public ::testing::Test {
track_type.size(), message, message_length);
}
}
if (!content_id.empty()) {
AddParam(CONTENT_ID, reinterpret_cast<const uint8_t*>(content_id.c_str()),
content_id.size(), message, message_length);
}
if (!content_provider.empty()) {
AddParam(CONTENT_PROVIDER,
reinterpret_cast<const uint8_t*>(content_provider.c_str()),
content_provider.size(), message, message_length);
}
if (!entitlements.empty()) {
for (const auto& entitlement : entitlements) {
AddParam(ENTITLEMENT_ID_KEY_COMBINATION,
@@ -306,36 +292,10 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceWithPrivateParameters) {
EXPECT_EQ(0, response_len_);
}
TEST_F(EcmgClientHandlerTest, SuccessSequenceGeneratedRequests) {
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
kCryptoMode, {kTrackTypesHD, kTrackTypesSD},
kContentId, kContentProvider, /*entitlements*/ {},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
kTrackTypesSD, {kContentKeyEven, kContentKeyEven},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
const 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(sizeof(kTestEcmgEcmResponse), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27));
}
TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
BuildChannelSetupRequest(
kChannelId, kSuperCasId, kAgeRestriction, kCryptoMode,
{kTrackTypesHD, kTrackTypesSD},
/*ContentId*/ "", /*ContentProvider*/ "",
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd),
absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
@@ -361,6 +321,18 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27));
}
TEST_F(EcmgClientHandlerTest, SuccessChannelError) {
SetupValidChannel();
handler_->HandleRequest(kTestEcmgChannelError, response_, &response_len_);
EXPECT_EQ(0, response_len_);
}
TEST_F(EcmgClientHandlerTest, SuccessStreamError) {
SetupValidChannelStream();
handler_->HandleRequest(kTestEcmgStreamError, response_, &response_len_);
EXPECT_EQ(0, response_len_);
}
TEST_F(EcmgClientHandlerTest, WrongSequenceInvalidChannel) {
// ChannelTest without a valid channel (error).
handler_->HandleRequest(kTestEcmgChannelTest, response_, &response_len_);
@@ -438,10 +410,12 @@ TEST_F(EcmgClientHandlerTest, WrongParameterInsufficientKey) {
TEST_F(EcmgClientHandlerTest, WrongParameterSuperCasId) {
// Setup a channel with an unexpected super cas id (expecting kSuperCasId).
BuildChannelSetupRequest(kChannelId, 0, kAgeRestriction, kCryptoMode,
{kTrackTypesHD, kTrackTypesSD}, kContentId,
kContentProvider, /*entitlements*/ {}, request_,
&request_len_);
BuildChannelSetupRequest(
kChannelId, 0, kAgeRestriction, kCryptoMode,
{kTrackTypesHD, kTrackTypesSD},
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
CheckChannelError(UNKNOWN_SUPER_CAS_ID_VALUE, response_, response_len_);
}
@@ -457,10 +431,12 @@ TEST_F(EcmgClientHandlerTest, WrongParameterChannelId) {
}
TEST_F(EcmgClientHandlerTest, WrongParameterCryptoMode) {
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
"someCryptoMode", {kTrackTypesHD, kTrackTypesSD},
kContentId, kContentProvider, /*entitlements*/ {},
request_, &request_len_);
BuildChannelSetupRequest(
kChannelId, kSuperCasId, kAgeRestriction, "someCryptoMode",
{kTrackTypesHD, kTrackTypesSD},
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
CheckChannelError(INVALID_VALUE_FOR_DVB_PARAMETER, response_, response_len_);
}
@@ -473,10 +449,9 @@ TEST_F(EcmgClientHandlerTest, WrongParameterLengthSuperCasId) {
response_len_);
}
TEST_F(EcmgClientHandlerTest, NoEntitlementsNoContentIdProvider) {
TEST_F(EcmgClientHandlerTest, NoInjectedEntitlements) {
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
kCryptoMode, {kTrackTypesHD, kTrackTypesSD},
/*ContentId*/ "", /*ContentProvider*/ "",
kCryptoMode, {kTrackTypesSD},
/*entitlements*/ {}, request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
@@ -501,7 +476,6 @@ TEST_F(EcmgClientHandlerTest, NotEnoughInjectedEntitlements) {
BuildChannelSetupRequest(
kChannelId, kSuperCasId, kAgeRestriction, kCryptoMode,
{kTrackTypesHD, kTrackTypesSD},
/*ContentId*/ "", /*ContentProvider*/ "",
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
request_, &request_len_);
@@ -524,6 +498,32 @@ TEST_F(EcmgClientHandlerTest, NotEnoughInjectedEntitlements) {
CheckStreamError(MISSING_MANDATORY_DVB_PARAMETER, response_, response_len_);
}
TEST_F(EcmgClientHandlerTest, TooManyInjectedEntitlements) {
BuildChannelSetupRequest(
kChannelId, kSuperCasId, kAgeRestriction, kCryptoMode, {kTrackTypesSD},
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd),
absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
kTrackTypesSD, {kContentKeyEven, kContentKeyEven},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
const 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_);
CheckStreamError(MISSING_MANDATORY_DVB_PARAMETER, response_, response_len_);
}
TEST_F(EcmgClientHandlerTest, WrongMessageLength) {
// Setup a channel with a wrong message length specified (too large).
handler_->HandleRequest(kTestEcmgChannelSetupWrongMessageLength, response_,

View File

@@ -105,5 +105,6 @@
#define CP_DURATION_SIZE (2)
#define CP_SIZE (2)
#define AGE_RESTRICTION_SIZE (1)
#define ERROR_STATUS_SIZE (2)
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CONSTANTS_H_

View File

@@ -16,6 +16,8 @@
#include "glog/logging.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "media_cas_packager_sdk/internal/emmg_constants.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
@@ -24,6 +26,9 @@
#include "media_cas_packager_sdk/internal/util.h"
#include "protos/public/media_cas.pb.h"
// Minimum sending interval in milliseconds.
static constexpr uint16_t KMinSendIntervalMs = 2;
namespace widevine {
namespace cas {
@@ -61,7 +66,7 @@ std::string MessageTypeToString(uint16_t message_type) {
case EMMG_STREAM_BW_REQUEST:
return "EMMG_STREAM_BW_REQUEST";
case EMMG_STREAM_BW_ALLOCATION:
return "";
return "EMMG_STREAM_BW_ALLOCATION";
case EMMG_DATA_PROVISION:
return "EMMG_DATA_PROVISION";
default:
@@ -69,6 +74,68 @@ std::string MessageTypeToString(uint16_t message_type) {
}
}
Status HandleParameters(const char* const response, size_t response_length,
EmmgParameters* params) {
DCHECK(response);
DCHECK(params);
Status status;
uint16_t param_type;
uint16_t param_length;
// 'offset' is used to track where we are within |request|.
size_t offset = 0;
while (offset < response_length) {
BigEndianToHost16(&param_type, response + offset);
offset += PARAMETER_TYPE_SIZE;
BigEndianToHost16(&param_length, response + offset);
offset += PARAMETER_LENGTH_SIZE;
switch (param_type) {
case EMMG_CLIENT_ID:
if (param_length != EMMG_CLIENT_ID_SIZE) {
return Status(error::FAILED_PRECONDITION,
absl::StrCat("Invalid parameter length ", param_length,
" for parameter type ", param_type));
}
BigEndianToHost32(&params->client_id, response + offset);
offset += param_length;
break;
case EMMG_DATA_CHANNEL_ID:
if (param_length != EMMG_DATA_CHANNEL_ID_SIZE) {
return Status(error::FAILED_PRECONDITION,
absl::StrCat("Invalid parameter length ", param_length,
" for parameter type ", param_type));
}
BigEndianToHost16(&params->data_channel_id, response + offset);
offset += param_length;
break;
case EMMG_DATA_STREAM_ID:
if (param_length != EMMG_DATA_STREAM_ID_SIZE) {
return Status(error::FAILED_PRECONDITION,
absl::StrCat("Invalid parameter length ", param_length,
" for parameter type ", param_type));
}
BigEndianToHost16(&params->data_stream_id, response + offset);
offset += param_length;
break;
case EMMG_BANDWIDTH:
if (param_length != EMMG_BANDWIDTH_SIZE) {
return Status(error::FAILED_PRECONDITION,
absl::StrCat("Invalid parameter length ", param_length,
" for parameter type ", param_type));
}
BigEndianToHost16(&params->bandwidth, response + offset);
offset += param_length;
break;
default:
return Status(
error::UNIMPLEMENTED,
absl::StrCat("No implementation yet to process parameter of type ",
param_type));
break;
}
}
return OkStatus();
}
} // namespace
Emmg::Emmg(EmmgConfig* emmg_config, int server_socket_fd) {
@@ -80,9 +147,16 @@ Emmg::Emmg(EmmgConfig* emmg_config, int server_socket_fd) {
void Emmg::Start() {
SendChannelSetup();
SendStreamSetup();
// TODO(user): Send stream_BW_request message.
// TODO(user): Send stream_BW_allocation message.
SendDataProvision();
UpdateSendInterval(emmg_config_->bandwidth);
SendStreamBwRequest();
for (size_t i = 0; i < emmg_config_->max_num_message; i++) {
SendDataProvision();
absl::SleepFor(
absl::Milliseconds(std::max(KMinSendIntervalMs, send_interval_ms_)));
}
SendStreamCloseRequest();
SendChannelClose();
}
@@ -126,6 +200,26 @@ void Emmg::BuildStreamSetup() {
Host16ToBigEndian(request_ + 3, &total_param_length);
}
void Emmg::BuildStreamBwRequest() {
bzero(request_, BUFFER_SIZE);
request_length_ = 0;
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION,
EMMG_STREAM_BW_REQUEST, request_,
&request_length_);
simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id,
request_, &request_length_);
simulcrypt_util::AddUint16Param(EMMG_DATA_CHANNEL_ID,
emmg_config_->data_channel_id, request_,
&request_length_);
simulcrypt_util::AddUint16Param(EMMG_DATA_STREAM_ID,
emmg_config_->data_stream_id, request_,
&request_length_);
simulcrypt_util::AddUint16Param(EMMG_BANDWIDTH, emmg_config_->bandwidth,
request_, &request_length_);
uint16_t total_param_length = request_length_ - 5;
Host16ToBigEndian(request_ + 3, &total_param_length);
}
Status Emmg::GeneratePrivateData(const std::string& content_provider,
const std::string& content_id, uint8_t* buffer) {
DCHECK(buffer);
@@ -228,6 +322,13 @@ void Emmg::SendStreamSetup() {
ReceiveResponseAndVerify(EMMG_STREAM_STATUS);
}
void Emmg::SendStreamBwRequest() {
BuildStreamBwRequest();
Send(EMMG_STREAM_BW_REQUEST);
ReceiveResponseAndVerify(EMMG_STREAM_BW_ALLOCATION);
HandleResponse(response_);
}
void Emmg::SendDataProvision() {
BuildDataProvision();
Send(EMMG_DATA_PROVISION);
@@ -246,6 +347,52 @@ void Emmg::SendChannelClose() {
// No response for Channel_close message.
}
void Emmg::HandleResponse(const char* const response) {
DCHECK(response);
uint8_t protocol_version;
uint16_t response_type;
uint16_t response_length;
// 'offset' is used to track where we are within |request|.
size_t offset = 0;
memcpy(&protocol_version, response, PROTOCOL_VERSION_SIZE);
if (protocol_version != EMMG_MUX_PROTOCOL_VERSION) {
LOG(ERROR) << "Unexpected protocol version.";
return;
}
offset += PROTOCOL_VERSION_SIZE;
BigEndianToHost16(&response_type, response + offset);
offset += MESSAGE_TYPE_SIZE;
BigEndianToHost16(&response_length, response + offset);
offset += MESSAGE_LENGTH_SIZE;
EmmgParameters params;
Status status = HandleParameters(response + offset, response_length, &params);
if (!status.ok()) {
LOG(ERROR) << status.ToString();
return;
}
switch (response_type) {
case EMMG_STREAM_BW_ALLOCATION:
HandleStreamBwAllocation(params);
break;
default:
break;
}
}
void Emmg::UpdateSendInterval(uint16_t bandwidth_kbps) {
if (bandwidth_kbps == 0) {
return;
}
send_interval_ms_ =
std::max(KMinSendIntervalMs,
static_cast<uint16_t>((kTsPacketSize * 8) / bandwidth_kbps));
LOG(INFO) << "Send interval set to " << send_interval_ms_ << " ms.";
}
void Emmg::HandleStreamBwAllocation(const EmmgParameters& params) {
UpdateSendInterval(params.bandwidth);
}
void Emmg::ReceiveResponseAndVerify(uint16_t expected_type) {
// Read response from socket.
bzero(response_, BUFFER_SIZE);

View File

@@ -30,6 +30,16 @@ struct EmmgConfig {
uint8_t data_type;
std::string content_provider;
std::string content_id;
uint16_t bandwidth;
uint32_t max_num_message;
};
// A struct that is used to hold all possible params for a EMMG request.
struct EmmgParameters {
uint32_t client_id;
uint16_t data_channel_id;
uint16_t data_stream_id;
uint16_t bandwidth;
};
// A class that sends EMMG/PDG message to the MUX server.
@@ -46,20 +56,32 @@ class Emmg {
// Protected visibility for unit testing.
void BuildChannelSetup();
void BuildStreamSetup();
void BuildStreamBwRequest();
void BuildDataProvision();
void BuildStreamCloseRequest();
void BuildChannelClose();
void HandleResponse(const char* const response);
size_t request_length_ = 0;
char request_[BUFFER_SIZE];
// Sending interval in milliseconds. Stream specific parameter but currently
// only one stream will be setup.
uint16_t send_interval_ms_ = 0;
private:
void SendChannelSetup();
void SendStreamSetup();
void SendStreamBwRequest();
void SendDataProvision();
void SendStreamCloseRequest();
void SendChannelClose();
void HandleStreamBwAllocation(const EmmgParameters& params);
void UpdateSendInterval(uint16_t bandwidth_kbps);
Status GeneratePrivateData(const std::string& content_provider,
const std::string& content_id, uint8_t* buffer);
void ReceiveResponseAndVerify(uint16_t expected_type);

View File

@@ -65,4 +65,10 @@
#define UNKNOWN_ERROR (0x7000)
#define UNRECOVERABLE_ERROR (0x7001)
// Size (in # of bytes) of various fields.
#define EMMG_CLIENT_ID_SIZE (4)
#define EMMG_DATA_CHANNEL_ID_SIZE (2)
#define EMMG_DATA_STREAM_ID_SIZE (2)
#define EMMG_BANDWIDTH_SIZE (2)
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_CONSTANTS_H_

View File

@@ -29,10 +29,15 @@ class TestableEmmg : public Emmg {
: Emmg(emmg_config, /* server_socket_fd= */ 1) {}
void PublicBuildChannelSetup() { BuildChannelSetup(); }
void PublicBuildStreamSetup() { BuildStreamSetup(); }
void PublicBuildStreamBwRequest() { BuildStreamBwRequest(); }
void PublicBuildDataProvision() { BuildDataProvision(); }
void PublicBuildStreamCloseRequest() { BuildStreamCloseRequest(); }
void PublicBuildChannelClose() { BuildChannelClose(); }
void PublicHandleResponse(const char* const response) {
HandleResponse(response);
}
char* GetRequest() { return request_; }
uint16_t GetSendInterval() { return send_interval_ms_; }
};
class EmmgTest : public ::testing::Test {
@@ -46,6 +51,8 @@ class EmmgTest : public ::testing::Test {
config_.data_type = 0x01;
config_.content_provider = "widevine_test";
config_.content_id = "CasTsFake";
config_.bandwidth = 100;
config_.max_num_message = 100;
emmg_ = absl::make_unique<TestableEmmg>(&config_);
}
@@ -79,6 +86,17 @@ TEST_F(EmmgTest, BuildStreamSetup) {
sizeof(kTestEmmgStreamSetup)));
}
TEST_F(EmmgTest, BuildStreamBwRequest) {
emmg_->PublicBuildStreamBwRequest();
EXPECT_EQ(0, memcmp(kTestEmmgStreamBwRequest, emmg_->GetRequest(),
sizeof(kTestEmmgStreamBwRequest)));
}
TEST_F(EmmgTest, HandleStreamBwAllocation) {
emmg_->PublicHandleResponse(kTestEmmgStreamBwAllocation);
EXPECT_EQ(30, emmg_->GetSendInterval());
}
TEST_F(EmmgTest, BuildDataProvision) {
emmg_->PublicBuildDataProvision();
EXPECT_EQ(0, memcmp(kTestEmmgDataProvision, emmg_->GetRequest(),

View File

@@ -8,16 +8,31 @@
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
#include "glog/logging.h"
#include "google/protobuf/util/json_util.h"
#include "protos/public/media_cas_encryption.pb.h"
namespace widevine {
namespace cas {
Status FixedKeyFetcher::RequestEntitlementKey(
const std::string& request_string,
std::string* signed_response_string) const {
Status FixedKeyFetcher::MakeHttpRequest(const std::string& signed_request_json,
std::string* http_response_json) const {
Status status;
SignedCasEncryptionRequest signed_request;
if (!google::protobuf::util::JsonStringToMessage(signed_request_json, &signed_request)
.ok()) {
status = {error::INTERNAL,
"Unable to convert to SignedCasEncryptionRequest."};
LOG(ERROR) << status;
return status;
}
CasEncryptionRequest request;
request.ParseFromString(request_string);
if (!google::protobuf::util::JsonStringToMessage(signed_request.request(), &request)
.ok()) {
LOG(ERROR) << "Unable to understand signed_request_json.";
return Status(error::INTERNAL);
}
CasEncryptionResponse response;
response.set_status(CasEncryptionResponse::OK);
@@ -45,10 +60,20 @@ Status FixedKeyFetcher::RequestEntitlementKey(
}
}
std::string response_string;
response.SerializeToString(&response_string);
if (!google::protobuf::util::MessageToJsonString(response, &response_string).ok()) {
LOG(ERROR) << "MessageToJsonString(response, &response_string)";
return Status(error::INTERNAL);
}
SignedCasEncryptionResponse signed_response;
signed_response.set_response(response_string);
signed_response.SerializeToString(signed_response_string);
if (!google::protobuf::util::MessageToJsonString(signed_response, http_response_json)
.ok()) {
LOG(ERROR) << "MessageToJsonString(signed_response, http_response_json)";
return Status(error::INTERNAL);
}
return OkStatus();
}

View File

@@ -12,20 +12,26 @@
#include <string>
#include "common/status.h"
#include "media_cas_packager_sdk/internal/key_fetcher.h"
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
namespace widevine {
namespace cas {
const char kSigningProvider[] = "widevine_test";
const char kSingingKey[] =
"1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9";
const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249";
// Perform the same role as a WV CAS KeyFetcher, but return a
// locally-constructed response that has known (predefined) entitlement keys.
class FixedKeyFetcher : public KeyFetcher {
class FixedKeyFetcher : public WvCasKeyFetcher {
public:
// Key IDs are 16 bytes, keys are 32 bytes.
// TODO(user): There should be a single entitlement key for both even
// and odd keys. Shouldn't have two different types of entitlement keys.
FixedKeyFetcher()
: even_entitlement_key_id_("fake_key_id1...."),
: WvCasKeyFetcher(kSigningProvider, kSingingKey, kSingingIv),
even_entitlement_key_id_("fake_key_id1...."),
even_entitlement_key_("fakefakefakefakefakefakefake1..."),
odd_entitlement_key_id_("fake_key_id2...."),
odd_entitlement_key_("fakefakefakefakefakefakefake2...") {}
@@ -35,7 +41,8 @@ class FixedKeyFetcher : public KeyFetcher {
const std::string& even_entitlement_key,
const std::string& odd_entitlement_key_id,
const std::string& odd_entitlement_key)
: even_entitlement_key_id_(even_entitlement_key_id),
: WvCasKeyFetcher(kSigningProvider, kSingingKey, kSingingIv),
even_entitlement_key_id_(even_entitlement_key_id),
even_entitlement_key_(even_entitlement_key),
odd_entitlement_key_id_(odd_entitlement_key_id),
odd_entitlement_key_(odd_entitlement_key) {}
@@ -43,18 +50,8 @@ class FixedKeyFetcher : public KeyFetcher {
FixedKeyFetcher& operator=(const FixedKeyFetcher&) = delete;
~FixedKeyFetcher() override = default;
// Get entitlement keys. Process a CasEncryptionRequest message to
// determine the keys that are needed, generate a fixed set of keys,
// and package them into a SignedCasEncryptionResponse message.
// Args:
// |request_string| a serialized CasEncryptionRequest message, produced
// by WvCasEcm::Initialize().
// |signed_response_string| a serialized SignedCasEncryptionResponse
// message. It should be passed into
// WvCasEcm::ProcessCasEncryptionResponse().
Status RequestEntitlementKey(
const std::string& request_string,
std::string* signed_response_string) const override;
Status MakeHttpRequest(const std::string& signed_request_json,
std::string* http_response_json) const override;
private:
std::string even_entitlement_key_id_;

View File

@@ -1,44 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 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_KEY_FETCHER_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_KEY_FETCHER_H_
#include <string>
#include "common/status.h"
namespace widevine {
namespace cas {
// Interface for fetching various types of keys.
class KeyFetcher {
public:
KeyFetcher() = default;
KeyFetcher(const KeyFetcher&) = delete;
KeyFetcher& operator=(const KeyFetcher&) = delete;
virtual ~KeyFetcher() = default;
// Get entitlement keys. Process a CasEncryptionRequest message to
// determine the keys that are needed, generate a fixed set of keys,
// and package them into a SignedCasEncryptionResponse message.
// Args:
// |request_string| a serialized CasEncryptionRequest message, produced
// by WvCasEcm::Initialize().
// |signed_response_string| a serialized SignedCasEncryptionResponse
// message. It should be passed into
// WvCasEcm::ProcessCasEncryptionResponse().
virtual Status RequestEntitlementKey(
const std::string& request_string,
std::string* signed_response_string) const = 0;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_KEY_FETCHER_H_