Minimal implementation of Widevine MediaCAS ECMG.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=226515998
This commit is contained in:
@@ -28,9 +28,9 @@ cc_library(
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:status",
|
||||
"//common:aes_cbc_util",
|
||||
"//common:random_util",
|
||||
"//common:status",
|
||||
"//common:string_util",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
@@ -76,27 +76,42 @@ cc_test(
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ecmg",
|
||||
srcs = ["ecmg.cc"],
|
||||
name = "ecmg_client_handler",
|
||||
srcs = ["ecmg_client_handler.cc"],
|
||||
hdrs = [
|
||||
"ecmg.h",
|
||||
"ecmg_client_handler.h",
|
||||
"ecmg_constants.h",
|
||||
],
|
||||
deps = [
|
||||
":ecm",
|
||||
":ecm_generator",
|
||||
":fixed_key_fetcher",
|
||||
":mpeg2ts",
|
||||
":util",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:crypto_util",
|
||||
"//common:status",
|
||||
"//example:constants",
|
||||
"//media_cas_packager_sdk/public:wv_cas_ecm",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ecmg_client_handler_test",
|
||||
size = "small",
|
||||
srcs = ["ecmg_client_handler_test.cc"],
|
||||
deps = [
|
||||
":ecmg_client_handler",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"//example:test_ecmg_messages",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "fixed_key_fetcher",
|
||||
srcs = [
|
||||
@@ -131,34 +146,6 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "simulcrypt",
|
||||
srcs = ["simulcrypt.cc"],
|
||||
hdrs = [
|
||||
"simulcrypt.h",
|
||||
"simulcrypt_constants.h",
|
||||
],
|
||||
deps = [
|
||||
":ecmg",
|
||||
":util",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "simulcrypt_test",
|
||||
size = "small",
|
||||
srcs = ["simulcrypt_test.cc"],
|
||||
deps = [
|
||||
":simulcrypt",
|
||||
"//testing:gunit_main",
|
||||
"//example:test_simulcrypt_messages",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ts_packet",
|
||||
srcs = [
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/status.h"
|
||||
#include "common/aes_cbc_util.h"
|
||||
#include "common/random_util.h"
|
||||
#include "common/status.h"
|
||||
#include "common/string_util.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
@@ -112,31 +112,31 @@ bool ConvertIvSizeParam(EcmIvSize param, size_t* size) {
|
||||
|
||||
} // namespace
|
||||
|
||||
util::Status CasEcm::Initialize(const std::string& content_id,
|
||||
const std::string& content_provider,
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
std::string* key_request_message) {
|
||||
Status CasEcm::Initialize(const std::string& content_id,
|
||||
const std::string& content_provider,
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
std::string* key_request_message) {
|
||||
if (initialized_) {
|
||||
return {util::error::INTERNAL, "Already initialized."};
|
||||
return {error::INTERNAL, "Already initialized."};
|
||||
}
|
||||
if (content_id.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Content ID is empty."};
|
||||
return {error::INVALID_ARGUMENT, "Content ID is empty."};
|
||||
}
|
||||
if (content_provider.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Content Provider is empty."};
|
||||
return {error::INVALID_ARGUMENT, "Content Provider is empty."};
|
||||
}
|
||||
if (key_request_message == nullptr) {
|
||||
return {util::error::INVALID_ARGUMENT, "key_request_message is null."};
|
||||
return {error::INVALID_ARGUMENT, "key_request_message is null."};
|
||||
}
|
||||
|
||||
if (ecm_init_parameters.track_types.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
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 {util::error::INVALID_ARGUMENT,
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Parameter content_iv_size must be kIvSize8 or kIvSize16."};
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ util::Status CasEcm::Initialize(const std::string& content_id,
|
||||
generation_ = kMaxGeneration;
|
||||
|
||||
// Construct and return CasEncryptionRequest message for caller to use.
|
||||
util::Status status = CreateEntitlementRequest(key_request_message);
|
||||
Status status = CreateEntitlementRequest(key_request_message);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Entitlement request message could not be created.";
|
||||
return status;
|
||||
@@ -160,31 +160,30 @@ util::Status CasEcm::Initialize(const std::string& content_id,
|
||||
// Everything is set up except entitlement keys.
|
||||
ClearEntitlementKeys();
|
||||
initialized_ = true;
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ProcessCasEncryptionResponse(const std::string& response) {
|
||||
Status CasEcm::ProcessCasEncryptionResponse(const std::string& response) {
|
||||
if (!initialized_) {
|
||||
return {util::error::INTERNAL, "Not initialized."};
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (response.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Response std::string is empty."};
|
||||
return {error::INVALID_ARGUMENT, "Response std::string is empty."};
|
||||
}
|
||||
return ParseEntitlementResponse(response);
|
||||
}
|
||||
|
||||
util::Status CasEcm::GenerateEcm(EntitledKeyInfo* even_key,
|
||||
EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation) {
|
||||
Status CasEcm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type, std::string* serialized_ecm,
|
||||
uint32_t* generation) {
|
||||
if (!initialized_) {
|
||||
return {util::error::INTERNAL, "Not initialized."};
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (!HaveEntitlementKeys()) {
|
||||
return {util::error::INTERNAL, "Need entitlement key."};
|
||||
return {error::INTERNAL, "Need entitlement key."};
|
||||
}
|
||||
if (!paired_keys_required_) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Key rotation not enabled - use GenerateSingleKeyEcm()."};
|
||||
}
|
||||
|
||||
@@ -195,18 +194,18 @@ util::Status CasEcm::GenerateEcm(EntitledKeyInfo* even_key,
|
||||
return GenerateEcmCommon(keys, track_type, serialized_ecm, generation);
|
||||
}
|
||||
|
||||
util::Status CasEcm::GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm,
|
||||
uint32_t* generation) {
|
||||
Status CasEcm::GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm,
|
||||
uint32_t* generation) {
|
||||
if (!initialized_) {
|
||||
return {util::error::INTERNAL, "Not initialized."};
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (!HaveEntitlementKeys()) {
|
||||
return {util::error::INTERNAL, "Need entitlement key."};
|
||||
return {error::INTERNAL, "Need entitlement key."};
|
||||
}
|
||||
if (paired_keys_required_) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Key rotation enabled - use GenerateEcm()."};
|
||||
}
|
||||
|
||||
@@ -216,17 +215,17 @@ util::Status CasEcm::GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
return GenerateEcmCommon(keys, track_type, serialized_ecm, generation);
|
||||
}
|
||||
|
||||
util::Status CasEcm::GenerateEcmCommon(
|
||||
const std::vector<EntitledKeyInfo*>& keys, const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation) {
|
||||
Status CasEcm::GenerateEcmCommon(const std::vector<EntitledKeyInfo*>& keys,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation) {
|
||||
if (serialized_ecm == nullptr) {
|
||||
return {util::error::INVALID_ARGUMENT, "No return ecm std::string pointer."};
|
||||
return {error::INVALID_ARGUMENT, "No return ecm std::string pointer."};
|
||||
}
|
||||
if (generation == nullptr) {
|
||||
return {util::error::INVALID_ARGUMENT, "No return generation pointer."};
|
||||
return {error::INVALID_ARGUMENT, "No return generation pointer."};
|
||||
}
|
||||
|
||||
util::Status status = ValidateKeys(keys);
|
||||
Status status = ValidateKeys(keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -254,37 +253,34 @@ util::Status CasEcm::GenerateEcmCommon(
|
||||
if (kMaxEcmSizeBytes < serialized_ecm->size()) {
|
||||
generation_ = previous_generation;
|
||||
serialized_ecm->clear();
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Maximum size of ECM has been exceeded.");
|
||||
return Status(error::INTERNAL, "Maximum size of ECM has been exceeded.");
|
||||
}
|
||||
*generation = generation_;
|
||||
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
void CasEcm::IncrementGeneration() {
|
||||
generation_ = (generation_ >= kMaxGeneration) ? 0 : generation_ + 1;
|
||||
}
|
||||
|
||||
util::Status CasEcm::WrapEntitledKeys(
|
||||
const std::string& track_type, const std::vector<EntitledKeyInfo*> keys) {
|
||||
Status CasEcm::WrapEntitledKeys(const std::string& track_type,
|
||||
const std::vector<EntitledKeyInfo*> keys) {
|
||||
if (!initialized_) {
|
||||
return {util::error::INTERNAL, "Not initialized."};
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (keys.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
"Vector of EntitledKeyInfo is empty."};
|
||||
return {error::INVALID_ARGUMENT, "Vector of EntitledKeyInfo is empty."};
|
||||
}
|
||||
|
||||
auto ekey_map_entry = entitlement_keys_.find(track_type);
|
||||
if (ekey_map_entry == entitlement_keys_.end()) {
|
||||
return {util::error::INTERNAL,
|
||||
"No Entitlement Key found for given track_type."};
|
||||
return {error::INTERNAL, "No Entitlement Key found for given track_type."};
|
||||
}
|
||||
|
||||
const auto& ekey_list = ekey_map_entry->second;
|
||||
if (ekey_list.size() != keys.size()) {
|
||||
return {util::error::INTERNAL,
|
||||
return {error::INTERNAL,
|
||||
"Number of Entitled keys and Entitlement keys must match."};
|
||||
}
|
||||
|
||||
@@ -298,7 +294,7 @@ util::Status CasEcm::WrapEntitledKeys(
|
||||
if (entitled_key->wrapped_key_iv.empty()) {
|
||||
CHECK(RandomBytes(kWrappedKeyIvSizeBytes, &entitled_key->wrapped_key_iv));
|
||||
}
|
||||
util::Status status =
|
||||
Status status =
|
||||
WrapKey(entitlement_key->key_value, entitled_key->wrapped_key_iv,
|
||||
entitled_key->key_value, &entitled_key->wrapped_key_value);
|
||||
if (!status.ok()) {
|
||||
@@ -306,13 +302,12 @@ util::Status CasEcm::WrapEntitledKeys(
|
||||
}
|
||||
entitlement_key++;
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::WrapKey(const std::string& wrapping_key,
|
||||
const std::string& wrapping_iv, const std::string& key_value,
|
||||
std::string* wrapped_key) {
|
||||
util::Status status = ValidateKeyValue(wrapping_key, kWrappingKeySizeBytes);
|
||||
Status CasEcm::WrapKey(const std::string& wrapping_key, const std::string& wrapping_iv,
|
||||
const std::string& key_value, std::string* wrapped_key) {
|
||||
Status status = ValidateKeyValue(wrapping_key, kWrappingKeySizeBytes);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -328,14 +323,14 @@ util::Status CasEcm::WrapKey(const std::string& wrapping_key,
|
||||
*wrapped_key =
|
||||
crypto_util::EncryptAesCbcNoPad(wrapping_key, wrapping_iv, key_value);
|
||||
if (wrapped_key->empty()) {
|
||||
return util::Status(util::error::INTERNAL, "Failed to wrap key");
|
||||
return Status(error::INTERNAL, "Failed to wrap key");
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ValidateKeys(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
Status CasEcm::ValidateKeys(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
for (const auto& key : keys) {
|
||||
util::Status status;
|
||||
Status status;
|
||||
status = ValidateKeyId(key->key_id);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -349,13 +344,12 @@ util::Status CasEcm::ValidateKeys(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ValidateWrappedKeys(
|
||||
const std::vector<EntitledKeyInfo*>& keys) {
|
||||
Status CasEcm::ValidateWrappedKeys(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
for (const auto& key : keys) {
|
||||
util::Status status;
|
||||
Status status;
|
||||
status = ValidateKeyId(key->key_id);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -374,31 +368,31 @@ util::Status CasEcm::ValidateWrappedKeys(
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ValidateKeyId(const std::string& key_id) {
|
||||
Status CasEcm::ValidateKeyId(const std::string& key_id) {
|
||||
if (key_id.size() != kKeyIdSizeBytes) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key ID must be 16 bytes."};
|
||||
return {error::INVALID_ARGUMENT, "Key ID must be 16 bytes."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ValidateKeyValue(const std::string& key_value,
|
||||
size_t key_value_size) {
|
||||
Status CasEcm::ValidateKeyValue(const std::string& key_value,
|
||||
size_t key_value_size) {
|
||||
if (key_value.size() != key_value_size) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
return Status(
|
||||
error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Key is wrong size (", key_value.size(), " bytes)."));
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ValidateIv(const std::string& iv, size_t size) {
|
||||
Status CasEcm::ValidateIv(const std::string& iv, size_t size) {
|
||||
if (iv.size() != size) {
|
||||
return {util::error::INVALID_ARGUMENT, "IV is wrong size."};
|
||||
return {error::INVALID_ARGUMENT, "IV is wrong size."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
std::string CasEcm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
@@ -427,7 +421,7 @@ std::string CasEcm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
LOG(FATAL) << "ECM bitset incorret size: " << ecm_bitset.size();
|
||||
}
|
||||
std::string serialized_ecm;
|
||||
util::Status status =
|
||||
Status status =
|
||||
string_util::BitsetStringToBinaryString(ecm_bitset, &serialized_ecm);
|
||||
if (!status.ok()) {
|
||||
LOG(FATAL) << "Failed to convert ECM bitset to std::string";
|
||||
@@ -441,7 +435,7 @@ std::string CasEcm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
return serialized_ecm;
|
||||
}
|
||||
|
||||
util::Status CasEcm::CreateEntitlementRequest(std::string* request_string) {
|
||||
Status CasEcm::CreateEntitlementRequest(std::string* request_string) {
|
||||
CasEncryptionRequest request;
|
||||
|
||||
request.set_content_id(content_id_);
|
||||
@@ -454,43 +448,42 @@ util::Status CasEcm::CreateEntitlementRequest(std::string* request_string) {
|
||||
|
||||
if (!request.SerializeToString(request_string)) {
|
||||
request_string->clear();
|
||||
return {util::error::INTERNAL, "Failure serializing request."};
|
||||
return {error::INTERNAL, "Failure serializing request."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ParseEntitlementResponse(const std::string& response_string) {
|
||||
Status CasEcm::ParseEntitlementResponse(const std::string& response_string) {
|
||||
// TODO(user): parse valid response. NOT Implemented.
|
||||
ClearEntitlementKeys();
|
||||
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
if (!signed_response.ParseFromString(response_string)) {
|
||||
return {util::error::INTERNAL, "Failure parsing signed response."};
|
||||
return {error::INTERNAL, "Failure parsing signed response."};
|
||||
}
|
||||
// TODO(user): Should verify signature.
|
||||
CasEncryptionResponse response;
|
||||
if (!response.ParseFromString(signed_response.response())) {
|
||||
return {util::error::INTERNAL, "Failure parsing signed response."};
|
||||
return {error::INTERNAL, "Failure parsing signed response."};
|
||||
}
|
||||
if (response.status() != CasEncryptionResponse_Status_OK) {
|
||||
return util::Status(
|
||||
util::error::INTERNAL,
|
||||
absl::StrCat("Failure reported by server: ", response.status(), " : ",
|
||||
response.status_message()));
|
||||
return Status(error::INTERNAL, absl::StrCat("Failure reported by server: ",
|
||||
response.status(), " : ",
|
||||
response.status_message()));
|
||||
}
|
||||
if (content_id_ != response.content_id()) {
|
||||
return util::Status(
|
||||
util::error::INTERNAL,
|
||||
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 {util::error::INTERNAL, "Failure: no entitlement keys in response."};
|
||||
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 util::Status(
|
||||
util::error::INTERNAL,
|
||||
return Status(
|
||||
error::INTERNAL,
|
||||
absl::StrCat(
|
||||
"Wrong number of keys in Entitlement Response - expected: ",
|
||||
keys_needed, " got: ", response.entitlement_keys().size()));
|
||||
@@ -509,7 +502,7 @@ util::Status CasEcm::ParseEntitlementResponse(const std::string& response_string
|
||||
EntitlementKeyInfo ekey;
|
||||
ekey.key_id = key.key_id();
|
||||
ekey.key_value = key.key();
|
||||
util::Status status = ValidateKeyValue(key.key(), kWrappingKeySizeBytes);
|
||||
Status status = ValidateKeyValue(key.key(), kWrappingKeySizeBytes);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -534,10 +527,9 @@ util::Status CasEcm::ParseEntitlementResponse(const std::string& response_string
|
||||
if (!CheckEntitlementKeys()) {
|
||||
LOG(ERROR) << "Could not stage entitlement keys from response:";
|
||||
response.ShortDebugString();
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"No suitable entitlement key was found.");
|
||||
return Status(error::INTERNAL, "No suitable entitlement key was found.");
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
size_t CasEcm::CountEntitlementKeys() const {
|
||||
|
||||
@@ -91,10 +91,10 @@ class CasEcm {
|
||||
// Notes:
|
||||
// The returned |key_request_message| must be sent to the server and
|
||||
// the response correctly parsed before ECMs can be generated.
|
||||
virtual util::Status Initialize(const std::string& content_id,
|
||||
const std::string& content_provider,
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
std::string* key_request_message);
|
||||
virtual Status Initialize(const std::string& content_id,
|
||||
const std::string& content_provider,
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
std::string* key_request_message);
|
||||
|
||||
// Parse a CasEncryptionResponse message holding the entitlement keys for
|
||||
// generating the ECM stream. The entitlement keys are used to encrypt the
|
||||
@@ -102,7 +102,7 @@ class CasEcm {
|
||||
// Args:
|
||||
// |response| a serialized CasEncryptionRequest message from the server
|
||||
// holding entitlement key information (or error information).
|
||||
virtual util::Status ProcessCasEncryptionResponse(const std::string& response);
|
||||
virtual Status ProcessCasEncryptionResponse(const std::string& response);
|
||||
|
||||
// Accept keys and IVs and construct an ECM that will fit into a Transport
|
||||
// Stream packet payload (184 bytes).
|
||||
@@ -118,10 +118,9 @@ class CasEcm {
|
||||
// entitlement key. Wrapping modifies the original structure.
|
||||
// Generation is a mod 32 counter. If the ECM has any changes from the
|
||||
// previous ECM, the generation is increased by one.
|
||||
virtual util::Status GenerateEcm(EntitledKeyInfo* even_key,
|
||||
EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation);
|
||||
virtual Status GenerateEcm(EntitledKeyInfo* even_key,
|
||||
EntitledKeyInfo* odd_key, const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation);
|
||||
|
||||
// Accept a key and IV and construct an ECM that will fit into a Transport
|
||||
// Stream packet payload (184 bytes). This call is specifically for the case
|
||||
@@ -135,10 +134,10 @@ class CasEcm {
|
||||
// with the initialized settings.
|
||||
// Generation is a mod 32 counter. If the ECM has any changes from the
|
||||
// previous ECM, the generation is increased by one.
|
||||
virtual util::Status GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm,
|
||||
uint32_t* generation);
|
||||
virtual Status GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm,
|
||||
uint32_t* generation);
|
||||
|
||||
protected: // For unit tests.
|
||||
// Take the input entitled |keys| and our current state, and generate
|
||||
@@ -154,8 +153,8 @@ class CasEcm {
|
||||
// |track_type| the track type for the keys. The type_track must match one
|
||||
// of the |EcmInitParameters::track_types| strings passed into Initialize().
|
||||
// |key| the Entitled Key to be wrapped.
|
||||
virtual util::Status WrapEntitledKeys(
|
||||
const std::string& track_type, const std::vector<EntitledKeyInfo*> keys);
|
||||
virtual Status WrapEntitledKeys(const std::string& track_type,
|
||||
const std::vector<EntitledKeyInfo*> keys);
|
||||
|
||||
private:
|
||||
// Entitlement key - |key_value| is used to wrap the content key, and |key_id|
|
||||
@@ -194,29 +193,27 @@ class CasEcm {
|
||||
}
|
||||
|
||||
// Common helper for GenerateEcm() and GenerateSingleKeyEcm()
|
||||
virtual util::Status GenerateEcmCommon(
|
||||
const std::vector<EntitledKeyInfo*>& keys, const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation);
|
||||
virtual Status GenerateEcmCommon(const std::vector<EntitledKeyInfo*>& keys,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation);
|
||||
|
||||
// Wrap |key_value| using |wrapping_key| (entitlement key) and |wrapping_iv|.
|
||||
// Returns the resulting wrapped key in |wrapped_key|.
|
||||
// Return a status indicating whether there has been any error.
|
||||
virtual util::Status WrapKey(const std::string& wrapping_key,
|
||||
const std::string& wrapping_iv,
|
||||
const std::string& key_value, std::string* wrapped_key);
|
||||
virtual Status WrapKey(const std::string& wrapping_key, const std::string& wrapping_iv,
|
||||
const std::string& key_value, std::string* wrapped_key);
|
||||
|
||||
virtual util::Status ValidateKeys(const std::vector<EntitledKeyInfo*>& keys);
|
||||
virtual util::Status ValidateWrappedKeys(
|
||||
const std::vector<EntitledKeyInfo*>& keys);
|
||||
virtual Status ValidateKeys(const std::vector<EntitledKeyInfo*>& keys);
|
||||
virtual Status ValidateWrappedKeys(const std::vector<EntitledKeyInfo*>& keys);
|
||||
|
||||
util::Status ValidateKeyId(const std::string& key_id);
|
||||
util::Status ValidateKeyValue(const std::string& key_value, size_t key_value_size);
|
||||
util::Status ValidateIv(const std::string& iv, size_t size);
|
||||
Status ValidateKeyId(const std::string& key_id);
|
||||
Status ValidateKeyValue(const std::string& key_value, size_t key_value_size);
|
||||
Status ValidateIv(const std::string& iv, size_t size);
|
||||
|
||||
// TODO(user): need unit tests for CreateEntitlementRequest.
|
||||
virtual util::Status CreateEntitlementRequest(std::string* request_string);
|
||||
virtual Status CreateEntitlementRequest(std::string* request_string);
|
||||
// TODO(user): need unit tests for ParseEntitlementResponse.
|
||||
virtual util::Status ParseEntitlementResponse(const std::string& response_string);
|
||||
virtual Status ParseEntitlementResponse(const std::string& response_string);
|
||||
|
||||
virtual uint32_t generation() const { return generation_; }
|
||||
virtual CryptoMode crypto_mode() const { return crypto_mode_; }
|
||||
|
||||
@@ -20,7 +20,7 @@ static constexpr int kMaxBytesKeyIdField = 16;
|
||||
|
||||
std::string CasEcmGenerator::GenerateEcm(const EcmParameters& params) {
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
util::Status status = ProcessEcmParameters(params, &keys);
|
||||
Status status = ProcessEcmParameters(params, &keys);
|
||||
if (!status.ok() || !initialized_) {
|
||||
LOG(ERROR) << " EcmParameters is not set up properly: " << status;
|
||||
return "";
|
||||
@@ -43,7 +43,7 @@ std::string CasEcmGenerator::GenerateEcm(const EcmParameters& params) {
|
||||
return serialized_ecm;
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ProcessEcmParameters(
|
||||
Status CasEcmGenerator::ProcessEcmParameters(
|
||||
const EcmParameters& ecm_params, std::vector<EntitledKeyInfo>* keys) {
|
||||
initialized_ = false;
|
||||
rotation_enabled_ = ecm_params.rotation_enabled;
|
||||
@@ -52,11 +52,11 @@ util::Status CasEcmGenerator::ProcessEcmParameters(
|
||||
keys->clear();
|
||||
uint32_t keys_needed = ecm_params.rotation_enabled ? 2 : 1;
|
||||
if (ecm_params.key_params.size() < keys_needed) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Number of supplied keys is wrong (check rotation periods)."};
|
||||
}
|
||||
for (int i = 0; i < keys_needed; i++) {
|
||||
util::Status status = ValidateKeyParameters(ecm_params.key_params[i]);
|
||||
Status status = ValidateKeyParameters(ecm_params.key_params[i]);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -70,80 +70,77 @@ util::Status CasEcmGenerator::ProcessEcmParameters(
|
||||
current_key_index_ = 0;
|
||||
current_key_even_ = true;
|
||||
initialized_ = true;
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateKeyId(const std::string& id) {
|
||||
Status CasEcmGenerator::ValidateKeyId(const std::string& id) {
|
||||
if (id.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key id is empty."};
|
||||
return {error::INVALID_ARGUMENT, "Key id is empty."};
|
||||
}
|
||||
if (id.size() > kMaxBytesKeyIdField) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key id is too long."};
|
||||
return {error::INVALID_ARGUMENT, "Key id is too long."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateKeyData(const std::string& key_data) {
|
||||
Status CasEcmGenerator::ValidateKeyData(const std::string& key_data) {
|
||||
if (key_data.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key data is empty."};
|
||||
return {error::INVALID_ARGUMENT, "Key data is empty."};
|
||||
}
|
||||
if (key_data.size() != kKeyDataSize) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key data is wrong size."};
|
||||
return {error::INVALID_ARGUMENT, "Key data is wrong size."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateIv(const std::string& iv,
|
||||
size_t required_size) {
|
||||
Status CasEcmGenerator::ValidateIv(const std::string& iv, size_t required_size) {
|
||||
if (iv.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "IV is empty."};
|
||||
return {error::INVALID_ARGUMENT, "IV is empty."};
|
||||
}
|
||||
if (required_size != 8 && required_size != 16) {
|
||||
return {util::error::INTERNAL, "IV size has not been set up correctly."};
|
||||
return {error::INTERNAL, "IV size has not been set up correctly."};
|
||||
}
|
||||
|
||||
if (iv.size() != required_size) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
"IV has wrong or inconsistent size."};
|
||||
return {error::INVALID_ARGUMENT, "IV has wrong or inconsistent size."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateWrappedKeyIv(const std::string& iv) {
|
||||
Status CasEcmGenerator::ValidateWrappedKeyIv(const std::string& iv) {
|
||||
// All wrapped key IVs must be 16 bytes.
|
||||
util::Status status = ValidateIv(iv, kIvSize16);
|
||||
Status status = ValidateIv(iv, kIvSize16);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << " Wrapped key IV is not valid: " << status;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateContentIv(const std::string& iv) {
|
||||
Status CasEcmGenerator::ValidateContentIv(const std::string& iv) {
|
||||
// If content_iv_size_ is zero, use this IV as the size for all future IVs in
|
||||
// this stream.
|
||||
if (content_iv_size_ == 0) {
|
||||
content_iv_size_ = iv.size();
|
||||
}
|
||||
util::Status status = ValidateIv(iv, content_iv_size_);
|
||||
Status status = ValidateIv(iv, content_iv_size_);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << " Content IV is not valid: " << status;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateKeyParameters(
|
||||
const KeyParameters& key_params) {
|
||||
util::Status status;
|
||||
Status CasEcmGenerator::ValidateKeyParameters(const KeyParameters& key_params) {
|
||||
Status status;
|
||||
status = ValidateKeyId(key_params.key_id);
|
||||
if (!status.ok()) return status;
|
||||
if (key_params.content_ivs.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Content IVs is empty."};
|
||||
return {error::INVALID_ARGUMENT, "Content IVs is empty."};
|
||||
}
|
||||
for (int i = 0; i < key_params.content_ivs.size(); i++) {
|
||||
status = ValidateContentIv(key_params.content_ivs[i]);
|
||||
if (!status.ok()) return status;
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -74,16 +74,16 @@ class CasEcmGenerator {
|
||||
private:
|
||||
friend class CasEcmGeneratorTest;
|
||||
|
||||
util::Status ProcessEcmParameters(const EcmParameters& ecm_params,
|
||||
std::vector<EntitledKeyInfo>* keys);
|
||||
Status ProcessEcmParameters(const EcmParameters& ecm_params,
|
||||
std::vector<EntitledKeyInfo>* keys);
|
||||
|
||||
util::Status ProcessEcmParameters(const EcmParameters& ecm_params);
|
||||
util::Status ValidateKeyId(const std::string& id);
|
||||
util::Status ValidateKeyData(const std::string& key_data);
|
||||
util::Status ValidateWrappedKeyIv(const std::string& iv);
|
||||
util::Status ValidateIv(const std::string& iv, size_t required_size);
|
||||
util::Status ValidateContentIv(const std::string& iv);
|
||||
util::Status ValidateKeyParameters(const KeyParameters& key_params);
|
||||
Status ProcessEcmParameters(const EcmParameters& ecm_params);
|
||||
Status ValidateKeyId(const std::string& id);
|
||||
Status ValidateKeyData(const std::string& key_data);
|
||||
Status ValidateWrappedKeyIv(const std::string& iv);
|
||||
Status ValidateIv(const std::string& iv, size_t required_size);
|
||||
Status ValidateContentIv(const std::string& iv);
|
||||
Status ValidateKeyParameters(const KeyParameters& key_params);
|
||||
|
||||
bool initialized_ = false;
|
||||
uint32_t generation_ = 0;
|
||||
|
||||
@@ -54,8 +54,8 @@ constexpr char kFakeCasEncryptionResponseKeyId[] = "fake_key_id.....";
|
||||
constexpr char kFakeCasEncryptionResponseKeyData[] =
|
||||
"fakefakefakefakefakefakefakefake";
|
||||
|
||||
util::Status HandleCasEncryptionRequest(const std::string& request_string,
|
||||
std::string* signed_response_string) {
|
||||
Status HandleCasEncryptionRequest(const std::string& request_string,
|
||||
std::string* signed_response_string) {
|
||||
CasEncryptionRequest request;
|
||||
request.ParseFromString(request_string);
|
||||
|
||||
@@ -89,7 +89,7 @@ util::Status HandleCasEncryptionRequest(const std::string& request_string,
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response_string);
|
||||
signed_response.SerializeToString(signed_response_string);
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -99,8 +99,8 @@ class CasEcmGeneratorTest : public testing::Test {
|
||||
void SetUp() override {
|
||||
}
|
||||
|
||||
util::Status ProcessEcmParameters(const EcmParameters& params,
|
||||
std::vector<EntitledKeyInfo>* keys) {
|
||||
Status ProcessEcmParameters(const EcmParameters& params,
|
||||
std::vector<EntitledKeyInfo>* keys) {
|
||||
return ecm_gen_.ProcessEcmParameters(params, keys);
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ TEST_F(CasEcmGeneratorTest, InitializeNoRotation) {
|
||||
|
||||
SetTestConfig1(&ecm_params);
|
||||
|
||||
util::Status status = ProcessEcmParameters(ecm_params, &keys);
|
||||
Status status = ProcessEcmParameters(ecm_params, &keys);
|
||||
ASSERT_OK(status);
|
||||
|
||||
ASSERT_EQ(EntitlementKeySize(ecm_params), 16);
|
||||
@@ -254,7 +254,7 @@ TEST_F(CasEcmGeneratorTest, InitializeSimpleRotation) {
|
||||
SetTestConfig2(&ecm_params);
|
||||
ecm_init_params_.key_rotation_enabled = true;
|
||||
|
||||
util::Status status = ProcessEcmParameters(ecm_params, &keys);
|
||||
Status status = ProcessEcmParameters(ecm_params, &keys);
|
||||
|
||||
EXPECT_TRUE(status.ok());
|
||||
EXPECT_TRUE(ecm_gen_.initialized());
|
||||
|
||||
@@ -68,14 +68,14 @@ class MockCasEcm : public CasEcm {
|
||||
return SerializeEcm(keys);
|
||||
}
|
||||
|
||||
virtual util::Status MockWrapEntitledKeys(
|
||||
virtual Status MockWrapEntitledKeys(
|
||||
const std::string& track_type, const std::vector<EntitledKeyInfo*>& keys) {
|
||||
for (auto entitled_key : keys) {
|
||||
entitled_key->entitlement_key_id = "entitlement_Mock";
|
||||
entitled_key->wrapped_key_value = "MockMockMockMock";
|
||||
entitled_key->wrapped_key_iv = "WRAPPED_KIV....x";
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
void MockSetup(bool two_keys, const std::string& track_type, uint32_t generation,
|
||||
@@ -226,7 +226,7 @@ TEST_F(CasEcmTest, GenerateEcmNotInitialized) {
|
||||
EntitledKeyInfo key1;
|
||||
std::string ecm_data;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm_data, &gen)
|
||||
.error_code());
|
||||
}
|
||||
@@ -237,7 +237,7 @@ TEST_F(CasEcmTest, GenerateEcm2NotInitialized) {
|
||||
EntitledKeyInfo key2;
|
||||
std::string ecm_data;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data, &gen)
|
||||
.error_code());
|
||||
}
|
||||
@@ -245,7 +245,7 @@ TEST_F(CasEcmTest, GenerateEcm2NotInitialized) {
|
||||
TEST_F(CasEcmTest, SetResponseNotInitialized) {
|
||||
CasEcm ecm_gen;
|
||||
const std::string response("anything");
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.ProcessCasEncryptionResponse(response).error_code());
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ TEST_F(CasEcmTest, InitNoTracksFail) {
|
||||
CasEcm ecm_gen;
|
||||
std::string request;
|
||||
EXPECT_EQ(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(content_id_, provider_, params_default_, &request)
|
||||
.error_code());
|
||||
}
|
||||
@@ -270,7 +270,7 @@ TEST_F(CasEcmTest, SecondInitFail) {
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request));
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request)
|
||||
.error_code());
|
||||
}
|
||||
@@ -280,7 +280,7 @@ TEST_F(CasEcmTest, InitEmptyContentIdFail) {
|
||||
const std::string empty_content_id;
|
||||
std::string request;
|
||||
EXPECT_EQ(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(empty_content_id, provider_, params_simple_, &request)
|
||||
.error_code());
|
||||
}
|
||||
@@ -290,7 +290,7 @@ TEST_F(CasEcmTest, InitEmptyProviderFail) {
|
||||
const std::string empty_provider;
|
||||
std::string request;
|
||||
EXPECT_EQ(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(content_id_, empty_provider, params_simple_, &request)
|
||||
.error_code());
|
||||
}
|
||||
@@ -315,7 +315,7 @@ TEST_F(CasEcmTest, GenerateWithNoEntitlementOneKeyFail) {
|
||||
uint32_t gen;
|
||||
// This will fail because the entitlement key has not been acquired
|
||||
// (via server call and ProcessCasEncryptionResponse()).
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen)
|
||||
.error_code());
|
||||
}
|
||||
@@ -334,13 +334,13 @@ TEST_F(CasEcmTest, GenerateWithNoEntitlementTwoKeysFail) {
|
||||
// This will fail because the entitlement keys have not been acquired
|
||||
// (via server call and ProcessCasEncryptionResponse()).
|
||||
EXPECT_EQ(
|
||||
util::error::INTERNAL,
|
||||
error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, RequestNullFail) {
|
||||
CasEcm ecm_gen;
|
||||
EXPECT_EQ(util::error::INVALID_ARGUMENT,
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(content_id_, provider_, params_simple_, nullptr)
|
||||
.error_code());
|
||||
}
|
||||
@@ -374,7 +374,7 @@ TEST_F(CasEcmTest, BadResponseFail) {
|
||||
std::string response;
|
||||
ServerCall(request, &response, false, true);
|
||||
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.ProcessCasEncryptionResponse(response).error_code());
|
||||
}
|
||||
|
||||
@@ -411,7 +411,7 @@ TEST_F(CasEcmTest, GenerateOneKeyNoGenFail) {
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
EXPECT_EQ(util::error::INVALID_ARGUMENT,
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, nullptr)
|
||||
.error_code());
|
||||
}
|
||||
@@ -431,7 +431,7 @@ TEST_F(CasEcmTest, GenerateOneKeyBadKeyIdFail) {
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(util::error::INVALID_ARGUMENT,
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen)
|
||||
.error_code());
|
||||
}
|
||||
@@ -455,7 +455,7 @@ TEST_F(CasEcmTest, GenerateOneKeyWrong) {
|
||||
EXPECT_THAT(0, gen);
|
||||
EXPECT_THAT(77, ecm.size());
|
||||
EXPECT_EQ(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateEcm(&key1, &key1, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
}
|
||||
|
||||
@@ -477,7 +477,7 @@ TEST_F(CasEcmTest, GenerateTwoKeysIvSizeFail) {
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
}
|
||||
|
||||
@@ -548,7 +548,7 @@ TEST_F(CasEcmTest, GenerateThreeKeysIvSize16x16Fail) {
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(
|
||||
util::error::INTERNAL,
|
||||
error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,282 +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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "media_cas_packager_sdk/internal/ecmg.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/status.h"
|
||||
#include "example/constants.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_generator.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
// Local helper function that processes all the parameters in an ECMG message.
|
||||
util::Status ProcessParameters(const char* message, size_t message_length,
|
||||
EcmgParameters* parameters) {
|
||||
DCHECK(message);
|
||||
DCHECK(parameters);
|
||||
|
||||
uint16_t parameter_type;
|
||||
uint16_t parameter_length;
|
||||
// 'offset' is used to track where we are within |message|.
|
||||
size_t offset = 0;
|
||||
// There could be CW_per_msg instances of CP_CW_combinations,
|
||||
// so we need to track how many we have processed so far
|
||||
// in order to know where to store the next CP_CW_combination.
|
||||
int current_cp_cw_combination_index = 0;
|
||||
while (offset != message_length) {
|
||||
BigEndianToHost16(¶meter_type, message + offset);
|
||||
offset += PARAMETER_TYPE_SIZE;
|
||||
BigEndianToHost16(¶meter_length, message + offset);
|
||||
offset += PARAMETER_LENGTH_SIZE;
|
||||
switch (parameter_type) {
|
||||
case ACCESS_CRITERIA: {
|
||||
LOG(WARNING) << "Ignoring access_criteria parameter of "
|
||||
<< parameter_length << " bytes long";
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case ECM_CHANNEL_ID: {
|
||||
if (parameter_length != ECM_CHANNEL_ID_SIZE) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", parameter_length,
|
||||
" for parameter type ", parameter_type));
|
||||
}
|
||||
BigEndianToHost16(¶meters->ecm_channel_id, message + offset);
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case ECM_STREAM_ID: {
|
||||
if (parameter_length != ECM_STREAM_ID_SIZE) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", parameter_length,
|
||||
" for parameter type ", parameter_type));
|
||||
}
|
||||
BigEndianToHost16(¶meters->ecm_stream_id, message + offset);
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case CP_CW_COMBINATION: {
|
||||
if (current_cp_cw_combination_index > 2) {
|
||||
// We can have at most 3 CP_CW_Combinations.
|
||||
return util::Status(util::error::INVALID_ARGUMENT,
|
||||
"We only support up to 2 control words in the "
|
||||
"CW_provision message");
|
||||
}
|
||||
EcmgCpCwCombination* combination =
|
||||
¶meters->cp_cw_combinations[current_cp_cw_combination_index++];
|
||||
BigEndianToHost16(&combination->cp, message + offset);
|
||||
offset += CP_SIZE;
|
||||
size_t cw_size = parameter_length - CP_SIZE;
|
||||
combination->cw = std::string(message + offset, cw_size);
|
||||
offset += cw_size;
|
||||
// TODO(user): This is a temporary hack to let the ECM generator
|
||||
// know how many keys to include in the ECM.
|
||||
// CW_per_msg should have been set during channel set-up instead.
|
||||
parameters->cw_per_msg = current_cp_cw_combination_index;
|
||||
break;
|
||||
}
|
||||
case CP_DURATION: {
|
||||
if (parameter_length != CP_DURATION_SIZE) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", parameter_length,
|
||||
" for parameter type ", parameter_type));
|
||||
}
|
||||
BigEndianToHost16(¶meters->cp_duration, message + offset);
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case CP_NUMBER: {
|
||||
if (parameter_length != CP_NUMBER_SIZE) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", parameter_length,
|
||||
" for parameter type ", parameter_type));
|
||||
}
|
||||
BigEndianToHost16(¶meters->cp_number, message + offset);
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case CW_ENCRYPTION: {
|
||||
LOG(WARNING) << "Ignoring CW_encryption parameter of "
|
||||
<< parameter_length << " bytes long";
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case NOMINAL_CP_DURATION: {
|
||||
if (parameter_length != NOMINAL_CP_DURATION_SIZE) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", parameter_length,
|
||||
" for parameter type ", parameter_type));
|
||||
}
|
||||
BigEndianToHost16(¶meters->nominal_cp_duration, message + offset);
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return util::Status(
|
||||
util::error::UNIMPLEMENTED,
|
||||
absl::StrCat("No implementation yet to process parameter of type ",
|
||||
parameter_type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
util::Status Ecmg::ProcessStreamSetupMessage(const char* message,
|
||||
size_t message_length) {
|
||||
DCHECK(message);
|
||||
|
||||
EcmgParameters parameters;
|
||||
util::Status status = ProcessParameters(message, message_length, ¶meters);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (parameters.ecm_channel_id == 0 || parameters.ecm_stream_id == 0 ||
|
||||
parameters.nominal_cp_duration == 0) {
|
||||
return util::Status(util::error::INVALID_ARGUMENT,
|
||||
"Missing required parameter");
|
||||
}
|
||||
|
||||
if (channels_.find(parameters.ecm_channel_id) == channels_.end()) {
|
||||
std::unique_ptr<EcmgChannel> new_channel = absl::make_unique<EcmgChannel>();
|
||||
channels_[parameters.ecm_channel_id] = std::move(new_channel);
|
||||
}
|
||||
EcmgChannel* channel =
|
||||
channels_.find(parameters.ecm_channel_id)->second.get();
|
||||
auto stream_entry = channel->streams.find(parameters.ecm_stream_id);
|
||||
if (stream_entry == channel->streams.end()) {
|
||||
std::unique_ptr<EcmgStream> new_stream = absl::make_unique<EcmgStream>();
|
||||
new_stream->nominal_cp_duration = parameters.nominal_cp_duration;
|
||||
channel->streams[parameters.ecm_stream_id] = std::move(new_stream);
|
||||
} else {
|
||||
EcmgStream* existing_stream = stream_entry->second.get();
|
||||
existing_stream->nominal_cp_duration = parameters.nominal_cp_duration;
|
||||
}
|
||||
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status Ecmg::ProcessCwProvisionMessage(const char* message,
|
||||
size_t message_length,
|
||||
std::string* response) {
|
||||
DCHECK(message);
|
||||
DCHECK(response);
|
||||
|
||||
EcmgParameters parameters;
|
||||
util::Status status = ProcessParameters(message, message_length, ¶meters);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (parameters.ecm_channel_id == 0 || parameters.ecm_stream_id == 0 ||
|
||||
parameters.cp_number == 0 || parameters.cw_per_msg == 0) {
|
||||
return util::Status(util::error::INVALID_ARGUMENT,
|
||||
"Missing required parameter");
|
||||
}
|
||||
|
||||
// TODO(user): Figure out what to do with ECM_channel_ID and ECM_stream_ID.
|
||||
// - We certainly need to check the channel/stream has been setup
|
||||
// - Retrieve config parameters such as lead_CW and CW_per_msg
|
||||
// - In some config, we need to keep CW for previous CP to be included in the
|
||||
// current ECM
|
||||
// TODO(user): Remove debug loop below.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < parameters.cp_cw_combinations[i].cw.size(); j++) {
|
||||
printf("%x ",
|
||||
static_cast<uint16_t>(parameters.cp_cw_combinations[i].cw[j]));
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
bool key_rotation_enabled = parameters.cw_per_msg > 1;
|
||||
|
||||
// Create an instance of CasEcm in order to set the entitlement keys.
|
||||
// TODO(user): The section of code below for constructing CasEcm should
|
||||
// be optimized. There should be a single instance of CasEcm for each stream.
|
||||
// Right now, this is hard to do because CasEcmGenerator contains the CasEcm.
|
||||
std::unique_ptr<CasEcm> ecm = absl::make_unique<CasEcm>();
|
||||
// TODO(user): Revisit this hardcoded ecm_init_params.
|
||||
EcmInitParameters ecm_init_params;
|
||||
ecm_init_params.content_iv_size = kIvSize8;
|
||||
ecm_init_params.key_rotation_enabled = key_rotation_enabled;
|
||||
// TODO(user): Allow caller to specify the crypto mode.
|
||||
ecm_init_params.crypto_mode = CryptoMode::kAesCtr;
|
||||
// Only encrypt one video track.
|
||||
ecm_init_params.track_types.push_back(kDefaultTrackTypeSd);
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
// 'content_id' and 'provider' are used in entitlement key request/response
|
||||
// only, NOT needed for generating ECM.
|
||||
// So for initial demo, we can just use hardcoded value because we are using
|
||||
// hardcoded entitlement key anyway.
|
||||
// TODO(user): When we want to retrieve entitlement key from License Server
|
||||
// we need to figure out a way to provide real 'content_id' and 'provder'
|
||||
// to this function here from the Simulcrypt API.
|
||||
if (!(status = ecm->Initialize(kDefaultContentId, kDefaultProvider,
|
||||
ecm_init_params, &entitlement_request))
|
||||
.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (!(status = fixed_key_fetcher_.RequestEntitlementKey(
|
||||
entitlement_request, &entitlement_response))
|
||||
.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (!(status = ecm->ProcessCasEncryptionResponse(entitlement_response))
|
||||
.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
CasEcmGenerator ecm_generator;
|
||||
ecm_generator.set_ecm(std::move(ecm));
|
||||
EcmParameters ecm_param;
|
||||
ecm_param.rotation_enabled = key_rotation_enabled;
|
||||
for (int i = 0; i <= parameters.cw_per_msg; i++) {
|
||||
ecm_param.key_params.emplace_back();
|
||||
ecm_param.key_params[i].key_data = parameters.cp_cw_combinations[i].cw;
|
||||
// TODO(user): MUST have a better way to derive/retrieve key_id.
|
||||
// Currently set it to be the same as the key itself just for demo purpose.
|
||||
ecm_param.key_params[i].key_id = ecm_param.key_params[i].key_data;
|
||||
// TODO(user): MUST have a better way to generate/retrieve content_iv.
|
||||
ecm_param.key_params[i].content_ivs.push_back(
|
||||
std::string(kDefaultContentIv8Bytes));
|
||||
}
|
||||
std::string serialized_ecm = ecm_generator.GenerateEcm(ecm_param);
|
||||
std::cout << "serialized_ecm: " << serialized_ecm << std::endl;
|
||||
for (int i = 0; i < serialized_ecm.size(); i++) {
|
||||
printf("'\\x%x', ", static_cast<uint16_t>(serialized_ecm.at(i)));
|
||||
}
|
||||
std::cout << std::endl;
|
||||
LOG(INFO) << "ECM size: " << serialized_ecm.size();
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
@@ -1,94 +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_ECMG_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// A struct that represent a CP_CW_Combination.
|
||||
struct EcmgCpCwCombination {
|
||||
uint16_t cp = 0; // crypto period
|
||||
std::string cw; // control word
|
||||
};
|
||||
|
||||
// A struct that is used to hold all possible parameters for a ECMG message.
|
||||
struct EcmgParameters {
|
||||
// Default value of 0 for fields below is usually considered invalid.
|
||||
// Hence checking against 0 is used to detect whether each parameter
|
||||
// is set in the message.
|
||||
uint8_t cw_per_msg = 0;
|
||||
uint16_t ecm_channel_id = 0;
|
||||
uint16_t ecm_stream_id = 0;
|
||||
uint16_t nominal_cp_duration = 0;
|
||||
uint16_t cp_number = 0; // crypto period number
|
||||
uint16_t cp_duration = 0; // crypto period duration
|
||||
// CW_per_msg could 1, 2, or 3,
|
||||
// so there can be up to 3 CP_CW_Combinations
|
||||
EcmgCpCwCombination cp_cw_combinations[3];
|
||||
};
|
||||
|
||||
// A struct that holds information about a ECMG stream within a channel.
|
||||
struct EcmgStream {
|
||||
uint16_t nominal_cp_duration = 0;
|
||||
};
|
||||
|
||||
// A struct that holds information about a ECMG channel.
|
||||
struct EcmgChannel {
|
||||
// Map from ECM_stream_ID to an instance of EcmgStream.
|
||||
std::map<uint16_t, std::unique_ptr<EcmgStream>> streams;
|
||||
};
|
||||
|
||||
// A class that process Simulcrypt ECMG messages.
|
||||
// This class is NOT thread-safe.
|
||||
class Ecmg {
|
||||
public:
|
||||
Ecmg() = default;
|
||||
Ecmg(const Ecmg&) = delete;
|
||||
Ecmg& operator=(const Ecmg&) = delete;
|
||||
virtual ~Ecmg() = default;
|
||||
|
||||
// Process |message| of length |message_length|.
|
||||
// |message| is expected to be a Stream_set-up message.
|
||||
// Any error during processing would be turned via util::Status.
|
||||
util::Status ProcessStreamSetupMessage(const char* message,
|
||||
size_t message_length);
|
||||
|
||||
// Process |message| of length |message_length|.
|
||||
// |message| is expected to be a CW_provision request message.
|
||||
// ECM_response response message will be returned via |response|.
|
||||
// Any error during processing would be turned via util::Status.
|
||||
util::Status ProcessCwProvisionMessage(const char* message,
|
||||
size_t message_length,
|
||||
std::string* response);
|
||||
|
||||
private:
|
||||
// Keep track of all the channels.
|
||||
std::map<uint16_t, std::unique_ptr<EcmgChannel>> channels_;
|
||||
FixedKeyFetcher fixed_key_fetcher_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_H_
|
||||
619
media_cas_packager_sdk/internal/ecmg_client_handler.cc
Normal file
619
media_cas_packager_sdk/internal/ecmg_client_handler.cc
Normal file
@@ -0,0 +1,619 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/crypto_util.h"
|
||||
#include "example/constants.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_generator.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.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_types.h"
|
||||
|
||||
// 'section_TSpkt_flag' defines the format of the ECM.
|
||||
// We only support MPEG-2 transport stream packet format for now.
|
||||
// We do NOT support MPEG-2 section format yet.
|
||||
// TODO(user): Understand the difference between the two formats.
|
||||
static constexpr uint8_t kSectionTSpktFlag = 0x01;
|
||||
// 'max_stream' parameter defines the max number of simultaneous opened streams
|
||||
// suppported by an ECMG on a channel.
|
||||
// A value of 0 means that this maximum is not known.
|
||||
static constexpr uint16_t kMaxStream = 0;
|
||||
// 'lead_CW' parameter defines the number of contro lwords required in
|
||||
// advance to build an ECM.
|
||||
// TODO(user): Support other values of 'lead_CW' in combination with
|
||||
// other values for 'CW_per_msg'.
|
||||
static constexpr uint8_t kLeadCw = 1;
|
||||
// ' CW_per_msg' parameter defines the number of control words needed by the
|
||||
// ECMG per control word provision message.
|
||||
static constexpr uint8_t kCwPerMsg = 2;
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
// Local helper function that processes all the params in an ECMG request.
|
||||
Status HandleParameters(const char* const request, size_t request_length,
|
||||
EcmgParameters* params) {
|
||||
DCHECK(request);
|
||||
DCHECK(params);
|
||||
uint16_t param_type;
|
||||
uint16_t param_length;
|
||||
// 'offset' is used to track where we are within |request|.
|
||||
size_t offset = 0;
|
||||
// There could be CW_per_msg instances of CP_CW_combinations,
|
||||
// so we need to track how many we have processed so far
|
||||
// in order to know where to store the next CP_CW_combination.
|
||||
int current_cp_cw_combination_index = 0;
|
||||
while (offset != request_length) {
|
||||
BigEndianToHost16(¶m_type, request + offset);
|
||||
offset += PARAMETER_TYPE_SIZE;
|
||||
BigEndianToHost16(¶m_length, request + offset);
|
||||
offset += PARAMETER_LENGTH_SIZE;
|
||||
switch (param_type) {
|
||||
case ACCESS_CRITERIA: {
|
||||
LOG(WARNING) << "Ignoring access_criteria parameter of " << param_length
|
||||
<< " bytes long";
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case CP_CW_COMBINATION: {
|
||||
if (current_cp_cw_combination_index > 2) {
|
||||
// We can have at most 3 CP_CW_Combinations.
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
"We only support up to 2 control words in the "
|
||||
"CW_provision request");
|
||||
}
|
||||
EcmgCpCwCombination* combination =
|
||||
¶ms->cp_cw_combinations[current_cp_cw_combination_index++];
|
||||
BigEndianToHost16(&combination->cp, request + offset);
|
||||
offset += CP_SIZE;
|
||||
size_t cw_size = param_length - CP_SIZE;
|
||||
combination->cw = std::string(request + offset, cw_size);
|
||||
offset += cw_size;
|
||||
// TODO(user): This is a temporary hack to let the ECM generator
|
||||
// know how many keys to include in the ECM.
|
||||
// CW_per_msg should have been set during channel set-up instead.
|
||||
params->cw_per_msg = current_cp_cw_combination_index;
|
||||
break;
|
||||
}
|
||||
case CP_DURATION: {
|
||||
if (param_length != CP_DURATION_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
BigEndianToHost16(¶ms->cp_duration, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case CP_NUMBER: {
|
||||
if (param_length != CP_NUMBER_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
BigEndianToHost16(¶ms->cp_number, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case CW_ENCRYPTION: {
|
||||
LOG(WARNING) << "Ignoring CW_encryption parameter of " << param_length
|
||||
<< " bytes long";
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case ECM_CHANNEL_ID: {
|
||||
if (param_length != ECM_CHANNEL_ID_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
BigEndianToHost16(¶ms->ecm_channel_id, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case ECM_ID: {
|
||||
if (param_length != ECM_ID_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
BigEndianToHost16(¶ms->ecm_id, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case ECM_STREAM_ID: {
|
||||
if (param_length != ECM_STREAM_ID_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
BigEndianToHost16(¶ms->ecm_stream_id, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case NOMINAL_CP_DURATION: {
|
||||
if (param_length != NOMINAL_CP_DURATION_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
BigEndianToHost16(¶ms->nominal_cp_duration, request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
}
|
||||
case SUPER_CAS_ID: {
|
||||
if (param_length != SUPER_CAS_ID_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
BigEndianToHost32(¶ms->super_cas_id, request + 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();
|
||||
}
|
||||
|
||||
// Add 'protocol_version', 'message_type', 'message_length' to the message.
|
||||
// TODO(user): Per jfore@, consider pass in a pointer to a structure
|
||||
// #pragma pack(push, 1) // exact fit - no padding
|
||||
// struct MessageHeader{
|
||||
// uint8_t protocol_version;
|
||||
// uint16_t message_type;
|
||||
// uint16_t message_length;
|
||||
// };
|
||||
// #pragma pack(pop) // restore previous pack
|
||||
void BuildMessageHeader(uint16_t message_type, char* message,
|
||||
size_t* message_length) {
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
*message_length = 0;
|
||||
message[*message_length] = ECMG_SCS_PROTOCOL_VERSION;
|
||||
*message_length += PROTOCOL_VERSION_SIZE;
|
||||
Host16ToBigEndian(message + *message_length, &message_type);
|
||||
*message_length += MESSAGE_TYPE_SIZE;
|
||||
// NOTE: 'message_length' needs to be updated later after we have added all
|
||||
// the params so we know the exact length of the all the params.
|
||||
// Use 0 for 'total_param_length' until we know the length of the message.
|
||||
uint16_t total_param_length = 0;
|
||||
Host16ToBigEndian(message + *message_length, &total_param_length);
|
||||
*message_length += MESSAGE_LENGTH_SIZE;
|
||||
}
|
||||
|
||||
// Add a uint16_t parameter to the message.
|
||||
void AddUint16Param(uint16_t param_type, uint16_t param_value, char* message,
|
||||
size_t* message_length) {
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
Host16ToBigEndian(message + *message_length, ¶m_type);
|
||||
*message_length += 2;
|
||||
uint16_t param_length = 2;
|
||||
Host16ToBigEndian(message + *message_length, ¶m_length);
|
||||
*message_length += 2;
|
||||
Host16ToBigEndian(message + *message_length, ¶m_value);
|
||||
*message_length += param_length;
|
||||
}
|
||||
|
||||
// Add a uint8_t parameter to the message.
|
||||
void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message,
|
||||
size_t* message_length) {
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
Host16ToBigEndian(message + *message_length, ¶m_type);
|
||||
*message_length += 2;
|
||||
uint16_t param_length = 1;
|
||||
Host16ToBigEndian(message + *message_length, ¶m_length);
|
||||
*message_length += 2;
|
||||
memcpy(message + *message_length, ¶m_value, param_length);
|
||||
*message_length += param_length;
|
||||
}
|
||||
|
||||
// Add a param that is |param_length| bytes long.
|
||||
void AddParam(uint16_t param_type, uint8_t* param_value, uint16_t param_length,
|
||||
char* message, size_t* message_length) {
|
||||
DCHECK(param_value);
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
Host16ToBigEndian(message + *message_length, ¶m_type);
|
||||
*message_length += 2;
|
||||
Host16ToBigEndian(message + *message_length, ¶m_length);
|
||||
*message_length += 2;
|
||||
memcpy(message + *message_length, param_value, param_length);
|
||||
*message_length += param_length;
|
||||
}
|
||||
|
||||
void BuildChannelError(uint16_t channel_id, uint16_t error_status, char* message,
|
||||
size_t* message_length) {
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
BuildMessageHeader(ECMG_CHANNEL_ERROR, message, message_length);
|
||||
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
|
||||
AddUint16Param(ERROR_STATUS, error_status, message, message_length);
|
||||
// No setting Error_information parameter yet.
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message,
|
||||
size_t* message_length) {
|
||||
DCHECK(config);
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
BuildMessageHeader(ECMG_CHANNEL_STATUS, message, message_length);
|
||||
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
|
||||
AddUint8Param(SECTION_TSPKT_FLAG, kSectionTSpktFlag, message, message_length);
|
||||
// No setting AC_delay_start parameter yet.
|
||||
// No setting AC_delay_stop parameter yet.
|
||||
AddUint16Param(DELAY_START, config->delay_start, message, message_length);
|
||||
AddUint16Param(DELAY_STOP, config->delay_stop, message, message_length);
|
||||
// No setting transition_delay_start parameter yet.
|
||||
// No setting transition_delay_stop parameter yet.
|
||||
AddUint16Param(ECM_REP_PERIOD, config->ecm_rep_period, message,
|
||||
message_length);
|
||||
AddUint16Param(MAX_STREAMS, kMaxStream, message, message_length);
|
||||
// min_CP_duration needs to be at least max_comp_time. So we just use
|
||||
// max_comp_time here for now.
|
||||
AddUint16Param(MIN_CP_DURATION, config->max_comp_time, message,
|
||||
message_length);
|
||||
AddUint8Param(LEAD_CW, kLeadCw, message, message_length);
|
||||
AddUint8Param(CW_PER_MESSAGE, kCwPerMsg, message, message_length);
|
||||
AddUint16Param(MAX_COMP_TIME, config->max_comp_time, message, message_length);
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
void BuildStreamError(uint16_t channel_id, uint16_t stream_id, uint16_t error_status,
|
||||
char* message, size_t* message_length) {
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
BuildMessageHeader(ECMG_STREAM_ERROR, message, message_length);
|
||||
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
|
||||
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
|
||||
AddUint16Param(ERROR_STATUS, error_status, message, message_length);
|
||||
// No setting Error_information parameter yet.
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
void BuildStreamStatus(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id,
|
||||
uint8_t access_criteria_transfer_mode, char* message,
|
||||
size_t* message_length) {
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
BuildMessageHeader(ECMG_STREAM_STATUS, message, message_length);
|
||||
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
|
||||
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
|
||||
AddUint16Param(ECM_ID, ecm_id, message, message_length);
|
||||
AddUint8Param(ACCESS_CRITERIA_TRANSFER_MODE, access_criteria_transfer_mode,
|
||||
message, message_length);
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id,
|
||||
char* message, size_t* message_length) {
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
BuildMessageHeader(ECMG_STREAM_CLOSE_RESPONSE, message, message_length);
|
||||
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
|
||||
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, uint16_t cp_number,
|
||||
uint8_t* ecm_datagram, char* message,
|
||||
size_t* message_length) {
|
||||
DCHECK(ecm_datagram);
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
BuildMessageHeader(ECMG_ECM_RESPONSE, message, message_length);
|
||||
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
|
||||
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
|
||||
AddUint16Param(CP_NUMBER, cp_number, message, message_length);
|
||||
AddParam(ECM_DATAGRAM, ecm_datagram, kTsPacketSize, message, message_length);
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config) {
|
||||
DCHECK(ecmg_config);
|
||||
ecmg_config_ = ecmg_config;
|
||||
channel_id_set_ = false;
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
size_t* response_length) {
|
||||
DCHECK(request);
|
||||
DCHECK(response);
|
||||
|
||||
uint8_t protocol_version;
|
||||
uint16_t request_type;
|
||||
uint16_t request_length;
|
||||
// 'offset' is used to track where we are within |request|.
|
||||
size_t offset = 0;
|
||||
memcpy(&protocol_version, request, PROTOCOL_VERSION_SIZE);
|
||||
if (protocol_version != ECMG_SCS_PROTOCOL_VERSION) {
|
||||
// TODO(user): Should send an error response.
|
||||
return;
|
||||
}
|
||||
offset += PROTOCOL_VERSION_SIZE;
|
||||
BigEndianToHost16(&request_type, request + offset);
|
||||
offset += MESSAGE_TYPE_SIZE;
|
||||
BigEndianToHost16(&request_length, request + offset);
|
||||
offset += MESSAGE_LENGTH_SIZE;
|
||||
EcmgParameters params;
|
||||
Status status = HandleParameters(request + offset, request_length, ¶ms);
|
||||
if (!status.ok()) {
|
||||
// TODO(user): Should send an error response.
|
||||
return;
|
||||
}
|
||||
switch (request_type) {
|
||||
case ECMG_CHANNEL_SETUP: {
|
||||
HandleChannelSetup(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_CHANNEL_CLOSE: {
|
||||
HandleChannelClose(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_STREAM_SETUP: {
|
||||
HandleStreamSetup(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_STREAM_CLOSE_REQUEST: {
|
||||
HandleStreamCloseRequest(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_CW_PROVISION: {
|
||||
HandleCwProvision(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Unhandled or unknown request types.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
// TODO(user): Check SUPER_CAS_ID, if it is unexpected, return
|
||||
// UNKNOWN_SUPER_CAS_ID_VALUE error.
|
||||
if (channel_id_set_) {
|
||||
BuildChannelError(params.ecm_channel_id, INVAID_MESSAGE, response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
// TODO(user): To support multi-threading of serving concurrent clients,
|
||||
// there needs to be a set of channel_ids shared by multiple client handlers
|
||||
// threads.
|
||||
// And here we need to check if the channel_id is already in the set, if
|
||||
// yes, return an error.
|
||||
channel_id_ = params.ecm_channel_id;
|
||||
channel_id_set_ = true;
|
||||
BuildChannelStatus(params.ecm_channel_id, ecmg_config_, response,
|
||||
response_length);
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
if (channel_id_ != params.ecm_channel_id) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
UNKNOWN_ECM_CHANNEL_ID_VALUE, response, response_length);
|
||||
return;
|
||||
}
|
||||
channel_id_set_ = false;
|
||||
streams_.clear();
|
||||
*response_length = 0;
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
if (channel_id_ != params.ecm_channel_id) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
UNKNOWN_ECM_CHANNEL_ID_VALUE, response, response_length);
|
||||
return;
|
||||
}
|
||||
// TODO(user): To support multi-threading of serving concurrent clients,
|
||||
// there needs to be a set of stream_ids shared by multiple client handlers
|
||||
// threads.
|
||||
// And here we need to check if the stream_id is already in the set, if
|
||||
// yes, return an error.
|
||||
if (streams_.count(params.ecm_stream_id) > 0) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
ECM_STREAM_ID_VALUE_ALREADY_IN_USE, response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
// TODO(user): What do I do with the ECM_id here, currently not doing
|
||||
// anything with it?
|
||||
streams_[params.ecm_stream_id] = params.ecm_id;
|
||||
BuildStreamStatus(params.ecm_channel_id, params.ecm_stream_id, params.ecm_id,
|
||||
ecmg_config_->access_criteria_transfer_mode, response,
|
||||
response_length);
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
if (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_.count(params.ecm_stream_id) == 0) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
UNKNOWN_ECM_STREAM_ID_VALUE, response, response_length);
|
||||
return;
|
||||
}
|
||||
streams_.erase(params.ecm_stream_id);
|
||||
BuildStreamCloseResponse(params.ecm_channel_id, params.ecm_stream_id,
|
||||
response, response_length);
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) {
|
||||
DCHECK(response);
|
||||
DCHECK(response_length);
|
||||
if (channel_id_ != params.ecm_channel_id) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE,
|
||||
response, response_length);
|
||||
return;
|
||||
}
|
||||
if (streams_.count(params.ecm_stream_id) == 0) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_STREAM_ID_VALUE,
|
||||
response, response_length);
|
||||
return;
|
||||
}
|
||||
if (params.cw_per_msg < kCwPerMsg) {
|
||||
BuildChannelError(params.ecm_channel_id,
|
||||
NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM, response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
uint8_t ecm_datagram[kTsPacketSize];
|
||||
BuildEcmDatagram(params, ecm_datagram);
|
||||
BuildEcmResponse(params.ecm_channel_id, params.ecm_stream_id,
|
||||
params.cp_number, ecm_datagram, response, response_length);
|
||||
}
|
||||
|
||||
void EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
uint8_t* ecm_datagram) {
|
||||
DCHECK(ecm_datagram);
|
||||
// TODO(user): Remove debug loop below.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
std::cout << "CW: ";
|
||||
for (int j = 0; j < params.cp_cw_combinations[i].cw.size(); j++) {
|
||||
printf("'\\x%02x', ",
|
||||
static_cast<uint16_t>(params.cp_cw_combinations[i].cw[j]));
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// Step 1: Generate entitlement keys.
|
||||
bool key_rotation_enabled = params.cw_per_msg > 1;
|
||||
// Create an instance of CasEcm in order to set the entitlement keys.
|
||||
// TODO(user): The section of code below for constructing CasEcm should
|
||||
// be optimized. There should be a single instance of CasEcm for each stream.
|
||||
// Right now, this is hard to do because CasEcmGenerator contains the CasEcm.
|
||||
std::unique_ptr<CasEcm> ecm = absl::make_unique<CasEcm>();
|
||||
// TODO(user): Revisit this hardcoded ecm_init_params.
|
||||
EcmInitParameters ecm_init_params;
|
||||
ecm_init_params.content_iv_size = kIvSize8;
|
||||
ecm_init_params.key_rotation_enabled = key_rotation_enabled;
|
||||
// TODO(user): Allow crypto mode to be set to different value? Via flag?
|
||||
ecm_init_params.crypto_mode = CryptoMode::kDvbCsa2;
|
||||
// Only encrypt one video track.
|
||||
// TODO(user): Support multiple tracks? How?
|
||||
ecm_init_params.track_types.push_back(kDefaultTrackTypeSd);
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
// 'content_id' and 'provider' are used in entitlement key request/response
|
||||
// only, NOT needed for generating ECM.
|
||||
// So for initial demo, we can just use hardcoded value because we are using
|
||||
// hardcoded entitlement key anyway.
|
||||
// TODO(user): When we want to retrieve entitlement key from License Server
|
||||
// we need to figure out a way to provide real 'content_id' and 'provder'
|
||||
// to this function here from the Simulcrypt API.
|
||||
Status status;
|
||||
if (!(status = ecm->Initialize(kDefaultContentId, kDefaultProvider,
|
||||
ecm_init_params, &entitlement_request))
|
||||
.ok()) {
|
||||
// TODO(user): Should send an error response.
|
||||
return;
|
||||
}
|
||||
if (!(status = fixed_key_fetcher_.RequestEntitlementKey(
|
||||
entitlement_request, &entitlement_response))
|
||||
.ok()) {
|
||||
// TODO(user): Should send an error Channel_status.
|
||||
return;
|
||||
}
|
||||
if (!(status = ecm->ProcessCasEncryptionResponse(entitlement_response))
|
||||
.ok()) {
|
||||
// TODO(user): Should send an error Channel_status.
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2: Generate serialized ECM.
|
||||
CasEcmGenerator ecm_generator;
|
||||
ecm_generator.set_ecm(std::move(ecm));
|
||||
EcmParameters ecm_param;
|
||||
ecm_param.rotation_enabled = key_rotation_enabled;
|
||||
for (int i = 0; i <= params.cw_per_msg; i++) {
|
||||
ecm_param.key_params.emplace_back();
|
||||
ecm_param.key_params[i].key_data = params.cp_cw_combinations[i].cw;
|
||||
ecm_param.key_params[i].key_id = widevine::crypto_util::DeriveKeyId(
|
||||
ecm_param.key_params[i].key_data);
|
||||
// TODO(user): How to derive this content_iv, maybe based on key?
|
||||
std::string content_iv = "12345678";
|
||||
ecm_param.key_params[i].content_ivs.push_back(content_iv);
|
||||
}
|
||||
std::string serialized_ecm = ecm_generator.GenerateEcm(ecm_param);
|
||||
std::cout << "serialized_ecm: " << serialized_ecm << std::endl;
|
||||
for (int i = 0; i < serialized_ecm.size(); i++) {
|
||||
printf("'\\x%x', ", static_cast<uint16_t>(serialized_ecm.at(i)));
|
||||
}
|
||||
std::cout << std::endl;
|
||||
LOG(INFO) << "ECM size: " << serialized_ecm.size();
|
||||
|
||||
// Step 3: Make a TS packet carrying the serialized ECM.
|
||||
// TODO(user): Is it correct to set 'pid' using ECM_id?
|
||||
// TODO(user): Where do I get the continuity_counter?
|
||||
uint8_t continuity_counter = 0;
|
||||
WvCasEcm wv_cas_ecm;
|
||||
WvCasStatus cas_status = wv_cas_ecm.GenerateTsPacket(
|
||||
serialized_ecm, params.ecm_id,
|
||||
params.cp_number % 2 == 0 ? kTsPacketTableId80 : kTsPacketTableId81,
|
||||
&continuity_counter, ecm_datagram);
|
||||
if (cas_status != OK) {
|
||||
// TODO(user): Should send an error Channel_status.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
101
media_cas_packager_sdk/internal/ecmg_client_handler.h
Normal file
101
media_cas_packager_sdk/internal/ecmg_client_handler.h
Normal file
@@ -0,0 +1,101 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_ECMG_CLIENT_HANDLER_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CLIENT_HANDLER_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// A struct that captures the Ecmg configs.
|
||||
struct EcmgConfig {
|
||||
int16_t delay_start;
|
||||
int16_t delay_stop;
|
||||
uint16_t ecm_rep_period;
|
||||
uint16_t max_comp_time;
|
||||
uint8_t access_criteria_transfer_mode;
|
||||
};
|
||||
|
||||
// A struct that represent a CP_CW_Combination.
|
||||
struct EcmgCpCwCombination {
|
||||
uint16_t cp; // crypto period
|
||||
std::string cw; // control word
|
||||
};
|
||||
|
||||
// A struct that is used to hold all possible params for a ECMG request.
|
||||
struct EcmgParameters {
|
||||
// CW_per_msg could 1, 2, or 3,
|
||||
// so there can be up to 3 CP_CW_Combinations
|
||||
EcmgCpCwCombination cp_cw_combinations[3];
|
||||
uint16_t cp_duration; // crypto period duration
|
||||
uint16_t cp_number; // crypto period number
|
||||
uint8_t cw_per_msg;
|
||||
uint16_t ecm_channel_id;
|
||||
uint16_t ecm_stream_id;
|
||||
uint16_t ecm_id;
|
||||
uint16_t nominal_cp_duration;
|
||||
uint32_t super_cas_id;
|
||||
};
|
||||
|
||||
// A class that handles one (and only one) ECMG client.
|
||||
// This class is NOT thread-safe.
|
||||
class EcmgClientHandler {
|
||||
public:
|
||||
explicit EcmgClientHandler(EcmgConfig* ecmg_config);
|
||||
EcmgClientHandler(const EcmgClientHandler&) = delete;
|
||||
EcmgClientHandler& operator=(const EcmgClientHandler&) = delete;
|
||||
virtual ~EcmgClientHandler() = default;
|
||||
|
||||
// Handle a |request| from the client.
|
||||
// If any response is generated, it would returned via |response|
|
||||
// and |response_length|.
|
||||
void HandleRequest(const char* const request, char* response,
|
||||
size_t* response_length);
|
||||
|
||||
private:
|
||||
void HandleChannelSetup(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
// TODO(user): HandleChannelTest()
|
||||
void HandleChannelClose(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
void HandleStreamSetup(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
// TODO(user): HandleStreamTest()
|
||||
void HandleStreamCloseRequest(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
void HandleCwProvision(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
void BuildEcmDatagram(const EcmgParameters& params, uint8_t* ecm_datagram);
|
||||
|
||||
EcmgConfig* ecmg_config_;
|
||||
// Per spec, "There is always one (and only one) channel per TCP connection".
|
||||
bool channel_id_set_;
|
||||
uint16_t channel_id_;
|
||||
// Map from ECM_stream_id to ECM_id.
|
||||
std::unordered_map<uint16_t, uint16_t> streams_;
|
||||
FixedKeyFetcher fixed_key_fetcher_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CLIENT_HANDLER_H_
|
||||
85
media_cas_packager_sdk/internal/ecmg_client_handler_test.cc
Normal file
85
media_cas_packager_sdk/internal/ecmg_client_handler_test.cc
Normal file
@@ -0,0 +1,85 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "example/test_ecmg_messages.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
namespace {
|
||||
|
||||
#define BUFFER_SIZE (1024)
|
||||
|
||||
class EcmgClientHandlerTest : public ::testing::Test {
|
||||
protected:
|
||||
EcmgClientHandlerTest() {
|
||||
config_.delay_start = 200;
|
||||
config_.delay_stop = 200;
|
||||
config_.ecm_rep_period = 100;
|
||||
config_.max_comp_time = 100;
|
||||
config_.access_criteria_transfer_mode = 1;
|
||||
client_handler_ = absl::make_unique<EcmgClientHandler>(&config_);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Helper function for debugging the tests.
|
||||
void PrintMessage(const std::string &description, const char *const message,
|
||||
size_t length) {
|
||||
printf("%s ", description.c_str());
|
||||
fflush(stdout);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
printf("'\\x%02x', ", static_cast<uint16_t>(*(message + i)));
|
||||
fflush(stdout);
|
||||
}
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
EcmgConfig config_;
|
||||
std::unique_ptr<EcmgClientHandler> client_handler_;
|
||||
};
|
||||
|
||||
// TODO(user): Add unit tests for error cases.
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessSequence) {
|
||||
char response[BUFFER_SIZE];
|
||||
size_t response_length;
|
||||
|
||||
client_handler_->HandleRequest(kTestChannelSetup, response, &response_length);
|
||||
EXPECT_EQ(62, response_length);
|
||||
EXPECT_EQ(0, memcmp(kTestChannelStatus, response, response_length));
|
||||
|
||||
client_handler_->HandleRequest(kTestStreamSetup, response, &response_length);
|
||||
EXPECT_EQ(28, response_length);
|
||||
EXPECT_EQ(0, memcmp(kTestStreamStatus, response, response_length));
|
||||
|
||||
client_handler_->HandleRequest(kTestCwProvision, response, &response_length);
|
||||
EXPECT_EQ(215, response_length);
|
||||
// Only comparing the bytes in front of the ECM_datagram, because
|
||||
// random wrapping IV is generated each time causing the ECM_datagram
|
||||
// to be non-deterministic.
|
||||
EXPECT_EQ(0, memcmp(kTestEcmResponse, response, 27));
|
||||
|
||||
client_handler_->HandleRequest(kTestStreamCloseRequest, response,
|
||||
&response_length);
|
||||
EXPECT_EQ(17, response_length);
|
||||
EXPECT_EQ(0, memcmp(kTestStreamCloseResponse, response, response_length));
|
||||
|
||||
client_handler_->HandleRequest(kTestChannelClose, response, &response_length);
|
||||
EXPECT_EQ(0, response_length);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
@@ -9,45 +9,95 @@
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CONSTANTS_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CONSTANTS_H_
|
||||
|
||||
// ECMG <> SCS
|
||||
// Parameter_type values
|
||||
#define DVB_RESERVED 0x0000
|
||||
#define SUPER_CAS_ID 0x0001
|
||||
#define SECTION_TSPKT_FLAG 0x0002
|
||||
#define DELAY_START 0x0003
|
||||
#define DELAY_STOP 0x0004
|
||||
#define TRANSITION_DELAY_START 0x0005
|
||||
#define TRANSITION_DELAY_STOP 0x0006
|
||||
#define ECM_REP_PERIOD 0x0007
|
||||
#define MAX_STREAMS 0x0008
|
||||
#define MIN_CP_DURATION 0x0009
|
||||
#define LEAD_CW 0x000A
|
||||
#define CW_PER_MESSAGE 0x000B
|
||||
#define MAX_COMP_TIME 0x000C
|
||||
#define ACCESS_CRITERIA 0x000D
|
||||
#define ECM_CHANNEL_ID 0x000E
|
||||
#define ECM_STREAM_ID 0x000F
|
||||
#define NOMINAL_CP_DURATION 0x0010
|
||||
#define ACCESS_CRITERIA_TRANSFER_MODE 0x0011
|
||||
#define CP_NUMBER 0x0012
|
||||
#define CP_DURATION 0x0013
|
||||
#define CP_CW_COMBINATION 0x0014
|
||||
#define ECM_DATAGRAM 0x0015
|
||||
#define AC_DELAY_START 0x0016
|
||||
#define AC_DELAY_STOP 0x0017
|
||||
#define CW_ENCRYPTION 0x0018
|
||||
#define ECM_ID 0x0019
|
||||
#define ERROR_STATUS 0x7000
|
||||
#define ERROR_INFORMATION 0x7001
|
||||
// ECMG <=> SCS protocol_version.
|
||||
#define ECMG_SCS_PROTOCOL_VERSION (0x03)
|
||||
|
||||
// ECMG message type values.
|
||||
// 0x0000 DVB reserved.
|
||||
#define ECMG_CHANNEL_SETUP (0x0001)
|
||||
#define ECMG_CHANNEL_TEST (0x0002)
|
||||
#define ECMG_CHANNEL_STATUS (0x0003)
|
||||
#define ECMG_CHANNEL_CLOSE (0x0004)
|
||||
#define ECMG_CHANNEL_ERROR (0x0005)
|
||||
// 0x0016 - 0x0100 DVB reserved.
|
||||
#define ECMG_STREAM_SETUP (0x0101)
|
||||
#define ECMG_STREAM_TEST (0x0102)
|
||||
#define ECMG_STREAM_STATUS (0x0103)
|
||||
#define ECMG_STREAM_CLOSE_REQUEST (0x0104)
|
||||
#define ECMG_STREAM_CLOSE_RESPONSE (0x0105)
|
||||
#define ECMG_STREAM_ERROR (0x0106)
|
||||
// 0x0119 - 0x0200 DVB reserved.
|
||||
#define ECMG_CW_PROVISION (0x0201)
|
||||
#define ECMG_ECM_RESPONSE (0x0202)
|
||||
|
||||
// ECMG parameter type values.
|
||||
#define DVB_RESERVED (0x0000)
|
||||
#define SUPER_CAS_ID (0x0001)
|
||||
#define SECTION_TSPKT_FLAG (0x0002)
|
||||
#define DELAY_START (0x0003)
|
||||
#define DELAY_STOP (0x0004)
|
||||
#define TRANSITION_DELAY_START (0x0005)
|
||||
#define TRANSITION_DELAY_STOP (0x0006)
|
||||
#define ECM_REP_PERIOD (0x0007)
|
||||
#define MAX_STREAMS (0x0008)
|
||||
#define MIN_CP_DURATION (0x0009)
|
||||
#define LEAD_CW (0x000A)
|
||||
#define CW_PER_MESSAGE (0x000B)
|
||||
#define MAX_COMP_TIME (0x000C)
|
||||
#define ACCESS_CRITERIA (0x000D)
|
||||
#define ECM_CHANNEL_ID (0x000E)
|
||||
#define ECM_STREAM_ID (0x000F)
|
||||
#define NOMINAL_CP_DURATION (0x0010)
|
||||
#define ACCESS_CRITERIA_TRANSFER_MODE (0x0011)
|
||||
#define CP_NUMBER (0x0012)
|
||||
#define CP_DURATION (0x0013)
|
||||
#define CP_CW_COMBINATION (0x0014)
|
||||
#define ECM_DATAGRAM (0x0015)
|
||||
#define AC_DELAY_START (0x0016)
|
||||
#define AC_DELAY_STOP (0x0017)
|
||||
#define CW_ENCRYPTION (0x0018)
|
||||
#define ECM_ID (0x0019)
|
||||
#define ERROR_STATUS (0x7000)
|
||||
#define ERROR_INFORMATION (0x7001)
|
||||
|
||||
// ECMG protocol error values.
|
||||
#define INVAID_MESSAGE (0x0001)
|
||||
#define UNSUPPORTED_PROTOCOL_VERSION (0X0002)
|
||||
#define UNKNOWN_MESSAGE_TYPE_VALUE (0X0003)
|
||||
#define MESSAGE_TOO_LONG (0X0004)
|
||||
#define UNKNOWN_SUPER_CAS_ID_VALUE (0X0005)
|
||||
#define UNKNOWN_ECM_CHANNEL_ID_VALUE (0X0006)
|
||||
#define UNKNOWN_ECM_STREAM_ID_VALUE (0X0007)
|
||||
#define TOO_MANY_CHANNELS_ON_THIS_ECMG (0X0008)
|
||||
#define TOO_MANY_ECM_STREAMS_ON_THIS_CHANNEL (0X0009)
|
||||
#define TOO_MANY_ECM_STREAMS_ON_THIS_ECMG (0X000A)
|
||||
#define NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM (0X000B)
|
||||
#define ECMG_OUT_OF_STORAGE_CAPACITY (0X000C)
|
||||
#define ECMG_OUT_OF_COMPUTATIONAL_RESOURCES (0X000D)
|
||||
#define UNKNOWN_PARAMETER_TYPE_VALUE (0X000E)
|
||||
#define INCONSISTENT_LENGTH_FOR_DVB_PARAMETER (0X000F)
|
||||
#define MISSING_MANDATORY_DVB_PARAMETER (0X0010)
|
||||
#define INVALID_VALUE_FOR_DVB_PARAMETER (0X0011)
|
||||
#define UNKNOWN_ECM_ID_VALUE (0X0012)
|
||||
#define ECM_CHANNEL_ID_VALUE_ALREADY_IN_USE (0X0013)
|
||||
#define ECM_STREAM_ID_VALUE_ALREADY_IN_USE (0X0014)
|
||||
#define ECM_ID_VALUE_ALREADY_IN_USE (0X0015)
|
||||
#define UNKNOWN_ERROR (0X7000)
|
||||
#define UNRECOVERABLE_ERROR (0X7001)
|
||||
|
||||
// Size (in # of bytes) of various fields.
|
||||
#define PARAMETER_TYPE_SIZE 2
|
||||
#define PARAMETER_LENGTH_SIZE 2
|
||||
#define ECM_CHANNEL_ID_SIZE 2
|
||||
#define ECM_STREAM_ID_SIZE 2
|
||||
#define NOMINAL_CP_DURATION_SIZE 2
|
||||
#define CP_NUMBER_SIZE 2
|
||||
#define CP_DURATION_SIZE 2
|
||||
#define CP_SIZE 2
|
||||
#define PROTOCOL_VERSION_SIZE (1)
|
||||
#define MESSAGE_TYPE_SIZE (2)
|
||||
#define MESSAGE_LENGTH_SIZE (2)
|
||||
#define PARAMETER_TYPE_SIZE (2)
|
||||
#define PARAMETER_LENGTH_SIZE (2)
|
||||
#define SUPER_CAS_ID_SIZE (4)
|
||||
#define ECM_CHANNEL_ID_SIZE (2)
|
||||
#define ECM_ID_SIZE (2)
|
||||
#define ECM_STREAM_ID_SIZE (2)
|
||||
#define NOMINAL_CP_DURATION_SIZE (2)
|
||||
#define CP_NUMBER_SIZE (2)
|
||||
#define CP_DURATION_SIZE (2)
|
||||
#define CP_SIZE (2)
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CONSTANTS_H_
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
util::Status FixedKeyFetcher::RequestEntitlementKey(
|
||||
const std::string& request_string, std::string* signed_response_string) {
|
||||
Status FixedKeyFetcher::RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) {
|
||||
CasEncryptionRequest request;
|
||||
request.ParseFromString(request_string);
|
||||
|
||||
@@ -49,7 +49,7 @@ util::Status FixedKeyFetcher::RequestEntitlementKey(
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response_string);
|
||||
signed_response.SerializeToString(signed_response_string);
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -49,8 +49,8 @@ class FixedKeyFetcher : public KeyFetcher {
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// WvCasEcm::ProcessCasEncryptionResponse().
|
||||
util::Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) override;
|
||||
Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) override;
|
||||
|
||||
private:
|
||||
std::string even_entitlement_key_id_;
|
||||
|
||||
@@ -33,8 +33,8 @@ class KeyFetcher {
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// WvCasEcm::ProcessCasEncryptionResponse().
|
||||
virtual util::Status RequestEntitlementKey(
|
||||
const std::string& request_string, std::string* signed_response_string) = 0;
|
||||
virtual Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) = 0;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -1,67 +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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg.h"
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// TODO(user): Caller should check |message| is at lest 5 bytes long.
|
||||
util::Status Simulcrypt::ProcessMessage(const char* message, std::string* response) {
|
||||
DCHECK(message);
|
||||
DCHECK(response);
|
||||
|
||||
uint8_t protocol_version;
|
||||
uint16_t message_type;
|
||||
uint16_t message_length;
|
||||
// 'offset' is used to track where we are within |message|.
|
||||
size_t offset = 0;
|
||||
memcpy(&protocol_version, message, PROTOCOL_VERSION_SIZE);
|
||||
if (protocol_version != EXPECTED_PROTOCOL_VERSION) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid protocol version ", protocol_version));
|
||||
}
|
||||
offset += PROTOCOL_VERSION_SIZE;
|
||||
BigEndianToHost16(&message_type, message + offset);
|
||||
offset += MESSAGE_TYPE_SIZE;
|
||||
BigEndianToHost16(&message_length, message + offset);
|
||||
offset += MESSAGE_LENGTH_SIZE;
|
||||
switch (message_type) {
|
||||
case ECMG_STREAM_SETUP: {
|
||||
return ecmg_.ProcessStreamSetupMessage(message + offset, message_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_CW_PROVISION: {
|
||||
return ecmg_.ProcessCwProvisionMessage(message + offset, message_length,
|
||||
response);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return util::Status(
|
||||
util::error::UNIMPLEMENTED,
|
||||
absl::StrCat("No implementation yet to process message of type ",
|
||||
message_type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
@@ -1,50 +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_SIMULCRYPT_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// A class that handles Simulcrypt messages.
|
||||
// The expected usage is by a TCP server that receives Simulcrypt message
|
||||
// from the network then forward that message to an instance of this class
|
||||
// for processing.
|
||||
// This class is NOT thread-safe.
|
||||
class Simulcrypt {
|
||||
public:
|
||||
Simulcrypt() = default;
|
||||
Simulcrypt(const Simulcrypt&) = delete;
|
||||
Simulcrypt& operator=(const Simulcrypt&) = delete;
|
||||
virtual ~Simulcrypt() = default;
|
||||
|
||||
// Process a Simulcrypt |message|.
|
||||
// If any response is generated, it would returned via |response|.
|
||||
// Any error during processing would be turned via util::Status.
|
||||
util::Status ProcessMessage(const char* message, std::string* response);
|
||||
|
||||
private:
|
||||
Ecmg ecmg_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_H_
|
||||
@@ -1,55 +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_SIMULCRYPT_CONSTANTS_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_CONSTANTS_H_
|
||||
|
||||
// Message_type Values
|
||||
// 0x0000 DVB reserved.
|
||||
#define ECMG_CHANNEL_SETUP 0x0001
|
||||
#define ECMG_CHANNEL_TEST 0x0002
|
||||
#define ECMG_CHANNEL_STATUS 0x0003
|
||||
#define ECMG_CHANNEL_CLOSE 0x0004
|
||||
#define ECMG_CHANNEL_ERROR 0x0005
|
||||
// 0x0006 - 0x0010 DVB reserved.
|
||||
#define EMMG_CHANNEL_SETUP 0x0011
|
||||
#define EMMG_CHANNEL_TEST 0x0012
|
||||
#define EMMG_CHANNEL_STATUS 0x0013
|
||||
#define EMMG_CHANNEL_CLOSE 0x0014
|
||||
#define EMMG_CHANNEL_ERROR 0x0015
|
||||
// 0x0016 - 0x0100 DVB reserved.
|
||||
#define ECMG_STREAM_SETUP 0x0101
|
||||
#define ECMG_STREAM_TEST 0x0102
|
||||
#define ECMG_STREAM_STATUS 0x0103
|
||||
#define ECMG_STREAM_CLOSE_REQUEST 0x0104
|
||||
#define ECMG_STREAM_CLOSE_RESPONSE 0x0105
|
||||
#define ECMG_STREAM_ERROR 0x0106
|
||||
// 0x0107 - 0x0110 DVB reserved.
|
||||
#define EMMG_STREAM_SETUP 0x0111
|
||||
#define EMMG_STREAM_TEST 0x0112
|
||||
#define EMMG_STREAM_STATUS 0x0113
|
||||
#define EMMG_STREAM_CLOSE_REQUEST 0x0114
|
||||
#define EMMG_STREAM_CLOSE_RESPONSE 0x0115
|
||||
#define EMMG_STREAM_ERROR 0x0116
|
||||
#define EMMG_STREAM_BW_REQUEST 0x0117
|
||||
#define EMMG_STREAM_BW_ALLOCATION 0x0118
|
||||
// 0x0119 - 0x0200 DVB reserved.
|
||||
#define ECMG_CW_PROVISION 0x0201
|
||||
#define ECMG_ECM_RESPONSE 0x0202
|
||||
// 0x0203 - 0x0210 DVB reserved.
|
||||
#define EMMG_DATA_PROVISION 0x0211
|
||||
// 0x0212 - 0x0300 DVB reserved.
|
||||
|
||||
#define EXPECTED_PROTOCOL_VERSION 0x01
|
||||
|
||||
// Size (in # of bytes) of various fields.
|
||||
#define PROTOCOL_VERSION_SIZE 1
|
||||
#define MESSAGE_TYPE_SIZE 2
|
||||
#define MESSAGE_LENGTH_SIZE 2
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_CONSTANTS_H_
|
||||
@@ -1,54 +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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "example/test_simulcrypt_messages.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
namespace {
|
||||
|
||||
class SimulcryptTest : public ::testing::Test {
|
||||
protected:
|
||||
SimulcryptTest() {}
|
||||
|
||||
protected:
|
||||
Simulcrypt simulcrypt_;
|
||||
};
|
||||
|
||||
TEST_F(SimulcryptTest, ProcessEcmgStreamSetupMessage) {
|
||||
std::string response = "";
|
||||
EXPECT_OK(simulcrypt_.ProcessMessage(kTestEcmgStreamSetupMessage, &response));
|
||||
|
||||
EXPECT_EQ("", response);
|
||||
}
|
||||
|
||||
TEST_F(SimulcryptTest, ProcessEcmgCwProvisionMessageWithOneCw) {
|
||||
std::string response = "";
|
||||
EXPECT_OK(simulcrypt_.ProcessMessage(kTestEcmgCwProvisionMessageWithOneCw,
|
||||
&response));
|
||||
|
||||
EXPECT_EQ("", response);
|
||||
}
|
||||
|
||||
TEST_F(SimulcryptTest, ProcessEcmgCwProvisionMessageWithTwoCw) {
|
||||
std::string response = "";
|
||||
EXPECT_OK(simulcrypt_.ProcessMessage(kTestEcmgCwProvisionMessageWithTwoCw,
|
||||
&response));
|
||||
|
||||
EXPECT_EQ("", response);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
@@ -18,7 +18,7 @@
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
util::Status TsPacket::Write(std::string* output) const {
|
||||
Status TsPacket::Write(std::string* output) const {
|
||||
DCHECK(output);
|
||||
output->resize(kTsPacketSize);
|
||||
|
||||
@@ -32,8 +32,8 @@ util::Status TsPacket::Write(std::string* output) const {
|
||||
std::bitset<2> adaptation_field_control(adaptation_field_control_);
|
||||
std::bitset<4> continuity_counter(continuity_counter_);
|
||||
if (adaptation_field_control_ & kAdaptationFieldOnly) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"TsPacket does NOT handle adaptation field yet");
|
||||
return Status(error::INTERNAL,
|
||||
"TsPacket does NOT handle adaptation field yet");
|
||||
}
|
||||
|
||||
// Converts header bitset to string.
|
||||
@@ -43,29 +43,28 @@ util::Status TsPacket::Write(std::string* output) const {
|
||||
pid.to_string(), transport_scrambling_control.to_string(),
|
||||
adaptation_field_control.to_string(), continuity_counter.to_string());
|
||||
if (header_bitset.size() != 4 * 8) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
absl::StrCat("TS packet header bitset incorret size: ",
|
||||
header_bitset.size()));
|
||||
return Status(error::INTERNAL,
|
||||
absl::StrCat("TS packet header bitset incorret size: ",
|
||||
header_bitset.size()));
|
||||
}
|
||||
std::string serialized_header;
|
||||
util::Status status = string_util::BitsetStringToBinaryString(
|
||||
header_bitset, &serialized_header);
|
||||
Status status = string_util::BitsetStringToBinaryString(header_bitset,
|
||||
&serialized_header);
|
||||
if (!status.ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert TS packet header bitset to std::string");
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert TS packet header bitset to std::string");
|
||||
}
|
||||
|
||||
// Write TsPacket payload.
|
||||
if (payload_.size() != CalculatePayloadSize()) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Incorrect payload size: ", payload_.size()));
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Incorrect payload size: ", payload_.size()));
|
||||
}
|
||||
|
||||
// Return header + payload as a TS packet.
|
||||
*output = serialized_header + payload_;
|
||||
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
int32_t TsPacket::CalculatePayloadSize() const {
|
||||
|
||||
@@ -63,7 +63,7 @@ class TsPacket {
|
||||
virtual ~TsPacket() = default;
|
||||
|
||||
// Writes the packet into the provided output. Returns kOk on success.
|
||||
virtual util::Status Write(std::string* output) const;
|
||||
virtual Status Write(std::string* output) const;
|
||||
|
||||
// Returns the size of payload data for the current TS packet configuration.
|
||||
int32_t CalculatePayloadSize() const;
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "media_cas_packager_sdk/internal/ts_packet.h"
|
||||
@@ -30,14 +30,36 @@ ContinuityCounter Increment(ContinuityCounter continuity_counter) {
|
||||
void BigEndianToHost16(uint16_t* destination, const void* source) {
|
||||
DCHECK(destination);
|
||||
DCHECK(source);
|
||||
uint16_t big_endian_number;
|
||||
uint16_t big_endian_number = 0;
|
||||
memcpy(&big_endian_number, source, 2);
|
||||
*destination = ntohs(big_endian_number);
|
||||
}
|
||||
|
||||
util::Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
|
||||
uint8_t table_id, ContinuityCounter* cc,
|
||||
uint8_t* buffer, ssize_t* bytes_modified) {
|
||||
void BigEndianToHost32(uint32_t* destination, const void* source) {
|
||||
DCHECK(destination);
|
||||
DCHECK(source);
|
||||
uint32_t big_endian_number = 0;
|
||||
memcpy(&big_endian_number, source, 4);
|
||||
*destination = ntohl(big_endian_number);
|
||||
}
|
||||
|
||||
void Host16ToBigEndian(void* destination, const uint16_t* source) {
|
||||
DCHECK(destination);
|
||||
DCHECK(source);
|
||||
uint16_t big_endian_number = htons(*source);
|
||||
memcpy(destination, &big_endian_number, 2);
|
||||
}
|
||||
|
||||
void Host16ToBigEndian(void* destination, const int16_t* source) {
|
||||
DCHECK(destination);
|
||||
DCHECK(source);
|
||||
uint16_t big_endian_number = htons(static_cast<uint16_t>(*source));
|
||||
memcpy(destination, &big_endian_number, 2);
|
||||
}
|
||||
|
||||
Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid, uint8_t table_id,
|
||||
ContinuityCounter* cc, uint8_t* buffer,
|
||||
ssize_t* bytes_modified) {
|
||||
DCHECK(cc);
|
||||
DCHECK(buffer);
|
||||
DCHECK(bytes_modified);
|
||||
@@ -75,7 +97,7 @@ util::Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
|
||||
|
||||
// And write the packet.
|
||||
std::string ecm_ts_packet;
|
||||
util::Status status = ecm_packet.Write(&ecm_ts_packet);
|
||||
Status status = ecm_packet.Write(&ecm_ts_packet);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -83,7 +105,7 @@ util::Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
|
||||
memcpy(buffer + *bytes_modified, ecm_ts_packet.data(), ecm_ts_packet.size());
|
||||
*bytes_modified += ecm_ts_packet.size();
|
||||
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -24,6 +24,16 @@ namespace cas {
|
||||
// the result in |destination|.
|
||||
void BigEndianToHost16(uint16_t* destination, const void* source);
|
||||
|
||||
// Read 32 bits (long int) from |source|, treat it as a big-endian number
|
||||
// (network byte order), finally covert it to host endianness and return
|
||||
// the result in |destination|.
|
||||
void BigEndianToHost32(uint32_t* destination, const void* source);
|
||||
|
||||
// Covert |source| to big-endian (network byte order) and copy it to
|
||||
// |destination|.
|
||||
void Host16ToBigEndian(void* destination, const uint16_t* source);
|
||||
void Host16ToBigEndian(void* destination, const int16_t* source);
|
||||
|
||||
// Packages an ECM as a TS packet and inserts it into a buffer.
|
||||
// Args:
|
||||
// - |ecm| is the serialized ECM.
|
||||
@@ -42,9 +52,9 @@ void BigEndianToHost16(uint16_t* destination, const void* source);
|
||||
// the |buffer| and is used as an offset.
|
||||
// |bytes_modified| will be incremented by 188 if insertion of ECM into
|
||||
// |buffer| is successful.
|
||||
util::Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
|
||||
uint8_t table_id, ContinuityCounter* cc,
|
||||
uint8_t* buffer, ssize_t* bytes_modified);
|
||||
Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid, uint8_t table_id,
|
||||
ContinuityCounter* cc, uint8_t* buffer,
|
||||
ssize_t* bytes_modified);
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -58,6 +58,9 @@ constexpr char kExpectedEcmPacket[] = {
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// TODO(user): Add unit tests for BigEndianToHost16, BigEndianToHost32 and
|
||||
// Host16ToBigEndian.
|
||||
|
||||
TEST(InsertEcmAsTsPacketTest, BasicHappyPath) {
|
||||
// declare variables used by InsertEcmAsTsPacket()
|
||||
ContinuityCounter ecm_cc_ = 0;
|
||||
|
||||
@@ -20,7 +20,9 @@ PUBLIC_COPTS = ["-fvisibility=default"]
|
||||
|
||||
filegroup(
|
||||
name = "binary_release_files",
|
||||
srcs = glob(["*.h"]),
|
||||
srcs = glob(["*.h"]) + [
|
||||
":wv_ecmg",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
@@ -49,15 +51,6 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "simulcrypt_server",
|
||||
srcs = ["simulcrypt_server.cc"],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "wv_cas_ca_descriptor",
|
||||
srcs = ["wv_cas_ca_descriptor.cc"],
|
||||
@@ -97,8 +90,8 @@ cc_library(
|
||||
"@abseil_repo//absl/base:core_headers", # buildcleaner: keep
|
||||
"@abseil_repo//absl/memory", # buildcleaner: keep
|
||||
"@abseil_repo//absl/strings", # buildcleaner: keep
|
||||
"//common:status",
|
||||
"//common:crypto_util",
|
||||
"//common:status",
|
||||
"//example:constants",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
"//media_cas_packager_sdk/internal:ecm_generator",
|
||||
@@ -134,7 +127,6 @@ cc_library(
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@curl_repo//:curl",
|
||||
"//common:status",
|
||||
"//common:signature_util",
|
||||
"//media_cas_packager_sdk/internal:key_fetcher",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
@@ -153,7 +145,6 @@ cc_test(
|
||||
"//external:protobuf",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:status",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
],
|
||||
)
|
||||
@@ -175,3 +166,14 @@ cc_test(
|
||||
"//testing:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "wv_ecmg",
|
||||
srcs = ["wv_ecmg.cc"],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//media_cas_packager_sdk/internal:ecmg_client_handler",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,82 +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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Example server that listens on a port for Simulcrypt API messages.
|
||||
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "glog/logging.h"
|
||||
|
||||
DEFINE_int32(port, 0, "Server port number");
|
||||
|
||||
constexpr uint32_t kBufferSize = 256;
|
||||
constexpr uint32_t kLicenseBacklog = 5;
|
||||
constexpr uint32_t kWriteChunkSize = 18;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
CHECK(FLAGS_port != 0) << "need --port";
|
||||
|
||||
struct sockaddr_in server_address;
|
||||
bzero(reinterpret_cast<char *>(&server_address), sizeof(server_address));
|
||||
server_address.sin_family = AF_INET;
|
||||
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
server_address.sin_port = htons(FLAGS_port);
|
||||
|
||||
int listen_socket_fd = socket(AF_INET, SOCK_STREAM, /* protocol= */ 0);
|
||||
CHECK(listen_socket_fd >= 0) << "failed to open socket";
|
||||
CHECK(bind(listen_socket_fd, (struct sockaddr *)&server_address,
|
||||
sizeof(server_address)) >= 0)
|
||||
<< "error on binding";
|
||||
std::cout << "Server listening ..." << std::endl << std::flush;
|
||||
int return_val = listen(listen_socket_fd, kLicenseBacklog);
|
||||
switch (return_val) {
|
||||
case EADDRINUSE:
|
||||
LOG(FATAL) << "Another socket is already listening on the same port.";
|
||||
break;
|
||||
case EBADF:
|
||||
LOG(FATAL) << "The argument sockfd is not a valid descriptor.";
|
||||
break;
|
||||
case ENOTSOCK:
|
||||
LOG(FATAL) << "The argument sockfd is not a socket.";
|
||||
break;
|
||||
case EOPNOTSUPP:
|
||||
LOG(FATAL) << "The socket is not of a type that supports the listen() "
|
||||
"operation.";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
struct sockaddr_in client_address;
|
||||
socklen_t clilet_address_size = sizeof(client_address);
|
||||
int client_socket_fd = accept(
|
||||
listen_socket_fd, reinterpret_cast<struct sockaddr *>(&client_address),
|
||||
&clilet_address_size);
|
||||
CHECK(client_socket_fd >= 0) << "error on accept";
|
||||
|
||||
char buffer[kBufferSize];
|
||||
bzero(buffer, kBufferSize);
|
||||
if (read(client_socket_fd, buffer, kBufferSize - 1) < 0) {
|
||||
LOG(FATAL) << "ERROR reading from socket";
|
||||
}
|
||||
printf("Here is the message: %s", buffer);
|
||||
if (write(client_socket_fd, "I got your message", kWriteChunkSize) < 0) {
|
||||
LOG(FATAL) << "ERROR writing to socket";
|
||||
}
|
||||
|
||||
close(client_socket_fd);
|
||||
close(listen_socket_fd);
|
||||
return 0;
|
||||
}
|
||||
@@ -95,7 +95,7 @@ WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor(
|
||||
return INTERNAL;
|
||||
}
|
||||
std::string descriptor;
|
||||
util::Status status =
|
||||
Status status =
|
||||
string_util::BitsetStringToBinaryString(descriptor_bitset, &descriptor);
|
||||
|
||||
*serialized_ca_desc = descriptor;
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "common/status.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/crypto_util.h"
|
||||
#include "common/status.h"
|
||||
#include "example/constants.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_generator.h"
|
||||
@@ -154,7 +154,7 @@ WvCasStatus WvCasEcm::GenerateEcm(
|
||||
// TODO(user): When we want to retrieve entitlement key from License Server
|
||||
// we need to figure out a way to provide real 'content_id' and 'provder'
|
||||
// to this function here.
|
||||
util::Status status;
|
||||
Status status;
|
||||
if (!(status = cas_ecm->Initialize(kDefaultContentId, kDefaultProvider,
|
||||
ecm_init_params, &entitlement_request))
|
||||
.ok()) {
|
||||
@@ -265,7 +265,7 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm(
|
||||
// TODO(user): When we want to retrieve entitlement key from License Server
|
||||
// we need to figure out a way to provide real 'content_id' and 'provder'
|
||||
// to this function here.
|
||||
util::Status status;
|
||||
Status status;
|
||||
if (!(status = cas_ecm->Initialize(kDefaultContentId, kDefaultProvider,
|
||||
ecm_init_params, &entitlement_request))
|
||||
.ok()) {
|
||||
@@ -319,8 +319,8 @@ WvCasStatus WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t* continuity_counter,
|
||||
uint8_t* packet) {
|
||||
ssize_t bytes_modified = 0;
|
||||
util::Status status = InsertEcmAsTsPacket(
|
||||
ecm, pid, table_id, continuity_counter, packet, &bytes_modified);
|
||||
Status status = InsertEcmAsTsPacket(ecm, pid, table_id, continuity_counter,
|
||||
packet, &bytes_modified);
|
||||
if (!status.ok() || bytes_modified != kTsPacketSize) {
|
||||
memset(packet, 0, kTsPacketSize);
|
||||
LOG(ERROR) << "Failed to generate TS packet: " << status;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "curl/curl.h"
|
||||
#include "curl/easy.h"
|
||||
#include "common/status.h"
|
||||
#include "common/signature_util.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
@@ -41,12 +40,12 @@ DEFINE_string(signing_iv, "",
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
util::Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
const std::string& request_string, std::string* signed_response_string) {
|
||||
Status WvCasKeyFetcher::RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) {
|
||||
if (FLAGS_signing_provider.empty() || FLAGS_signing_key.empty() ||
|
||||
FLAGS_signing_iv.empty()) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
return Status(
|
||||
error::INVALID_ARGUMENT,
|
||||
"Flag 'signing_provider', 'signing_key' or 'signing_iv' is empty");
|
||||
}
|
||||
|
||||
@@ -63,8 +62,8 @@ util::Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
// NOTE: MessageToJsonString will automatically converts 'bytes' type fields
|
||||
// to base64. For example content ID '21140844' becomes 'MjExNDA4NDQ='.
|
||||
if (!MessageToJsonString(request, &request_json, print_options).ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert request message to json.");
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert request message to json.");
|
||||
}
|
||||
LOG(INFO) << "Json CasEncryptionRequest: " << request_json;
|
||||
|
||||
@@ -76,7 +75,7 @@ util::Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
request_json, absl::HexStringToBytes(FLAGS_signing_key),
|
||||
absl::HexStringToBytes(FLAGS_signing_iv), &signature)
|
||||
.ok()) {
|
||||
return util::Status(util::error::INTERNAL, "Failed to sign the request.");
|
||||
return Status(error::INTERNAL, "Failed to sign the request.");
|
||||
}
|
||||
signed_request.set_signature(signature);
|
||||
signed_request.set_signer(FLAGS_signing_provider);
|
||||
@@ -85,23 +84,22 @@ util::Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
// 'signature' fields in SignedCasEncryptionRequest to base64, because they
|
||||
// are of type 'bytes'.
|
||||
if (!MessageToJsonString(signed_request, &signed_request_json).ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert signed request message to json.");
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert signed request message to json.");
|
||||
}
|
||||
LOG(INFO) << "Json SignedCasEncryptionRequest: " << signed_request_json;
|
||||
|
||||
// Makes HTTP request against License Server.
|
||||
std::string http_response_json;
|
||||
util::Status status =
|
||||
MakeHttpRequest(signed_request_json, &http_response_json);
|
||||
Status status = MakeHttpRequest(signed_request_json, &http_response_json);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
LOG(INFO) << "Json HTTP response: " << http_response_json;
|
||||
HttpResponse http_response;
|
||||
if (!JsonStringToMessage(http_response_json, &http_response).ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert http response json to message.");
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert http response json to message.");
|
||||
}
|
||||
|
||||
// Processes signed response.
|
||||
@@ -110,13 +108,13 @@ util::Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
LOG(INFO) << "Json CasEncryptionResponse: " << http_response.response();
|
||||
CasEncryptionResponse response;
|
||||
if (!JsonStringToMessage(http_response.response(), &response).ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert response json to message.");
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert response json to message.");
|
||||
}
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response.SerializeAsString());
|
||||
signed_response.SerializeToString(signed_response_string);
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
size_t AppendToString(void* ptr, size_t size, size_t count, std::string* output) {
|
||||
@@ -125,12 +123,11 @@ size_t AppendToString(void* ptr, size_t size, size_t count, std::string* output)
|
||||
return data.size();
|
||||
}
|
||||
|
||||
util::Status WvCasKeyFetcher::MakeHttpRequest(
|
||||
const std::string& signed_request_json, std::string* http_response_json) const {
|
||||
Status WvCasKeyFetcher::MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const {
|
||||
CHECK(http_response_json);
|
||||
if (FLAGS_license_server.empty()) {
|
||||
return util::Status(util::error::INVALID_ARGUMENT,
|
||||
"Flag 'license_server' is empty");
|
||||
return Status(error::INVALID_ARGUMENT, "Flag 'license_server' is empty");
|
||||
}
|
||||
CURL* curl;
|
||||
CURLcode curl_code;
|
||||
@@ -145,15 +142,14 @@ util::Status WvCasKeyFetcher::MakeHttpRequest(
|
||||
(int64_t)strlen(signed_request_json.c_str()));
|
||||
curl_code = curl_easy_perform(curl);
|
||||
if (curl_code != CURLE_OK) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"curl_easy_perform() failed: " +
|
||||
std::string(curl_easy_strerror(curl_code)));
|
||||
return Status(error::INTERNAL, "curl_easy_perform() failed: " +
|
||||
std::string(curl_easy_strerror(curl_code)));
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
} else {
|
||||
return util::Status(util::error::INTERNAL, "curl_easy_init() failed");
|
||||
return Status(error::INTERNAL, "curl_easy_init() failed");
|
||||
}
|
||||
return util::OkStatus();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/key_fetcher.h"
|
||||
|
||||
DECLARE_string(license_server);
|
||||
@@ -41,15 +40,15 @@ class WvCasKeyFetcher : public KeyFetcher {
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// widevine::cas::Ecm::ProcessCasEncryptionResponse().
|
||||
virtual util::Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string);
|
||||
Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) override;
|
||||
|
||||
protected:
|
||||
// Makes a HTTP request to License Server for entitlement key(s).
|
||||
// Returns the HTTP response in Json format in |http_response_json|.
|
||||
// Protected visibility to support unit testing.
|
||||
virtual util::Status MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const;
|
||||
virtual Status MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "common/status.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
using testing::_;
|
||||
@@ -55,9 +54,8 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher {
|
||||
public:
|
||||
MockWvCasKeyFetcher() : WvCasKeyFetcher() {}
|
||||
~MockWvCasKeyFetcher() override {}
|
||||
MOCK_CONST_METHOD2(MakeHttpRequest,
|
||||
util::Status(const std::string& signed_request_json,
|
||||
std::string* http_response_json));
|
||||
MOCK_CONST_METHOD2(MakeHttpRequest, Status(const std::string& signed_request_json,
|
||||
std::string* http_response_json));
|
||||
};
|
||||
|
||||
class WvCasKeyFetcherTest : public ::testing::Test {
|
||||
@@ -93,7 +91,7 @@ TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
|
||||
EXPECT_CALL(mock_key_fetcher_,
|
||||
MakeHttpRequest(kSignedCasEncryptionRequest, _))
|
||||
.WillOnce(DoAll(SetArgumentPointee<1>(std::string(kHttpResponse)),
|
||||
Return(util::OkStatus())));
|
||||
Return(OkStatus())));
|
||||
|
||||
std::string actual_signed_response;
|
||||
EXPECT_OK(mock_key_fetcher_.RequestEntitlementKey(
|
||||
|
||||
190
media_cas_packager_sdk/public/wv_ecmg.cc
Normal file
190
media_cas_packager_sdk/public/wv_ecmg.cc
Normal file
@@ -0,0 +1,190 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Example server that listens on a port for Simulcrypt API messages.
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
|
||||
|
||||
static constexpr int32_t kDefaultDelayStart = 200;
|
||||
static constexpr int32_t kDefaultDelayStop = 200;
|
||||
static constexpr int32_t kDefaultEcmRepPeriod = 100;
|
||||
static constexpr int32_t kDefaultMaxCompTime = 100;
|
||||
static constexpr int32_t kAccessCriteriaTransferMode = 1;
|
||||
|
||||
DEFINE_int32(port, 0, "Server port number");
|
||||
|
||||
// ECMG related flags.
|
||||
// TODO(user): Consider adding flags 'ac_delay_start', 'ac_delay_stop',
|
||||
// 'transition_delay_start', 'transition_delay_stop'.
|
||||
DEFINE_int32(delay_start, kDefaultDelayStart,
|
||||
absl::StrCat("This flag sets the DVB SimulCrypt delay_start "
|
||||
"parameter, in milliseconds. Default: ",
|
||||
kDefaultDelayStart, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(delay_stop, kDefaultDelayStop,
|
||||
absl::StrCat("This flag sets the DVB SimulCrypt delay_stop "
|
||||
"parameter, in milliseconds. Default: ",
|
||||
kDefaultDelayStop, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(ecm_rep_period, kDefaultEcmRepPeriod,
|
||||
absl::StrCat("It sets the DVB SimulCrypt parameter "
|
||||
"ECM_rep_period, in milliseconds. Default: ",
|
||||
kDefaultEcmRepPeriod, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(max_comp_time, kDefaultMaxCompTime,
|
||||
absl::StrCat("It sets the DVB SimulCrypt parameter max_comp_time, "
|
||||
"in milliseconds. Default: ",
|
||||
kDefaultMaxCompTime, " ms")
|
||||
.c_str());
|
||||
DEFINE_int32(access_criteria_transfer_mode, kAccessCriteriaTransferMode,
|
||||
absl::StrCat("It sets the DVB SimulCrypt parameter "
|
||||
"access_criteria_transfer_mode. Default: ",
|
||||
kAccessCriteriaTransferMode)
|
||||
.c_str());
|
||||
|
||||
#define LISTEN_QUEUE_SIZE (20)
|
||||
#define BUFFER_SIZE (1024)
|
||||
|
||||
using widevine::cas::EcmgClientHandler;
|
||||
using widevine::cas::EcmgConfig;
|
||||
|
||||
void BuildEcmgConfig(EcmgConfig* config) {
|
||||
DCHECK(config);
|
||||
config->delay_start = FLAGS_delay_start;
|
||||
config->delay_stop = FLAGS_delay_stop;
|
||||
config->ecm_rep_period = FLAGS_ecm_rep_period;
|
||||
config->max_comp_time = FLAGS_max_comp_time;
|
||||
config->access_criteria_transfer_mode = FLAGS_access_criteria_transfer_mode;
|
||||
}
|
||||
|
||||
void PrintMessage(const std::string& description, const char* const message,
|
||||
size_t length) {
|
||||
LOG(INFO) << description;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
printf("'\\x%02x', ", static_cast<uint16_t>(*(message + i)));
|
||||
fflush(stdout);
|
||||
}
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void ServeClient(int socket_fd, EcmgClientHandler* ecmg) {
|
||||
DCHECK(ecmg);
|
||||
char request[BUFFER_SIZE];
|
||||
char response[BUFFER_SIZE];
|
||||
while (true) {
|
||||
bzero(request, BUFFER_SIZE);
|
||||
bzero(response, BUFFER_SIZE);
|
||||
size_t response_length = 0;
|
||||
size_t request_length = recv(socket_fd, request, BUFFER_SIZE, 0);
|
||||
if (request_length == 0) {
|
||||
LOG(ERROR) << "No more request from client";
|
||||
return;
|
||||
}
|
||||
if (request_length < 0) {
|
||||
LOG(ERROR) << "Failed to receive request from client";
|
||||
return;
|
||||
}
|
||||
PrintMessage("Request", request, request_length);
|
||||
ecmg->HandleRequest(request, response, &response_length);
|
||||
PrintMessage("Response", response, response_length);
|
||||
if (send(socket_fd, response, response_length, 0) < 0) {
|
||||
LOG(INFO) << "Failed to send response to client";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
CHECK(FLAGS_port != 0) << "need --port";
|
||||
|
||||
EcmgConfig ecmg_config;
|
||||
BuildEcmgConfig(&ecmg_config);
|
||||
|
||||
// Server address.
|
||||
struct sockaddr_in server_address;
|
||||
bzero(reinterpret_cast<char*>(&server_address), sizeof(server_address));
|
||||
server_address.sin_family = AF_INET;
|
||||
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
server_address.sin_port = htons(FLAGS_port);
|
||||
|
||||
// Create a listening socket.
|
||||
int listen_socket_fd = socket(AF_INET, SOCK_STREAM, /* protocol= */ 0);
|
||||
CHECK(listen_socket_fd >= 0) << "Failed to open listening socket";
|
||||
|
||||
// Set SO_REUSEADDR on a socket to true (1).
|
||||
int optval = 1;
|
||||
setsockopt(listen_socket_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
|
||||
sizeof(optval));
|
||||
|
||||
// Bind address.
|
||||
CHECK(bind(listen_socket_fd, (struct sockaddr*)&server_address,
|
||||
sizeof(server_address)) >= 0)
|
||||
<< "Failed to bind on server socket";
|
||||
|
||||
// Listen for connection from clients.
|
||||
std::cout << "Server listening ..." << std::endl << std::flush;
|
||||
int return_val = listen(listen_socket_fd, LISTEN_QUEUE_SIZE);
|
||||
switch (return_val) {
|
||||
case EADDRINUSE:
|
||||
LOG(FATAL) << "Another socket is already listening on the same port.";
|
||||
break;
|
||||
case EBADF:
|
||||
LOG(FATAL) << "The argument sockfd is not a valid descriptor.";
|
||||
break;
|
||||
case ENOTSOCK:
|
||||
LOG(FATAL) << "The argument sockfd is not a socket.";
|
||||
break;
|
||||
case EOPNOTSUPP:
|
||||
LOG(FATAL) << "The socket is not of a type that supports the listen() "
|
||||
"operation.";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// A single client handler, allow only 1 TCP connection / 1 channel at a time.
|
||||
EcmgClientHandler client_handler(&ecmg_config);
|
||||
|
||||
// While loop to serve different client connections.
|
||||
while (true) {
|
||||
struct sockaddr_in client_address;
|
||||
socklen_t client_address_size = sizeof(client_address);
|
||||
int client_socket_fd = accept(
|
||||
listen_socket_fd, reinterpret_cast<struct sockaddr*>(&client_address),
|
||||
&client_address_size);
|
||||
LOG(INFO) << "\nTCP connection start\n";
|
||||
if (client_socket_fd < 0) {
|
||||
LOG(ERROR) << "Failed to accept connection request from client";
|
||||
} else {
|
||||
// TODO(user): Support multi-threading of serving concurrent clients.
|
||||
// TODO(user): Per jfore@ suggestion, look into using
|
||||
// http://man7.org/linux/man-pages/man7/epoll.7.html
|
||||
ServeClient(client_socket_fd, &client_handler);
|
||||
}
|
||||
LOG(INFO) << "\nTCP connection closed\n";
|
||||
close(client_socket_fd);
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
// Close listening socket.
|
||||
close(listen_socket_fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user