Minimal implementation of Widevine MediaCAS ECMG.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=226515998
This commit is contained in:
Fang Yu
2018-12-21 11:17:37 -08:00
parent 7487ce5aa8
commit bc68878bdf
88 changed files with 2456 additions and 2774 deletions

View File

@@ -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 = [

View File

@@ -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 {

View File

@@ -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_; }

View File

@@ -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

View File

@@ -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;

View File

@@ -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());

View File

@@ -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());
}

View File

@@ -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(&parameter_type, message + offset);
offset += PARAMETER_TYPE_SIZE;
BigEndianToHost16(&parameter_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(&parameters->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(&parameters->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 =
&parameters->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(&parameters->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(&parameters->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(&parameters->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, &parameters);
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, &parameters);
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

View File

@@ -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_

View 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(&param_type, request + offset);
offset += PARAMETER_TYPE_SIZE;
BigEndianToHost16(&param_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 =
&params->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(&params->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(&params->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(&params->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(&params->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(&params->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(&params->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(&params->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, &param_type);
*message_length += 2;
uint16_t param_length = 2;
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2;
Host16ToBigEndian(message + *message_length, &param_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, &param_type);
*message_length += 2;
uint16_t param_length = 1;
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2;
memcpy(message + *message_length, &param_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, &param_type);
*message_length += 2;
Host16ToBigEndian(message + *message_length, &param_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, &params);
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

View 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_

View 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

View File

@@ -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_

View File

@@ -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

View File

@@ -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_;

View File

@@ -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

View File

@@ -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

View File

@@ -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_

View File

@@ -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_

View File

@@ -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

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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",
],
)

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View 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;
}