Specify widevine/media_cas_packager_sdk/presubmit in media_cas_packager_sdk METADATA file.
------------- Moves ecm_generator to media_cas_packager_sdk/internal. ------------- Add a simple TCP server listening on a port. My intention is to use this server to support the Simulcrypt APIs (TODO). Also add a simple TCP client binary for testing the server and also demo how to call the Simulcrypt APIs (TODO). ------------- If only a single key is in the ECM, it is the EVEN key. To make the code matches this understanding, change a parameter from 'false' to 'true'. But this change has NO impact on the produced ECM, regardless this parameter is 'false' or 'true' (i.e., whether using push_front or push_back), only a single key is in the ECM. ------------- Add classes that process Simulcrypt ECMG messages 1) Stream_set-up 2) CW_provision ------------- Renames server and client binaries. ------------- Make ecmg call ecm_generator to generate ecm. The return of the ecm to Simulcrypt caller will be implemented in the next CL. For now, using the 'key' (control word) in CW_provision message also as the 'key_id'. ------------- Move common folder ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217358698
This commit is contained in:
@@ -48,3 +48,109 @@ cc_test(
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ecm_generator",
|
||||
srcs = ["ecm_generator.cc"],
|
||||
hdrs = ["ecm_generator.h"],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"//util:status",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ecm_generator_test",
|
||||
size = "small",
|
||||
srcs = ["ecm_generator_test.cc"],
|
||||
deps = [
|
||||
":ecm_generator",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"//common:aes_cbc_util",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ecmg",
|
||||
srcs = ["ecmg.cc"],
|
||||
hdrs = [
|
||||
"ecmg.h",
|
||||
"ecmg_constants.h",
|
||||
],
|
||||
deps = [
|
||||
":ecm",
|
||||
":ecm_generator",
|
||||
":fixed_key_fetcher",
|
||||
":util",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//util:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "fixed_key_fetcher",
|
||||
srcs = [
|
||||
"fixed_key_fetcher.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"fixed_key_fetcher.h",
|
||||
],
|
||||
deps = [
|
||||
":key_fetcher",
|
||||
"//util:status",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "key_fetcher",
|
||||
hdrs = [
|
||||
"key_fetcher.h",
|
||||
],
|
||||
deps = ["//util:status"],
|
||||
)
|
||||
|
||||
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",
|
||||
"//util:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "simulcrypt_test",
|
||||
size = "small",
|
||||
srcs = ["simulcrypt_test.cc"],
|
||||
deps = [
|
||||
":simulcrypt",
|
||||
"//testing:gunit_main",
|
||||
"//example:test_simulcrypt_messages",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "util",
|
||||
srcs = ["util.cc"],
|
||||
hdrs = ["util.h"],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -61,6 +61,7 @@ static constexpr int kNumBitsUnusedField = 6;
|
||||
static constexpr size_t kKeyIdSizeBytes = 16;
|
||||
static constexpr size_t kKeyDataSizeBytes = 16;
|
||||
static constexpr size_t kWrappedKeyIvSizeBytes = 16;
|
||||
static constexpr size_t kWrappingKeyIvSizeBytes = 16;
|
||||
|
||||
// BitField constants for the ECM payload
|
||||
|
||||
@@ -309,6 +310,9 @@ util::Status CasEcm::WrapEntitledKeys(
|
||||
|
||||
std::string CasEcm::WrapKey(const std::string& wrapping_key, const std::string& iv,
|
||||
const std::string& key_value) {
|
||||
if (iv.size() != kWrappingKeyIvSizeBytes) {
|
||||
LOG(WARNING) << "Incorrect iv size for WrapKey(): " << iv.size();
|
||||
}
|
||||
// Wrapped key IV is always 16 bytes.
|
||||
return crypto_util::EncryptAesCbcNoPad(wrapping_key, iv, key_value);
|
||||
}
|
||||
@@ -491,13 +495,13 @@ util::Status CasEcm::ParseEntitlementResponse(const std::string& response_string
|
||||
}
|
||||
if (paired_keys_required()) {
|
||||
if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_EVEN) {
|
||||
PushEntitlementKey(key.track_type(), true, ekey);
|
||||
PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey);
|
||||
} else if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_ODD) {
|
||||
PushEntitlementKey(key.track_type(), false, ekey);
|
||||
PushEntitlementKey(key.track_type(), /* is_even_key= */ false, ekey);
|
||||
}
|
||||
} else {
|
||||
if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_SINGLE) {
|
||||
PushEntitlementKey(key.track_type(), false, ekey);
|
||||
PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ struct EcmInitParameters {
|
||||
// TODO(user): Add usage example.
|
||||
//
|
||||
// Class CasEcm is not thread safe.
|
||||
// TODO(user): Rename class to Ecm.
|
||||
class CasEcm {
|
||||
public:
|
||||
CasEcm() = default;
|
||||
|
||||
149
media_cas_packager_sdk/internal/ecm_generator.cc
Normal file
149
media_cas_packager_sdk/internal/ecm_generator.cc
Normal file
@@ -0,0 +1,149 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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/ecm_generator.h"
|
||||
|
||||
#include "glog/logging.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
static constexpr int kKeyDataSize = 16;
|
||||
static constexpr int kKeyIvSize8 = 8;
|
||||
static constexpr int kKeyIvSize16 = 16;
|
||||
static constexpr int kMaxBytesKeyIdField = 16;
|
||||
|
||||
std::string CasEcmGenerator::GenerateEcm(const EcmParameters& params) {
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
util::Status status = ProcessEcmParameters(params, &keys);
|
||||
if (!status.ok() || !initialized_) {
|
||||
LOG(ERROR) << " EcmParameters is not set up properly: " << status;
|
||||
return "";
|
||||
}
|
||||
std::string serialized_ecm;
|
||||
uint32_t generation;
|
||||
// TODO(user): need track_type
|
||||
std::string track_type = "SD";
|
||||
if (params.rotation_enabled) {
|
||||
status = ecm_->GenerateEcm(&keys[0], &keys[1], track_type, &serialized_ecm,
|
||||
&generation);
|
||||
} else {
|
||||
status = ecm_->GenerateSingleKeyEcm(&keys[0], track_type, &serialized_ecm,
|
||||
&generation);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << " Call to CasEcm's ECM Generator failed: " << status;
|
||||
return "";
|
||||
}
|
||||
return serialized_ecm;
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ProcessEcmParameters(
|
||||
const EcmParameters& ecm_params, std::vector<EntitledKeyInfo>* keys) {
|
||||
initialized_ = false;
|
||||
rotation_enabled_ = ecm_params.rotation_enabled;
|
||||
|
||||
// Validate and add key data
|
||||
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,
|
||||
"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]);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
keys->emplace_back(EntitledKeyInfo());
|
||||
EntitledKeyInfo& key = keys->back();
|
||||
key.key_id = ecm_params.key_params[i].key_id;
|
||||
key.key_value = ecm_params.key_params[i].key_data;
|
||||
key.content_iv = ecm_params.key_params[i].content_ivs[0];
|
||||
}
|
||||
current_key_index_ = 0;
|
||||
current_key_even_ = true;
|
||||
initialized_ = true;
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateKeyId(const std::string& id) {
|
||||
if (id.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key id is empty."};
|
||||
}
|
||||
if (id.size() > kMaxBytesKeyIdField) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key id is too long."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateKeyData(const std::string& key_data) {
|
||||
if (key_data.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key data is empty."};
|
||||
}
|
||||
if (key_data.size() != kKeyDataSize) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key data is wrong size."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateIv(const std::string& iv,
|
||||
size_t required_size) {
|
||||
if (iv.empty()) {
|
||||
return {util::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."};
|
||||
}
|
||||
|
||||
if (iv.size() != required_size) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
"IV has wrong or inconsistent size."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateWrappedKeyIv(const std::string& iv) {
|
||||
// All wrapped key IVs must be 16 bytes.
|
||||
util::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) {
|
||||
// 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_);
|
||||
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 = 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."};
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
98
media_cas_packager_sdk/internal/ecm_generator.h
Normal file
98
media_cas_packager_sdk/internal/ecm_generator.h
Normal file
@@ -0,0 +1,98 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_ECM_GENERATOR_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// KeyParameters carries key information for a single encryption key.
|
||||
// Instances of this struct are owned by an EcmParameters struct.
|
||||
struct KeyParameters {
|
||||
std::string key_id;
|
||||
std::string key_data;
|
||||
// TODO(user): wrapped_key_data does not seem to be used, but assumed
|
||||
// to exist by unit tests.
|
||||
std::string wrapped_key_data;
|
||||
// wrapped_key_iv is randomly generated right before it is used to encrypt
|
||||
// key_data in ecm.cc.
|
||||
std::string wrapped_key_iv;
|
||||
// TODO(user): Probably only need a single content_iv instead of a vector.
|
||||
std::vector<std::string> content_ivs;
|
||||
};
|
||||
|
||||
// EcmParameters holds information that is needed by the EcmGenerator. It is
|
||||
// partially set up with data from a KeyGenerator (obtained via GenerateKey()).
|
||||
// IVs are added later.
|
||||
// TODO(user): Consider rename to EcmGeneratorParameters.
|
||||
struct EcmParameters {
|
||||
// TODO(user): entitlement_key_id does not seem to be used, but assumed
|
||||
// to exist by unit tests.
|
||||
std::string entitlement_key_id;
|
||||
bool rotation_enabled = true;
|
||||
// TODO(user): rotation_periods does not seem to be used, but assumed
|
||||
// to exist by unit tests.
|
||||
uint32_t rotation_periods;
|
||||
// TODO(user): Consider changing the vector to just two variables,
|
||||
// one for even key, the other for odd key.
|
||||
std::vector<KeyParameters> key_params;
|
||||
};
|
||||
|
||||
// ECM Generator for Widevine/MediaCAS entitled keys.
|
||||
// TODO(user): Rename class to EcmGenerator.
|
||||
class CasEcmGenerator {
|
||||
public:
|
||||
CasEcmGenerator() = default;
|
||||
virtual ~CasEcmGenerator() = default;
|
||||
|
||||
virtual std::string GenerateEcm(const EcmParameters& params);
|
||||
|
||||
// Query the state of this ECM Generator
|
||||
bool initialized() { return initialized_; }
|
||||
bool rotation_enabled() { return rotation_enabled_; }
|
||||
|
||||
void set_ecm(std::unique_ptr<CasEcm> ecm) { ecm_ = std::move(ecm); }
|
||||
|
||||
private:
|
||||
friend class CasEcmGeneratorTest;
|
||||
|
||||
util::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);
|
||||
|
||||
bool initialized_ = false;
|
||||
uint32_t generation_ = 0;
|
||||
bool rotation_enabled_ = false;
|
||||
uint32_t current_key_index_ = 0;
|
||||
bool current_key_even_ = true;
|
||||
uint32_t content_iv_size_ = 0;
|
||||
std::unique_ptr<CasEcm> ecm_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_
|
||||
311
media_cas_packager_sdk/internal/ecm_generator_test.cc
Normal file
311
media_cas_packager_sdk/internal/ecm_generator_test.cc
Normal file
@@ -0,0 +1,311 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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/ecm_generator.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "common/aes_cbc_util.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kCADescriptor[] = "TestCa";
|
||||
constexpr char kEcm[] = "TestEcm";
|
||||
constexpr char kProvider[] = "Gfiber";
|
||||
constexpr char kContentId[] = "TestContent";
|
||||
constexpr int kDefaultKeyRotationPeriodMilliseconds = 30 * 60 * 1000;
|
||||
constexpr char kPsshData[] = "TestPsshData";
|
||||
|
||||
constexpr char kEntitlementKeySingle[] = "testEKId12345678";
|
||||
constexpr char kEntitlementKeyDouble[] = "testEKIdabcdefgh";
|
||||
|
||||
constexpr char kEcmKeyIdSingle[] = "key-id-One123456";
|
||||
constexpr char kEcmKeyDataSingle[] = "0123456701234567";
|
||||
constexpr char kEcmWrappedKeySingle[] = "1234567890123456";
|
||||
constexpr char kEcmWrappedKeyIvSingle[] = "abcdefghacbdefgh";
|
||||
constexpr char kEcmContentIvSingle[] = "ABCDEFGH";
|
||||
|
||||
constexpr char kEcmKeyIdEven[] = "key-Id-One123456";
|
||||
constexpr char kEcmKeyDataEven[] = "KKEEYYDDAATTAAee";
|
||||
constexpr char kEcmWrappedKeyEven[] = "1234567890123456";
|
||||
constexpr char kEcmWrappedKeyIvEven[] = "abcdefghacbdefgh";
|
||||
constexpr char kEcmContentIvEven[] = "ABCDEFGH";
|
||||
|
||||
constexpr char kEcmKeyIdOdd[] = "key-Id-Two456789";
|
||||
constexpr char kEcmKeyDataOdd[] = "kkeeyyddaattaaOO";
|
||||
constexpr char kEcmWrappedKeyOdd[] = "9876543210654321";
|
||||
constexpr char kEcmWrappedKeyIvOdd[] = "a1c2e3g4a1c2e3g4";
|
||||
constexpr char kEcmContentIvOdd[] = "AaCbEcGd";
|
||||
|
||||
constexpr char kFakeCasEncryptionResponseKeyId[] = "fake_key_id.....";
|
||||
constexpr char kFakeCasEncryptionResponseKeyData[] =
|
||||
"fakefakefakefakefakefakefakefake";
|
||||
|
||||
util::Status HandleCasEncryptionRequest(const std::string& request_string,
|
||||
std::string* signed_response_string) {
|
||||
CasEncryptionRequest request;
|
||||
request.ParseFromString(request_string);
|
||||
|
||||
CasEncryptionResponse response;
|
||||
response.set_status(CasEncryptionResponse_Status_OK);
|
||||
response.set_content_id(request.content_id());
|
||||
for (const auto& track_type : request.track_types()) {
|
||||
if (request.key_rotation()) {
|
||||
// Add the Even key.
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id(kFakeCasEncryptionResponseKeyId);
|
||||
key->set_key(kFakeCasEncryptionResponseKeyData);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN);
|
||||
// Add the Odd key.
|
||||
key = response.add_entitlement_keys();
|
||||
key->set_key_id(kFakeCasEncryptionResponseKeyId);
|
||||
key->set_key(kFakeCasEncryptionResponseKeyData);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_ODD);
|
||||
} else {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id(kFakeCasEncryptionResponseKeyId);
|
||||
key->set_key(kFakeCasEncryptionResponseKeyData);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE);
|
||||
}
|
||||
}
|
||||
std::string response_string;
|
||||
response.SerializeToString(&response_string);
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response_string);
|
||||
signed_response.SerializeToString(signed_response_string);
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class CasEcmGeneratorTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
}
|
||||
|
||||
util::Status ProcessEcmParameters(const EcmParameters& params,
|
||||
std::vector<EntitledKeyInfo>* keys) {
|
||||
return ecm_gen_.ProcessEcmParameters(params, keys);
|
||||
}
|
||||
|
||||
int EntitlementKeySize(const EcmParameters& params) const {
|
||||
return params.entitlement_key_id.size();
|
||||
}
|
||||
|
||||
void SetTestConfig1(EcmParameters* params) {
|
||||
params->entitlement_key_id = kEntitlementKeySingle;
|
||||
params->rotation_enabled = false;
|
||||
params->key_params.push_back(kKeyParamsSingle);
|
||||
}
|
||||
|
||||
void SetTestConfig2(EcmParameters* params) {
|
||||
KeyParameters even_key_params;
|
||||
KeyParameters odd_key_params;
|
||||
params->entitlement_key_id = kEntitlementKeyDouble;
|
||||
params->rotation_enabled = true;
|
||||
params->rotation_periods = 2;
|
||||
params->key_params.push_back(kKeyParamsEven);
|
||||
params->key_params.push_back(kKeyParamsOdd);
|
||||
}
|
||||
|
||||
// Call this to setup the guts (CasEcm) of the ECM Generator.
|
||||
void PrepareEcmGenerator(bool key_rotation_enabled) {
|
||||
ecm_ = absl::make_unique<CasEcm>();
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
ecm_init_params_.key_rotation_enabled = key_rotation_enabled;
|
||||
ecm_init_params_.track_types.push_back("SD");
|
||||
ASSERT_OK(ecm_->Initialize(kContentId, kProvider, ecm_init_params_,
|
||||
&entitlement_request));
|
||||
ASSERT_OK(
|
||||
HandleCasEncryptionRequest(entitlement_request, &entitlement_response));
|
||||
ASSERT_OK(ecm_->ProcessCasEncryptionResponse(entitlement_response));
|
||||
ecm_gen_.set_ecm(std::move(ecm_));
|
||||
}
|
||||
|
||||
const KeyParameters kKeyParamsSingle{kEcmKeyIdSingle,
|
||||
kEcmKeyDataSingle,
|
||||
kEcmWrappedKeySingle,
|
||||
kEcmWrappedKeyIvSingle,
|
||||
{kEcmContentIvSingle}};
|
||||
const KeyParameters kKeyParamsEven{kEcmKeyIdEven,
|
||||
kEcmKeyDataEven,
|
||||
kEcmWrappedKeyEven,
|
||||
kEcmWrappedKeyIvEven,
|
||||
{kEcmContentIvEven}};
|
||||
const KeyParameters kKeyParamsOdd{kEcmKeyIdOdd,
|
||||
kEcmKeyDataOdd,
|
||||
kEcmWrappedKeyOdd,
|
||||
kEcmWrappedKeyIvOdd,
|
||||
{kEcmContentIvOdd}};
|
||||
|
||||
std::unique_ptr<CasEcm> ecm_;
|
||||
EcmInitParameters ecm_init_params_;
|
||||
CasEcmGenerator ecm_gen_;
|
||||
};
|
||||
|
||||
TEST_F(CasEcmGeneratorTest, InitializeNoRotation) {
|
||||
EXPECT_FALSE(ecm_gen_.initialized());
|
||||
|
||||
PrepareEcmGenerator(false);
|
||||
|
||||
EcmParameters ecm_params;
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
|
||||
SetTestConfig1(&ecm_params);
|
||||
|
||||
util::Status status = ProcessEcmParameters(ecm_params, &keys);
|
||||
ASSERT_OK(status);
|
||||
|
||||
ASSERT_EQ(EntitlementKeySize(ecm_params), 16);
|
||||
ASSERT_TRUE(ecm_gen_.initialized());
|
||||
EXPECT_FALSE(ecm_gen_.rotation_enabled());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmGeneratorTest, GenerateNoRotation) {
|
||||
PrepareEcmGenerator(false);
|
||||
EcmParameters ecm_params;
|
||||
|
||||
SetTestConfig1(&ecm_params);
|
||||
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
|
||||
// Expected size (bytes):
|
||||
// CA system ID: 2 bytes
|
||||
// version: 1 byte
|
||||
// generation + flags: 1 byte
|
||||
// flags: 1 byte
|
||||
// entitlement key ID: 16 bytes
|
||||
// Single key: ID (16), Data (16), IV (16), IV (8) = 56
|
||||
// total = 77
|
||||
ASSERT_EQ(77, ecm_string.size());
|
||||
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
|
||||
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
|
||||
EXPECT_EQ('\x01', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x02', ecm_string[3]); // generation + flags
|
||||
EXPECT_EQ('\x80', ecm_string[4]); // flags
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
|
||||
EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16));
|
||||
EXPECT_NE(kEcmWrappedKeySingle, ecm_string.substr(37, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16));
|
||||
// Unwrap key and compare with original.
|
||||
std::string wrapping_key = kFakeCasEncryptionResponseKeyData;
|
||||
std::string wrapping_iv = ecm_string.substr(53, 16);
|
||||
std::string wrapped_key = ecm_string.substr(37, 16);
|
||||
std::string unwrapped_key =
|
||||
crypto_util::DecryptAesCbcNoPad(wrapping_key, wrapping_iv, wrapped_key);
|
||||
EXPECT_EQ(kEcmKeyDataSingle, unwrapped_key);
|
||||
EXPECT_EQ(kEcmContentIvSingle, ecm_string.substr(69, 8));
|
||||
}
|
||||
|
||||
TEST_F(CasEcmGeneratorTest, Generate2NoRotation) {
|
||||
PrepareEcmGenerator(false);
|
||||
EcmParameters ecm_params;
|
||||
|
||||
SetTestConfig1(&ecm_params);
|
||||
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
|
||||
// Second generate should have higher generation number.
|
||||
ASSERT_EQ(77, ecm_string.size());
|
||||
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
|
||||
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
|
||||
EXPECT_EQ('\x01', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x0A', ecm_string[3]); // generation + flags
|
||||
EXPECT_EQ('\x80', ecm_string[4]); // flags
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
|
||||
EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16));
|
||||
EXPECT_NE(kEcmWrappedKeySingle, ecm_string.substr(37, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16));
|
||||
// Unwrap key and compare with original.
|
||||
std::string wrapping_key = kFakeCasEncryptionResponseKeyData;
|
||||
std::string wrapping_iv = ecm_string.substr(53, 16);
|
||||
std::string wrapped_key = ecm_string.substr(37, 16);
|
||||
std::string unwrapped_key =
|
||||
crypto_util::DecryptAesCbcNoPad(wrapping_key, wrapping_iv, wrapped_key);
|
||||
EXPECT_EQ(kEcmKeyDataSingle, unwrapped_key);
|
||||
EXPECT_EQ(kEcmContentIvSingle, ecm_string.substr(69, 8));
|
||||
}
|
||||
|
||||
TEST_F(CasEcmGeneratorTest, InitializeSimpleRotation) {
|
||||
EXPECT_FALSE(ecm_gen_.initialized());
|
||||
EcmParameters ecm_params;
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
|
||||
PrepareEcmGenerator(true);
|
||||
|
||||
SetTestConfig2(&ecm_params);
|
||||
ecm_init_params_.key_rotation_enabled = true;
|
||||
|
||||
util::Status status = ProcessEcmParameters(ecm_params, &keys);
|
||||
|
||||
EXPECT_TRUE(status.ok());
|
||||
EXPECT_TRUE(ecm_gen_.initialized());
|
||||
EXPECT_TRUE(ecm_gen_.rotation_enabled());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmGeneratorTest, GenerateSimpleRotation) {
|
||||
EcmParameters ecm_params;
|
||||
|
||||
PrepareEcmGenerator(true);
|
||||
|
||||
SetTestConfig2(&ecm_params);
|
||||
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
|
||||
// Expected size (bytes):
|
||||
// same as no rotation case = 77
|
||||
// second entitlement key ID: 16 bytes
|
||||
// Second key: ID (16), Data (16), IV (16), IV (8) = 56
|
||||
// total = 149
|
||||
ASSERT_EQ(149, ecm_string.size());
|
||||
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
|
||||
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
|
||||
EXPECT_EQ('\x01', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x03', ecm_string[3]); // generation + flags
|
||||
EXPECT_EQ('\x80', ecm_string[4]); // flags
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
|
||||
EXPECT_EQ(kEcmKeyIdEven, ecm_string.substr(21, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyEven, ecm_string.substr(37, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyIvEven, ecm_string.substr(53, 16));
|
||||
EXPECT_EQ(kEcmContentIvEven, ecm_string.substr(69, 8));
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(77, 16));
|
||||
EXPECT_EQ(kEcmKeyIdOdd, ecm_string.substr(93, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyOdd, ecm_string.substr(109, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyIvOdd, ecm_string.substr(125, 16));
|
||||
EXPECT_EQ(kEcmContentIvOdd, ecm_string.substr(141, 8));
|
||||
// Unwrap even key and compare with original.
|
||||
std::string wrapping_key_even = kFakeCasEncryptionResponseKeyData;
|
||||
std::string wrapping_iv_even = ecm_string.substr(53, 16);
|
||||
std::string wrapped_key_even = ecm_string.substr(37, 16);
|
||||
std::string unwrapped_key_even = crypto_util::DecryptAesCbcNoPad(
|
||||
wrapping_key_even, wrapping_iv_even, wrapped_key_even);
|
||||
EXPECT_EQ(kEcmKeyDataEven, unwrapped_key_even);
|
||||
// Unwrap odd key and compare with original.
|
||||
std::string wrapping_key_odd = kFakeCasEncryptionResponseKeyData;
|
||||
std::string wrapping_iv_odd = ecm_string.substr(125, 16);
|
||||
std::string wrapped_key_odd = ecm_string.substr(109, 16);
|
||||
std::string unwrapped_key_odd = crypto_util::DecryptAesCbcNoPad(
|
||||
wrapping_key_odd, wrapping_iv_odd, wrapped_key_odd);
|
||||
EXPECT_EQ(kEcmKeyDataOdd, unwrapped_key_odd);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
287
media_cas_packager_sdk/internal/ecmg.cc
Normal file
287
media_cas_packager_sdk/internal/ecmg.cc
Normal file
@@ -0,0 +1,287 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 <string.h>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "util/status.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"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
static const char kDefaultContentId[] = "21140844";
|
||||
static const char kDefaultProvider[] = "widevine";
|
||||
// Size of this IV needs to match ecm_init_params.content_iv_size.
|
||||
static const char kDefaultContentIv[] = {'\x01', '\x01', '\x01', '\x01',
|
||||
'\x01', '\x01', '\x01', '\x01'};
|
||||
static const char kDefaultTrackTypeSd[] = "SD";
|
||||
|
||||
// Local helper function that processes all the parameters in an ECMG message.
|
||||
util::Status ProcessParameters(const char* message, size_t message_length,
|
||||
EcmgParameters* parameters) {
|
||||
DCHECK(message);
|
||||
DCHECK(parameters);
|
||||
|
||||
uint16_t parameter_type;
|
||||
uint16_t parameter_length;
|
||||
// 'offset' is used to track where we are within |message|.
|
||||
size_t offset = 0;
|
||||
// There could be CW_per_msg instances of CP_CW_combinations,
|
||||
// so we need to track how many we have processed so far
|
||||
// in order to know where to store the next CP_CW_combination.
|
||||
int current_cp_cw_combination_index = 0;
|
||||
while (offset != message_length) {
|
||||
BigEndianToHost16(¶meter_type, message + offset);
|
||||
offset += PARAMETER_TYPE_SIZE;
|
||||
BigEndianToHost16(¶meter_length, message + offset);
|
||||
offset += PARAMETER_LENGTH_SIZE;
|
||||
switch (parameter_type) {
|
||||
case ACCESS_CRITERIA: {
|
||||
LOG(WARNING) << "Ignoring access_criteria parameter of "
|
||||
<< parameter_length << " bytes long";
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case ECM_CHANNEL_ID: {
|
||||
if (parameter_length != ECM_CHANNEL_ID_SIZE) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", parameter_length,
|
||||
" for parameter type ", parameter_type));
|
||||
}
|
||||
BigEndianToHost16(¶meters->ecm_channel_id, message + offset);
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case ECM_STREAM_ID: {
|
||||
if (parameter_length != ECM_STREAM_ID_SIZE) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", parameter_length,
|
||||
" for parameter type ", parameter_type));
|
||||
}
|
||||
BigEndianToHost16(¶meters->ecm_stream_id, message + offset);
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case CP_CW_COMBINATION: {
|
||||
if (current_cp_cw_combination_index > 2) {
|
||||
// We can have at most 3 CP_CW_Combinations.
|
||||
return util::Status(util::error::INVALID_ARGUMENT,
|
||||
"We only support up to 2 control words in the "
|
||||
"CW_provision message");
|
||||
}
|
||||
EcmgCpCwCombination* combination =
|
||||
¶meters->cp_cw_combinations[current_cp_cw_combination_index++];
|
||||
BigEndianToHost16(&combination->cp, message + offset);
|
||||
offset += CP_SIZE;
|
||||
size_t cw_size = parameter_length - CP_SIZE;
|
||||
combination->cw = std::string(message + offset, cw_size);
|
||||
offset += cw_size;
|
||||
// TODO(user): This is a temporary hack to let the ECM generator
|
||||
// know how many keys to include in the ECM.
|
||||
// CW_per_msg should have been set during channel set-up instead.
|
||||
parameters->cw_per_msg = current_cp_cw_combination_index;
|
||||
break;
|
||||
}
|
||||
case CP_DURATION: {
|
||||
if (parameter_length != CP_DURATION_SIZE) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", parameter_length,
|
||||
" for parameter type ", parameter_type));
|
||||
}
|
||||
BigEndianToHost16(¶meters->cp_duration, message + offset);
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case CP_NUMBER: {
|
||||
if (parameter_length != CP_NUMBER_SIZE) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", parameter_length,
|
||||
" for parameter type ", parameter_type));
|
||||
}
|
||||
BigEndianToHost16(¶meters->cp_number, message + offset);
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case CW_ENCRYPTION: {
|
||||
LOG(WARNING) << "Ignoring CW_encryption parameter of "
|
||||
<< parameter_length << " bytes long";
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
case NOMINAL_CP_DURATION: {
|
||||
if (parameter_length != NOMINAL_CP_DURATION_SIZE) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", parameter_length,
|
||||
" for parameter type ", parameter_type));
|
||||
}
|
||||
BigEndianToHost16(¶meters->nominal_cp_duration, message + offset);
|
||||
offset += parameter_length;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return util::Status(
|
||||
util::error::UNIMPLEMENTED,
|
||||
absl::StrCat("No implementation yet to process parameter of type ",
|
||||
parameter_type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
util::Status Ecmg::ProcessStreamSetupMessage(const char* message,
|
||||
size_t message_length) {
|
||||
DCHECK(message);
|
||||
|
||||
EcmgParameters parameters;
|
||||
util::Status status = ProcessParameters(message, message_length, ¶meters);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (parameters.ecm_channel_id == 0 || parameters.ecm_stream_id == 0 ||
|
||||
parameters.nominal_cp_duration == 0) {
|
||||
return util::Status(util::error::INVALID_ARGUMENT,
|
||||
"Missing required parameter");
|
||||
}
|
||||
|
||||
if (channels_.find(parameters.ecm_channel_id) == channels_.end()) {
|
||||
std::unique_ptr<EcmgChannel> new_channel = absl::make_unique<EcmgChannel>();
|
||||
channels_[parameters.ecm_channel_id] = std::move(new_channel);
|
||||
}
|
||||
EcmgChannel* channel =
|
||||
channels_.find(parameters.ecm_channel_id)->second.get();
|
||||
auto stream_entry = channel->streams.find(parameters.ecm_stream_id);
|
||||
if (stream_entry == channel->streams.end()) {
|
||||
std::unique_ptr<EcmgStream> new_stream = absl::make_unique<EcmgStream>();
|
||||
new_stream->nominal_cp_duration = parameters.nominal_cp_duration;
|
||||
channel->streams[parameters.ecm_stream_id] = std::move(new_stream);
|
||||
} else {
|
||||
EcmgStream* existing_stream = stream_entry->second.get();
|
||||
existing_stream->nominal_cp_duration = parameters.nominal_cp_duration;
|
||||
}
|
||||
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status Ecmg::ProcessCwProvisionMessage(const char* message,
|
||||
size_t message_length,
|
||||
std::string* response) {
|
||||
DCHECK(message);
|
||||
DCHECK(response);
|
||||
|
||||
EcmgParameters parameters;
|
||||
util::Status status = ProcessParameters(message, message_length, ¶meters);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (parameters.ecm_channel_id == 0 || parameters.ecm_stream_id == 0 ||
|
||||
parameters.cp_number == 0 || parameters.cw_per_msg == 0) {
|
||||
return util::Status(util::error::INVALID_ARGUMENT,
|
||||
"Missing required parameter");
|
||||
}
|
||||
|
||||
// TODO(user): Figure out what to do with ECM_channel_ID and ECM_stream_ID.
|
||||
// - We certainly need to check the channel/stream has been setup
|
||||
// - Retrieve config parameters such as lead_CW and CW_per_msg
|
||||
// - In some config, we need to keep CW for previous CP to be included in the
|
||||
// current ECM
|
||||
// TODO(user): Remove debug loop below.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < parameters.cp_cw_combinations[i].cw.size(); j++) {
|
||||
printf("%x ",
|
||||
static_cast<uint16_t>(parameters.cp_cw_combinations[i].cw[j]));
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
bool key_rotation_enabled = parameters.cw_per_msg > 1;
|
||||
|
||||
// Create an instance of CasEcm in order to set the entitlement keys.
|
||||
// TODO(user): The section of code below for constructing CasEcm should
|
||||
// be optimized. There should be a single instance of CasEcm for each stream.
|
||||
// Right now, this is hard to do because CasEcmGenerator contains the CasEcm.
|
||||
std::unique_ptr<CasEcm> ecm = absl::make_unique<CasEcm>();
|
||||
// TODO(user): Revisit this hardcoded ecm_init_params.
|
||||
EcmInitParameters ecm_init_params;
|
||||
ecm_init_params.content_iv_size = kIvSize8;
|
||||
ecm_init_params.key_rotation_enabled = key_rotation_enabled;
|
||||
// Only CTR is supported for now.
|
||||
ecm_init_params.crypto_mode = CasCryptoMode::CTR;
|
||||
// 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(kDefaultContentIv));
|
||||
}
|
||||
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
|
||||
94
media_cas_packager_sdk/internal/ecmg.h
Normal file
94
media_cas_packager_sdk/internal/ecmg.h
Normal file
@@ -0,0 +1,94 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "util/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_
|
||||
53
media_cas_packager_sdk/internal/ecmg_constants.h
Normal file
53
media_cas_packager_sdk/internal/ecmg_constants.h
Normal file
@@ -0,0 +1,53 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_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
|
||||
|
||||
// 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
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CONSTANTS_H_
|
||||
66
media_cas_packager_sdk/internal/fixed_key_fetcher.cc
Normal file
66
media_cas_packager_sdk/internal/fixed_key_fetcher.cc
Normal file
@@ -0,0 +1,66 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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/fixed_key_fetcher.h"
|
||||
|
||||
#include "util/status.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
// Key IDs are 16 bytes, keys are 32 bytes.
|
||||
static const char* kKeyId1 = "fake_key_id1....";
|
||||
static const char* kKey1 = "fakefakefakefakefakefakefake1...";
|
||||
static const char* kKeyId2 = "fake_key_id2....";
|
||||
static const char* kKey2 = "fakefakefakefakefakefakefake2...";
|
||||
|
||||
} // namespace
|
||||
|
||||
util::Status FixedKeyFetcher::RequestEntitlementKey(
|
||||
const std::string& request_string, std::string* signed_response_string) {
|
||||
CasEncryptionRequest request;
|
||||
request.ParseFromString(request_string);
|
||||
|
||||
CasEncryptionResponse response;
|
||||
response.set_status(CasEncryptionResponse_Status_OK);
|
||||
response.set_content_id(request.content_id());
|
||||
for (const auto& track_type : request.track_types()) {
|
||||
if (request.key_rotation()) {
|
||||
// Add the Even key.
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id(kKeyId1);
|
||||
key->set_key(kKey1);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN);
|
||||
// Add the Odd key.
|
||||
key = response.add_entitlement_keys();
|
||||
key->set_key_id(kKeyId2);
|
||||
key->set_key(kKey2);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_ODD);
|
||||
} else {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id(kKeyId1);
|
||||
key->set_key(kKey1);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE);
|
||||
}
|
||||
}
|
||||
std::string response_string;
|
||||
response.SerializeToString(&response_string);
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response_string);
|
||||
signed_response.SerializeToString(signed_response_string);
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
40
media_cas_packager_sdk/internal/fixed_key_fetcher.h
Normal file
40
media_cas_packager_sdk/internal/fixed_key_fetcher.h
Normal file
@@ -0,0 +1,40 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_FIXED_KEY_FETCHER_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_FIXED_KEY_FETCHER_H_
|
||||
|
||||
#include "media_cas_packager_sdk/internal/key_fetcher.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// Perform the same role as a WV CAS KeyFetcher, but return a
|
||||
// locally-constructed response that has known (predefined) entitlement keys.
|
||||
class FixedKeyFetcher : public KeyFetcher {
|
||||
public:
|
||||
FixedKeyFetcher() {}
|
||||
~FixedKeyFetcher() override = default;
|
||||
|
||||
// Get entitlement keys. Process a CasEncryptionRequest message to
|
||||
// determine the keys that are needed, generate a fixed set of keys,
|
||||
// and package them into a SignedCasEncryptionResponse message.
|
||||
// Args:
|
||||
// |request_string| a serialized CasEncryptionRequest message, produced
|
||||
// by WvCasEcm::Initialize().
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// WvCasEcm::ProcessCasEncryptionResponse().
|
||||
util::Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string) override;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_FIXED_KEY_FETCHER_H_
|
||||
43
media_cas_packager_sdk/internal/key_fetcher.h
Normal file
43
media_cas_packager_sdk/internal/key_fetcher.h
Normal file
@@ -0,0 +1,43 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_KEY_FETCHER_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_KEY_FETCHER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "util/status.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// Interface for fetching various types of keys.
|
||||
class KeyFetcher {
|
||||
public:
|
||||
KeyFetcher() = default;
|
||||
KeyFetcher(const KeyFetcher&) = delete;
|
||||
KeyFetcher& operator=(const KeyFetcher&) = delete;
|
||||
virtual ~KeyFetcher() = default;
|
||||
|
||||
// Get entitlement keys. Process a CasEncryptionRequest message to
|
||||
// determine the keys that are needed, generate a fixed set of keys,
|
||||
// and package them into a SignedCasEncryptionResponse message.
|
||||
// Args:
|
||||
// |request_string| a serialized CasEncryptionRequest message, produced
|
||||
// by WvCasEcm::Initialize().
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// WvCasEcm::ProcessCasEncryptionResponse().
|
||||
virtual util::Status RequestEntitlementKey(
|
||||
const std::string& request_string, std::string* signed_response_string) = 0;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_KEY_FETCHER_H_
|
||||
67
media_cas_packager_sdk/internal/simulcrypt.cc
Normal file
67
media_cas_packager_sdk/internal/simulcrypt.cc
Normal file
@@ -0,0 +1,67 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "util/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
|
||||
50
media_cas_packager_sdk/internal/simulcrypt.h
Normal file
50
media_cas_packager_sdk/internal/simulcrypt.h
Normal file
@@ -0,0 +1,50 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "util/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_
|
||||
55
media_cas_packager_sdk/internal/simulcrypt_constants.h
Normal file
55
media_cas_packager_sdk/internal/simulcrypt_constants.h
Normal file
@@ -0,0 +1,55 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_
|
||||
54
media_cas_packager_sdk/internal/simulcrypt_test.cc
Normal file
54
media_cas_packager_sdk/internal/simulcrypt_test.cc
Normal file
@@ -0,0 +1,54 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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
|
||||
28
media_cas_packager_sdk/internal/util.cc
Normal file
28
media_cas_packager_sdk/internal/util.cc
Normal file
@@ -0,0 +1,28 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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/util.h"
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "glog/logging.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
void BigEndianToHost16(uint16_t* destination, const void* source) {
|
||||
DCHECK(destination);
|
||||
DCHECK(source);
|
||||
uint16_t big_endian_number;
|
||||
memcpy(&big_endian_number, source, 2);
|
||||
*destination = ntohs(big_endian_number);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
25
media_cas_packager_sdk/internal/util.h
Normal file
25
media_cas_packager_sdk/internal/util.h
Normal file
@@ -0,0 +1,25 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_UTIL_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// Read 16 bits (short 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 BigEndianToHost16(uint16_t* destination, const void* source);
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_
|
||||
Reference in New Issue
Block a user