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:
Fang Yu
2018-10-16 11:56:49 -07:00
parent ba0d63e2c1
commit 9962e87676
61 changed files with 2294 additions and 1251 deletions

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/public/ecm_generator.h"
#include "media_cas_packager_sdk/internal/ecm_generator.h"
#include "glog/logging.h"
@@ -56,6 +56,10 @@ util::Status CasEcmGenerator::ProcessEcmParameters(
"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;
@@ -131,10 +135,6 @@ util::Status CasEcmGenerator::ValidateKeyParameters(
util::Status status;
status = ValidateKeyId(key_params.key_id);
if (!status.ok()) return status;
status = ValidateKeyData(key_params.wrapped_key_data);
if (!status.ok()) return status;
status = ValidateWrappedKeyIv(key_params.wrapped_key_iv);
if (!status.ok()) return status;
if (key_params.content_ivs.empty()) {
return {util::error::INVALID_ARGUMENT, "Content IVs is empty."};
}

View File

@@ -6,8 +6,8 @@
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_ECM_GENERATOR_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_ECM_GENERATOR_H_
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_
#include <stddef.h>
#include <memory>
@@ -27,32 +27,35 @@ namespace cas {
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): may need a starting crypto period index.
// TODO(user): Consider rename to EcmGeneratorParameters.
struct EcmParameters {
static constexpr int kDefaultIVSize = 8;
int iv_size = kDefaultIVSize;
bool current_key_even = true;
uint32_t current_key_index = 0;
// 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;
uint16_t program_id;
uint64_t rotation_period_microseconds;
// For video, this is zero. For audio, this is the size of the audio frame,
// which is a constant in the audio track.
uint16_t offset = 0;
};
// ECM Generator for Widevine/MediaCAS entitled keys.
// TODO(user): Rename class to EcmGenerator.
class CasEcmGenerator {
public:
CasEcmGenerator() = default;
@@ -92,4 +95,4 @@ class CasEcmGenerator {
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_ECM_GENERATOR_H_
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_

View File

@@ -6,7 +6,7 @@
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/public/ecm_generator.h"
#include "media_cas_packager_sdk/internal/ecm_generator.h"
#include <memory>
#include <string>

View 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(&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;
// 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

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

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

View 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

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

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

View 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

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

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

View 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

View 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

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

View File

@@ -20,13 +20,15 @@ PUBLIC_COPTS = ["-fvisibility=default"]
filegroup(
name = "binary_release_files",
srcs = glob(["*.h"]),
srcs = glob(["*.h"]) + [
"simulcrypt_server.cc",
":simulcrypt_server",
],
)
cc_binary(
name = "libmedia_cas_packager_sdk.so",
linkshared = 1,
deps = [":ecm_generator"],
)
cc_library(
@@ -38,31 +40,14 @@ cc_library(
"@abseil_repo//absl/base:core_headers",
"//util:status",
"//media_cas_packager_sdk/internal:ecm",
"//media_cas_packager_sdk/internal:ecm_generator",
],
)
cc_library(
name = "ecm_generator",
srcs = ["ecm_generator.cc"],
hdrs = ["ecm_generator.h"],
copts = PUBLIC_COPTS,
cc_binary(
name = "simulcrypt_server",
srcs = ["simulcrypt_server.cc"],
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",
],
)

View File

@@ -0,0 +1,84 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.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;
}