Decouple key fetcher; Update ECMG API
This commit is contained in:
@@ -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 = [
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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(¶ms_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(¶ms_one_key_, kTrackTypeSD, kIvSize16);
|
||||
InitParams(¶ms_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(¶ms_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(¶ms_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(¶ms_one_key_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
|
||||
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
InitParams(¶ms_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(¶ms_two_keys_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}));
|
||||
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
InitParams(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_two_keys_, kTrackTypeSD, kIvSize16);
|
||||
InitParams(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_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
|
||||
|
||||
@@ -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(¶ms->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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_,
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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(¶m_type, response + offset);
|
||||
offset += PARAMETER_TYPE_SIZE;
|
||||
BigEndianToHost16(¶m_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(¶ms->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(¶ms->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(¶ms->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(¶ms->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, ¶ms);
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user