Decouple key fetcher; Update ECMG API
This commit is contained in:
6
BUILD
6
BUILD
@@ -12,18 +12,20 @@ load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar")
|
||||
|
||||
pkg_tar(
|
||||
name = "media_cas_packager_sdk_files",
|
||||
strip_prefix = "/",
|
||||
files = [
|
||||
"//common:binary_release_files",
|
||||
"//example:binary_release_files",
|
||||
"//media_cas_packager_sdk/public:binary_release_files",
|
||||
"//protos/public:binary_release_files",
|
||||
"//util:binary_release_files",
|
||||
],
|
||||
mode = "0644",
|
||||
strip_prefix = "/",
|
||||
)
|
||||
|
||||
pkg_tar(
|
||||
name = "media_cas_packager_sdk-bin",
|
||||
deps = [":media_cas_packager_sdk_files"],
|
||||
files = ["//media_cas_packager_sdk/public:libmedia_cas_packager_sdk.so"],
|
||||
mode = "0755",
|
||||
deps = [":media_cas_packager_sdk_files"],
|
||||
)
|
||||
|
||||
@@ -22,8 +22,6 @@ using oemcrypto_core_message::serialize::
|
||||
using oemcrypto_core_message::serialize::CreateCoreRenewalResponse;
|
||||
using widevine::Sha256_Hash;
|
||||
|
||||
// TODO(user): Check the Core*RequestFromMessage and
|
||||
// CreateCore*ResponseFromProto return value when b/148472911 is fixed.
|
||||
namespace widevine {
|
||||
namespace core_message_util {
|
||||
bool GetCoreProvisioningResponse(
|
||||
@@ -43,29 +41,38 @@ bool GetCoreRenewalOrReleaseLicenseResponse(
|
||||
const std::string& request_core_message,
|
||||
std::string* response_core_message) {
|
||||
oemcrypto_core_message::ODK_RenewalRequest odk_renewal_request;
|
||||
CoreRenewalRequestFromMessage(request_core_message, &odk_renewal_request);
|
||||
// TODO(user): This function is going to need to know what the renewal
|
||||
// license is, and extract the renewal duration. This should be the sum of
|
||||
// renewal_delay_seconds + 2 * renewal_recovery_duration_seconds. Or if you
|
||||
// want, we could also create CreateCoreRenewalResponseFromProto -- is that
|
||||
// better?
|
||||
uint64_t renewal_duration_seconds = 3600; // I just made this up for now.
|
||||
CreateCoreRenewalResponse(odk_renewal_request, renewal_duration_seconds,
|
||||
response_core_message);
|
||||
return true;
|
||||
if (request_core_message.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (!CoreRenewalRequestFromMessage(request_core_message,
|
||||
&odk_renewal_request)) {
|
||||
return false;
|
||||
}
|
||||
// TODO(b/141762043): This function is going to need to know what the
|
||||
// renewal license is, and extract the renewal duration. This should be the
|
||||
// sum of renewal_delay_seconds + 2 * renewal_recovery_duration_seconds.
|
||||
uint64_t renewal_duration_seconds =
|
||||
3600; // PTAL when addressing b/141762043.
|
||||
return CreateCoreRenewalResponse(
|
||||
odk_renewal_request, renewal_duration_seconds, response_core_message);
|
||||
}
|
||||
|
||||
bool GetCoreNewLicenseResponse(const std::string& license,
|
||||
const std::string& request_core_message,
|
||||
const bool nonce_required,
|
||||
std::string* response_core_message) {
|
||||
if (request_core_message.empty()) {
|
||||
return false;
|
||||
}
|
||||
oemcrypto_core_message::ODK_LicenseRequest odk_license_request;
|
||||
CoreLicenseRequestFromMessage(request_core_message, &odk_license_request);
|
||||
if (!CoreLicenseRequestFromMessage(request_core_message,
|
||||
&odk_license_request)) {
|
||||
return false;
|
||||
}
|
||||
std::string core_request_sha256 = Sha256_Hash(request_core_message);
|
||||
CreateCoreLicenseResponseFromProto(license, odk_license_request,
|
||||
core_request_sha256, nonce_required,
|
||||
response_core_message);
|
||||
return true;
|
||||
return CreateCoreLicenseResponseFromProto(license, odk_license_request,
|
||||
core_request_sha256, nonce_required,
|
||||
response_core_message);
|
||||
}
|
||||
|
||||
} // namespace core_message_util
|
||||
|
||||
@@ -46,6 +46,7 @@ cc_binary(
|
||||
srcs = ["wv_cas_ecm_example.cc"],
|
||||
deps = [
|
||||
"//base",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/public:wv_cas_ecm",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
],
|
||||
@@ -55,12 +56,8 @@ cc_binary(
|
||||
name = "wv_cas_key_fetcher_example",
|
||||
srcs = ["wv_cas_key_fetcher_example.cc"],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/flags:flag",
|
||||
"@abseil_repo//absl/flags:parse",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/public:wv_cas_key_fetcher",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -27,43 +27,44 @@ constexpr char kTestEcmgChannelSetup[] = {
|
||||
};
|
||||
|
||||
constexpr char kTestEcmgChannelSetupWithPrivateParameters[] = {
|
||||
'\x03', // protocol_version
|
||||
'\x00', '\x01', // message_type - Channel_setup
|
||||
'\x00', '\x70', // message_length
|
||||
'\x00', '\x0e', // parameter_type - ECM_channel_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
'\x00', '\x01', // parameter_type- SUPER_CAS_ID
|
||||
'\x00', '\x04', // parameter_length
|
||||
'\x4a', '\xd4', '\x00', '\x00', // parameter_value
|
||||
'\x80', '\x00', // parameter_type - AGE_RESTRICTION
|
||||
'\x00', '\x01', // parameter_length
|
||||
'\x00', // parameter_value
|
||||
'\x80', '\x01', // parameter_type - CRYPTO_MODE
|
||||
'\x00', '\x07', // parameter_length
|
||||
'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value
|
||||
'\x80', '\x04', // parameter_type - TRACK_TYPES
|
||||
'\x00', '\x02', // parameter_length
|
||||
'S', 'D', // parameter_value
|
||||
'\x80', '\x04', // parameter_type - TRACK_TYPES
|
||||
'\x00', '\x02', // parameter_length
|
||||
'H', 'D', // parameter_value
|
||||
'\x80', '\x02', // parameter_type - CONTENT_ID
|
||||
'\x00', '\x09', // parameter_length
|
||||
'C', 'a', 's', 'T', 's', 'F', 'a', 'k',
|
||||
'e', // parameter_value - CasTsFake
|
||||
'\x80', '\x03', // parameter_type - CONTENT_PROVIDER
|
||||
'\x00', '\x0d', // parameter_length
|
||||
'w', 'i', 'd', 'e', 'v', 'i', 'n', 'e',
|
||||
'_', 't', 'e', 's', 't', // parameter_value - widevine_test
|
||||
'\x80', '\x06', // parameter_type - CONTENT_IV
|
||||
'\x00', '\x10', // parameter_length
|
||||
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
|
||||
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
|
||||
'\x80', '\x06', // parameter_type - CONTENT_IV
|
||||
'\x00', '\x10', // parameter_length
|
||||
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
|
||||
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'};
|
||||
'\x03', // protocol_version
|
||||
'\x00', '\x01', // message_type - Channel_setup
|
||||
'\x00', '\x8c', // message_length
|
||||
'\x00', '\x0e', // parameter_type - ECM_channel_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
'\x00', '\x01', // parameter_type- SUPER_CAS_ID
|
||||
'\x00', '\x04', // parameter_length
|
||||
'\x4a', '\xd4', '\x00', '\x00', // parameter_value
|
||||
'\x80', '\x00', // parameter_type - AGE_RESTRICTION
|
||||
'\x00', '\x01', // parameter_length
|
||||
'\x00', // parameter_value
|
||||
'\x80', '\x01', // parameter_type - CRYPTO_MODE
|
||||
'\x00', '\x07', // parameter_length
|
||||
'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value
|
||||
'\x80', '\x04', // parameter_type - TRACK_TYPES
|
||||
'\x00', '\x02', // parameter_length
|
||||
'S', 'D', // parameter_value
|
||||
'\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION
|
||||
'\x00', '\x30', // parameter_length
|
||||
// parameter_value - ENTITLEMENT_ID (16 bytes)
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
// parameter_value (continued) - ENTITLEMENT_KEY (32 bytes)
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
'\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION
|
||||
'\x00', '\x30', // parameter_length
|
||||
// parameter_value - ENTITLEMENT_ID (16 bytes)
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
|
||||
// parameter_value (continued) - ENTITLEMENT_KEY (32 bytes)
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68'};
|
||||
|
||||
constexpr char kTestEcmgChannelTest[] = {
|
||||
'\x03', // protocol_version
|
||||
@@ -74,6 +75,18 @@ constexpr char kTestEcmgChannelTest[] = {
|
||||
'\x00', '\x01', // parameter_value
|
||||
};
|
||||
|
||||
constexpr char kTestEcmgChannelError[] = {
|
||||
'\x03', // protocol_version
|
||||
'\x00', '\x05', // message_type - Stream_error
|
||||
'\x00', '\x0c', // message_length
|
||||
'\x00', '\x0e', // parameter_type - ECM_channel_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
'\x70', '\x00', // parameter_type - Error_status
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x70', '\x00', // parameter_value (UNKNOWN_ERROR)
|
||||
};
|
||||
|
||||
constexpr char kTestEcmgChannelStatus[] = {
|
||||
'\x03', // protocol_version
|
||||
'\x00', '\x03', // message_type - Channel_status
|
||||
@@ -113,7 +126,7 @@ constexpr char kTestEcmgChannelStatus[] = {
|
||||
constexpr char kTestEcmgStreamSetupWithPrivateParameters[] = {
|
||||
'\x03', // protocol_version
|
||||
'\x01', '\x01', // message_type - Stream_setup
|
||||
'\x00', '\x1e', // message_length
|
||||
'\x00', '\x46', // message_length
|
||||
'\x00', '\x0e', // parameter_type - ECM_channel_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
@@ -129,7 +142,14 @@ constexpr char kTestEcmgStreamSetupWithPrivateParameters[] = {
|
||||
'\x80', '\x05', // parameter_type - STREAM_TRACK_TYPE
|
||||
'\x00', '\x02', // parameter_length
|
||||
'S', 'D', // parameter_value
|
||||
};
|
||||
'\x80', '\x06', // parameter_type - CONTENT_IV
|
||||
'\x00', '\x10', // parameter_length
|
||||
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
|
||||
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
|
||||
'\x80', '\x06', // parameter_type - CONTENT_IV
|
||||
'\x00', '\x10', // parameter_length
|
||||
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
|
||||
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'};
|
||||
|
||||
constexpr char kTestEcmgStreamSetup[] = {
|
||||
'\x03', // protocol_version
|
||||
@@ -179,6 +199,24 @@ constexpr char kTestEcmgStreamStatus[] = {
|
||||
'\x01' // parameter_value
|
||||
};
|
||||
|
||||
constexpr char kTestEcmgStreamError[] = {
|
||||
'\x03', // protocol_version
|
||||
'\x01', '\x06', // message_type - Stream_error
|
||||
'\x00', '\x1a', // message_length
|
||||
'\x00', '\x0e', // parameter_type - ECM_channel_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
'\x00', '\x0f', // parameter_type - ECM_stream_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
'\x70', '\x00', // parameter_type - Error_status
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x70', '\x00', // parameter_value (UNKNOWN_ERROR)
|
||||
'\x70', '\x01', // parameter_type - Error_information
|
||||
'\x00', '\x04', // parameter_length
|
||||
'i', 'n', 'f', 'o' // parameter_value
|
||||
};
|
||||
|
||||
constexpr char kTestEcmgCwProvision[] = {
|
||||
'\x03', // protocol_version
|
||||
'\x02', '\x01', // message_type - CW_provision
|
||||
@@ -209,7 +247,7 @@ constexpr char kTestEcmgCwProvision[] = {
|
||||
constexpr char kTestEcmgCwProvisionWithAccessCriteria[] = {
|
||||
'\x03', // protocol_version
|
||||
'\x02', '\x01', // message_type - CW_provision
|
||||
'\x00', '\xaa', // message_length
|
||||
'\x00', '\xf4', // message_length
|
||||
'\x00', '\x0e', // parameter_type - ECM_channel_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
@@ -225,43 +263,55 @@ constexpr char kTestEcmgCwProvisionWithAccessCriteria[] = {
|
||||
'\x00', '\x14', // parameter_type - CP_CW_Combination
|
||||
'\x00', '\x12', // parameter_length
|
||||
'\x00', '\x00', // parameter_value - CP (2 bytes) then CW next (16 bytes)
|
||||
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
|
||||
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
|
||||
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', //
|
||||
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', //
|
||||
'\x00', '\x14', // parameter_type - CP_CW_Combination
|
||||
'\x00', '\x12', // parameter_length
|
||||
'\x00', '\x01', // parameter_value - CP (2 bytes) then CW next (16 bytes)
|
||||
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
|
||||
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
|
||||
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', //
|
||||
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', //
|
||||
'\x00', '\x0d', // parameter_type - access_criteria
|
||||
'\x00', '\x62', // parameter_length
|
||||
'\x00', '\xac', // parameter_length
|
||||
'\x80', '\x00', // access_criteria parameter_type - AGE_RESTRICTION
|
||||
'\x00', '\x01', // parameter_length
|
||||
'\x00', // parameter_value
|
||||
'\x80', '\x01', // access_criteria parameter_type - CRYPTO_MODE
|
||||
'\x00', '\x07', // parameter_length
|
||||
'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value
|
||||
'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value
|
||||
'\x80', '\x04', // access_criteria parameter_type - TRACK_TYPES
|
||||
'\x00', '\x02', // parameter_length
|
||||
'S', 'D', // parameter_value
|
||||
'S', 'D', // parameter_value
|
||||
'\x80', '\x05', // access_criteria parameter_type - STREAM_TRACK_TYPE
|
||||
'\x00', '\x02', // parameter_length
|
||||
'S', 'D', // parameter_value
|
||||
'\x80', '\x02', // access_criteria parameter_type - CONTENT_ID
|
||||
'\x00', '\x09', // parameter_length
|
||||
'C', 'a', 's', 'T', 's', 'F', 'a', 'k',
|
||||
'e', // parameter_value - CasTsFake
|
||||
'\x80', '\x03', // access_criteria parameter_type - CONTENT_PROVIDER
|
||||
'\x00', '\x0d', // parameter_length
|
||||
'w', 'i', 'd', 'e', 'v', 'i', 'n', 'e',
|
||||
'_', 't', 'e', 's', 't', // parameter_value - widevine_test
|
||||
'S', 'D', // parameter_value
|
||||
'\x80', '\x06', // access_criteria parameter_type - CONTENT_IV
|
||||
'\x00', '\x10', // parameter_length
|
||||
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
|
||||
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
|
||||
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', //
|
||||
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', //
|
||||
'\x80', '\x06', // access_criteria parameter_type - CONTENT_IV
|
||||
'\x00', '\x10', // parameter_length
|
||||
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
|
||||
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'};
|
||||
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', //
|
||||
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', //
|
||||
'\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION
|
||||
'\x00', '\x30', // parameter_length
|
||||
// parameter_value - ENTITLEMENT_ID (16 bytes)
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
// parameter_value (continued) - ENTITLEMENT_KEY (32 bytes)
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
|
||||
'\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION
|
||||
'\x00', '\x30', // parameter_length
|
||||
// parameter_value - ENTITLEMENT_ID (16 bytes)
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
|
||||
// parameter_value (continued) - ENTITLEMENT_KEY (32 bytes)
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
|
||||
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68'};
|
||||
|
||||
constexpr char kTestEcmgCwProvisionSingleKey[] = {
|
||||
'\x03', // protocol_version
|
||||
@@ -303,25 +353,25 @@ constexpr char kTestEcmgEcmResponse[] = {
|
||||
'\x00', '\xbc', // parameter_length
|
||||
// parameter_value - ECM_datagram
|
||||
'\x47', '\x40', '\x00', '\x10', '\x00', '\x80', '\x70', '\xa5', '\x4a',
|
||||
'\xd4', '\x02', '\x0b', '\xc0', '\x66', '\x61', '\x6b', '\x65', '\x5f',
|
||||
'\x6b', '\x65', '\x79', '\x5f', '\x69', '\x64', '\x31', '\x2e', '\x2e',
|
||||
'\x2e', '\x2e', '\xef', '\x40', '\x57', '\x48', '\xa7', '\xad', '\xdd',
|
||||
'\xd4', '\x02', '\x0b', '\xc0', '\x30', '\x31', '\x32', '\x33', '\x34',
|
||||
'\x35', '\x36', '\x37', '\x30', '\x31', '\x32', '\x33', '\x34', '\x35',
|
||||
'\x36', '\x37', '\xef', '\x40', '\x57', '\x48', '\xa7', '\xad', '\xdd',
|
||||
'\x34', '\x73', '\xfe', '\x5d', '\x1c', '\x65', '\xa0', '\xbf', '\x93',
|
||||
'\xfe', '\x01', '\x4b', '\x1d', '\xcd', '\x9e', '\x1d', '\x3a', '\x36',
|
||||
'\x99', '\x8f', '\x47', '\xa1', '\x3b', '\x46', '\xf1', '\xde', '\x9e',
|
||||
'\xc2', '\x88', '\xf8', '\x27', '\x2f', '\xea', '\xa1', '\x63', '\x9b',
|
||||
'\x1b', '\x6a', '\x56', '\x2d', '\x26', '\x31', '\x32', '\x33', '\x34',
|
||||
'\x35', '\x36', '\x37', '\x38', '\x66', '\x61', '\x6b', '\x65', '\x5f',
|
||||
'\x6b', '\x65', '\x79', '\x5f', '\x69', '\x64', '\x32', '\x2e', '\x2e',
|
||||
'\x2e', '\x2e', '\xf4', '\x71', '\x2a', '\x4b', '\x6d', '\x6d', '\x14',
|
||||
'\x4d', '\x2e', '\x53', '\xe7', '\x4b', '\x9f', '\x4b', '\x0a', '\x34',
|
||||
'\xb4', '\xfd', '\xbe', '\x86', '\x21', '\x35', '\x1e', '\xda', '\x81',
|
||||
'\x89', '\x6f', '\x70', '\xd3', '\xd2', '\xb2', '\x79', '\xf2', '\xcd',
|
||||
'\xeb', '\xc5', '\xaf', '\x89', '\xab', '\xeb', '\xf0', '\x1b', '\xd0',
|
||||
'\xd3', '\xe9', '\x7d', '\x81', '\x8a', '\x31', '\x32', '\x33', '\x34',
|
||||
'\x35', '\x36', '\x37', '\x38', '\xff', '\xff', '\xff', '\xff', '\xff',
|
||||
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
|
||||
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
|
||||
'\x90', '\x04', '\x1a', '\xab', '\x1a', '\x46', '\x00', '\xdb', '\xdb',
|
||||
'\x2f', '\xcb', '\xa5', '\x10', '\x2a', '\x3b', '\x73', '\xf1', '\xfe',
|
||||
'\x9d', '\x28', '\x2a', '\x3c', '\x0a', '\x5c', '\x58', '\xbc', '\x97',
|
||||
'\x1d', '\x81', '\x5b', '\x5b', '\xf7', '\x00', '\x01', '\x02', '\x03',
|
||||
'\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0a', '\x0b', '\x0c',
|
||||
'\x0d', '\x0e', '\x0f', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66',
|
||||
'\x67', '\x68', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67',
|
||||
'\x68', '\xf4', '\x71', '\x2a', '\x4b', '\x6d', '\x6d', '\x14', '\x4d',
|
||||
'\x2e', '\x53', '\xe7', '\x4b', '\x9f', '\x4b', '\x0a', '\x34', '\x84',
|
||||
'\xe7', '\xe8', '\xf5', '\x02', '\x39', '\x1f', '\x62', '\x43', '\xb5',
|
||||
'\xca', '\xb0', '\xee', '\x77', '\xaf', '\xe9', '\x84', '\x41', '\x0a',
|
||||
'\x16', '\x45', '\x23', '\x6c', '\x1d', '\xe7', '\xdb', '\xfd', '\x91',
|
||||
'\x91', '\xac', '\x47', '\xbc', '\x10', '\x11', '\x12', '\x13', '\x14',
|
||||
'\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d',
|
||||
'\x1e', '\x1f', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
|
||||
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff'};
|
||||
|
||||
constexpr char kTestEcmgStreamClose[] = {
|
||||
|
||||
@@ -73,6 +73,42 @@ const char kTestEmmgStreamSetup[] = {
|
||||
'\x01' // parameter_value - private data
|
||||
};
|
||||
|
||||
const char kTestEmmgStreamBwRequest[] = {
|
||||
'\x02', // protocol_version
|
||||
'\x01', '\x17', // message_type - Stream_BW_request
|
||||
'\x00', '\x1a', // message_length
|
||||
'\x00', '\x01', // parameter_type - client_id
|
||||
'\x00', '\x04', // parameter_length
|
||||
'\x4a', '\xd4', '\x00', '\x00', // parameter_value
|
||||
'\x00', '\x03', // parameter_type - data_channel_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
'\x00', '\x04', // parameter_type - data_stream_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
'\x00', '\x06', // parameter_type - bandwidth
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x64' // parameter_value
|
||||
};
|
||||
|
||||
const char kTestEmmgStreamBwAllocation[] = {
|
||||
'\x02', // protocol_version
|
||||
'\x01', '\x18', // message_type - Stream_BW_allocation
|
||||
'\x00', '\x1a', // message_length
|
||||
'\x00', '\x01', // parameter_type - client_id
|
||||
'\x00', '\x04', // parameter_length
|
||||
'\x4a', '\xd4', '\x00', '\x00', // parameter_value
|
||||
'\x00', '\x03', // parameter_type - data_channel_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
'\x00', '\x04', // parameter_type - data_stream_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
'\x00', '\x06', // parameter_type - bandwidth
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x32' // parameter_value (50 kbps)
|
||||
};
|
||||
|
||||
const char kTestEmmgDataProvision[] = {
|
||||
'\x02', // protocol_version
|
||||
'\x02', '\x11', // message_type - Data_provision
|
||||
|
||||
@@ -17,63 +17,125 @@
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
const size_t kContentIvSize = 8;
|
||||
const bool kKeyRotation = false; // whether key rotation is enabled
|
||||
const char kCryptoMode[] = "CTR"; // CBC, CTR, or CSA2
|
||||
const int kEcmPid = 149; // PID for the ECM packet
|
||||
const size_t kContentIvSize = 16; // 8 or 16
|
||||
const bool kKeyRotation = true; // whether key rotation is enabled
|
||||
const char kCryptoMode[] =
|
||||
"AesScte"; // "AesCbc", "AesCtr", "DvbCsa2", "DvbCsa3", "AesOfb", "AesScte"
|
||||
const int kEcmPid = 149; // PID for the ECM packet
|
||||
const int kAgeRestriction = 0; // Age restriction for the ECM
|
||||
const char kOutputFile[] =
|
||||
"/tmp/ecm.ts"; // ECM TS packet will be output to here
|
||||
"/tmp/ecm.ts"; // ECM TS packet will be output to here
|
||||
const uint8_t kTableId = 0x80; // 0x80 or 0x81
|
||||
const char kDefaultTrackTypeSd[] = "SD";
|
||||
|
||||
const char kCsaEvenKey[] = "even_key"; // 8 bytes
|
||||
const char kEvenKey[] = "even_key........"; // 16 bytes
|
||||
const char kEvenKeyId[] = "even_key_id....."; // 16 bytes
|
||||
const char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes
|
||||
const char kEvenContentIv16Bytes[] = "even_iv.even_iv."; // 16 bytes
|
||||
const char kEvenEntitlementKeyId[] = "fake_key_id1...."; // 16 bytes
|
||||
const char kEvenEntitlementKey[] =
|
||||
"fakefakefakefakefakefakefake1..."; // 32 bytes
|
||||
const char kCsaOddKey[] = "odd_key."; // 8 bytes
|
||||
"fakefakefakefakefakefakefake1..."; // 32 bytes
|
||||
const char kEvenWrapIv[] = "even_warp_iv...."; // 16 bytes
|
||||
|
||||
const char kOddKey[] = "odd_key........."; // 16 bytes
|
||||
const char kOddKeyId[] = "odd_key_id......"; // 16 bytes
|
||||
const char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes
|
||||
const char kOddContentIv16Bytes[] = "odd_iv..odd_iv.."; // 16 bytes
|
||||
const char kOddEntitlementKeyId[] = "fake_key_id2...."; // 16 bytes
|
||||
const char kOddEntitlementKey[] =
|
||||
"fakefakefakefakefakefakefake2..."; // 32 bytes
|
||||
const uint8_t kTableId = 0x80;
|
||||
"fakefakefakefakefakefakefake2..."; // 32 bytes
|
||||
const char kOddWrapIv[] = "odd_warp_iv....."; // 16 bytes
|
||||
|
||||
const size_t kTsPacketSize = 188;
|
||||
|
||||
widevine::cas::CryptoMode GetCryptoMode(const std::string& crypto_mode) {
|
||||
if (crypto_mode.compare("CBC") == 0) {
|
||||
return widevine::cas::CryptoMode::kAesCbc;
|
||||
using widevine::cas::EntitlementKeyInfo;
|
||||
using widevine::cas::WvCasContentKeyInfo;
|
||||
using widevine::cas::WvCasEcmParameters;
|
||||
|
||||
WvCasEcmParameters CreateWvCasEcmParameters(bool key_rotation,
|
||||
int content_iv_size) {
|
||||
WvCasEcmParameters params;
|
||||
params.content_iv_size = content_iv_size == 8
|
||||
? widevine::cas::kIvSize8
|
||||
: widevine::cas::kIvSize16;
|
||||
params.key_rotation_enabled = key_rotation;
|
||||
if (!widevine::cas::StringToCryptoMode(kCryptoMode,
|
||||
¶ms.crypto_mode)) {
|
||||
std::cerr << "Unsupported crypto mode " << kCryptoMode << std::endl;
|
||||
}
|
||||
if (crypto_mode.compare("CTR") == 0) {
|
||||
return widevine::cas::CryptoMode::kAesCtr;
|
||||
params.age_restriction = kAgeRestriction;
|
||||
return params;
|
||||
}
|
||||
|
||||
std::vector<EntitlementKeyInfo> CreateInjectedEntitlements(bool key_rotation) {
|
||||
std::vector<EntitlementKeyInfo> injected_entitlements;
|
||||
injected_entitlements.reserve(key_rotation ? 2 : 1);
|
||||
injected_entitlements.emplace_back();
|
||||
EntitlementKeyInfo* entitlement = &injected_entitlements.back();
|
||||
entitlement->key_id = kEvenEntitlementKeyId;
|
||||
entitlement->key_value = kEvenEntitlementKey;
|
||||
entitlement->is_even_key = true;
|
||||
entitlement->track_type = kDefaultTrackTypeSd;
|
||||
if (key_rotation) {
|
||||
injected_entitlements.emplace_back();
|
||||
EntitlementKeyInfo* entitlement = &injected_entitlements.back();
|
||||
entitlement->key_id = kOddEntitlementKeyId;
|
||||
entitlement->key_value = kOddEntitlementKey;
|
||||
entitlement->is_even_key = false;
|
||||
entitlement->track_type = kDefaultTrackTypeSd;
|
||||
}
|
||||
return widevine::cas::CryptoMode::kDvbCsa2;
|
||||
return injected_entitlements;
|
||||
}
|
||||
|
||||
std::vector<WvCasContentKeyInfo> CreateContentKeyInfo(bool key_rotation,
|
||||
int content_iv_size) {
|
||||
std::vector<WvCasContentKeyInfo> content_keys;
|
||||
content_keys.reserve(key_rotation ? 2 : 1);
|
||||
content_keys.emplace_back();
|
||||
WvCasContentKeyInfo* content_key = &content_keys.back();
|
||||
content_key->key = kEvenKey;
|
||||
content_key->key_id = kEvenKeyId;
|
||||
content_key->content_iv =
|
||||
content_iv_size == 8 ? kEvenContentIv8Bytes : kEvenContentIv16Bytes;
|
||||
content_key->wrapped_key_iv = kEvenWrapIv;
|
||||
if (key_rotation) {
|
||||
content_keys.emplace_back();
|
||||
WvCasContentKeyInfo* content_key = &content_keys.back();
|
||||
content_key->key = kOddKey;
|
||||
content_key->key_id = kOddKeyId;
|
||||
content_key->content_iv =
|
||||
content_iv_size == 8 ? kOddContentIv8Bytes : kOddContentIv16Bytes;
|
||||
content_key->wrapped_key_iv = kOddWrapIv;
|
||||
}
|
||||
|
||||
return content_keys;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// Generate ECM.
|
||||
widevine::cas::WvCasEcm wv_cas_ecm;
|
||||
widevine::cas::WvCasStatus status = wv_cas_ecm.Initialize(
|
||||
kContentIvSize, kKeyRotation, GetCryptoMode(kCryptoMode));
|
||||
if (status != widevine::cas::OK) {
|
||||
std::cerr << "Failed to initialize WV CAS ECM, error: "
|
||||
<< widevine::cas::GetWvCasStatusMessage(status)
|
||||
<< std::endl;
|
||||
}
|
||||
WvCasEcmParameters params =
|
||||
CreateWvCasEcmParameters(kKeyRotation, kContentIvSize);
|
||||
std::vector<EntitlementKeyInfo> entitlements =
|
||||
CreateInjectedEntitlements(kKeyRotation);
|
||||
widevine::cas::WvCasEcm wv_cas_ecm(params, entitlements);
|
||||
|
||||
std::vector<WvCasContentKeyInfo> content_keys =
|
||||
CreateContentKeyInfo(kKeyRotation, kContentIvSize);
|
||||
std::string ecm;
|
||||
widevine::Status status;
|
||||
if (kKeyRotation) {
|
||||
status = wv_cas_ecm.GenerateEcm(
|
||||
kCsaEvenKey, kEvenContentIv8Bytes, kEvenEntitlementKeyId,
|
||||
kEvenEntitlementKey, kCsaOddKey, kOddContentIv8Bytes,
|
||||
kOddEntitlementKeyId, kOddEntitlementKey, &ecm);
|
||||
status = wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1],
|
||||
kDefaultTrackTypeSd, &ecm);
|
||||
} else {
|
||||
status = wv_cas_ecm.GenerateSingleKeyEcm(kCsaEvenKey, kEvenContentIv8Bytes,
|
||||
kEvenEntitlementKeyId,
|
||||
kEvenEntitlementKey, &ecm);
|
||||
status = wv_cas_ecm.GenerateSingleKeyEcm(content_keys[0],
|
||||
kDefaultTrackTypeSd, &ecm);
|
||||
}
|
||||
if (status != widevine::cas::OK) {
|
||||
std::cerr << "Failed to generate WV CAS ECM, error: "
|
||||
<< widevine::cas::GetWvCasStatusMessage(status)
|
||||
|
||||
if (!status.ok()) {
|
||||
std::cerr << "Failed to generate WV CAS ECM, error: " << status
|
||||
<< std::endl;
|
||||
return -1;
|
||||
} else {
|
||||
@@ -89,8 +151,8 @@ int main(int argc, char** argv) {
|
||||
uint8_t continuity_counter; // not used.
|
||||
status = wv_cas_ecm.GenerateTsPacket(ecm, kEcmPid, kTableId,
|
||||
&continuity_counter, packet);
|
||||
if (status != widevine::cas::OK) {
|
||||
std::cerr << "Failed to create ECM TS packet" << std::endl;
|
||||
if (!status.ok()) {
|
||||
std::cerr << "Failed to create ECM TS packet: " << status << std::endl;
|
||||
return -1;
|
||||
} else {
|
||||
std::cout << "TS packet bytes: ";
|
||||
|
||||
@@ -7,65 +7,86 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/parse.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
ABSL_FLAG(std::string, content_id, "21140844", "Content ID");
|
||||
ABSL_FLAG(bool, key_rotation, true, "Whether key rotation is enabled");
|
||||
ABSL_FLAG(std::string, track_type, "SD", "Provider name");
|
||||
const char kContentId[] = "21140844";
|
||||
const char kContentProvider[] = "widevine";
|
||||
const char kTrackType[] = "SD";
|
||||
const bool kKeyRotation = false;
|
||||
const char kSigningProvider[] = "widevine_test";
|
||||
const char kSingingKey[] =
|
||||
"1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9";
|
||||
const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249";
|
||||
const char kHttpResponse[] =
|
||||
"{\"response\":"
|
||||
"\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu"
|
||||
"dF9rZXlzIjpbeyJrZXlfaWQiOiJNUGFndXhNb1hNNkUxUzhEOUF3RkNBPT0iLCJrZXkiOiJoZ1"
|
||||
"JycmdqeUg4NjQycjY3VHd0OHJ1cU5MUGNMRmtKcWRVSUROdm5GZDBNPSIsInRyYWNrX3R5cGUi"
|
||||
"OiJTRCIsImtleV9zbG90IjoiU0lOR0xFIn1dfQ==\"}";
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
absl::ParseCommandLine(argc, argv);
|
||||
CHECK(!absl::GetFlag(FLAGS_content_id).empty() &&
|
||||
!absl::GetFlag(FLAGS_track_type).empty())
|
||||
<< "Flags 'content_id' and 'track_type' are required";
|
||||
// Required flags in key fetcher.
|
||||
CHECK(!absl::GetFlag(FLAGS_license_server).empty() &&
|
||||
!absl::GetFlag(FLAGS_signing_provider).empty() &&
|
||||
!absl::GetFlag(FLAGS_signing_key).empty() &&
|
||||
!absl::GetFlag(FLAGS_signing_iv).empty())
|
||||
<< "Flags 'license_server', 'signing_provider', 'signing_key' "
|
||||
"and 'signing_iv' are required";
|
||||
using widevine::Status;
|
||||
using widevine::cas::EntitlementKeyInfo;
|
||||
using widevine::cas::EntitlementRequestParams;
|
||||
using widevine::cas::WvCasKeyFetcher;
|
||||
|
||||
class ExampleKeyFetcher : public WvCasKeyFetcher {
|
||||
public:
|
||||
ExampleKeyFetcher(const std::string& signing_provider,
|
||||
const std::string& signing_key,
|
||||
const std::string& signing_iv)
|
||||
: WvCasKeyFetcher(signing_provider, signing_key, signing_iv) {}
|
||||
|
||||
// An example that always returns the same response.
|
||||
Status MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const override {
|
||||
*http_response_json = kHttpResponse;
|
||||
return widevine::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// Initialize key fetcher.
|
||||
ExampleKeyFetcher key_fetcher(kSigningProvider, kSingingKey, kSingingIv);
|
||||
|
||||
// Create request string.
|
||||
std::string request_str;
|
||||
widevine::CasEncryptionRequest request;
|
||||
request.set_provider(absl::GetFlag(FLAGS_signing_provider));
|
||||
request.set_content_id(absl::GetFlag(FLAGS_content_id));
|
||||
request.set_key_rotation(absl::GetFlag(FLAGS_key_rotation));
|
||||
// Only 1 track in this example.
|
||||
request.add_track_types(absl::GetFlag(FLAGS_track_type));
|
||||
LOG(INFO) << "Request: " << request.ShortDebugString();
|
||||
if (!request.SerializeToString(&request_str)) {
|
||||
LOG(ERROR) << "Failed to serialize request";
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string signed_response_str;
|
||||
widevine::cas::WvCasKeyFetcher key_fetcher;
|
||||
widevine::Status status =
|
||||
key_fetcher.RequestEntitlementKey(request_str, &signed_response_str);
|
||||
EntitlementRequestParams request_params;
|
||||
request_params.content_id = kContentId;
|
||||
request_params.content_provider = kContentProvider;
|
||||
request_params.track_types = {kTrackType};
|
||||
request_params.key_rotation = kKeyRotation;
|
||||
Status status =
|
||||
key_fetcher.CreateEntitlementRequest(request_params, &request_str);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Failed to request entitlement key";
|
||||
return -1;
|
||||
std::cerr << "Failed to create entitlement request, error: " << status
|
||||
<< std::endl;
|
||||
}
|
||||
widevine::SignedCasEncryptionResponse signed_response;
|
||||
if (!signed_response.ParseFromString(signed_response_str)) {
|
||||
LOG(ERROR) << "Failed to deserialize signed response";
|
||||
return -1;
|
||||
std::cout << "Request: " << request_str << std::endl;
|
||||
|
||||
// Request entitlement keys.
|
||||
std::string signed_response_str;
|
||||
status = key_fetcher.MakeHttpRequest(request_str, &signed_response_str);
|
||||
if (!status.ok()) {
|
||||
std::cerr << "Failed to request entitlement key, error: " << status
|
||||
<< std::endl;
|
||||
}
|
||||
LOG(INFO) << "Signed response: " << signed_response.ShortDebugString();
|
||||
widevine::CasEncryptionResponse response;
|
||||
if (!response.ParseFromString(signed_response.response())) {
|
||||
LOG(ERROR) << "Failed to deserialize response";
|
||||
return -1;
|
||||
std::cout << "Response: " << signed_response_str << std::endl;
|
||||
|
||||
// Parse entitlement key response.
|
||||
std::vector<widevine::cas::EntitlementKeyInfo> entitlements;
|
||||
status =
|
||||
key_fetcher.ParseEntitlementResponse(signed_response_str, &entitlements);
|
||||
if (!status.ok()) {
|
||||
std::cerr << "Failed to parse entitlement response, error: " << status
|
||||
<< std::endl;
|
||||
}
|
||||
LOG(INFO) << "Response: " << response.ShortDebugString();
|
||||
std::cout << "Parsed: " << entitlements.size() << " entitlement keys."
|
||||
<< std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@ cc_library(
|
||||
srcs = ["ecm.cc"],
|
||||
hdrs = ["ecm.h"],
|
||||
deps = [
|
||||
":util",
|
||||
"//base",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:aes_cbc_util",
|
||||
"//common:status",
|
||||
"//common:string_util",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -41,8 +41,8 @@ cc_test(
|
||||
srcs = ["ecm_test.cc"],
|
||||
deps = [
|
||||
":ecm",
|
||||
":mpeg2ts",
|
||||
"//testing:gunit_main",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -63,6 +63,9 @@ cc_test(
|
||||
srcs = ["ecm_generator_test.cc"],
|
||||
deps = [
|
||||
":ecm_generator",
|
||||
":fixed_key_fetcher",
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"//common:aes_cbc_util",
|
||||
@@ -80,8 +83,6 @@ cc_library(
|
||||
],
|
||||
deps = [
|
||||
":ecm",
|
||||
":fixed_key_fetcher",
|
||||
":key_fetcher",
|
||||
":mpeg2ts",
|
||||
":simulcrypt_util",
|
||||
":util",
|
||||
@@ -92,8 +93,6 @@ cc_library(
|
||||
"//common:crypto_util",
|
||||
"//common:random_util",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/public:wv_cas_ecm",
|
||||
"//media_cas_packager_sdk/public:wv_cas_key_fetcher",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
],
|
||||
)
|
||||
@@ -129,6 +128,7 @@ cc_library(
|
||||
"//base",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/strings:str_format",
|
||||
"@abseil_repo//absl/time",
|
||||
"//common:status",
|
||||
"//protos/public:media_cas_cc_proto",
|
||||
],
|
||||
@@ -156,20 +156,14 @@ cc_library(
|
||||
"fixed_key_fetcher.h",
|
||||
],
|
||||
deps = [
|
||||
":key_fetcher",
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/public:wv_cas_key_fetcher",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "key_fetcher",
|
||||
hdrs = [
|
||||
"key_fetcher.h",
|
||||
],
|
||||
deps = ["//common:status"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "mpeg2ts",
|
||||
hdrs = [
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/aes_cbc_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
@@ -112,63 +113,14 @@ bool ConvertIvSizeParam(EcmIvSize param, size_t* size) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Status Ecm::Initialize(const std::string& content_id,
|
||||
const std::string& content_provider,
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
std::string* key_request_message) {
|
||||
if (initialized_) {
|
||||
return {error::INTERNAL, "Already initialized."};
|
||||
}
|
||||
if (content_id.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Content ID is empty."};
|
||||
}
|
||||
if (content_provider.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Content Provider is empty."};
|
||||
}
|
||||
if (key_request_message == nullptr) {
|
||||
return {error::INVALID_ARGUMENT, "key_request_message is null."};
|
||||
}
|
||||
|
||||
if (ecm_init_parameters.track_types.empty()) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Parameter track_ids must be set with one or more Track IDs."};
|
||||
}
|
||||
|
||||
if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size,
|
||||
&content_iv_size_)) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Parameter content_iv_size must be kIvSize8 or kIvSize16."};
|
||||
}
|
||||
|
||||
crypto_mode_ = ecm_init_parameters.crypto_mode;
|
||||
content_id_ = content_id;
|
||||
content_provider_ = content_provider;
|
||||
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
|
||||
track_types_ = ecm_init_parameters.track_types;
|
||||
age_restriction_ = ecm_init_parameters.age_restriction;
|
||||
|
||||
// Construct and return CasEncryptionRequest message for caller to use.
|
||||
Status status = CreateEntitlementRequest(key_request_message);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Entitlement request message could not be created.";
|
||||
return status;
|
||||
}
|
||||
|
||||
// Everything is set up except entitlement keys.
|
||||
ClearEntitlementKeys();
|
||||
initialized_ = true;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::Initialize(
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
const std::vector<InjectedEntitlementKeyInfo>& injected_entitlements) {
|
||||
const std::vector<EntitlementKeyInfo>& injected_entitlements) {
|
||||
if (initialized_) {
|
||||
return {error::INTERNAL, "Already initialized."};
|
||||
}
|
||||
if (ecm_init_parameters.track_types.empty()) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Parameter track_ids must be set with one or more Track IDs."};
|
||||
if (injected_entitlements.empty()) {
|
||||
return {error::NOT_FOUND, "Empty injected entitlements."};
|
||||
}
|
||||
if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size,
|
||||
&content_iv_size_)) {
|
||||
@@ -178,7 +130,6 @@ Status Ecm::Initialize(
|
||||
|
||||
crypto_mode_ = ecm_init_parameters.crypto_mode;
|
||||
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
|
||||
track_types_ = ecm_init_parameters.track_types;
|
||||
age_restriction_ = ecm_init_parameters.age_restriction;
|
||||
|
||||
ClearEntitlementKeys();
|
||||
@@ -197,16 +148,6 @@ Status Ecm::Initialize(
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::ProcessCasEncryptionResponse(const std::string& response) {
|
||||
if (!initialized_) {
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (response.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Response std::string is empty."};
|
||||
}
|
||||
return ParseEntitlementResponse(response);
|
||||
}
|
||||
|
||||
Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const {
|
||||
@@ -278,6 +219,18 @@ Status Ecm::GenerateEcmCommon(const std::vector<EntitledKeyInfo*>& keys,
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid, uint8_t table_id,
|
||||
uint8_t* continuity_counter, uint8_t* packet) {
|
||||
ssize_t bytes_modified = 0;
|
||||
Status status = InsertEcmAsTsPacket(ecm, pid, table_id, continuity_counter,
|
||||
packet, &bytes_modified);
|
||||
if (!status.ok() || bytes_modified != kTsPacketSize) {
|
||||
memset(packet, 0, kTsPacketSize);
|
||||
return {error::INTERNAL, "Failed to generate TS packet"};
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::WrapEntitledKeys(const std::string& track_type,
|
||||
const std::vector<EntitledKeyInfo*>& keys) const {
|
||||
if (!initialized_) {
|
||||
@@ -451,103 +404,6 @@ std::string Ecm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) const {
|
||||
return serialized_ecm;
|
||||
}
|
||||
|
||||
Status Ecm::CreateEntitlementRequest(std::string* request_string) const {
|
||||
CasEncryptionRequest request;
|
||||
|
||||
request.set_content_id(content_id_);
|
||||
request.set_provider(content_provider_);
|
||||
request.set_key_rotation(paired_keys_required_);
|
||||
// Add labels for tracks.
|
||||
for (const auto& track_type : track_types_) {
|
||||
request.add_track_types(track_type);
|
||||
}
|
||||
|
||||
if (!request.SerializeToString(request_string)) {
|
||||
request_string->clear();
|
||||
return {error::INTERNAL, "Failure serializing request."};
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::ParseEntitlementResponse(const std::string& response_string) {
|
||||
// TODO(user): parse valid response. NOT Implemented.
|
||||
ClearEntitlementKeys();
|
||||
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
if (!signed_response.ParseFromString(response_string)) {
|
||||
return {error::INTERNAL, "Failure parsing signed response."};
|
||||
}
|
||||
// TODO(user): Should verify signature.
|
||||
CasEncryptionResponse response;
|
||||
if (!response.ParseFromString(signed_response.response())) {
|
||||
return {error::INTERNAL, "Failure parsing signed response."};
|
||||
}
|
||||
if (response.status() != CasEncryptionResponse::OK) {
|
||||
return Status(error::INTERNAL, absl::StrCat("Failure reported by server: ",
|
||||
response.status(), " : ",
|
||||
response.status_message()));
|
||||
}
|
||||
if (content_id_ != response.content_id()) {
|
||||
return Status(
|
||||
error::INTERNAL,
|
||||
absl::StrCat("Content ID mismatch in Entitlement Response - expected: ",
|
||||
content_id_, " received: ", response.content_id()));
|
||||
}
|
||||
if (response.entitlement_keys().empty()) {
|
||||
return {error::INTERNAL, "Failure: no entitlement keys in response."};
|
||||
}
|
||||
size_t keys_needed = (paired_keys_required_ ? 2 : 1) * track_types_.size();
|
||||
if (keys_needed > response.entitlement_keys().size()) {
|
||||
return Status(
|
||||
error::INTERNAL,
|
||||
absl::StrCat(
|
||||
"Wrong number of keys in Entitlement Response - expected: ",
|
||||
keys_needed, " got: ", response.entitlement_keys().size()));
|
||||
}
|
||||
|
||||
// Scan available entitlement keys for the ones that are needed.
|
||||
// For non-key-rotation, this is a key with a track type and not even or odd.
|
||||
// For key rotation, this is a key with a track type and even or odd.
|
||||
ClearEntitlementKeys();
|
||||
for (const auto& key : response.entitlement_keys()) {
|
||||
if (!key.has_track_type()) {
|
||||
LOG(WARNING) << "Entitlement key missing track type, skipped.";
|
||||
// No track ID. Skip it.
|
||||
continue;
|
||||
}
|
||||
EntitlementKeyInfo ekey;
|
||||
ekey.key_id = key.key_id();
|
||||
ekey.key_value = key.key();
|
||||
Status status = ValidateKeyValue(key.key(), kWrappingKeySizeBytes);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Using only keys with correct KeySlot
|
||||
if (!key.has_key_slot()) {
|
||||
LOG(WARNING) << "Entitlement key missing key slot, skipped.";
|
||||
continue;
|
||||
}
|
||||
if (paired_keys_required()) {
|
||||
if (key.key_slot() == CasEncryptionResponse::KeyInfo::EVEN) {
|
||||
PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey);
|
||||
} else if (key.key_slot() == CasEncryptionResponse::KeyInfo::ODD) {
|
||||
PushEntitlementKey(key.track_type(), /* is_even_key= */ false, ekey);
|
||||
}
|
||||
} else {
|
||||
if (key.key_slot() == CasEncryptionResponse::KeyInfo::SINGLE) {
|
||||
PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!CheckEntitlementKeys()) {
|
||||
LOG(ERROR) << "Could not stage entitlement keys from response:";
|
||||
response.ShortDebugString();
|
||||
return Status(error::INTERNAL, "No suitable entitlement key was found.");
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
size_t Ecm::CountEntitlementKeys() const {
|
||||
size_t count = 0;
|
||||
for (const auto& track : entitlement_keys_) {
|
||||
@@ -557,15 +413,6 @@ size_t Ecm::CountEntitlementKeys() const {
|
||||
}
|
||||
|
||||
bool Ecm::CheckEntitlementKeys() const {
|
||||
// TODO(user): Cross-check entitlement_keys_ track types with track_types_.
|
||||
if (track_types_.size() > CountEntitlementTracks()) {
|
||||
// One or more tracks are missing.
|
||||
LOG(ERROR)
|
||||
<< "Entitlement keys for one or more tracks is missing - expected "
|
||||
<< track_types_.size() << " tracks, received "
|
||||
<< CountEntitlementTracks() << "tracks.";
|
||||
return false;
|
||||
}
|
||||
for (const auto& track : entitlement_keys_) {
|
||||
if (track.second.size() < (paired_keys_required() ? 2 : 1)) {
|
||||
LOG(ERROR) << " Wrong number of entitlement keys for track "
|
||||
|
||||
@@ -38,12 +38,6 @@ struct EntitledKeyInfo {
|
||||
std::string wrapped_key_iv;
|
||||
};
|
||||
|
||||
// Declare Content IV size in an ECM stream.
|
||||
// Content IVs may be encoded as 8 or 16 random bytes. The receiver is
|
||||
// responsible for appending 8 zeros to an 8-byte IV. All content IVs, once the
|
||||
// size is declared, must be the same size. Wrapped key IVs are always 16 bytes.
|
||||
enum EcmIvSize { kIvSize8 = 0, kIvSize16 = 1 };
|
||||
|
||||
// Information needed to start a new ECM stream.
|
||||
// Fields:
|
||||
// |content_iv_size| size of all content key IVs in the ECM stream.
|
||||
@@ -52,25 +46,17 @@ enum EcmIvSize { kIvSize8 = 0, kIvSize16 = 1 };
|
||||
// |crypto_mode| the encryption mode used for the content stream.
|
||||
// A constant of type CryptoMode.
|
||||
// |track_types| a vector of track ID (std::string) that specify the set of track
|
||||
// types of interest; controls the entitlement keys returned by the server.
|
||||
// types of interest. |track_types| is deprecated and is ignored by
|
||||
// Ecm::Initialize().
|
||||
// |age_restriction| minimum age required; the value represents actual age.
|
||||
struct EcmInitParameters {
|
||||
EcmIvSize content_iv_size = kIvSize8;
|
||||
bool key_rotation_enabled = true;
|
||||
CryptoMode crypto_mode = CryptoMode::kAesCtr;
|
||||
std::vector<std::string> track_types;
|
||||
std::vector<std::string> track_types; // deprecated.
|
||||
uint8_t age_restriction = 0;
|
||||
};
|
||||
|
||||
// Information needed for the injected entitlement keys. Used for Ecm
|
||||
// initialization.
|
||||
struct InjectedEntitlementKeyInfo {
|
||||
std::string track_type;
|
||||
bool is_even_key;
|
||||
std::string key_id; // must be 16 bytes.
|
||||
std::string key_value; // must be 32 bytes.
|
||||
};
|
||||
|
||||
// Generator for producing Widevine CAS-compliant ECMs. Used to construct the
|
||||
// Transport Stream packet payload of an ECM containing key information for
|
||||
// decrypting Widevine CAS encrypted content. The keys in the ECM are wrapped
|
||||
@@ -90,22 +76,6 @@ class Ecm {
|
||||
Ecm& operator=(const Ecm&) = delete;
|
||||
virtual ~Ecm() = default;
|
||||
|
||||
// Perform initialization for a new ECM stream.
|
||||
// Args:
|
||||
// |content_id| uniquely identifies the content (with |content_provider|)
|
||||
// |content_provider| unique std::string for provider of the content stream.
|
||||
// |ecm_init_parameters| encryption-related parameters for configuring
|
||||
// the ECM stream.
|
||||
// |key_request_message| pointer to a std::string to receive a CasEncryptionRequest
|
||||
// message.
|
||||
// Notes:
|
||||
// The returned |key_request_message| must be sent to the server and
|
||||
// the response correctly parsed before ECMs can be generated.
|
||||
virtual Status Initialize(const std::string& content_id,
|
||||
const std::string& content_provider,
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
std::string* key_request_message);
|
||||
|
||||
// Perform initialization for a new ECM stream with injected entitlement keys.
|
||||
// Args:
|
||||
// |ecm_init_parameters| encryption-related parameters for configuring
|
||||
@@ -114,15 +84,7 @@ class Ecm {
|
||||
// fetch keys from server.
|
||||
virtual Status Initialize(
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
const std::vector<InjectedEntitlementKeyInfo>& injected_entitlements);
|
||||
|
||||
// Parse a CasEncryptionResponse message holding the entitlement keys for
|
||||
// generating the ECM stream. The entitlement keys are used to encrypt the
|
||||
// keys conveyed in the ECM. The entitlement key IDs are also part of the ECM.
|
||||
// Args:
|
||||
// |response| a serialized CasEncryptionRequest message from the server
|
||||
// holding entitlement key information (or error information).
|
||||
virtual Status ProcessCasEncryptionResponse(const std::string& response);
|
||||
const std::vector<EntitlementKeyInfo>& injected_entitlements);
|
||||
|
||||
// Accept keys and IVs and construct an ECM that will fit into a Transport
|
||||
// Stream packet payload (184 bytes).
|
||||
@@ -153,6 +115,28 @@ class Ecm {
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const;
|
||||
|
||||
// Generate a TS packet with the given |ecm| as payload.
|
||||
//
|
||||
// Args (all pointer parameters must be not nullptr):
|
||||
// - |ecm| serialized ECM, e.g., generated by GenerateEcm() method above
|
||||
// - |pid| program ID for the ECM stream
|
||||
// - |table_id| is the table ID byte put in the section header, it should be
|
||||
// either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or
|
||||
// 0x81 to 0x80 is used to signal to the client that the key contained
|
||||
// in the ECM has changed. In other words, if you are building an ECM
|
||||
// with a new key that was not in any previous ECM, you should flip the
|
||||
// table ID so the client knows this is an important ECM it should process.
|
||||
// - |continuity_counter| continuity_counter for the ECM packet,
|
||||
// it will be incremented, only last 4 bits are used
|
||||
// - |packet| a buffer of size at least 188 bytes to be used to return
|
||||
// the generated TS packet
|
||||
//
|
||||
// Returns:
|
||||
// - A status indicating whether there was any error during processing
|
||||
static Status GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id, uint8_t* continuity_counter,
|
||||
uint8_t* packet);
|
||||
|
||||
protected: // For unit tests.
|
||||
// Take the input entitled |keys| and our current state, and generate
|
||||
// the ECM representing the keys.
|
||||
@@ -173,7 +157,7 @@ class Ecm {
|
||||
// Entitlement key - |key_value| is used to wrap the content key, and |key_id|
|
||||
// is added to the ECM. |track_type| allows the entitlement key to be
|
||||
// associated with the original request. See Initialize() for details.
|
||||
struct EntitlementKeyInfo {
|
||||
struct EntitlementKeyIdValue {
|
||||
std::string key_id;
|
||||
std::string key_value;
|
||||
};
|
||||
@@ -195,9 +179,9 @@ class Ecm {
|
||||
|
||||
// Add an entitlement key to our current state. Even key is placed first.
|
||||
void PushEntitlementKey(const std::string& track_type, bool is_even_key,
|
||||
const EntitlementKeyInfo& key) {
|
||||
auto emplaced =
|
||||
entitlement_keys_.emplace(track_type, std::list<EntitlementKeyInfo>{});
|
||||
const EntitlementKeyIdValue& key) {
|
||||
auto emplaced = entitlement_keys_.emplace(
|
||||
track_type, std::list<EntitlementKeyIdValue>{});
|
||||
if (is_even_key) {
|
||||
emplaced.first->second.push_front(key);
|
||||
} else {
|
||||
@@ -227,11 +211,6 @@ class Ecm {
|
||||
size_t key_value_size) const;
|
||||
Status ValidateIv(const std::string& iv, size_t size) const;
|
||||
|
||||
// TODO(user): need unit tests for CreateEntitlementRequest.
|
||||
virtual Status CreateEntitlementRequest(std::string* request_string) const;
|
||||
// TODO(user): need unit tests for ParseEntitlementResponse.
|
||||
virtual Status ParseEntitlementResponse(const std::string& response_string);
|
||||
|
||||
virtual CryptoMode crypto_mode() const { return crypto_mode_; }
|
||||
virtual uint8_t age_restriction() const { return age_restriction_; }
|
||||
virtual bool paired_keys_required() const { return paired_keys_required_; }
|
||||
@@ -245,15 +224,14 @@ class Ecm {
|
||||
std::string content_provider_;
|
||||
// Content IV size may be 8 or 16. Size is set once during Initialize().
|
||||
size_t content_iv_size_ = 8;
|
||||
// The set of tracks that are being encrypted and require ECM streams.
|
||||
std::vector<std::string> track_types_;
|
||||
// Remember if a pair of keys is required (for key rotation).
|
||||
bool paired_keys_required_ = false;
|
||||
CryptoMode crypto_mode_ = CryptoMode::kAesCtr;
|
||||
uint8_t age_restriction_ = 0;
|
||||
// Entitlement keys needed for ECM generation.
|
||||
// The keys are added when the CasEncryptionResponse is processed.
|
||||
std::map<std::string, std::list<EntitlementKeyInfo>> entitlement_keys_;
|
||||
// Maps from track_type to one/two EntitlementKeyIdValue with even key first.
|
||||
std::map<std::string, std::list<EntitlementKeyIdValue>> entitlement_keys_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
|
||||
#include "media_cas_packager_sdk/internal/ecm_generator.h"
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "google/protobuf/util/json_util.h"
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "common/aes_cbc_util.h"
|
||||
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
@@ -50,10 +53,21 @@ constexpr char kFakeCasEncryptionResponseKeyData[] =
|
||||
|
||||
constexpr char kTrackType[] = "SD";
|
||||
|
||||
Status HandleCasEncryptionRequest(const std::string& request_string,
|
||||
std::string* signed_response_string) {
|
||||
Status HandleCasEncryptionRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) {
|
||||
SignedCasEncryptionRequest signed_request;
|
||||
if (!google::protobuf::util::JsonStringToMessage(signed_request_json, &signed_request)
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Unable to parse signed_request_json.";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
CasEncryptionRequest request;
|
||||
request.ParseFromString(request_string);
|
||||
if (!google::protobuf::util::JsonStringToMessage(signed_request.request(), &request)
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Unable to understand signed_request_json.";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
CasEncryptionResponse response;
|
||||
response.set_status(CasEncryptionResponse::OK);
|
||||
@@ -81,10 +95,20 @@ Status HandleCasEncryptionRequest(const std::string& request_string,
|
||||
}
|
||||
}
|
||||
std::string response_string;
|
||||
response.SerializeToString(&response_string);
|
||||
if (!google::protobuf::util::MessageToJsonString(response, &response_string).ok()) {
|
||||
LOG(ERROR) << "MessageToJsonString(response, &response_string)";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response_string);
|
||||
signed_response.SerializeToString(signed_response_string);
|
||||
|
||||
if (!google::protobuf::util::MessageToJsonString(signed_response, http_response_json)
|
||||
.ok()) {
|
||||
LOG(ERROR) << "MessageToJsonString(signed_response, http_response_json)";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
@@ -123,16 +147,25 @@ class EcmGeneratorTest : public testing::Test {
|
||||
|
||||
// Call this to setup the guts (Ecm) of the ECM Generator.
|
||||
void PrepareEcmGenerator(bool key_rotation_enabled) {
|
||||
ecm_ = absl::make_unique<Ecm>();
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
ecm_init_params_.key_rotation_enabled = key_rotation_enabled;
|
||||
ecm_init_params_.track_types.push_back(kTrackType);
|
||||
ASSERT_OK(ecm_->Initialize(kContentId, kProvider, ecm_init_params_,
|
||||
&entitlement_request));
|
||||
FixedKeyFetcher key_fetcher;
|
||||
EntitlementRequestParams request_params;
|
||||
request_params.content_id = kContentId;
|
||||
request_params.content_provider = kProvider;
|
||||
request_params.track_types = {kTrackType};
|
||||
request_params.key_rotation = key_rotation_enabled;
|
||||
ASSERT_OK(key_fetcher.CreateEntitlementRequest(request_params,
|
||||
&entitlement_request));
|
||||
ASSERT_OK(
|
||||
HandleCasEncryptionRequest(entitlement_request, &entitlement_response));
|
||||
ASSERT_OK(ecm_->ProcessCasEncryptionResponse(entitlement_response));
|
||||
std::vector<EntitlementKeyInfo> entitlements;
|
||||
ASSERT_OK(key_fetcher.ParseEntitlementResponse(entitlement_response,
|
||||
&entitlements));
|
||||
|
||||
ecm_ = absl::make_unique<Ecm>();
|
||||
ASSERT_OK(ecm_->Initialize(ecm_init_params_, entitlements));
|
||||
ecm_gen_.set_ecm(std::move(ecm_));
|
||||
}
|
||||
|
||||
@@ -144,10 +177,8 @@ class EcmGeneratorTest : public testing::Test {
|
||||
kEcmKeyDataEven,
|
||||
kEcmWrappedKeyIvEven,
|
||||
{kEcmContentIvEven}};
|
||||
const KeyParameters kKeyParamsOdd{kEcmKeyIdOdd,
|
||||
kEcmKeyDataOdd,
|
||||
kEcmWrappedKeyIvOdd,
|
||||
{kEcmContentIvOdd}};
|
||||
const KeyParameters kKeyParamsOdd{
|
||||
kEcmKeyIdOdd, kEcmKeyDataOdd, kEcmWrappedKeyIvOdd, {kEcmContentIvOdd}};
|
||||
|
||||
std::unique_ptr<Ecm> ecm_;
|
||||
EcmInitParameters ecm_init_params_;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
|
||||
using ::testing::Return;
|
||||
|
||||
@@ -37,6 +37,45 @@ constexpr size_t kEcmIvSize8 = 8;
|
||||
constexpr size_t kEcmKeyInfoSize =
|
||||
kEcmKeyIdSize + kEcmKeyIdSize + kEcmKeyDataSize;
|
||||
|
||||
// ECM payload data taken from a CETS encrypted file at Google Fiber
|
||||
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
|
||||
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
|
||||
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4',
|
||||
'\xC6', '\x6D', '\x57', '\xDC'};
|
||||
// ECM packet for Video PID (TSheader + payload) taken from a CETS encrypted
|
||||
// file, payload_unit_start = 1, adaptation_field_control = 1, cc = 0
|
||||
constexpr char kExpectedEcmPacket[] = {
|
||||
// TS header.
|
||||
'\x47', '\x5F', '\xFD', '\x10',
|
||||
// Section header.
|
||||
'\x00', '\x80', '\x70', '\x1C',
|
||||
// ECM.
|
||||
'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
|
||||
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4', '\xC6', '\x6D', '\x57',
|
||||
'\xDC',
|
||||
// Padding.
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF'};
|
||||
|
||||
size_t IvExpectedSize(EcmIvSize iv_size) {
|
||||
return (iv_size == kIvSize8) ? kEcmIvSize8 : kEcmIvSize16;
|
||||
}
|
||||
@@ -103,82 +142,23 @@ class EcmTest : public testing::Test {
|
||||
|
||||
params_one_key_.key_rotation_enabled = false;
|
||||
params_two_keys_.key_rotation_enabled = true;
|
||||
params_simple_.track_types.push_back(kTrackTypeSD);
|
||||
|
||||
injected_entitlement_one_ = {kTrackTypeSD, true, "entitlement_id_1",
|
||||
"key__value.key__value.key__value"};
|
||||
injected_entitlement_two_ = {kTrackTypeSD, true, "entitlement_id_2",
|
||||
"key__value.key__value.key__value"};
|
||||
}
|
||||
|
||||
virtual void InitParams(EcmInitParameters* params,
|
||||
const std::string& track_type) {
|
||||
params->track_types.push_back(track_type);
|
||||
}
|
||||
|
||||
virtual void InitParams(EcmInitParameters* params,
|
||||
const std::string& track_type, EcmIvSize content_iv) {
|
||||
params->track_types.push_back(track_type);
|
||||
virtual void InitParams(EcmInitParameters* params, EcmIvSize content_iv) {
|
||||
params->content_iv_size = content_iv;
|
||||
}
|
||||
|
||||
virtual void ServerCall(const std::string& request_string,
|
||||
std::string* signed_response_string,
|
||||
bool report_status_ok, bool generate_valid_ecm) {
|
||||
CasEncryptionRequest request;
|
||||
ASSERT_TRUE(request.ParseFromString(request_string));
|
||||
|
||||
CasEncryptionResponse response;
|
||||
if (!report_status_ok) {
|
||||
response.set_status(CasEncryptionResponse::INTERNAL_ERROR);
|
||||
} else {
|
||||
response.set_status(CasEncryptionResponse::OK);
|
||||
response.set_content_id(request.content_id());
|
||||
for (const auto& track_type : request.track_types()) {
|
||||
if (request.key_rotation()) {
|
||||
// Add the Even key.
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
|
||||
// Add the Odd key.
|
||||
key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
|
||||
if (!generate_valid_ecm) {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
|
||||
key->set_track_type(track_type);
|
||||
}
|
||||
} else {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
|
||||
if (!generate_valid_ecm) {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
|
||||
key->set_track_type(track_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string response_string;
|
||||
ASSERT_TRUE(response.SerializeToString(&response_string));
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response_string);
|
||||
ASSERT_TRUE(signed_response.SerializeToString(signed_response_string));
|
||||
}
|
||||
|
||||
const std::string provider_ = "provider";
|
||||
const std::string content_id_ = "content-id";
|
||||
EcmInitParameters params_one_key_;
|
||||
EcmInitParameters params_two_keys_;
|
||||
EcmInitParameters params_simple_;
|
||||
EcmInitParameters params_default_;
|
||||
EntitlementKeyInfo injected_entitlement_one_;
|
||||
EntitlementKeyInfo injected_entitlement_two_;
|
||||
EntitledKeyInfo valid1_iv_16_8_;
|
||||
EntitledKeyInfo valid2_iv_16_8_;
|
||||
EntitledKeyInfo valid3_iv_16_16_;
|
||||
@@ -245,152 +225,80 @@ TEST_F(EcmTest, GenerateEcm2NotInitialized) {
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, SetResponseNotInitialized) {
|
||||
TEST_F(EcmTest, InitSucceedInjectedEntitlementSingle) {
|
||||
Ecm ecm_gen;
|
||||
const std::string response("anything");
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.ProcessCasEncryptionResponse(response).error_code());
|
||||
InitParams(¶ms_one_key_, kIvSize16);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, InitNoTracksFail) {
|
||||
TEST_F(EcmTest, InitSucceedInjectedEntitlementDouble) {
|
||||
Ecm ecm_gen;
|
||||
std::string request;
|
||||
EXPECT_EQ(
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(content_id_, provider_, params_default_, &request)
|
||||
.error_code());
|
||||
ASSERT_OK(ecm_gen.Initialize(
|
||||
params_simple_, {injected_entitlement_one_, injected_entitlement_two_}));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, InitSucceed) {
|
||||
TEST_F(EcmTest, InitFailEmptyInjectedEntitlement) {
|
||||
Ecm ecm_gen;
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request));
|
||||
ASSERT_EQ(error::NOT_FOUND,
|
||||
ecm_gen.Initialize(params_simple_, {}).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, InitFailNotEnoughInjectedEntitlement) {
|
||||
Ecm ecm_gen;
|
||||
ASSERT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(params_simple_, {injected_entitlement_one_})
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, SecondInitFail) {
|
||||
Ecm ecm_gen;
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request));
|
||||
ASSERT_OK(ecm_gen.Initialize(
|
||||
params_simple_, {injected_entitlement_one_, injected_entitlement_two_}));
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request)
|
||||
ecm_gen
|
||||
.Initialize(params_simple_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_})
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, InitEmptyContentIdFail) {
|
||||
Ecm ecm_gen;
|
||||
const std::string empty_content_id;
|
||||
std::string request;
|
||||
EXPECT_EQ(
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(empty_content_id, provider_, params_simple_, &request)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, InitEmptyProviderFail) {
|
||||
Ecm ecm_gen;
|
||||
const std::string empty_provider;
|
||||
std::string request;
|
||||
EXPECT_EQ(
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(content_id_, empty_provider, params_simple_, &request)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, InitIvSize16x16OK) {
|
||||
Ecm ecm_gen;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize16);
|
||||
InitParams(¶ms_one_key_, kIvSize16);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateWithNoEntitlementOneKeyFail) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string ecm;
|
||||
// This will fail because the entitlement key has not been acquired
|
||||
// (via server call and ProcessCasEncryptionResponse()).
|
||||
EXPECT_EQ(
|
||||
error::INTERNAL,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateWithNoEntitlementTwoKeysFail) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1;
|
||||
EntitledKeyInfo key2;
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string ecm;
|
||||
// This will fail because the entitlement keys have not been acquired
|
||||
// (via server call and ProcessCasEncryptionResponse()).
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, RequestNullFail) {
|
||||
Ecm ecm_gen;
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(content_id_, provider_, params_simple_, nullptr)
|
||||
.error_code());
|
||||
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateOneKeyOK) {
|
||||
Ecm ecm_gen;
|
||||
InitParams(¶ms_one_key_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
|
||||
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, BadResponseFail) {
|
||||
TEST_F(EcmTest, GenerateWithBadTrackType) {
|
||||
Ecm ecm_gen;
|
||||
InitParams(¶ms_two_keys_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}));
|
||||
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, false, true);
|
||||
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
std::string ecm;
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.ProcessCasEncryptionResponse(response).error_code());
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeHD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateTwoKeysOK) {
|
||||
Ecm ecm_gen;
|
||||
InitParams(¶ms_two_keys_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}));
|
||||
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
}
|
||||
@@ -398,15 +306,8 @@ TEST_F(EcmTest, GenerateTwoKeysOK) {
|
||||
TEST_F(EcmTest, GenerateOneKeyBadKeyIdFail) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1 = invalid1_key_id_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
InitParams(¶ms_one_key_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
|
||||
|
||||
std::string ecm;
|
||||
EXPECT_EQ(
|
||||
@@ -417,15 +318,8 @@ TEST_F(EcmTest, GenerateOneKeyBadKeyIdFail) {
|
||||
TEST_F(EcmTest, GenerateOneKeyWrong) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
InitParams(¶ms_one_key_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
|
||||
|
||||
std::string ecm;
|
||||
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm));
|
||||
@@ -439,16 +333,10 @@ TEST_F(EcmTest, GenerateTwoKeysIvSizeFail) {
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
// IV size mismatch.
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD, kIvSize16);
|
||||
InitParams(¶ms_two_keys_, kIvSize16);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}));
|
||||
std::string ecm;
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
|
||||
@@ -458,15 +346,9 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x8OK) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
InitParams(¶ms_two_keys_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}));
|
||||
|
||||
std::string ecm;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
@@ -479,16 +361,9 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x16OK) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid3_iv_16_16_;
|
||||
EntitledKeyInfo key2 = valid4_iv_16_16_;
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD, kIvSize16);
|
||||
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
InitParams(¶ms_two_keys_, kIvSize16);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}));
|
||||
|
||||
std::string ecm;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
@@ -497,26 +372,6 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x16OK) {
|
||||
EXPECT_THAT(165, ecm.size());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateThreeKeysIvSize16x16Fail) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid3_iv_16_16_;
|
||||
EntitledKeyInfo key2 = valid4_iv_16_16_;
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD, kIvSize16);
|
||||
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, false);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
// TODO(user): Add more unit tests for error paths around SerializeEcm.
|
||||
TEST_F(EcmSerializeEcmTest, SerializeEcmDoubleKey16ByteIvs) {
|
||||
MockEcm ecm_gen;
|
||||
@@ -615,5 +470,30 @@ TEST_F(EcmSerializeEcmTest, SerializeEcmAgeRestriction) {
|
||||
kIvSize16);
|
||||
}
|
||||
|
||||
class EcmTsPacketTest : public ::testing::Test {};
|
||||
|
||||
TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) {
|
||||
std::string ecm(kEcmPayload);
|
||||
ProgramId pid = 0x1FFD;
|
||||
ContinuityCounter cc = 0;
|
||||
uint8_t packet[188];
|
||||
EXPECT_OK(Ecm::GenerateTsPacket(ecm, pid, kTsPacketTableId80, &cc, packet));
|
||||
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, packet, sizeof(packet)));
|
||||
EXPECT_EQ(1, cc);
|
||||
}
|
||||
|
||||
TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId81) {
|
||||
std::string ecm(kEcmPayload);
|
||||
ProgramId pid = 0x1FFD;
|
||||
ContinuityCounter cc = 0;
|
||||
uint8_t packet[188];
|
||||
EXPECT_OK(Ecm::GenerateTsPacket(ecm, pid, kTsPacketTableId81, &cc, packet));
|
||||
char expected_ecm[188];
|
||||
memcpy(expected_ecm, kExpectedEcmPacket, 188);
|
||||
expected_ecm[5] = '\x81';
|
||||
EXPECT_EQ(0, memcmp(expected_ecm, packet, sizeof(packet)));
|
||||
EXPECT_EQ(1, cc);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -16,13 +16,10 @@
|
||||
#include "common/crypto_util.h"
|
||||
#include "common/random_util.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_util.h"
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
|
||||
|
||||
// CA System ID for Widevine.
|
||||
static constexpr uint16_t kWidevineSystemId = 0x4AD4;
|
||||
@@ -248,6 +245,21 @@ Status HandleParameters(const char* const request, size_t request_length,
|
||||
}
|
||||
offset += param_length;
|
||||
break;
|
||||
case ERROR_STATUS:
|
||||
if (param_length != ERROR_STATUS_SIZE) {
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
params->error_status.emplace_back();
|
||||
BigEndianToHost16(¶ms->error_status.back(), request + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
case ERROR_INFORMATION:
|
||||
params->error_information.push_back(
|
||||
std::string(request + offset, param_length));
|
||||
offset += param_length;
|
||||
break;
|
||||
default:
|
||||
status = ProcessPrivateParameters(request, param_type, param_length,
|
||||
&offset, params);
|
||||
@@ -453,39 +465,37 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
return;
|
||||
}
|
||||
switch (request_type) {
|
||||
case ECMG_CHANNEL_SETUP: {
|
||||
case ECMG_CHANNEL_SETUP:
|
||||
HandleChannelSetup(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_CHANNEL_TEST: {
|
||||
case ECMG_CHANNEL_TEST:
|
||||
HandleChannelTest(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_CHANNEL_CLOSE: {
|
||||
case ECMG_CHANNEL_CLOSE:
|
||||
HandleChannelClose(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_STREAM_SETUP: {
|
||||
case ECMG_STREAM_SETUP:
|
||||
HandleStreamSetup(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_STREAM_TEST: {
|
||||
case ECMG_STREAM_TEST:
|
||||
HandleStreamTest(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_STREAM_CLOSE_REQUEST: {
|
||||
case ECMG_STREAM_CLOSE_REQUEST:
|
||||
HandleStreamCloseRequest(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
case ECMG_CW_PROVISION: {
|
||||
case ECMG_CW_PROVISION:
|
||||
HandleCwProvision(params, response, response_length);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
case ECMG_CHANNEL_ERROR:
|
||||
HandleChannelError(params, response, response_length);
|
||||
break;
|
||||
case ECMG_STREAM_ERROR:
|
||||
HandleStreamError(params, response, response_length);
|
||||
break;
|
||||
default:
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_MESSAGE_TYPE_VALUE, "",
|
||||
response, response_length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,6 +564,26 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
|
||||
*response_length = 0;
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleChannelError(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) const {
|
||||
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
|
||||
response, response_length);
|
||||
return;
|
||||
}
|
||||
LOG(ERROR) << "Channel error received for channel id "
|
||||
<< params.ecm_channel_id;
|
||||
for (uint16_t error_code : params.error_status) {
|
||||
LOG(ERROR) << "Error status received: " << error_code;
|
||||
}
|
||||
for (const auto& error_info : params.error_information) {
|
||||
LOG(ERROR) << "Error info received: " << error_info;
|
||||
}
|
||||
|
||||
*response_length = 0;
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) {
|
||||
@@ -634,6 +664,33 @@ void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params,
|
||||
response, response_length);
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleStreamError(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) const {
|
||||
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
if (!streams_info_.contains(params.ecm_stream_id)) {
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
UNKNOWN_ECM_STREAM_ID_VALUE, "", response,
|
||||
response_length);
|
||||
return;
|
||||
}
|
||||
LOG(ERROR) << "Stream error received for channel id " << params.ecm_channel_id
|
||||
<< ", stream id " << params.ecm_stream_id;
|
||||
for (uint16_t error_code : params.error_status) {
|
||||
LOG(ERROR) << "Error status received: " << error_code;
|
||||
}
|
||||
for (const auto& error_info : params.error_information) {
|
||||
LOG(ERROR) << "Error info received: " << error_info;
|
||||
}
|
||||
|
||||
*response_length = 0;
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) {
|
||||
@@ -794,6 +851,15 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
|
||||
if (track_types_.empty()) {
|
||||
return {error::NOT_FOUND, "Track type not specified."};
|
||||
}
|
||||
if (entitlement_comb_.empty()) {
|
||||
return {error::NOT_FOUND, "Entitlement key id comb not specified."};
|
||||
}
|
||||
if (entitlement_comb_.size() !=
|
||||
track_types_.size() * ecmg_config_->number_of_content_keys) {
|
||||
return {error::NOT_FOUND,
|
||||
"Number of injected entitlement keys must equal to number of "
|
||||
"track types * number of content keys per ecm."};
|
||||
}
|
||||
|
||||
bool key_rotation = ecmg_config_->number_of_content_keys > 1;
|
||||
EcmInitParameters ecm_init_params;
|
||||
@@ -808,60 +874,20 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
|
||||
(!content_ivs_.empty() && content_ivs_[0].size() == 16)) {
|
||||
ecm_init_params.content_iv_size = kIvSize16;
|
||||
}
|
||||
ecm_init_params.track_types.assign(track_types_.begin(), track_types_.end());
|
||||
|
||||
std::vector<EntitlementKeyInfo> entitlements;
|
||||
entitlements.reserve(entitlement_comb_.size());
|
||||
for (size_t i = 0; i < entitlement_comb_.size(); i++) {
|
||||
entitlements.emplace_back();
|
||||
EntitlementKeyInfo* entitlement = &entitlements.back();
|
||||
entitlement->track_type = track_types_.at(key_rotation ? i / 2 : i);
|
||||
entitlement->is_even_key = key_rotation ? i % 2 == 0 : true;
|
||||
entitlement->key_id = entitlement_comb_[i].key_id;
|
||||
entitlement->key_value = entitlement_comb_[i].key_value;
|
||||
}
|
||||
|
||||
ecm_ = absl::make_unique<Ecm>();
|
||||
if (!entitlement_comb_.empty()) {
|
||||
// Using injected entitlement keys.
|
||||
if (entitlement_comb_.size() !=
|
||||
track_types_.size() * ecmg_config_->number_of_content_keys) {
|
||||
return {error::NOT_FOUND,
|
||||
"Number of injected entitlement keys must equal to number of "
|
||||
"track types * number of content keys per ecm."};
|
||||
}
|
||||
std::vector<InjectedEntitlementKeyInfo> entitlements;
|
||||
entitlements.reserve(entitlement_comb_.size());
|
||||
for (size_t i = 0; i < entitlement_comb_.size(); i++) {
|
||||
entitlements.emplace_back();
|
||||
InjectedEntitlementKeyInfo* entitlement = &entitlements.back();
|
||||
entitlement->track_type = track_types_.at(key_rotation ? i / 2 : i);
|
||||
entitlement->is_even_key = key_rotation ? i % 2 == 0 : true;
|
||||
entitlement->key_id = entitlement_comb_[i].key_id;
|
||||
entitlement->key_value = entitlement_comb_[i].key_value;
|
||||
}
|
||||
|
||||
return ecm_->Initialize(ecm_init_params, entitlements);
|
||||
}
|
||||
|
||||
// No injected enetitlement keys. Fetching entitlement keys from server.
|
||||
if (content_id_.empty()) {
|
||||
return {error::NOT_FOUND, "Content id not specified."};
|
||||
}
|
||||
if (content_provider_.empty()) {
|
||||
return {error::NOT_FOUND, "Content provider not specified."};
|
||||
}
|
||||
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
Status status = ecm_->Initialize(content_id_, content_provider_,
|
||||
ecm_init_params, &entitlement_request);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (key_fetcher_ == nullptr) {
|
||||
if (ecmg_config_->use_fixed_fetcher) {
|
||||
key_fetcher_ = absl::make_unique<FixedKeyFetcher>();
|
||||
} else {
|
||||
key_fetcher_ = absl::make_unique<WvCasKeyFetcher>();
|
||||
}
|
||||
}
|
||||
status = key_fetcher_->RequestEntitlementKey(entitlement_request,
|
||||
&entitlement_response);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
return ecm_->ProcessCasEncryptionResponse(entitlement_response);
|
||||
return ecm_->Initialize(ecm_init_params, entitlements);
|
||||
}
|
||||
|
||||
Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
@@ -905,12 +931,12 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
// the PID field in TS packet header.
|
||||
uint16_t pid = 0;
|
||||
uint8_t continuity_counter = params.cp_number & 0xff;
|
||||
WvCasEcm wv_cas_ecm;
|
||||
WvCasStatus cas_status = wv_cas_ecm.GenerateTsPacket(
|
||||
status = Ecm::GenerateTsPacket(
|
||||
serialized_ecm, pid,
|
||||
params.cp_number % 2 == 0 ? kTsPacketTableId80 : kTsPacketTableId81,
|
||||
&continuity_counter, ecm_datagram);
|
||||
if (cas_status != OK) {
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << status;
|
||||
return {error::INTERNAL, "GenerateTsPacket failed."};
|
||||
}
|
||||
return OkStatus();
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "absl/container/node_hash_map.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/internal/key_fetcher.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
namespace widevine {
|
||||
@@ -33,7 +32,6 @@ struct EcmgConfig {
|
||||
uint8_t access_criteria_transfer_mode;
|
||||
uint8_t number_of_content_keys;
|
||||
CryptoMode crypto_mode;
|
||||
bool use_fixed_fetcher = false;
|
||||
};
|
||||
|
||||
// A struct that represent a CP_CW_Combination.
|
||||
@@ -58,6 +56,8 @@ struct EcmgParameters {
|
||||
uint16_t ecm_id;
|
||||
uint16_t nominal_cp_duration;
|
||||
uint32_t super_cas_id;
|
||||
std::vector<uint16_t> error_status;
|
||||
std::vector<std::string> error_information;
|
||||
|
||||
// User defined paremeters below.
|
||||
uint8_t age_restriction = 0xff; // Assume 0xff (255) is an invalid value.
|
||||
@@ -103,12 +103,16 @@ class EcmgClientHandler {
|
||||
size_t* response_length) const;
|
||||
void HandleChannelClose(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
void HandleChannelError(const EcmgParameters& params, char* response,
|
||||
size_t* response_length) const;
|
||||
void HandleStreamSetup(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
void HandleStreamTest(const EcmgParameters& params, char* response,
|
||||
size_t* response_length) const;
|
||||
void HandleStreamCloseRequest(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
void HandleStreamError(const EcmgParameters& params, char* response,
|
||||
size_t* response_length) const;
|
||||
void HandleCwProvision(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
|
||||
@@ -141,7 +145,6 @@ class EcmgClientHandler {
|
||||
|
||||
// Map from ECM_stream_id to EcmgStreamInfo.
|
||||
absl::node_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_;
|
||||
std::unique_ptr<KeyFetcher> key_fetcher_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -50,8 +50,6 @@ static constexpr size_t kAgeRestriction = 3;
|
||||
static constexpr char kCryptoMode[] = "AesScte";
|
||||
static constexpr char kTrackTypesSD[] = "SD";
|
||||
static constexpr char kTrackTypesHD[] = "HD";
|
||||
static constexpr char kContentId[] = "CasTsFake";
|
||||
static constexpr char kContentProvider[] = "widevine_test";
|
||||
|
||||
class EcmgClientHandlerTest : public ::testing::Test {
|
||||
protected:
|
||||
@@ -62,7 +60,6 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
config_.max_comp_time = 100;
|
||||
config_.access_criteria_transfer_mode = 1;
|
||||
config_.number_of_content_keys = 2;
|
||||
config_.use_fixed_fetcher = true;
|
||||
handler_ = absl::make_unique<EcmgClientHandler>(&config_);
|
||||
}
|
||||
|
||||
@@ -97,8 +94,6 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
uint8_t age_restriction,
|
||||
const std::string& crypto_mode,
|
||||
const std::vector<std::string>& track_types,
|
||||
const std::string& content_id,
|
||||
const std::string& content_provider,
|
||||
const std::vector<std::string>& entitlements,
|
||||
char* message, size_t* message_length) {
|
||||
EXPECT_TRUE(message != nullptr);
|
||||
@@ -122,15 +117,6 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
track_type.size(), message, message_length);
|
||||
}
|
||||
}
|
||||
if (!content_id.empty()) {
|
||||
AddParam(CONTENT_ID, reinterpret_cast<const uint8_t*>(content_id.c_str()),
|
||||
content_id.size(), message, message_length);
|
||||
}
|
||||
if (!content_provider.empty()) {
|
||||
AddParam(CONTENT_PROVIDER,
|
||||
reinterpret_cast<const uint8_t*>(content_provider.c_str()),
|
||||
content_provider.size(), message, message_length);
|
||||
}
|
||||
if (!entitlements.empty()) {
|
||||
for (const auto& entitlement : entitlements) {
|
||||
AddParam(ENTITLEMENT_ID_KEY_COMBINATION,
|
||||
@@ -306,36 +292,10 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceWithPrivateParameters) {
|
||||
EXPECT_EQ(0, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessSequenceGeneratedRequests) {
|
||||
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
|
||||
kCryptoMode, {kTrackTypesHD, kTrackTypesSD},
|
||||
kContentId, kContentProvider, /*entitlements*/ {},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
kTrackTypesSD, {kContentKeyEven, kContentKeyEven},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
|
||||
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27));
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
|
||||
BuildChannelSetupRequest(
|
||||
kChannelId, kSuperCasId, kAgeRestriction, kCryptoMode,
|
||||
{kTrackTypesHD, kTrackTypesSD},
|
||||
/*ContentId*/ "", /*ContentProvider*/ "",
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd),
|
||||
absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
@@ -361,6 +321,18 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27));
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessChannelError) {
|
||||
SetupValidChannel();
|
||||
handler_->HandleRequest(kTestEcmgChannelError, response_, &response_len_);
|
||||
EXPECT_EQ(0, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessStreamError) {
|
||||
SetupValidChannelStream();
|
||||
handler_->HandleRequest(kTestEcmgStreamError, response_, &response_len_);
|
||||
EXPECT_EQ(0, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongSequenceInvalidChannel) {
|
||||
// ChannelTest without a valid channel (error).
|
||||
handler_->HandleRequest(kTestEcmgChannelTest, response_, &response_len_);
|
||||
@@ -438,10 +410,12 @@ TEST_F(EcmgClientHandlerTest, WrongParameterInsufficientKey) {
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongParameterSuperCasId) {
|
||||
// Setup a channel with an unexpected super cas id (expecting kSuperCasId).
|
||||
BuildChannelSetupRequest(kChannelId, 0, kAgeRestriction, kCryptoMode,
|
||||
{kTrackTypesHD, kTrackTypesSD}, kContentId,
|
||||
kContentProvider, /*entitlements*/ {}, request_,
|
||||
&request_len_);
|
||||
BuildChannelSetupRequest(
|
||||
kChannelId, 0, kAgeRestriction, kCryptoMode,
|
||||
{kTrackTypesHD, kTrackTypesSD},
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckChannelError(UNKNOWN_SUPER_CAS_ID_VALUE, response_, response_len_);
|
||||
}
|
||||
@@ -457,10 +431,12 @@ TEST_F(EcmgClientHandlerTest, WrongParameterChannelId) {
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongParameterCryptoMode) {
|
||||
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
|
||||
"someCryptoMode", {kTrackTypesHD, kTrackTypesSD},
|
||||
kContentId, kContentProvider, /*entitlements*/ {},
|
||||
request_, &request_len_);
|
||||
BuildChannelSetupRequest(
|
||||
kChannelId, kSuperCasId, kAgeRestriction, "someCryptoMode",
|
||||
{kTrackTypesHD, kTrackTypesSD},
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckChannelError(INVALID_VALUE_FOR_DVB_PARAMETER, response_, response_len_);
|
||||
}
|
||||
@@ -473,10 +449,9 @@ TEST_F(EcmgClientHandlerTest, WrongParameterLengthSuperCasId) {
|
||||
response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, NoEntitlementsNoContentIdProvider) {
|
||||
TEST_F(EcmgClientHandlerTest, NoInjectedEntitlements) {
|
||||
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
|
||||
kCryptoMode, {kTrackTypesHD, kTrackTypesSD},
|
||||
/*ContentId*/ "", /*ContentProvider*/ "",
|
||||
kCryptoMode, {kTrackTypesSD},
|
||||
/*entitlements*/ {}, request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
|
||||
@@ -501,7 +476,6 @@ TEST_F(EcmgClientHandlerTest, NotEnoughInjectedEntitlements) {
|
||||
BuildChannelSetupRequest(
|
||||
kChannelId, kSuperCasId, kAgeRestriction, kCryptoMode,
|
||||
{kTrackTypesHD, kTrackTypesSD},
|
||||
/*ContentId*/ "", /*ContentProvider*/ "",
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
request_, &request_len_);
|
||||
@@ -524,6 +498,32 @@ TEST_F(EcmgClientHandlerTest, NotEnoughInjectedEntitlements) {
|
||||
CheckStreamError(MISSING_MANDATORY_DVB_PARAMETER, response_, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, TooManyInjectedEntitlements) {
|
||||
BuildChannelSetupRequest(
|
||||
kChannelId, kSuperCasId, kAgeRestriction, kCryptoMode, {kTrackTypesSD},
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd),
|
||||
absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
kTrackTypesSD, {kContentKeyEven, kContentKeyEven},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
|
||||
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckStreamError(MISSING_MANDATORY_DVB_PARAMETER, response_, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongMessageLength) {
|
||||
// Setup a channel with a wrong message length specified (too large).
|
||||
handler_->HandleRequest(kTestEcmgChannelSetupWrongMessageLength, response_,
|
||||
|
||||
@@ -105,5 +105,6 @@
|
||||
#define CP_DURATION_SIZE (2)
|
||||
#define CP_SIZE (2)
|
||||
#define AGE_RESTRICTION_SIZE (1)
|
||||
#define ERROR_STATUS_SIZE (2)
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CONSTANTS_H_
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "media_cas_packager_sdk/internal/emmg_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
|
||||
@@ -24,6 +26,9 @@
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
#include "protos/public/media_cas.pb.h"
|
||||
|
||||
// Minimum sending interval in milliseconds.
|
||||
static constexpr uint16_t KMinSendIntervalMs = 2;
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
@@ -61,7 +66,7 @@ std::string MessageTypeToString(uint16_t message_type) {
|
||||
case EMMG_STREAM_BW_REQUEST:
|
||||
return "EMMG_STREAM_BW_REQUEST";
|
||||
case EMMG_STREAM_BW_ALLOCATION:
|
||||
return "";
|
||||
return "EMMG_STREAM_BW_ALLOCATION";
|
||||
case EMMG_DATA_PROVISION:
|
||||
return "EMMG_DATA_PROVISION";
|
||||
default:
|
||||
@@ -69,6 +74,68 @@ std::string MessageTypeToString(uint16_t message_type) {
|
||||
}
|
||||
}
|
||||
|
||||
Status HandleParameters(const char* const response, size_t response_length,
|
||||
EmmgParameters* params) {
|
||||
DCHECK(response);
|
||||
DCHECK(params);
|
||||
Status status;
|
||||
uint16_t param_type;
|
||||
uint16_t param_length;
|
||||
// 'offset' is used to track where we are within |request|.
|
||||
size_t offset = 0;
|
||||
while (offset < response_length) {
|
||||
BigEndianToHost16(¶m_type, response + offset);
|
||||
offset += PARAMETER_TYPE_SIZE;
|
||||
BigEndianToHost16(¶m_length, response + offset);
|
||||
offset += PARAMETER_LENGTH_SIZE;
|
||||
switch (param_type) {
|
||||
case EMMG_CLIENT_ID:
|
||||
if (param_length != EMMG_CLIENT_ID_SIZE) {
|
||||
return Status(error::FAILED_PRECONDITION,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
BigEndianToHost32(¶ms->client_id, response + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
case EMMG_DATA_CHANNEL_ID:
|
||||
if (param_length != EMMG_DATA_CHANNEL_ID_SIZE) {
|
||||
return Status(error::FAILED_PRECONDITION,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
BigEndianToHost16(¶ms->data_channel_id, response + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
case EMMG_DATA_STREAM_ID:
|
||||
if (param_length != EMMG_DATA_STREAM_ID_SIZE) {
|
||||
return Status(error::FAILED_PRECONDITION,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
BigEndianToHost16(¶ms->data_stream_id, response + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
case EMMG_BANDWIDTH:
|
||||
if (param_length != EMMG_BANDWIDTH_SIZE) {
|
||||
return Status(error::FAILED_PRECONDITION,
|
||||
absl::StrCat("Invalid parameter length ", param_length,
|
||||
" for parameter type ", param_type));
|
||||
}
|
||||
BigEndianToHost16(¶ms->bandwidth, response + offset);
|
||||
offset += param_length;
|
||||
break;
|
||||
default:
|
||||
return Status(
|
||||
error::UNIMPLEMENTED,
|
||||
absl::StrCat("No implementation yet to process parameter of type ",
|
||||
param_type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Emmg::Emmg(EmmgConfig* emmg_config, int server_socket_fd) {
|
||||
@@ -80,9 +147,16 @@ Emmg::Emmg(EmmgConfig* emmg_config, int server_socket_fd) {
|
||||
void Emmg::Start() {
|
||||
SendChannelSetup();
|
||||
SendStreamSetup();
|
||||
// TODO(user): Send stream_BW_request message.
|
||||
// TODO(user): Send stream_BW_allocation message.
|
||||
SendDataProvision();
|
||||
|
||||
UpdateSendInterval(emmg_config_->bandwidth);
|
||||
SendStreamBwRequest();
|
||||
|
||||
for (size_t i = 0; i < emmg_config_->max_num_message; i++) {
|
||||
SendDataProvision();
|
||||
absl::SleepFor(
|
||||
absl::Milliseconds(std::max(KMinSendIntervalMs, send_interval_ms_)));
|
||||
}
|
||||
|
||||
SendStreamCloseRequest();
|
||||
SendChannelClose();
|
||||
}
|
||||
@@ -126,6 +200,26 @@ void Emmg::BuildStreamSetup() {
|
||||
Host16ToBigEndian(request_ + 3, &total_param_length);
|
||||
}
|
||||
|
||||
void Emmg::BuildStreamBwRequest() {
|
||||
bzero(request_, BUFFER_SIZE);
|
||||
request_length_ = 0;
|
||||
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION,
|
||||
EMMG_STREAM_BW_REQUEST, request_,
|
||||
&request_length_);
|
||||
simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id,
|
||||
request_, &request_length_);
|
||||
simulcrypt_util::AddUint16Param(EMMG_DATA_CHANNEL_ID,
|
||||
emmg_config_->data_channel_id, request_,
|
||||
&request_length_);
|
||||
simulcrypt_util::AddUint16Param(EMMG_DATA_STREAM_ID,
|
||||
emmg_config_->data_stream_id, request_,
|
||||
&request_length_);
|
||||
simulcrypt_util::AddUint16Param(EMMG_BANDWIDTH, emmg_config_->bandwidth,
|
||||
request_, &request_length_);
|
||||
uint16_t total_param_length = request_length_ - 5;
|
||||
Host16ToBigEndian(request_ + 3, &total_param_length);
|
||||
}
|
||||
|
||||
Status Emmg::GeneratePrivateData(const std::string& content_provider,
|
||||
const std::string& content_id, uint8_t* buffer) {
|
||||
DCHECK(buffer);
|
||||
@@ -228,6 +322,13 @@ void Emmg::SendStreamSetup() {
|
||||
ReceiveResponseAndVerify(EMMG_STREAM_STATUS);
|
||||
}
|
||||
|
||||
void Emmg::SendStreamBwRequest() {
|
||||
BuildStreamBwRequest();
|
||||
Send(EMMG_STREAM_BW_REQUEST);
|
||||
ReceiveResponseAndVerify(EMMG_STREAM_BW_ALLOCATION);
|
||||
HandleResponse(response_);
|
||||
}
|
||||
|
||||
void Emmg::SendDataProvision() {
|
||||
BuildDataProvision();
|
||||
Send(EMMG_DATA_PROVISION);
|
||||
@@ -246,6 +347,52 @@ void Emmg::SendChannelClose() {
|
||||
// No response for Channel_close message.
|
||||
}
|
||||
|
||||
void Emmg::HandleResponse(const char* const response) {
|
||||
DCHECK(response);
|
||||
uint8_t protocol_version;
|
||||
uint16_t response_type;
|
||||
uint16_t response_length;
|
||||
// 'offset' is used to track where we are within |request|.
|
||||
size_t offset = 0;
|
||||
memcpy(&protocol_version, response, PROTOCOL_VERSION_SIZE);
|
||||
if (protocol_version != EMMG_MUX_PROTOCOL_VERSION) {
|
||||
LOG(ERROR) << "Unexpected protocol version.";
|
||||
return;
|
||||
}
|
||||
offset += PROTOCOL_VERSION_SIZE;
|
||||
BigEndianToHost16(&response_type, response + offset);
|
||||
offset += MESSAGE_TYPE_SIZE;
|
||||
BigEndianToHost16(&response_length, response + offset);
|
||||
offset += MESSAGE_LENGTH_SIZE;
|
||||
EmmgParameters params;
|
||||
Status status = HandleParameters(response + offset, response_length, ¶ms);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << status.ToString();
|
||||
return;
|
||||
}
|
||||
switch (response_type) {
|
||||
case EMMG_STREAM_BW_ALLOCATION:
|
||||
HandleStreamBwAllocation(params);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Emmg::UpdateSendInterval(uint16_t bandwidth_kbps) {
|
||||
if (bandwidth_kbps == 0) {
|
||||
return;
|
||||
}
|
||||
send_interval_ms_ =
|
||||
std::max(KMinSendIntervalMs,
|
||||
static_cast<uint16_t>((kTsPacketSize * 8) / bandwidth_kbps));
|
||||
LOG(INFO) << "Send interval set to " << send_interval_ms_ << " ms.";
|
||||
}
|
||||
|
||||
void Emmg::HandleStreamBwAllocation(const EmmgParameters& params) {
|
||||
UpdateSendInterval(params.bandwidth);
|
||||
}
|
||||
|
||||
void Emmg::ReceiveResponseAndVerify(uint16_t expected_type) {
|
||||
// Read response from socket.
|
||||
bzero(response_, BUFFER_SIZE);
|
||||
|
||||
@@ -30,6 +30,16 @@ struct EmmgConfig {
|
||||
uint8_t data_type;
|
||||
std::string content_provider;
|
||||
std::string content_id;
|
||||
uint16_t bandwidth;
|
||||
uint32_t max_num_message;
|
||||
};
|
||||
|
||||
// A struct that is used to hold all possible params for a EMMG request.
|
||||
struct EmmgParameters {
|
||||
uint32_t client_id;
|
||||
uint16_t data_channel_id;
|
||||
uint16_t data_stream_id;
|
||||
uint16_t bandwidth;
|
||||
};
|
||||
|
||||
// A class that sends EMMG/PDG message to the MUX server.
|
||||
@@ -46,20 +56,32 @@ class Emmg {
|
||||
// Protected visibility for unit testing.
|
||||
void BuildChannelSetup();
|
||||
void BuildStreamSetup();
|
||||
void BuildStreamBwRequest();
|
||||
void BuildDataProvision();
|
||||
void BuildStreamCloseRequest();
|
||||
void BuildChannelClose();
|
||||
|
||||
void HandleResponse(const char* const response);
|
||||
|
||||
size_t request_length_ = 0;
|
||||
char request_[BUFFER_SIZE];
|
||||
|
||||
// Sending interval in milliseconds. Stream specific parameter but currently
|
||||
// only one stream will be setup.
|
||||
uint16_t send_interval_ms_ = 0;
|
||||
|
||||
private:
|
||||
void SendChannelSetup();
|
||||
void SendStreamSetup();
|
||||
void SendStreamBwRequest();
|
||||
void SendDataProvision();
|
||||
void SendStreamCloseRequest();
|
||||
void SendChannelClose();
|
||||
|
||||
void HandleStreamBwAllocation(const EmmgParameters& params);
|
||||
|
||||
void UpdateSendInterval(uint16_t bandwidth_kbps);
|
||||
|
||||
Status GeneratePrivateData(const std::string& content_provider,
|
||||
const std::string& content_id, uint8_t* buffer);
|
||||
void ReceiveResponseAndVerify(uint16_t expected_type);
|
||||
|
||||
@@ -65,4 +65,10 @@
|
||||
#define UNKNOWN_ERROR (0x7000)
|
||||
#define UNRECOVERABLE_ERROR (0x7001)
|
||||
|
||||
// Size (in # of bytes) of various fields.
|
||||
#define EMMG_CLIENT_ID_SIZE (4)
|
||||
#define EMMG_DATA_CHANNEL_ID_SIZE (2)
|
||||
#define EMMG_DATA_STREAM_ID_SIZE (2)
|
||||
#define EMMG_BANDWIDTH_SIZE (2)
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_CONSTANTS_H_
|
||||
|
||||
@@ -29,10 +29,15 @@ class TestableEmmg : public Emmg {
|
||||
: Emmg(emmg_config, /* server_socket_fd= */ 1) {}
|
||||
void PublicBuildChannelSetup() { BuildChannelSetup(); }
|
||||
void PublicBuildStreamSetup() { BuildStreamSetup(); }
|
||||
void PublicBuildStreamBwRequest() { BuildStreamBwRequest(); }
|
||||
void PublicBuildDataProvision() { BuildDataProvision(); }
|
||||
void PublicBuildStreamCloseRequest() { BuildStreamCloseRequest(); }
|
||||
void PublicBuildChannelClose() { BuildChannelClose(); }
|
||||
void PublicHandleResponse(const char* const response) {
|
||||
HandleResponse(response);
|
||||
}
|
||||
char* GetRequest() { return request_; }
|
||||
uint16_t GetSendInterval() { return send_interval_ms_; }
|
||||
};
|
||||
|
||||
class EmmgTest : public ::testing::Test {
|
||||
@@ -46,6 +51,8 @@ class EmmgTest : public ::testing::Test {
|
||||
config_.data_type = 0x01;
|
||||
config_.content_provider = "widevine_test";
|
||||
config_.content_id = "CasTsFake";
|
||||
config_.bandwidth = 100;
|
||||
config_.max_num_message = 100;
|
||||
emmg_ = absl::make_unique<TestableEmmg>(&config_);
|
||||
}
|
||||
|
||||
@@ -79,6 +86,17 @@ TEST_F(EmmgTest, BuildStreamSetup) {
|
||||
sizeof(kTestEmmgStreamSetup)));
|
||||
}
|
||||
|
||||
TEST_F(EmmgTest, BuildStreamBwRequest) {
|
||||
emmg_->PublicBuildStreamBwRequest();
|
||||
EXPECT_EQ(0, memcmp(kTestEmmgStreamBwRequest, emmg_->GetRequest(),
|
||||
sizeof(kTestEmmgStreamBwRequest)));
|
||||
}
|
||||
|
||||
TEST_F(EmmgTest, HandleStreamBwAllocation) {
|
||||
emmg_->PublicHandleResponse(kTestEmmgStreamBwAllocation);
|
||||
EXPECT_EQ(30, emmg_->GetSendInterval());
|
||||
}
|
||||
|
||||
TEST_F(EmmgTest, BuildDataProvision) {
|
||||
emmg_->PublicBuildDataProvision();
|
||||
EXPECT_EQ(0, memcmp(kTestEmmgDataProvision, emmg_->GetRequest(),
|
||||
|
||||
@@ -8,16 +8,31 @@
|
||||
|
||||
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "google/protobuf/util/json_util.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
Status FixedKeyFetcher::RequestEntitlementKey(
|
||||
const std::string& request_string,
|
||||
std::string* signed_response_string) const {
|
||||
Status FixedKeyFetcher::MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const {
|
||||
Status status;
|
||||
SignedCasEncryptionRequest signed_request;
|
||||
if (!google::protobuf::util::JsonStringToMessage(signed_request_json, &signed_request)
|
||||
.ok()) {
|
||||
status = {error::INTERNAL,
|
||||
"Unable to convert to SignedCasEncryptionRequest."};
|
||||
LOG(ERROR) << status;
|
||||
return status;
|
||||
}
|
||||
|
||||
CasEncryptionRequest request;
|
||||
request.ParseFromString(request_string);
|
||||
if (!google::protobuf::util::JsonStringToMessage(signed_request.request(), &request)
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Unable to understand signed_request_json.";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
CasEncryptionResponse response;
|
||||
response.set_status(CasEncryptionResponse::OK);
|
||||
@@ -45,10 +60,20 @@ Status FixedKeyFetcher::RequestEntitlementKey(
|
||||
}
|
||||
}
|
||||
std::string response_string;
|
||||
response.SerializeToString(&response_string);
|
||||
if (!google::protobuf::util::MessageToJsonString(response, &response_string).ok()) {
|
||||
LOG(ERROR) << "MessageToJsonString(response, &response_string)";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response_string);
|
||||
signed_response.SerializeToString(signed_response_string);
|
||||
|
||||
if (!google::protobuf::util::MessageToJsonString(signed_response, http_response_json)
|
||||
.ok()) {
|
||||
LOG(ERROR) << "MessageToJsonString(signed_response, http_response_json)";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,20 +12,26 @@
|
||||
#include <string>
|
||||
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/key_fetcher.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
const char kSigningProvider[] = "widevine_test";
|
||||
const char kSingingKey[] =
|
||||
"1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9";
|
||||
const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249";
|
||||
|
||||
// Perform the same role as a WV CAS KeyFetcher, but return a
|
||||
// locally-constructed response that has known (predefined) entitlement keys.
|
||||
class FixedKeyFetcher : public KeyFetcher {
|
||||
class FixedKeyFetcher : public WvCasKeyFetcher {
|
||||
public:
|
||||
// Key IDs are 16 bytes, keys are 32 bytes.
|
||||
// TODO(user): There should be a single entitlement key for both even
|
||||
// and odd keys. Shouldn't have two different types of entitlement keys.
|
||||
FixedKeyFetcher()
|
||||
: even_entitlement_key_id_("fake_key_id1...."),
|
||||
: WvCasKeyFetcher(kSigningProvider, kSingingKey, kSingingIv),
|
||||
even_entitlement_key_id_("fake_key_id1...."),
|
||||
even_entitlement_key_("fakefakefakefakefakefakefake1..."),
|
||||
odd_entitlement_key_id_("fake_key_id2...."),
|
||||
odd_entitlement_key_("fakefakefakefakefakefakefake2...") {}
|
||||
@@ -35,7 +41,8 @@ class FixedKeyFetcher : public KeyFetcher {
|
||||
const std::string& even_entitlement_key,
|
||||
const std::string& odd_entitlement_key_id,
|
||||
const std::string& odd_entitlement_key)
|
||||
: even_entitlement_key_id_(even_entitlement_key_id),
|
||||
: WvCasKeyFetcher(kSigningProvider, kSingingKey, kSingingIv),
|
||||
even_entitlement_key_id_(even_entitlement_key_id),
|
||||
even_entitlement_key_(even_entitlement_key),
|
||||
odd_entitlement_key_id_(odd_entitlement_key_id),
|
||||
odd_entitlement_key_(odd_entitlement_key) {}
|
||||
@@ -43,18 +50,8 @@ class FixedKeyFetcher : public KeyFetcher {
|
||||
FixedKeyFetcher& operator=(const FixedKeyFetcher&) = delete;
|
||||
~FixedKeyFetcher() override = default;
|
||||
|
||||
// Get entitlement keys. Process a CasEncryptionRequest message to
|
||||
// determine the keys that are needed, generate a fixed set of keys,
|
||||
// and package them into a SignedCasEncryptionResponse message.
|
||||
// Args:
|
||||
// |request_string| a serialized CasEncryptionRequest message, produced
|
||||
// by WvCasEcm::Initialize().
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// WvCasEcm::ProcessCasEncryptionResponse().
|
||||
Status RequestEntitlementKey(
|
||||
const std::string& request_string,
|
||||
std::string* signed_response_string) const override;
|
||||
Status MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const override;
|
||||
|
||||
private:
|
||||
std::string even_entitlement_key_id_;
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_KEY_FETCHER_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_KEY_FETCHER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/status.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// Interface for fetching various types of keys.
|
||||
class KeyFetcher {
|
||||
public:
|
||||
KeyFetcher() = default;
|
||||
KeyFetcher(const KeyFetcher&) = delete;
|
||||
KeyFetcher& operator=(const KeyFetcher&) = delete;
|
||||
virtual ~KeyFetcher() = default;
|
||||
|
||||
// Get entitlement keys. Process a CasEncryptionRequest message to
|
||||
// determine the keys that are needed, generate a fixed set of keys,
|
||||
// and package them into a SignedCasEncryptionResponse message.
|
||||
// Args:
|
||||
// |request_string| a serialized CasEncryptionRequest message, produced
|
||||
// by WvCasEcm::Initialize().
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// WvCasEcm::ProcessCasEncryptionResponse().
|
||||
virtual Status RequestEntitlementKey(
|
||||
const std::string& request_string,
|
||||
std::string* signed_response_string) const = 0;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_KEY_FETCHER_H_
|
||||
@@ -22,7 +22,7 @@ filegroup(
|
||||
name = "binary_release_files",
|
||||
srcs = glob(["*.h"]) + [
|
||||
":wv_ecmg",
|
||||
"wv_cas_key_fetcher.cc",
|
||||
"wv_cas_curl_key_fetcher.cc",
|
||||
"wv_cas_types.cc",
|
||||
],
|
||||
)
|
||||
@@ -33,6 +33,7 @@ cc_binary(
|
||||
deps = [
|
||||
":wv_cas_ca_descriptor",
|
||||
":wv_cas_ecm",
|
||||
":wv_cas_key_fetcher",
|
||||
":wv_cas_types",
|
||||
],
|
||||
)
|
||||
@@ -46,7 +47,6 @@ cc_library(
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
"//media_cas_packager_sdk/internal:key_fetcher",
|
||||
"//protos/public:media_cas_cc_proto",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
@@ -89,16 +89,9 @@ cc_library(
|
||||
deps = [
|
||||
":wv_cas_types",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers", # buildcleaner: keep
|
||||
"@abseil_repo//absl/memory", # buildcleaner: keep
|
||||
"@abseil_repo//absl/strings", # buildcleaner: keep
|
||||
"//common:crypto_util",
|
||||
"@abseil_repo//absl/memory",
|
||||
"//common:status",
|
||||
"//example:constants",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
"//media_cas_packager_sdk/internal:fixed_key_fetcher",
|
||||
"//media_cas_packager_sdk/internal:mpeg2ts",
|
||||
"//media_cas_packager_sdk/internal:util",
|
||||
],
|
||||
# Make sure libmedia_cas_packager_sdk links in symbols defined in this
|
||||
# target.
|
||||
@@ -111,9 +104,13 @@ cc_test(
|
||||
srcs = ["wv_cas_ecm_test.cc"],
|
||||
deps = [
|
||||
":wv_cas_ecm",
|
||||
":wv_cas_types",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//media_cas_packager_sdk/internal:mpeg2ts",
|
||||
"//common:crypto_util",
|
||||
"//common:status",
|
||||
"//example:constants",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -126,14 +123,12 @@ cc_library(
|
||||
"wv_cas_key_fetcher.h",
|
||||
],
|
||||
deps = [
|
||||
":wv_cas_types",
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"@abseil_repo//absl/flags:flag",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@curl_repo//:curl",
|
||||
"//common:signature_util",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/internal:key_fetcher",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
@@ -149,13 +144,23 @@ cc_test(
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/flags:flag",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:status",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "wv_cas_curl_key_fetcher",
|
||||
srcs = ["wv_cas_curl_key_fetcher.cc"],
|
||||
hdrs = ["wv_cas_curl_key_fetcher.h"],
|
||||
deps = [
|
||||
":wv_cas_key_fetcher",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@curl_repo//:curl",
|
||||
"//common:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "wv_cas_types",
|
||||
srcs = ["wv_cas_types.cc"],
|
||||
@@ -164,6 +169,7 @@ cc_library(
|
||||
deps = [
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"//common:status",
|
||||
"//protos/public:media_cas_encryption_cc_proto",
|
||||
],
|
||||
# Make sure libmedia_cas_packager_sdk links in symbols defined in this
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/status.h"
|
||||
#include "common/string_util.h"
|
||||
#include "protos/public/media_cas.pb.h"
|
||||
|
||||
@@ -53,16 +52,15 @@ static constexpr uint32_t kMaxValidPID = 0x1FFE;
|
||||
|
||||
} // namespace
|
||||
|
||||
WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor(
|
||||
Status WvCasCaDescriptor::GenerateCaDescriptor(
|
||||
uint16_t ca_pid, const std::string& provider, const std::string& content_id,
|
||||
std::string* serialized_ca_desc) const {
|
||||
if (serialized_ca_desc == nullptr) {
|
||||
LOG(ERROR) << "Return CA descriptor std::string pointer is nullptr.";
|
||||
return INVALID_ARGUMENT;
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Return CA descriptor std::string pointer is nullptr."};
|
||||
}
|
||||
if (ca_pid < kMinValidPID || ca_pid > kMaxValidPID) {
|
||||
LOG(ERROR) << "PID value is out of the valid range.";
|
||||
return INVALID_ARGUMENT;
|
||||
return {error::INVALID_ARGUMENT, "PID value is out of the valid range."};
|
||||
}
|
||||
|
||||
std::string private_data = "";
|
||||
@@ -73,8 +71,7 @@ WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor(
|
||||
const size_t descriptor_length =
|
||||
kCaDescriptorBasePostLengthSize + private_data.size();
|
||||
if (0xFF < descriptor_length) {
|
||||
LOG(ERROR) << "Private data std::string is too large.";
|
||||
return INVALID_ARGUMENT;
|
||||
return {error::INVALID_ARGUMENT, "Private data std::string is too large."};
|
||||
}
|
||||
|
||||
std::bitset<kNumBitsCaDescriptorTagField> tag(kCaDescriptorTag);
|
||||
@@ -91,7 +88,7 @@ WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor(
|
||||
reserved.to_string(), pid.to_string());
|
||||
if (descriptor_bitset.size() != kCaDescriptorBaseSize * 8) {
|
||||
LOG(ERROR) << "Error creating CA descriptor.";
|
||||
return INTERNAL;
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
std::string descriptor;
|
||||
Status status =
|
||||
@@ -102,7 +99,7 @@ WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor(
|
||||
*serialized_ca_desc += private_data;
|
||||
}
|
||||
|
||||
return OK;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
size_t WvCasCaDescriptor::CaDescriptorBaseSize() const {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
#include "common/status.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
@@ -55,9 +55,10 @@ class WvCasCaDescriptor {
|
||||
// section (for an EMM stream) or into a TS Program Map Table section (for an
|
||||
// ECM stream). The descriptor will be 6 bytes plus any bytes added as
|
||||
// (user-defined) private data.
|
||||
virtual WvCasStatus GenerateCaDescriptor(
|
||||
uint16_t ca_pid, const std::string& provider, const std::string& content_id,
|
||||
std::string* serialized_ca_desc) const;
|
||||
virtual Status GenerateCaDescriptor(uint16_t ca_pid,
|
||||
const std::string& provider,
|
||||
const std::string& content_id,
|
||||
std::string* serialized_ca_desc) const;
|
||||
|
||||
// Return the base size (before private data is added) of the CA
|
||||
// descriptor. The user can call this to plan the layout of the Table section
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h"
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "protos/public/media_cas.pb.h"
|
||||
|
||||
@@ -37,118 +38,123 @@ TEST_F(WvCasCaDescriptorTest, BaseSize) {
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, BasicGoodGen) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x32", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, NoReturnStringFail) {
|
||||
EXPECT_EQ(INVALID_ARGUMENT,
|
||||
ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "", nullptr));
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "", nullptr)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidTooLowFail) {
|
||||
const uint32_t bad_pid = 0x10 - 1;
|
||||
EXPECT_EQ(INVALID_ARGUMENT, ca_descriptor_.GenerateCaDescriptor(
|
||||
bad_pid, "", "", &actual_ca_descriptor_));
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ca_descriptor_
|
||||
.GenerateCaDescriptor(bad_pid, "", "", &actual_ca_descriptor_)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidMinOK) {
|
||||
const uint32_t min_pid = 0x10;
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(min_pid, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(min_pid, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x10", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidMaxOK) {
|
||||
const uint32_t max_pid = 0x1FFE;
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(max_pid, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(max_pid, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\xfe");
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidTooHighFail) {
|
||||
const uint32_t bad_pid = 0x1FFF;
|
||||
EXPECT_EQ(INVALID_ARGUMENT, ca_descriptor_.GenerateCaDescriptor(
|
||||
bad_pid, "", "", &actual_ca_descriptor_));
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ca_descriptor_
|
||||
.GenerateCaDescriptor(bad_pid, "", "", &actual_ca_descriptor_)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidOneByte) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(255, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(
|
||||
ca_descriptor_.GenerateCaDescriptor(255, "", "", &actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\xff", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidSecondByte) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x1F00, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x1F00, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidTwelveBits) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0xFFF, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0xFFF, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xef\xff");
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x1000, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x1000, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xf0\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidTwelthBit) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x800, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x800, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe8\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidElevenththBit) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x400, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x400, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe4\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidTenthBit) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x200, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x200, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe2\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PidNinthBit) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x100, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x100, "", "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe1\x00", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PrivateDataOnlyProviderIgnored) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, "",
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, "",
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PrivateDataOnlyContentIdIgnored) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, "", kContentId,
|
||||
&actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, "", kContentId,
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
|
||||
TEST_F(WvCasCaDescriptorTest, PrivateData) {
|
||||
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, kContentId,
|
||||
&actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x19\x4a\xd4\xe0\x32", 6);
|
||||
CaDescriptorPrivateData private_data;
|
||||
private_data.set_provider(kProvider);
|
||||
@@ -176,8 +182,8 @@ class FakePrivateDataCaDescriptor : public WvCasCaDescriptor {
|
||||
TEST_F(WvCasCaDescriptorTest, PrivateDataOneByte) {
|
||||
FakePrivateDataCaDescriptor fake_descriptor;
|
||||
fake_descriptor.set_private_data("X");
|
||||
EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x05\x4a\xd4\xe0\x32X", 7);
|
||||
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
|
||||
}
|
||||
@@ -186,8 +192,8 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMultipleBytes) {
|
||||
const std::string private_data_bytes("X1234abcde");
|
||||
FakePrivateDataCaDescriptor fake_descriptor;
|
||||
fake_descriptor.set_private_data(private_data_bytes);
|
||||
EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\x0e\x4a\xd4\xe0\x32", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_);
|
||||
}
|
||||
@@ -196,8 +202,8 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMaxNumberBytes) {
|
||||
const std::string private_data_bytes(251, 'x');
|
||||
FakePrivateDataCaDescriptor fake_descriptor;
|
||||
fake_descriptor.set_private_data(private_data_bytes);
|
||||
EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
const std::string expected_ca_descriptor("\x09\xff\x4a\xd4\xe0\x32", 6);
|
||||
EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_);
|
||||
}
|
||||
@@ -206,9 +212,11 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataTooManyBytesFail) {
|
||||
const std::string private_data_bytes(252, 'X');
|
||||
FakePrivateDataCaDescriptor fake_descriptor;
|
||||
fake_descriptor.set_private_data(private_data_bytes);
|
||||
EXPECT_EQ(INVALID_ARGUMENT,
|
||||
fake_descriptor.GenerateCaDescriptor(
|
||||
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
fake_descriptor
|
||||
.GenerateCaDescriptor(kTestPid, kProvider, kContentId,
|
||||
&actual_ca_descriptor_)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
61
media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc
Normal file
61
media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc
Normal file
@@ -0,0 +1,61 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 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/public/wv_cas_curl_key_fetcher.h"
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "curl/curl.h"
|
||||
#include "curl/easy.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
size_t AppendToString(void* ptr, size_t size, size_t count,
|
||||
std::string* output) {
|
||||
const absl::string_view data(static_cast<char*>(ptr), size * count);
|
||||
absl::StrAppend(output, data);
|
||||
return data.size();
|
||||
}
|
||||
|
||||
Status WvCasCurlKeyFetcher::MakeHttpRequest(
|
||||
const std::string& signed_request_json,
|
||||
std::string* http_response_json) const {
|
||||
if (license_server_.empty()) {
|
||||
return Status(error::NOT_FOUND, "license_server is empty");
|
||||
}
|
||||
if (http_response_json == nullptr) {
|
||||
return Status(error::INVALID_ARGUMENT, "http_response_json is null");
|
||||
}
|
||||
|
||||
CURL* curl;
|
||||
CURLcode curl_code;
|
||||
curl = curl_easy_init();
|
||||
if (curl) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, license_server_.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, signed_request_json.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, http_response_json);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &AppendToString);
|
||||
// If we don't provide POSTFIELDSIZE, libcurl will strlen() by itself.
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
|
||||
(int64_t)strlen(signed_request_json.c_str()));
|
||||
curl_code = curl_easy_perform(curl);
|
||||
if (curl_code != CURLE_OK) {
|
||||
return Status(error::INTERNAL,
|
||||
"curl_easy_perform() failed: " +
|
||||
std::string(curl_easy_strerror(curl_code)));
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
} else {
|
||||
return Status(error::INTERNAL, "curl_easy_init() failed");
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
39
media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h
Normal file
39
media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h
Normal file
@@ -0,0 +1,39 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 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_PUBLIC_WV_CAS_CURL_KEY_FETCHER_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_CURL_KEY_FETCHER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
class WvCasCurlKeyFetcher : public WvCasKeyFetcher {
|
||||
public:
|
||||
WvCasCurlKeyFetcher(const std::string& license_server,
|
||||
const std::string& signing_provider,
|
||||
const std::string& signing_key,
|
||||
const std::string& signing_iv)
|
||||
: WvCasKeyFetcher(signing_provider, signing_key, signing_iv),
|
||||
license_server_(license_server) {}
|
||||
|
||||
Status MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const override;
|
||||
|
||||
private:
|
||||
std::string license_server_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_CURL_KEY_FETCHER_H_
|
||||
@@ -8,320 +8,85 @@
|
||||
|
||||
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "common/crypto_util.h"
|
||||
#include "common/status.h"
|
||||
#include "example/constants.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr size_t kContentKeySizeBytes = 16;
|
||||
static constexpr size_t kCsaContentKeySizeBytes = 8;
|
||||
static constexpr size_t kEntitlementKeyIdSizeBytes = 16;
|
||||
static constexpr size_t kEntitlementKeySizeBytes = 32;
|
||||
static constexpr size_t kEcmWith8BytesContentIvSizeBytes = 149;
|
||||
static constexpr size_t kEcmWith16BytesContentIvSizeBytes = 149 + (8 * 2);
|
||||
static constexpr size_t kSingleKeyEcmWith8BytesContentIvSizeBytes = 77;
|
||||
static constexpr size_t kSingleKeyEcmWith16BytesContentIvSizeBytes =
|
||||
kSingleKeyEcmWith8BytesContentIvSizeBytes + 8;
|
||||
|
||||
EcmInitParameters CreateEcmInitParameters(int content_iv_size,
|
||||
bool key_rotation_enabled,
|
||||
CryptoMode crypto_mode) {
|
||||
EcmInitParameters params;
|
||||
if (content_iv_size == 8) {
|
||||
params.content_iv_size = kIvSize8;
|
||||
} else {
|
||||
params.content_iv_size = kIvSize16;
|
||||
}
|
||||
params.key_rotation_enabled = key_rotation_enabled;
|
||||
params.crypto_mode = crypto_mode;
|
||||
// Internal ECM class can hold entitlement keys for multiple tracks.
|
||||
// So we need to set a default track type here to be associated with
|
||||
// the entitlement keys set later.
|
||||
params.track_types.push_back(kDefaultTrackTypeSd);
|
||||
return params;
|
||||
EntitledKeyInfo ConvertToEntitledKeyInfo(
|
||||
const WvCasContentKeyInfo& content_key_info) {
|
||||
EntitledKeyInfo entitled_key_info;
|
||||
entitled_key_info.key_id = content_key_info.key_id;
|
||||
entitled_key_info.key_value = content_key_info.key;
|
||||
entitled_key_info.content_iv = content_key_info.content_iv;
|
||||
entitled_key_info.wrapped_key_iv = content_key_info.wrapped_key_iv;
|
||||
return entitled_key_info;
|
||||
}
|
||||
|
||||
EcmInitParameters ConvertToEcmInitParameters(
|
||||
const WvCasEcmParameters& ecm_parameters) {
|
||||
EcmInitParameters init_params;
|
||||
init_params.content_iv_size = ecm_parameters.content_iv_size;
|
||||
init_params.key_rotation_enabled = ecm_parameters.key_rotation_enabled;
|
||||
init_params.crypto_mode = ecm_parameters.crypto_mode;
|
||||
init_params.age_restriction = ecm_parameters.age_restriction;
|
||||
return init_params;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
WvCasStatus WvCasEcm::Initialize(int content_iv_size, bool key_rotation_enabled,
|
||||
CryptoMode crypto_mode) {
|
||||
if (initialized_) {
|
||||
LOG(ERROR) << "Cannot initialize an instance of WvCasEcm more than once";
|
||||
return UNAVAILABLE;
|
||||
}
|
||||
if (content_iv_size != 8 && content_iv_size != 16) {
|
||||
LOG(ERROR) << "Only support content_iv_size being 8 now";
|
||||
return INVALID_ARGUMENT;
|
||||
}
|
||||
content_iv_size_ = content_iv_size;
|
||||
key_rotation_enabled_ = key_rotation_enabled;
|
||||
crypto_mode_ = crypto_mode;
|
||||
|
||||
initialized_ = true;
|
||||
|
||||
return OK;
|
||||
WvCasEcm::WvCasEcm(
|
||||
const WvCasEcmParameters& ecm_parameters,
|
||||
const std::vector<EntitlementKeyInfo>& injected_entitlements) {
|
||||
CHECK(!injected_entitlements.empty());
|
||||
ecm_param_ = ecm_parameters;
|
||||
injected_entitlements_.assign(injected_entitlements.begin(),
|
||||
injected_entitlements.end());
|
||||
}
|
||||
|
||||
WvCasStatus WvCasEcm::GenerateEcm(
|
||||
const char* const even_key, const char* const even_content_iv,
|
||||
const char* const even_entitlement_key_id,
|
||||
const char* const even_entitlement_key, const char* const odd_key,
|
||||
const char* const odd_content_iv, const char* const odd_entitlement_key_id,
|
||||
const char* const odd_entitlement_key, std::string* ecm) const {
|
||||
DCHECK(even_key);
|
||||
DCHECK(even_content_iv);
|
||||
DCHECK(even_entitlement_key_id);
|
||||
DCHECK(even_entitlement_key);
|
||||
DCHECK(odd_key);
|
||||
DCHECK(odd_content_iv);
|
||||
DCHECK(odd_entitlement_key_id);
|
||||
DCHECK(odd_entitlement_key);
|
||||
DCHECK(ecm);
|
||||
|
||||
if (!initialized_) {
|
||||
LOG(ERROR) << "WvCasEcm has not been properly initialized";
|
||||
return UNAVAILABLE;
|
||||
}
|
||||
if (!key_rotation_enabled_) {
|
||||
LOG(ERROR) << "Please call GenerateSingleKeyEcm() instead when key "
|
||||
"rotation is disabled";
|
||||
return UNAVAILABLE;
|
||||
}
|
||||
|
||||
// Create strings in such way to handle possible '\x00' bytes in the input.
|
||||
std::string even_key_str;
|
||||
if (crypto_mode_ == CryptoMode::kDvbCsa2) {
|
||||
even_key_str = std::string(even_key, kCsaContentKeySizeBytes);
|
||||
even_key_str = even_key_str + even_key_str; // Make it 16 bytes.
|
||||
} else {
|
||||
even_key_str = std::string(even_key, kContentKeySizeBytes);
|
||||
}
|
||||
std::string even_content_iv_str(even_content_iv, content_iv_size_);
|
||||
std::string even_entitlement_key_id_str(even_entitlement_key_id,
|
||||
kEntitlementKeyIdSizeBytes);
|
||||
std::string even_entitlement_key_str(even_entitlement_key,
|
||||
kEntitlementKeySizeBytes);
|
||||
std::string odd_key_str;
|
||||
if (crypto_mode_ == CryptoMode::kDvbCsa2) {
|
||||
odd_key_str = std::string(odd_key, kCsaContentKeySizeBytes);
|
||||
odd_key_str = odd_key_str + odd_key_str; // Make it 16 bytes.
|
||||
} else {
|
||||
odd_key_str = std::string(odd_key, kContentKeySizeBytes);
|
||||
}
|
||||
std::string odd_content_iv_str(odd_content_iv, content_iv_size_);
|
||||
std::string odd_entitlement_key_id_str(odd_entitlement_key_id,
|
||||
kEntitlementKeyIdSizeBytes);
|
||||
std::string odd_entitlement_key_str(odd_entitlement_key,
|
||||
kEntitlementKeySizeBytes);
|
||||
|
||||
// Double check some input sizes.
|
||||
if (even_key_str.size() != kContentKeySizeBytes ||
|
||||
odd_key_str.size() != kContentKeySizeBytes) {
|
||||
LOG(ERROR) << "Size of content key is incorrect";
|
||||
return INVALID_ARGUMENT;
|
||||
}
|
||||
if (even_content_iv_str.size() != content_iv_size_ ||
|
||||
odd_content_iv_str.size() != content_iv_size_) {
|
||||
LOG(ERROR) << "Size of content IV is incorrect";
|
||||
return INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key,
|
||||
const WvCasContentKeyInfo& odd_key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const {
|
||||
// Create an instance of Ecm in order to set the entitlement keys.
|
||||
auto cas_ecm = absl::make_unique<Ecm>();
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
EcmInitParameters ecm_init_params = CreateEcmInitParameters(
|
||||
content_iv_size_, key_rotation_enabled_, crypto_mode_);
|
||||
// 'content_id' and 'provider' are used in entitlement key request/response
|
||||
// only, NOT needed for constructing the ECM. So we just use hardcoded value
|
||||
// here for now.
|
||||
// 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.
|
||||
Status status;
|
||||
if (!(status = cas_ecm->Initialize(kDefaultContentId, kDefaultProvider,
|
||||
ecm_init_params, &entitlement_request))
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Failed to initialize ECM class.";
|
||||
return INTERNAL;
|
||||
EcmInitParameters init_params = ConvertToEcmInitParameters(ecm_param_);
|
||||
Status status = cas_ecm->Initialize(init_params, injected_entitlements_);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Failed to get initialize ECM class." << status;
|
||||
return status;
|
||||
}
|
||||
FixedKeyFetcher fixed_key_fetcher(
|
||||
/* even_entitlement_key_id= */ even_entitlement_key_id_str,
|
||||
/* even_entitlement_key= */ even_entitlement_key_str,
|
||||
/* odd_entitlement_key_id= */ odd_entitlement_key_id_str,
|
||||
/* odd_entitlement_key= */ odd_entitlement_key_str);
|
||||
if (!(status = fixed_key_fetcher.RequestEntitlementKey(entitlement_request,
|
||||
&entitlement_response))
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Failed to get entitlement key.";
|
||||
return INTERNAL;
|
||||
}
|
||||
if (!(status = cas_ecm->ProcessCasEncryptionResponse(entitlement_response))
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Failed to process entitlement key.";
|
||||
return INTERNAL;
|
||||
}
|
||||
|
||||
// Generate ECM.
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
keys.reserve(2);
|
||||
// Add even entitlement key.
|
||||
keys.emplace_back();
|
||||
keys[0].key_value = even_key_str;
|
||||
keys[0].wrapped_key_iv = crypto_util::DeriveIv(even_key_str);
|
||||
keys[0].key_id = crypto_util::DeriveKeyId(even_key_str);
|
||||
keys[0].content_iv = even_content_iv_str;
|
||||
// Add odd entitlement key.
|
||||
keys.emplace_back();
|
||||
keys[1].key_value = odd_key_str;
|
||||
keys[1].wrapped_key_iv = crypto_util::DeriveIv(odd_key_str);
|
||||
keys[1].key_id = crypto_util::DeriveKeyId(odd_key_str);
|
||||
keys[1].content_iv = odd_content_iv_str;
|
||||
status = cas_ecm->GenerateEcm(&keys[0], &keys[1], kDefaultTrackTypeSd, ecm);
|
||||
|
||||
size_t expected_ecm_size = content_iv_size_ == 8
|
||||
? kEcmWith8BytesContentIvSizeBytes
|
||||
: kEcmWith16BytesContentIvSizeBytes;
|
||||
if (ecm->size() != expected_ecm_size) {
|
||||
LOG(ERROR) << "Generated an ECM with invalid size: " << ecm->size();
|
||||
ecm->clear();
|
||||
return INTERNAL;
|
||||
}
|
||||
|
||||
return OK;
|
||||
EntitledKeyInfo entitled_even_key = ConvertToEntitledKeyInfo(even_key);
|
||||
EntitledKeyInfo entitled_odd_key = ConvertToEntitledKeyInfo(odd_key);
|
||||
return cas_ecm->GenerateEcm(&entitled_even_key, &entitled_odd_key, track_type,
|
||||
serialized_ecm);
|
||||
}
|
||||
|
||||
WvCasStatus WvCasEcm::GenerateSingleKeyEcm(
|
||||
const char* const even_key, const char* const even_content_iv,
|
||||
const char* const even_entitlement_key_id,
|
||||
const char* const even_entitlement_key, std::string* ecm) const {
|
||||
DCHECK(even_key);
|
||||
DCHECK(even_content_iv);
|
||||
DCHECK(even_entitlement_key_id);
|
||||
DCHECK(even_entitlement_key);
|
||||
DCHECK(ecm);
|
||||
|
||||
if (!initialized_) {
|
||||
LOG(ERROR) << "WvCasEcm has not been properly initialized";
|
||||
return UNAVAILABLE;
|
||||
}
|
||||
if (key_rotation_enabled_) {
|
||||
LOG(ERROR)
|
||||
<< "Please call GenerateEcm() instead when key rotation is enabled";
|
||||
return UNAVAILABLE;
|
||||
}
|
||||
|
||||
// Create strings in such way to handle possible '\x00' bytes in the input.
|
||||
std::string even_key_str;
|
||||
if (crypto_mode_ == CryptoMode::kDvbCsa2) {
|
||||
even_key_str = std::string(even_key, kCsaContentKeySizeBytes);
|
||||
even_key_str = even_key_str + even_key_str; // Make it 16 bytes.
|
||||
} else {
|
||||
even_key_str = std::string(even_key, kContentKeySizeBytes);
|
||||
}
|
||||
std::string even_content_iv_str(even_content_iv, content_iv_size_);
|
||||
std::string even_entitlement_key_id_str(even_entitlement_key_id,
|
||||
kEntitlementKeyIdSizeBytes);
|
||||
std::string even_entitlement_key_str(even_entitlement_key,
|
||||
kEntitlementKeySizeBytes);
|
||||
|
||||
// Double check some input sizes.
|
||||
if (even_key_str.size() != kContentKeySizeBytes) {
|
||||
LOG(ERROR) << "Size of content key is incorrect";
|
||||
return INVALID_ARGUMENT;
|
||||
}
|
||||
if (even_content_iv_str.size() != content_iv_size_) {
|
||||
LOG(ERROR) << "Size of content IV is incorrect";
|
||||
return INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
Status WvCasEcm::GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const {
|
||||
// Create an instance of Ecm in order to set the entitlement keys.
|
||||
auto cas_ecm = absl::make_unique<Ecm>();
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
EcmInitParameters ecm_init_params = CreateEcmInitParameters(
|
||||
content_iv_size_, key_rotation_enabled_, crypto_mode_);
|
||||
// 'content_id' and 'provider' are used in entitlement key request/response
|
||||
// only, NOT needed for constructing the ECM. So we just use hardcoded value
|
||||
// here for now.
|
||||
// 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.
|
||||
Status status;
|
||||
if (!(status = cas_ecm->Initialize(kDefaultContentId, kDefaultProvider,
|
||||
ecm_init_params, &entitlement_request))
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Failed to get initialize ECM class.";
|
||||
return INTERNAL;
|
||||
}
|
||||
FixedKeyFetcher fixed_key_fetcher(
|
||||
/* even_entitlement_key_id= */ even_entitlement_key_id_str,
|
||||
/* even_entitlement_key= */ even_entitlement_key_str,
|
||||
/* odd_entitlement_key_id= */ "",
|
||||
/* odd_entitlement_key= */ "");
|
||||
if (!(status = fixed_key_fetcher.RequestEntitlementKey(entitlement_request,
|
||||
&entitlement_response))
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Failed to get entitlement key.";
|
||||
return INTERNAL;
|
||||
}
|
||||
if (!(status = cas_ecm->ProcessCasEncryptionResponse(entitlement_response))
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Failed to process entitlement key.";
|
||||
return INTERNAL;
|
||||
EcmInitParameters init_params = ConvertToEcmInitParameters(ecm_param_);
|
||||
Status status = cas_ecm->Initialize(init_params, injected_entitlements_);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Failed to get initialize ECM class." << status;
|
||||
return status;
|
||||
}
|
||||
|
||||
// Generate ECM.
|
||||
EntitledKeyInfo key;
|
||||
key.key_value = even_key_str;
|
||||
key.wrapped_key_iv = crypto_util::DeriveIv(even_key_str);
|
||||
key.key_id = crypto_util::DeriveKeyId(key.key_value);
|
||||
key.content_iv = even_content_iv_str;
|
||||
status = cas_ecm->GenerateSingleKeyEcm(&key, kDefaultTrackTypeSd, ecm);
|
||||
|
||||
size_t expected_ecm_size = content_iv_size_ == 8
|
||||
? kSingleKeyEcmWith8BytesContentIvSizeBytes
|
||||
: kSingleKeyEcmWith16BytesContentIvSizeBytes;
|
||||
if (ecm->size() != expected_ecm_size) {
|
||||
LOG(ERROR) << "Generated an ECM with invalid size: " << ecm->size();
|
||||
ecm->clear();
|
||||
return INTERNAL;
|
||||
}
|
||||
|
||||
return OK;
|
||||
EntitledKeyInfo entitled_key = ConvertToEntitledKeyInfo(key);
|
||||
return cas_ecm->GenerateSingleKeyEcm(&entitled_key, track_type,
|
||||
serialized_ecm);
|
||||
}
|
||||
|
||||
WvCasStatus WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id,
|
||||
uint8_t* continuity_counter,
|
||||
uint8_t* packet) const {
|
||||
ssize_t bytes_modified = 0;
|
||||
Status status = InsertEcmAsTsPacket(ecm, pid, table_id, continuity_counter,
|
||||
packet, &bytes_modified);
|
||||
if (!status.ok() || bytes_modified != kTsPacketSize) {
|
||||
memset(packet, 0, kTsPacketSize);
|
||||
LOG(ERROR) << "Failed to generate TS packet: " << status;
|
||||
return INTERNAL;
|
||||
}
|
||||
|
||||
return OK;
|
||||
Status WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id, uint8_t* continuity_counter,
|
||||
uint8_t* packet) {
|
||||
return Ecm::GenerateTsPacket(ecm, pid, table_id, continuity_counter, packet);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -10,102 +10,91 @@
|
||||
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// Information needed to generate content key portion of ECM.
|
||||
// Fields:
|
||||
// |key_id| key ID for the content key, must be 16 bytes.
|
||||
// |key| content key value (aka control word), must be 16 bytes. It will be
|
||||
// wrapped (encrypted) by corresponding entitlement key (decided by track
|
||||
// type), together with |wrapped_key_iv| as the initial vector.
|
||||
// |content_iv| content initial vector, must be 8 or 16 bytes. It is used for
|
||||
// decrypting the content stream.
|
||||
// |wrapped_key_iv| must be 16 bytes. It is used to encrypt |key|.
|
||||
struct WvCasContentKeyInfo {
|
||||
std::string key_id;
|
||||
std::string key;
|
||||
std::string content_iv;
|
||||
std::string wrapped_key_iv;
|
||||
};
|
||||
|
||||
// Information needed to start a new ECM stream.
|
||||
// Fields:
|
||||
// |content_iv_size| size of all content key IVs in the ECM stream.
|
||||
// A constant of type EcmIvSize specifying 8 or 16.
|
||||
// |key_rotation_enabled| the encryption uses multiple keys in rotation.
|
||||
// |crypto_mode| the encryption mode used for the content stream.
|
||||
// A constant of type CryptoMode.
|
||||
// |age_restriction| minimum age required; the value represents actual age.
|
||||
struct WvCasEcmParameters {
|
||||
EcmIvSize content_iv_size = kIvSize8;
|
||||
bool key_rotation_enabled = true;
|
||||
CryptoMode crypto_mode = CryptoMode::kAesCtr;
|
||||
uint8_t age_restriction = 0;
|
||||
};
|
||||
|
||||
// Class for generating Widevine CAS ECMs.
|
||||
// See wv_cas_ecm_test.cc for example usage.
|
||||
// This class is NOT thread-safe.
|
||||
class WvCasEcm {
|
||||
public:
|
||||
WvCasEcm() = default;
|
||||
// Construct of class WvCasEcm.
|
||||
// Args:
|
||||
// |ecm_init_parameters| encryption-related parameters for configuring
|
||||
// the ECM stream.
|
||||
// |injected_entitlement| entitlement key info. May be fetched from the server
|
||||
// with WvCasKeyFetcher.
|
||||
WvCasEcm(const WvCasEcmParameters& ecm_parameters,
|
||||
const std::vector<EntitlementKeyInfo>& injected_entitlements);
|
||||
WvCasEcm(const WvCasEcm&) = delete;
|
||||
WvCasEcm& operator=(const WvCasEcm&) = delete;
|
||||
virtual ~WvCasEcm() = default;
|
||||
|
||||
// Initialize an instance of this class.
|
||||
//
|
||||
// Accept keys and IVs and construct an ECM that will fit into a Transport
|
||||
// Stream packet payload (184 bytes).
|
||||
// Args:
|
||||
// - |content_iv_size| iv size in bytes for encrypting the content,
|
||||
// only support 8 bytes or 16 bytes
|
||||
// When using 8 bytes content_iv, we assume additional 8 bytes of '\x00' are
|
||||
// appended to the iv to form a 16 bytes AES IV when content is encrypted.
|
||||
// - |key_rotation_enabled| whether key rotation is enabled,
|
||||
// if this is 'true' only subsequent call to GenerateEcm will be allowed,
|
||||
// if this is 'false' only subsequent call to GenerateSingleKeyEcm will
|
||||
// be allowed
|
||||
// - |crypto_mode| crypto mode for encrypting content
|
||||
//
|
||||
// Returns:
|
||||
// - A status indicating whether there was any error during initialization
|
||||
//
|
||||
// Note:
|
||||
// - 'even'/'odd' key in the ECM will be be encrypted using AEC_CBC
|
||||
virtual WvCasStatus Initialize(int content_iv_size, bool key_rotation_enabled,
|
||||
CryptoMode crypto_mode);
|
||||
// |even_key| information for even key to be encoded into ECM.
|
||||
// |odd_key| information for odd key to be encoded into ECM.
|
||||
// |track_type| the track that the keys are being used to encrypt.
|
||||
// |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
|
||||
// The |even_key| and |odd_key| contents (specifically IV sizes) must be
|
||||
// consistent with the initialized settings.
|
||||
// The even_key and odd_key will be wrapped using the appropriate
|
||||
// entitlement key. Wrapping modifies the original structure.
|
||||
virtual Status GenerateEcm(const WvCasContentKeyInfo& even_key,
|
||||
const WvCasContentKeyInfo& odd_key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const;
|
||||
|
||||
// Generate an ECM containing two keys (even and odd). Can be called when
|
||||
// |key_rotation_enabled| is initialized to 'true'.
|
||||
//
|
||||
// Args (all pointer parameters must be not nullptr):
|
||||
// - |even_key| clear even content key, must be 8 bytes for kDvbCsa2,
|
||||
// 16 bytes for kAesCtr or kAesCbc
|
||||
// - |even_content_iv| iv used along with |even_key| for encrypting content,
|
||||
// length must match |content_iv_size| set during initialization
|
||||
// - |even_entitlement_key_id| key id for |even_entitlement_key|,
|
||||
// must be 16 bytes length
|
||||
// - |even_entitlement_key| entitlement key used to encrypt even key,
|
||||
// must be 32 bytes length
|
||||
// - |odd_key| clear odd content key, must be 8 bytes for kDvbCsa2,
|
||||
// 16 bytes for kAesCtr or kAesCbc
|
||||
// - |odd_content_iv| iv used along with |odd_key| for encrypting content,
|
||||
// length must match |content_iv_size| set during initialization
|
||||
// - |odd_entitlement_key_id| key id for |odd_entitlement_key|,
|
||||
// must be 16 bytes length
|
||||
// - |odd_entitlement_key| entitlement key used to encrypt odd key,
|
||||
// must be 32 bytes length
|
||||
// - |ecm| for returning the generated ECM,
|
||||
// size of the generated ecm is 149 bytes when content iv is 8 bytes
|
||||
// 165 bytes when content iv is 16 bytes
|
||||
//
|
||||
// Returns:
|
||||
// - A status indicating whether there was any error during processing
|
||||
virtual WvCasStatus GenerateEcm(const char* const even_key,
|
||||
const char* const even_content_iv,
|
||||
const char* const even_entitlement_key_id,
|
||||
const char* const even_entitlement_key,
|
||||
const char* const odd_key,
|
||||
const char* const odd_content_iv,
|
||||
const char* const odd_entitlement_key_id,
|
||||
const char* const odd_entitlement_key,
|
||||
std::string* ecm) const;
|
||||
|
||||
// Generate an ECM containing only a singe even key. Can be called when
|
||||
// |key_rotation_enabled| is initialized to 'false'.
|
||||
//
|
||||
// Args (all pointer parameters must be not nullptr):
|
||||
// - |even_key| clear even content key, must be 8 bytes for kDvbCsa2,
|
||||
// 16 bytes for kAesCtr or kAesCbc
|
||||
// - |even_content_iv| iv used along with |even_key| for encrypting content,
|
||||
// length must match |content_iv_size| set during initialization
|
||||
// - |even_entitlement_key_id| key id for |even_entitlement_key|,
|
||||
// must be 16 bytes length
|
||||
// - |even_entitlement_key| entitlement key used to encrypt even key,
|
||||
// must be 32 bytes length
|
||||
// - |ecm| for returning the generated ECM,
|
||||
// size of the generated ecm is 77 bytes when content iv is 8 bytes
|
||||
// 85 bytes when content iv is 16 bytes
|
||||
//
|
||||
// Returns:
|
||||
// - A status indicating whether there was any error during processing
|
||||
virtual WvCasStatus GenerateSingleKeyEcm(
|
||||
const char* const even_key, const char* const even_content_iv,
|
||||
const char* const even_entitlement_key_id,
|
||||
const char* const even_entitlement_key, std::string* ecm) const;
|
||||
// Accept a key and IV and construct an ECM that will fit into a Transport
|
||||
// Stream packet payload (184 bytes). This call is specifically for the case
|
||||
// where key rotation is disabled.
|
||||
// Args:
|
||||
// |key| information for key to be encoded into ECM.
|
||||
// |track_type| the track that the key is being used to encrypt.
|
||||
// |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
|
||||
// The |key| contents (specifically IV sizes) must be consistent
|
||||
// with the initialized settings.
|
||||
virtual Status GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const;
|
||||
|
||||
// Generate a TS packet with the given |ecm| as payload.
|
||||
//
|
||||
@@ -125,16 +114,13 @@ class WvCasEcm {
|
||||
//
|
||||
// Returns:
|
||||
// - A status indicating whether there was any error during processing
|
||||
virtual WvCasStatus GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id,
|
||||
uint8_t* continuity_counter,
|
||||
uint8_t* packet) const;
|
||||
static Status GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id, uint8_t* continuity_counter,
|
||||
uint8_t* packet);
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
int content_iv_size_;
|
||||
bool key_rotation_enabled_;
|
||||
CryptoMode crypto_mode_;
|
||||
WvCasEcmParameters ecm_param_;
|
||||
std::vector<EntitlementKeyInfo> injected_entitlements_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -8,385 +8,194 @@
|
||||
|
||||
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
|
||||
using ::testing::Test;
|
||||
#include "common/crypto_util.h"
|
||||
#include "common/status.h"
|
||||
#include "example/constants.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
const char kEvenKey[] = "even_key........"; // 16 bytes
|
||||
const char kCsaEvenKey[] = "even_key"; // 8 bytes
|
||||
const char kCsaEvenKeyWithNul[] = {'\x01', '\x00', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x00', '\x01'};
|
||||
const char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes
|
||||
const char kEvenContentIv16Bytes[] = "even_iv........."; // 16 bytes
|
||||
const char kEvenContentIv16Bytes[] = "even_iv.even_iv."; // 16 bytes
|
||||
const char kEvenEntitlementKeyId[] = "even_ent_key_id."; // 16 bytes
|
||||
const char kEvenEntitlementKey[] =
|
||||
"even_entitlement_key............"; // 32 bytes
|
||||
const char kOddKey[] = "odd_key........."; // 16 bytes
|
||||
const char kCsaOddKey[] = "odd_key."; // 8 bytes
|
||||
const char kCsaOddKeyWithNul[] = {'\x00', '\x02', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x02', '\x00'};
|
||||
"even_entitlement_key............"; // 32 bytes
|
||||
const char kEvenEntitlementWrappedKeyIv[] = "1234567812345678"; // 16 bytes
|
||||
|
||||
const char kOddKey[] = "odd_key........."; // 16 bytes
|
||||
const char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes
|
||||
const char kOddContentIv16Bytes[] = "od_iv..........."; // 16 bytes
|
||||
const char kOddContentIv16Bytes[] = "odd_iv..odd_iv.."; // 16 bytes
|
||||
const char kOddEntitlementKeyId[] = "odd_ent_key_id.."; // 16 bytes
|
||||
const char kOddEntitlementKey[] =
|
||||
"odd_entitlement_key............."; // 32 bytes
|
||||
"odd_entitlement_key............."; // 32 bytes
|
||||
const char kOddEntitlementWrappedKeyIv[] = "1234567812345678"; // 16 bytes
|
||||
|
||||
// ECM payload data taken from a CETS encrypted file at Google Fiber
|
||||
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
|
||||
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
|
||||
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4',
|
||||
'\xC6', '\x6D', '\x57', '\xDC'};
|
||||
// ECM packet for Video PID (TSheader + payload) taken from a CETS encrypted
|
||||
// file, payload_unit_start = 1, adaptation_field_control = 1, cc = 0
|
||||
constexpr char kExpectedEcmPacket[] = {
|
||||
// TS header.
|
||||
'\x47', '\x5F', '\xFD', '\x10',
|
||||
// Section header.
|
||||
'\x00', '\x80', '\x70', '\x1C',
|
||||
// ECM.
|
||||
'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
|
||||
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4', '\xC6', '\x6D', '\x57',
|
||||
'\xDC',
|
||||
// Padding.
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF'};
|
||||
static constexpr size_t kEcmWith8BytesContentIvSizeBytes = 149;
|
||||
static constexpr size_t kEcmWith16BytesContentIvSizeBytes = 149 + (8 * 2);
|
||||
static constexpr size_t kSingleKeyEcmWith8BytesContentIvSizeBytes = 77;
|
||||
static constexpr size_t kSingleKeyEcmWith16BytesContentIvSizeBytes =
|
||||
kSingleKeyEcmWith8BytesContentIvSizeBytes + 8;
|
||||
} // namespace
|
||||
|
||||
class WvCasEcmTest : public Test {
|
||||
class WvCasEcmTest
|
||||
: public ::testing::Test,
|
||||
public ::testing::WithParamInterface<::testing::tuple<
|
||||
int /*content_iv_size*/, bool /*key_rotation_enabled*/>> {
|
||||
protected:
|
||||
WvCasEcmTest() {}
|
||||
WvCasEcm wv_cas_ecm_;
|
||||
WvCasEcmTest() {
|
||||
content_iv_size_ = std::get<0>(GetParam());
|
||||
key_rotation_ = std::get<1>(GetParam());
|
||||
}
|
||||
|
||||
WvCasEcmParameters CreateEcmInitParameters(bool key_rotation,
|
||||
int content_iv_size) {
|
||||
WvCasEcmParameters params;
|
||||
params.content_iv_size = content_iv_size == 8 ? kIvSize8 : kIvSize16;
|
||||
params.key_rotation_enabled = key_rotation;
|
||||
params.crypto_mode = CryptoMode::kAesScte;
|
||||
params.age_restriction = 0;
|
||||
return params;
|
||||
}
|
||||
|
||||
std::vector<EntitlementKeyInfo> CreateInjectedEntitlements(
|
||||
bool key_rotation) {
|
||||
std::vector<EntitlementKeyInfo> injected_entitlements;
|
||||
injected_entitlements.reserve(key_rotation ? 2 : 1);
|
||||
injected_entitlements.emplace_back();
|
||||
EntitlementKeyInfo* entitlement = &injected_entitlements.back();
|
||||
entitlement->key_id = kEvenEntitlementKeyId;
|
||||
entitlement->key_value = kEvenEntitlementKey;
|
||||
entitlement->is_even_key = true;
|
||||
entitlement->track_type = kDefaultTrackTypeSd;
|
||||
if (key_rotation) {
|
||||
injected_entitlements.emplace_back();
|
||||
EntitlementKeyInfo* entitlement = &injected_entitlements.back();
|
||||
entitlement->key_id = kOddEntitlementKeyId;
|
||||
entitlement->key_value = kOddEntitlementKey;
|
||||
entitlement->is_even_key = false;
|
||||
entitlement->track_type = kDefaultTrackTypeSd;
|
||||
}
|
||||
return injected_entitlements;
|
||||
}
|
||||
|
||||
std::vector<WvCasContentKeyInfo> CreateContentKeyInfo(bool key_rotation,
|
||||
int content_iv_size) {
|
||||
std::vector<WvCasContentKeyInfo> content_keys;
|
||||
content_keys.reserve(key_rotation ? 2 : 1);
|
||||
content_keys.emplace_back();
|
||||
WvCasContentKeyInfo* content_key = &content_keys.back();
|
||||
content_key->key = kEvenKey;
|
||||
content_key->key_id = crypto_util::DeriveKeyId(content_key->key);
|
||||
content_key->content_iv =
|
||||
content_iv_size == 8 ? kEvenContentIv8Bytes : kEvenContentIv16Bytes;
|
||||
content_key->wrapped_key_iv = kEvenEntitlementWrappedKeyIv;
|
||||
if (key_rotation) {
|
||||
content_keys.emplace_back();
|
||||
WvCasContentKeyInfo* content_key = &content_keys.back();
|
||||
content_key->key = kOddKey;
|
||||
content_key->key_id = crypto_util::DeriveKeyId(content_key->key);
|
||||
content_key->content_iv =
|
||||
content_iv_size == 8 ? kOddContentIv8Bytes : kOddContentIv16Bytes;
|
||||
content_key->wrapped_key_iv = kOddEntitlementWrappedKeyIv;
|
||||
}
|
||||
|
||||
return content_keys;
|
||||
}
|
||||
|
||||
int content_iv_size_;
|
||||
bool key_rotation_;
|
||||
};
|
||||
|
||||
TEST_F(WvCasEcmTest, Initialize_Twice_Error) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
|
||||
/* key_rotation_enabled= */ true,
|
||||
CryptoMode::kAesCtr));
|
||||
EXPECT_EQ(UNAVAILABLE,
|
||||
wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
|
||||
/* key_rotation_enabled= */ true,
|
||||
CryptoMode::kAesCtr));
|
||||
TEST_P(WvCasEcmTest, InitializeSuccess) {
|
||||
WvCasEcmParameters params =
|
||||
CreateEcmInitParameters(key_rotation_, content_iv_size_);
|
||||
std::vector<EntitlementKeyInfo> entitlements =
|
||||
CreateInjectedEntitlements(key_rotation_);
|
||||
WvCasEcm wv_cas_ecm(params, entitlements);
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, Initialize_InvalidContentIvSize_Error) {
|
||||
EXPECT_EQ(INVALID_ARGUMENT,
|
||||
wv_cas_ecm_.Initialize(/* content_iv_size= */ 4,
|
||||
/* key_rotation_enabled= */ true,
|
||||
CryptoMode::kAesCtr));
|
||||
}
|
||||
TEST_P(WvCasEcmTest, GenerateSingleKeyEcmKeyRotationEnabledError) {
|
||||
WvCasEcmParameters params = CreateEcmInitParameters(
|
||||
/*key_rotation*/ true, content_iv_size_);
|
||||
std::vector<EntitlementKeyInfo> entitlements =
|
||||
CreateInjectedEntitlements(/*key_rotation*/ true);
|
||||
WvCasEcm wv_cas_ecm(params, entitlements);
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_KeyRotationEnabled_Error) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
|
||||
/* key_rotation_enabled= */ true,
|
||||
CryptoMode::kAesCtr));
|
||||
std::vector<WvCasContentKeyInfo> content_keys =
|
||||
CreateContentKeyInfo(/*key_rotation*/ false, content_iv_size_);
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(UNAVAILABLE,
|
||||
wv_cas_ecm_.GenerateSingleKeyEcm("", "", "", "", &actual_ecm));
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
wv_cas_ecm
|
||||
.GenerateSingleKeyEcm(content_keys[0], kDefaultTrackTypeSd,
|
||||
&actual_ecm)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateEcm_KeyRotationDisabled_Error) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
|
||||
/* key_rotation_enabled= */ false,
|
||||
CryptoMode::kAesCtr));
|
||||
TEST_P(WvCasEcmTest, GenerateEcmKeyRotationDisabledError) {
|
||||
WvCasEcmParameters params = CreateEcmInitParameters(
|
||||
/*key_rotation*/ false, content_iv_size_);
|
||||
std::vector<EntitlementKeyInfo> entitlements =
|
||||
CreateInjectedEntitlements(/*key_rotation*/ false);
|
||||
WvCasEcm wv_cas_ecm(params, entitlements);
|
||||
|
||||
std::vector<WvCasContentKeyInfo> content_keys =
|
||||
CreateContentKeyInfo(/*key_rotation*/ true, content_iv_size_);
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(UNAVAILABLE, wv_cas_ecm_.GenerateEcm("", "", "", "", "", "", "", "",
|
||||
&actual_ecm));
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
wv_cas_ecm
|
||||
.GenerateEcm(content_keys[0], content_keys[1],
|
||||
kDefaultTrackTypeSd, &actual_ecm)
|
||||
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Ctr_Success) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
|
||||
/* key_rotation_enabled= */ true,
|
||||
CryptoMode::kAesCtr));
|
||||
TEST_P(WvCasEcmTest, GenerateEcmSuccess) {
|
||||
WvCasEcmParameters params =
|
||||
CreateEcmInitParameters(key_rotation_, content_iv_size_);
|
||||
std::vector<EntitlementKeyInfo> entitlements =
|
||||
CreateInjectedEntitlements(key_rotation_);
|
||||
WvCasEcm wv_cas_ecm(params, entitlements);
|
||||
|
||||
std::vector<WvCasContentKeyInfo> content_keys =
|
||||
CreateContentKeyInfo(key_rotation_, content_iv_size_);
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(OK,
|
||||
wv_cas_ecm_.GenerateEcm(
|
||||
/* even_key= */ kEvenKey,
|
||||
/* even_content_iv= */ kEvenContentIv8Bytes,
|
||||
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey,
|
||||
/* odd_key= */ kOddKey,
|
||||
/* odd_content_iv= */ kOddContentIv8Bytes,
|
||||
/* odd_entitlement_key_id= */ kOddEntitlementKeyId,
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40203806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e6f64645f656e745f6b65795f69642e2e34cd74b6b998889aad0e71b44bdd8c"
|
||||
"0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3ebba4a0bd876f6464"
|
||||
"5f69762e2e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
if (key_rotation_) {
|
||||
EXPECT_OK(wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1],
|
||||
kDefaultTrackTypeSd, &actual_ecm));
|
||||
} else {
|
||||
EXPECT_OK(wv_cas_ecm.GenerateSingleKeyEcm(
|
||||
content_keys[0], kDefaultTrackTypeSd, &actual_ecm));
|
||||
}
|
||||
EXPECT_EQ(absl::BytesToHexString(actual_ecm).substr(0, 7), "4ad4020");
|
||||
if (content_iv_size_ == 8) {
|
||||
if (key_rotation_) {
|
||||
EXPECT_EQ(actual_ecm.size(), kEcmWith8BytesContentIvSizeBytes);
|
||||
} else {
|
||||
EXPECT_EQ(actual_ecm.size(), kSingleKeyEcmWith8BytesContentIvSizeBytes);
|
||||
}
|
||||
} else {
|
||||
if (key_rotation_) {
|
||||
EXPECT_EQ(actual_ecm.size(), kEcmWith16BytesContentIvSizeBytes);
|
||||
} else {
|
||||
EXPECT_EQ(actual_ecm.size(), kSingleKeyEcmWith16BytesContentIvSizeBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_8BytesContentIv_Ctr_Success) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
|
||||
/* key_rotation_enabled= */ false,
|
||||
CryptoMode::kAesCtr));
|
||||
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(OK,
|
||||
wv_cas_ecm_.GenerateSingleKeyEcm(
|
||||
/* even_key= */ kEvenKey,
|
||||
/* even_content_iv= */ kEvenContentIv8Bytes,
|
||||
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40202806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateEcm_16BytesContentIv_Ctr_Success) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 16,
|
||||
/* key_rotation_enabled= */ true,
|
||||
CryptoMode::kAesCtr));
|
||||
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(OK,
|
||||
wv_cas_ecm_.GenerateEcm(
|
||||
/* even_key= */ kEvenKey,
|
||||
/* even_content_iv= */
|
||||
kEvenContentIv16Bytes,
|
||||
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey,
|
||||
/* odd_key= */ kOddKey,
|
||||
/* odd_content_iv= */
|
||||
kOddContentIv16Bytes,
|
||||
/* odd_entitlement_key_id= */ kOddEntitlementKeyId,
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40203c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e2e2e2e2e2e2e2e2e6f64645f656e745f6b65795f69642e2e34cd74b6b99888"
|
||||
"9aad0e71b44bdd8c0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3e"
|
||||
"bba4a0bd876f645f69762e2e2e2e2e2e2e2e2e2e2e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_16BytesContentIv_Ctr_Success) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 16,
|
||||
/* key_rotation_enabled= */ false,
|
||||
CryptoMode::kAesCtr));
|
||||
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(OK,
|
||||
wv_cas_ecm_.GenerateSingleKeyEcm(
|
||||
/* even_key= */ kEvenKey,
|
||||
/* even_content_iv= */
|
||||
kEvenContentIv16Bytes,
|
||||
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40202c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e2e2e2e2e2e2e2e2e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateEcm_16BytesContentIv_Cbc_Success) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 16,
|
||||
/* key_rotation_enabled= */ true,
|
||||
CryptoMode::kAesCbc));
|
||||
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(OK,
|
||||
wv_cas_ecm_.GenerateEcm(
|
||||
/* even_key= */ kEvenKey,
|
||||
/* even_content_iv= */
|
||||
kEvenContentIv16Bytes,
|
||||
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey,
|
||||
/* odd_key= */ kOddKey,
|
||||
/* odd_content_iv= */
|
||||
kOddContentIv16Bytes,
|
||||
/* odd_entitlement_key_id= */ kOddEntitlementKeyId,
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40201c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e2e2e2e2e2e2e2e2e6f64645f656e745f6b65795f69642e2e34cd74b6b99888"
|
||||
"9aad0e71b44bdd8c0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3e"
|
||||
"bba4a0bd876f645f69762e2e2e2e2e2e2e2e2e2e2e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_16BytesContentIv_Cbc_Success) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 16,
|
||||
/* key_rotation_enabled= */ false,
|
||||
CryptoMode::kAesCbc));
|
||||
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(OK,
|
||||
wv_cas_ecm_.GenerateSingleKeyEcm(
|
||||
/* even_key= */ kEvenKey,
|
||||
/* even_content_iv= */
|
||||
kEvenContentIv16Bytes,
|
||||
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40200c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e2e2e2e2e2e2e2e2e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_Success) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
|
||||
/* key_rotation_enabled= */ true,
|
||||
CryptoMode::kDvbCsa2));
|
||||
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(OK,
|
||||
wv_cas_ecm_.GenerateEcm(
|
||||
/* even_key= */ kCsaEvenKey,
|
||||
/* even_content_iv= */
|
||||
kEvenContentIv8Bytes,
|
||||
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey,
|
||||
/* odd_key= */ kCsaOddKey,
|
||||
/* odd_content_iv= */
|
||||
kOddContentIv8Bytes,
|
||||
/* odd_entitlement_key_id= */ kOddEntitlementKeyId,
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40205806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
|
||||
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
|
||||
"6e5f69762e6f64645f656e745f6b65795f69642e2e7f655fe61e99e89e03ac23df98cc02"
|
||||
"1cf21dfe9637c72c3480727ab18332d4ee219e81b8f34c9df2704b0595501832736f6464"
|
||||
"5f69762e2e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_8BytesContentIv_Csa_Success) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
|
||||
/* key_rotation_enabled= */ false,
|
||||
CryptoMode::kDvbCsa2));
|
||||
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(OK,
|
||||
wv_cas_ecm_.GenerateSingleKeyEcm(
|
||||
/* even_key= */ kCsaEvenKey,
|
||||
/* even_content_iv= */
|
||||
kEvenContentIv8Bytes,
|
||||
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40204806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
|
||||
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
|
||||
"6e5f69762e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_NulCharInKey_Success) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
|
||||
/* key_rotation_enabled= */ true,
|
||||
CryptoMode::kDvbCsa2));
|
||||
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(OK,
|
||||
wv_cas_ecm_.GenerateEcm(
|
||||
/* even_key= */ kCsaEvenKeyWithNul,
|
||||
/* even_content_iv= */
|
||||
kEvenContentIv8Bytes,
|
||||
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey,
|
||||
/* odd_key= */ kCsaOddKeyWithNul,
|
||||
/* odd_content_iv= */
|
||||
kOddContentIv8Bytes,
|
||||
/* odd_entitlement_key_id= */ kOddEntitlementKeyId,
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40205806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
|
||||
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
|
||||
"6e5f69762e6f64645f656e745f6b65795f69642e2e874aab870ffba640875a4521d3cd57"
|
||||
"02f26d0f9c7e9c69d7059c9ad42b091ec1f151aaa190536f4f330edebe84fe5a786f6464"
|
||||
"5f69762e2e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest,
|
||||
GenerateSingleKeyEcm_8BytesContentIv_Csa_NulCharInKey_Success) {
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
|
||||
/* key_rotation_enabled= */ false,
|
||||
CryptoMode::kDvbCsa2));
|
||||
|
||||
std::string actual_ecm;
|
||||
EXPECT_EQ(OK,
|
||||
wv_cas_ecm_.GenerateSingleKeyEcm(
|
||||
/* even_key= */ kCsaEvenKeyWithNul,
|
||||
/* even_content_iv= */
|
||||
kEvenContentIv8Bytes,
|
||||
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40204806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
|
||||
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
|
||||
"6e5f69762e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateTsPacket_TableId80) {
|
||||
std::string ecm(kEcmPayload);
|
||||
ProgramId pid = 0x1FFD;
|
||||
ContinuityCounter cc = 0;
|
||||
uint8_t packet[188];
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.GenerateTsPacket(ecm, pid, kTsPacketTableId80, &cc,
|
||||
packet));
|
||||
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, packet, sizeof(packet)));
|
||||
EXPECT_EQ(1, cc);
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateTsPacket_TableId81) {
|
||||
std::string ecm(kEcmPayload);
|
||||
ProgramId pid = 0x1FFD;
|
||||
ContinuityCounter cc = 0;
|
||||
uint8_t packet[188];
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.GenerateTsPacket(ecm, pid, kTsPacketTableId81, &cc,
|
||||
packet));
|
||||
char expected_ecm[188];
|
||||
memcpy(expected_ecm, kExpectedEcmPacket, 188);
|
||||
expected_ecm[5] = '\x81';
|
||||
EXPECT_EQ(0, memcmp(expected_ecm, packet, sizeof(packet)));
|
||||
EXPECT_EQ(1, cc);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(WvCasEcmTests, WvCasEcmTest,
|
||||
::testing::Combine(::testing::Values(8, 16),
|
||||
::testing::Bool()));
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -11,49 +11,61 @@
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "google/protobuf/util/json_util.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "curl/curl.h"
|
||||
#include "curl/easy.h"
|
||||
#include "common/signature_util.h"
|
||||
#include "common/status.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
using google::protobuf::util::JsonPrintOptions;
|
||||
using google::protobuf::util::JsonStringToMessage;
|
||||
using google::protobuf::util::MessageToJsonString;
|
||||
|
||||
ABSL_FLAG(std::string, license_server, "",
|
||||
"HTTP URL to the license server for making CAS encryption request.");
|
||||
ABSL_FLAG(std::string, signing_provider, "",
|
||||
"Name of the provider signing the CAS encryption request.");
|
||||
ABSL_FLAG(std::string, signing_key, "",
|
||||
"AES key (in hex) for signing the CAS encryption request.");
|
||||
ABSL_FLAG(std::string, signing_iv, "",
|
||||
"AES iv (in hex) for signing the CAS encryption request.");
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
const std::string& request_string,
|
||||
std::string* signed_response_string) const {
|
||||
if (absl::GetFlag(FLAGS_signing_provider).empty() ||
|
||||
absl::GetFlag(FLAGS_signing_key).empty() ||
|
||||
absl::GetFlag(FLAGS_signing_iv).empty()) {
|
||||
return Status(
|
||||
error::INVALID_ARGUMENT,
|
||||
"Flag 'signing_provider', 'signing_key' or 'signing_iv' is empty");
|
||||
WvCasKeyFetcher::WvCasKeyFetcher(const std::string& signing_provider,
|
||||
const std::string& signing_key,
|
||||
const std::string& signing_iv) {
|
||||
signing_provider_ = signing_provider;
|
||||
signing_key_ = signing_key;
|
||||
signing_iv_ = signing_iv;
|
||||
}
|
||||
|
||||
Status WvCasKeyFetcher::CreateEntitlementRequest(
|
||||
const EntitlementRequestParams& request_params,
|
||||
std::string* signed_request_json) const {
|
||||
if (request_params.content_id.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Content ID is empty."};
|
||||
}
|
||||
if (request_params.content_provider.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Content Provider is empty."};
|
||||
}
|
||||
if (request_params.track_types.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Track Types is empty."};
|
||||
}
|
||||
if (signed_request_json == nullptr) {
|
||||
return {error::INVALID_ARGUMENT, "signed_request_json is null."};
|
||||
}
|
||||
if (signing_provider_.empty() || signing_key_.empty() ||
|
||||
signing_iv_.empty()) {
|
||||
return Status(error::NOT_FOUND,
|
||||
"'signing_provider', 'signing_key' or 'signing_iv' is empty");
|
||||
}
|
||||
|
||||
// Processes request.
|
||||
CasEncryptionRequest request;
|
||||
request.ParseFromString(request_string);
|
||||
request.set_content_id(request_params.content_id);
|
||||
request.set_provider(request_params.content_provider);
|
||||
request.set_key_rotation(request_params.key_rotation);
|
||||
// Add labels for tracks.
|
||||
for (const auto& track_type : request_params.track_types) {
|
||||
request.add_track_types(track_type);
|
||||
}
|
||||
|
||||
std::string request_json;
|
||||
JsonPrintOptions print_options;
|
||||
// Set this option so that the json output is
|
||||
@@ -74,87 +86,102 @@ Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
signed_request.set_request(request_json);
|
||||
std::string signature;
|
||||
if (!signature_util::GenerateAesSignature(
|
||||
request_json,
|
||||
absl::HexStringToBytes(absl::GetFlag(FLAGS_signing_key)),
|
||||
absl::HexStringToBytes(absl::GetFlag(FLAGS_signing_iv)), &signature)
|
||||
request_json, absl::HexStringToBytes(signing_key_),
|
||||
absl::HexStringToBytes(signing_iv_), &signature)
|
||||
.ok()) {
|
||||
return Status(error::INTERNAL, "Failed to sign the request.");
|
||||
}
|
||||
signed_request.set_signature(signature);
|
||||
signed_request.set_signer(absl::GetFlag(FLAGS_signing_provider));
|
||||
std::string signed_request_json;
|
||||
signed_request.set_signer(signing_provider_);
|
||||
|
||||
// NOTE: MessageToJsonString will automatically converts the 'request' and
|
||||
// 'signature' fields in SignedCasEncryptionRequest to base64, because they
|
||||
// are of type 'bytes'.
|
||||
if (!MessageToJsonString(signed_request, &signed_request_json).ok()) {
|
||||
if (!MessageToJsonString(signed_request, signed_request_json).ok()) {
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert signed request message to json.");
|
||||
}
|
||||
LOG(INFO) << "Json SignedCasEncryptionRequest: " << signed_request_json;
|
||||
LOG(INFO) << "Json SignedCasEncryptionRequest: " << *signed_request_json;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
// Makes HTTP request against License Server.
|
||||
std::string http_response_json;
|
||||
Status status = MakeHttpRequest(signed_request_json, &http_response_json);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
Status WvCasKeyFetcher::ParseEntitlementResponse(
|
||||
const std::string& http_response_json,
|
||||
std::vector<EntitlementKeyInfo>* entitlements) {
|
||||
if (http_response_json.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "Response std::string is empty."};
|
||||
}
|
||||
if (entitlements == nullptr) {
|
||||
return {error::INVALID_ARGUMENT, "entitlements is null."};
|
||||
}
|
||||
if (!entitlements->empty()) {
|
||||
return {error::INVALID_ARGUMENT, "entitlements is not empty."};
|
||||
}
|
||||
|
||||
LOG(INFO) << "Json HTTP response: " << http_response_json;
|
||||
HttpResponse http_response;
|
||||
if (!JsonStringToMessage(http_response_json, &http_response).ok()) {
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert http response json to message.");
|
||||
}
|
||||
|
||||
// Processes signed response.
|
||||
LOG(INFO) << "Json CasEncryptionResponse: " << http_response.response();
|
||||
|
||||
CasEncryptionResponse response;
|
||||
if (!JsonStringToMessage(http_response.response(), &response).ok()) {
|
||||
return Status(error::INTERNAL,
|
||||
"Failed to convert response json to message.");
|
||||
}
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response.SerializeAsString());
|
||||
signed_response.SerializeToString(signed_response_string);
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
size_t AppendToString(void* ptr, size_t size, size_t count,
|
||||
std::string* output) {
|
||||
const absl::string_view data(static_cast<char*>(ptr), size * count);
|
||||
absl::StrAppend(output, data);
|
||||
return data.size();
|
||||
}
|
||||
|
||||
Status WvCasKeyFetcher::MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const {
|
||||
CHECK(http_response_json);
|
||||
if (absl::GetFlag(FLAGS_license_server).empty()) {
|
||||
return Status(error::INVALID_ARGUMENT, "Flag 'license_server' is empty");
|
||||
if (response.status() != CasEncryptionResponse::OK) {
|
||||
return Status(error::INTERNAL, absl::StrCat("Failure reported by server: ",
|
||||
response.status(), " : ",
|
||||
response.status_message()));
|
||||
}
|
||||
CURL* curl;
|
||||
CURLcode curl_code;
|
||||
curl = curl_easy_init();
|
||||
if (curl) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL,
|
||||
absl::GetFlag(FLAGS_license_server).c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, signed_request_json.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, http_response_json);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &AppendToString);
|
||||
// If we don't provide POSTFIELDSIZE, libcurl will strlen() by itself.
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
|
||||
(int64_t)strlen(signed_request_json.c_str()));
|
||||
curl_code = curl_easy_perform(curl);
|
||||
if (curl_code != CURLE_OK) {
|
||||
return Status(error::INTERNAL,
|
||||
"curl_easy_perform() failed: " +
|
||||
std::string(curl_easy_strerror(curl_code)));
|
||||
if (response.entitlement_keys().empty()) {
|
||||
return {error::INTERNAL,
|
||||
"Malformed entitlement response: missing entitlement keys field."};
|
||||
}
|
||||
|
||||
// Scan available entitlement keys for the ones that are needed.
|
||||
// For non-key-rotation, this is a key with a track type and not even or odd.
|
||||
// For key rotation, this is a key with a track type and even or odd.
|
||||
for (const auto& key : response.entitlement_keys()) {
|
||||
if (!key.has_track_type()) {
|
||||
return {error::INTERNAL,
|
||||
"Malformed entitlement response: missing track type field."};
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
} else {
|
||||
return Status(error::INTERNAL, "curl_easy_init() failed");
|
||||
if (!key.has_key_slot()) {
|
||||
return {error::INTERNAL,
|
||||
"Malformed entitlement response: missing key slot field."};
|
||||
}
|
||||
|
||||
entitlements->emplace_back();
|
||||
EntitlementKeyInfo* entitlement = &entitlements->back();
|
||||
entitlement->key_id = key.key_id();
|
||||
entitlement->key_value = key.key();
|
||||
entitlement->track_type = key.track_type();
|
||||
// EVEN or SINGLE are both marked as is_even_key.
|
||||
entitlement->is_even_key =
|
||||
key.key_slot() != CasEncryptionResponse::KeyInfo::ODD;
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status WvCasKeyFetcher::FetchEntitlements(
|
||||
const EntitlementRequestParams& request_params,
|
||||
std::vector<EntitlementKeyInfo>* entitlements) const {
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
Status status =
|
||||
CreateEntitlementRequest(request_params, &entitlement_request);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
status = MakeHttpRequest(entitlement_request, &entitlement_response);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
return ParseEntitlementResponse(entitlement_response, entitlements);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -11,50 +11,86 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "absl/flags/declare.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/key_fetcher.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// Paramerters that are needed to construct an entitlement key rquest message.
|
||||
// Fields:
|
||||
// |content_id| uniquely identifies the content (with |content_provider|)
|
||||
// |content_provider| unique std::string for provider of the content stream.
|
||||
// |track_types| a vector of track ID (std::string) that specify the set of track
|
||||
// types of interest; controls the entitlement keys returned by the
|
||||
// server.
|
||||
// |key_rotation| the encryption uses two keys if set. Two entitlement keys
|
||||
// are requested for each track type.
|
||||
struct EntitlementRequestParams {
|
||||
std::string content_id;
|
||||
std::string content_provider;
|
||||
std::vector<std::string> track_types;
|
||||
bool key_rotation;
|
||||
};
|
||||
|
||||
// WV CAS KeyFetcher. Performs the communication with the Widevine License
|
||||
// Server to get entitlement keys on behalf of a WvCasEcm object.
|
||||
class WvCasKeyFetcher : public KeyFetcher {
|
||||
class WvCasKeyFetcher {
|
||||
public:
|
||||
WvCasKeyFetcher() = default;
|
||||
// Initialize an WvCasKeyFetcher object with required infomartion.
|
||||
// Args:
|
||||
// |signing_provider| name of the provider signing the CAS encryption
|
||||
// request.
|
||||
// |signing_key| AES key (in hex) for signing the CAS encryption request.
|
||||
// |signing_iv| AES iv (in hex) for signing the CAS encryption request.
|
||||
WvCasKeyFetcher(const std::string& signing_provider,
|
||||
const std::string& signing_key,
|
||||
const std::string& signing_iv);
|
||||
WvCasKeyFetcher(const WvCasKeyFetcher&) = delete;
|
||||
WvCasKeyFetcher& operator=(const WvCasKeyFetcher&) = delete;
|
||||
~WvCasKeyFetcher() override = default;
|
||||
virtual ~WvCasKeyFetcher() = default;
|
||||
|
||||
// Get entitlement keys from the license server. Send a
|
||||
// SignedCasEncryptionRequest message to the license server, receive a
|
||||
// SignedCasEncryptionResponse and return it to the caller.
|
||||
// Create a SignedCasEncryptionRequest message that can be used to request
|
||||
// entitlement keys from a server.
|
||||
// Args:
|
||||
// |request_string| a serialized CasEncryptionRequest message, produced
|
||||
// by WvCasEcm::Initialize().
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// widevine::cas::Ecm::ProcessCasEncryptionResponse().
|
||||
Status RequestEntitlementKey(
|
||||
const std::string& request_string,
|
||||
std::string* signed_response_string) const override;
|
||||
// |request_params| paramerters that are needed to request entitlement keys
|
||||
// from the server.
|
||||
// |signed_request_json| the created serialized SignedCasEncryptionRequest
|
||||
// message.
|
||||
virtual Status CreateEntitlementRequest(
|
||||
const EntitlementRequestParams& request_params,
|
||||
std::string* signed_request_json) const;
|
||||
|
||||
// Parse a CasEncryptionResponse message holding the entitlement keys for
|
||||
// generating the ECM stream. The entitlement keys are used to encrypt the
|
||||
// keys conveyed in the ECM. The entitlement key IDs are also part of the ECM.
|
||||
// Args:
|
||||
// |response_string| a serialized CasEncryptionRequest message from the server
|
||||
// holding entitlement key information (or error information).
|
||||
// |entitlements| a pointer holding the resulting entitlement keys parsed from
|
||||
// the response string.
|
||||
static Status ParseEntitlementResponse(
|
||||
const std::string& http_response_json,
|
||||
std::vector<EntitlementKeyInfo>* entitlements);
|
||||
|
||||
protected:
|
||||
// Makes a HTTP request to License Server for entitlement key(s).
|
||||
// Returns the HTTP response in Json format in |http_response_json|.
|
||||
// Protected visibility to support unit testing.
|
||||
virtual Status MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const;
|
||||
std::string* http_response_json) const = 0;
|
||||
|
||||
// Conventient function that calls CreateEntitlementRequest,
|
||||
// MakeHttpRequest, and ParseEntitlementResponse in sequence.
|
||||
virtual Status FetchEntitlements(
|
||||
const EntitlementRequestParams& request_params,
|
||||
std::vector<EntitlementKeyInfo>* entitlements) const;
|
||||
|
||||
private:
|
||||
std::string signing_provider_;
|
||||
std::string signing_key_;
|
||||
std::string signing_iv_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
// Exposed for testing and example.
|
||||
ABSL_DECLARE_FLAG(std::string, license_server);
|
||||
ABSL_DECLARE_FLAG(std::string, signing_provider);
|
||||
ABSL_DECLARE_FLAG(std::string, signing_key);
|
||||
ABSL_DECLARE_FLAG(std::string, signing_iv);
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_KEY_FETCHER_H_
|
||||
|
||||
@@ -9,12 +9,10 @@
|
||||
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "google/protobuf/text_format.h"
|
||||
#include "google/protobuf/util/json_util.h"
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "common/status.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
using testing::_;
|
||||
@@ -45,64 +43,233 @@ const char kHttpResponse[] =
|
||||
"dF9rZXlzIjpbeyJrZXlfaWQiOiJNUGFndXhNb1hNNkUxUzhEOUF3RkNBPT0iLCJrZXkiOiJoZ1"
|
||||
"JycmdqeUg4NjQycjY3VHd0OHJ1cU5MUGNMRmtKcWRVSUROdm5GZDBNPSIsInRyYWNrX3R5cGUi"
|
||||
"OiJTRCIsImtleV9zbG90IjoiU0lOR0xFIn1dfQ==\"}";
|
||||
|
||||
const char kTrackTypeSD[] = "SD";
|
||||
const char kTrackTypeHD[] = "HD";
|
||||
const char kProvider[] = "widevine";
|
||||
const char kContentId[] = "21140844";
|
||||
} // namespace
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
class MockWvCasKeyFetcher : public WvCasKeyFetcher {
|
||||
class HardcodedWvCasKeyFetcher : public WvCasKeyFetcher {
|
||||
public:
|
||||
MockWvCasKeyFetcher() : WvCasKeyFetcher() {}
|
||||
~MockWvCasKeyFetcher() override {}
|
||||
HardcodedWvCasKeyFetcher(const std::string& signing_provider,
|
||||
const std::string& signing_key,
|
||||
const std::string& signing_iv)
|
||||
: WvCasKeyFetcher(signing_provider, signing_key, signing_iv) {}
|
||||
~HardcodedWvCasKeyFetcher() override {}
|
||||
MOCK_CONST_METHOD2(MakeHttpRequest,
|
||||
Status(const std::string& signed_request_json,
|
||||
std::string* http_response_json));
|
||||
};
|
||||
|
||||
class WvCasKeyFetcherTest : public ::testing::Test {
|
||||
class MockWvCasKeyFetcher : public WvCasKeyFetcher {
|
||||
public:
|
||||
WvCasKeyFetcherTest() {}
|
||||
void SetUp() override {
|
||||
absl::SetFlag(&FLAGS_signing_provider, kSigningProvider);
|
||||
absl::SetFlag(&FLAGS_signing_key, kSingingKey);
|
||||
absl::SetFlag(&FLAGS_signing_iv, kSingingIv);
|
||||
MockWvCasKeyFetcher(const std::string& signing_provider,
|
||||
const std::string& signing_key,
|
||||
const std::string& signing_iv)
|
||||
: WvCasKeyFetcher(signing_provider, signing_key, signing_iv) {}
|
||||
~MockWvCasKeyFetcher() override {}
|
||||
void set_report_status_ok(bool report_ok) { report_status_ok_ = report_ok; }
|
||||
|
||||
CHECK(
|
||||
google::protobuf::TextFormat::ParseFromString(kCasEncryptionRequest, &request_));
|
||||
Status MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const override {
|
||||
SignedCasEncryptionRequest signed_request;
|
||||
if (!google::protobuf::util::JsonStringToMessage(signed_request_json, &signed_request)
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Unable to parse signed_request_json.";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
response_.set_status(CasEncryptionResponse::OK);
|
||||
response_.set_content_id("21140844");
|
||||
CasEncryptionResponse::KeyInfo* entitlement_keys =
|
||||
response_.add_entitlement_keys();
|
||||
CHECK(absl::Base64Unescape("MPaguxMoXM6E1S8D9AwFCA==",
|
||||
entitlement_keys->mutable_key_id()));
|
||||
CHECK(absl::Base64Unescape("hgRrrgjyH8642r67Twt8ruqNLPcLFkJqdUIDNvnFd0M=",
|
||||
entitlement_keys->mutable_key()));
|
||||
entitlement_keys->set_track_type("SD");
|
||||
entitlement_keys->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
|
||||
CasEncryptionRequest request;
|
||||
if (!google::protobuf::util::JsonStringToMessage(signed_request.request(), &request)
|
||||
.ok()) {
|
||||
LOG(ERROR) << "Unable to understand signed_request_json.";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
CasEncryptionResponse response;
|
||||
if (!report_status_ok_) {
|
||||
response.set_status(CasEncryptionResponse::INTERNAL_ERROR);
|
||||
} else {
|
||||
response.set_status(CasEncryptionResponse::OK);
|
||||
response.set_content_id(request.content_id());
|
||||
for (const auto& track_type : request.track_types()) {
|
||||
if (request.key_rotation()) {
|
||||
// Add the Even key.
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
|
||||
// Add the Odd key.
|
||||
key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
|
||||
} else {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string response_string;
|
||||
if (!google::protobuf::util::MessageToJsonString(response, &response_string).ok()) {
|
||||
LOG(ERROR) << "MessageToJsonString(response, &response_string)";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response_string);
|
||||
|
||||
if (!google::protobuf::util::MessageToJsonString(signed_response, http_response_json)
|
||||
.ok()) {
|
||||
LOG(ERROR) << "MessageToJsonString(signed_response, http_response_json)";
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
protected:
|
||||
MockWvCasKeyFetcher mock_key_fetcher_;
|
||||
CasEncryptionRequest request_;
|
||||
CasEncryptionResponse response_;
|
||||
bool report_status_ok_ = true;
|
||||
};
|
||||
|
||||
class WvCasKeyFetcherTest : public ::testing::Test {
|
||||
public:
|
||||
WvCasKeyFetcherTest() {}
|
||||
|
||||
protected:
|
||||
EntitlementRequestParams CreateRequestParamsStruct(
|
||||
const std::string& content_id, const std::string& content_provider,
|
||||
const std::vector<std::string>& track_types, bool key_rotation) {
|
||||
EntitlementRequestParams request_params;
|
||||
request_params.content_id = content_id;
|
||||
request_params.content_provider = content_provider;
|
||||
request_params.track_types.assign(track_types.begin(), track_types.end());
|
||||
request_params.key_rotation = key_rotation;
|
||||
return request_params;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
|
||||
EXPECT_CALL(mock_key_fetcher_,
|
||||
MakeHttpRequest(kSignedCasEncryptionRequest, _))
|
||||
HardcodedWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
|
||||
kSingingIv);
|
||||
EntitlementRequestParams request_params = CreateRequestParamsStruct(
|
||||
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation=*/false);
|
||||
std::string signed_request_json;
|
||||
mock_key_fetcher.CreateEntitlementRequest(request_params,
|
||||
&signed_request_json);
|
||||
EXPECT_EQ(signed_request_json, kSignedCasEncryptionRequest);
|
||||
|
||||
EXPECT_CALL(mock_key_fetcher, MakeHttpRequest(kSignedCasEncryptionRequest, _))
|
||||
.WillOnce(DoAll(SetArgumentPointee<1>(std::string(kHttpResponse)),
|
||||
Return(OkStatus())));
|
||||
|
||||
std::string actual_signed_response;
|
||||
EXPECT_OK(mock_key_fetcher_.RequestEntitlementKey(
|
||||
request_.SerializeAsString(), &actual_signed_response));
|
||||
EXPECT_OK(mock_key_fetcher.MakeHttpRequest(signed_request_json,
|
||||
&actual_signed_response));
|
||||
|
||||
SignedCasEncryptionResponse expected_signed_response;
|
||||
expected_signed_response.set_response(response_.SerializeAsString());
|
||||
EXPECT_EQ(expected_signed_response.SerializeAsString(),
|
||||
actual_signed_response);
|
||||
std::vector<EntitlementKeyInfo> entitlements;
|
||||
EXPECT_OK(mock_key_fetcher.ParseEntitlementResponse(actual_signed_response,
|
||||
&entitlements));
|
||||
|
||||
ASSERT_EQ(entitlements.size(), 1);
|
||||
const EntitlementKeyInfo& entitlement = entitlements.at(0);
|
||||
EXPECT_EQ(entitlement.track_type, "SD");
|
||||
EXPECT_EQ(entitlement.is_even_key, true);
|
||||
std::string expected_key_id;
|
||||
CHECK(absl::Base64Unescape("MPaguxMoXM6E1S8D9AwFCA==", &expected_key_id));
|
||||
EXPECT_EQ(entitlement.key_id, expected_key_id);
|
||||
std::string expected_key_value;
|
||||
CHECK(absl::Base64Unescape("hgRrrgjyH8642r67Twt8ruqNLPcLFkJqdUIDNvnFd0M=",
|
||||
&expected_key_value));
|
||||
EXPECT_EQ(entitlement.key_value, expected_key_value);
|
||||
}
|
||||
|
||||
TEST_F(WvCasKeyFetcherTest, OneKeyOK) {
|
||||
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
|
||||
kSingingIv);
|
||||
std::string request;
|
||||
EntitlementRequestParams request_params = CreateRequestParamsStruct(
|
||||
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ false);
|
||||
ASSERT_OK(
|
||||
mock_key_fetcher.CreateEntitlementRequest(request_params, &request));
|
||||
|
||||
std::string response;
|
||||
ASSERT_OK(mock_key_fetcher.MakeHttpRequest(request, &response));
|
||||
|
||||
std::vector<EntitlementKeyInfo> entitlements;
|
||||
ASSERT_OK(mock_key_fetcher.ParseEntitlementResponse(response, &entitlements));
|
||||
ASSERT_EQ(entitlements.size(), 1);
|
||||
const EntitlementKeyInfo& entitlement = entitlements[0];
|
||||
EXPECT_EQ(entitlement.track_type, kTrackTypeSD);
|
||||
EXPECT_EQ(entitlement.is_even_key, true);
|
||||
EXPECT_TRUE(!entitlement.key_id.empty());
|
||||
EXPECT_TRUE(!entitlement.key_value.empty());
|
||||
}
|
||||
|
||||
TEST_F(WvCasKeyFetcherTest, OneKeyConvenientOK) {
|
||||
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
|
||||
kSingingIv);
|
||||
EntitlementRequestParams request_params = CreateRequestParamsStruct(
|
||||
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ false);
|
||||
std::vector<EntitlementKeyInfo> entitlements;
|
||||
ASSERT_OK(mock_key_fetcher.FetchEntitlements(request_params, &entitlements));
|
||||
|
||||
ASSERT_EQ(entitlements.size(), 1);
|
||||
const EntitlementKeyInfo& entitlement = entitlements[0];
|
||||
EXPECT_EQ(entitlement.track_type, kTrackTypeSD);
|
||||
EXPECT_EQ(entitlement.is_even_key, true);
|
||||
EXPECT_TRUE(!entitlement.key_id.empty());
|
||||
EXPECT_TRUE(!entitlement.key_value.empty());
|
||||
}
|
||||
|
||||
TEST_F(WvCasKeyFetcherTest, TwoKeysOK) {
|
||||
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
|
||||
kSingingIv);
|
||||
EntitlementRequestParams request_params = CreateRequestParamsStruct(
|
||||
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ true);
|
||||
std::vector<EntitlementKeyInfo> entitlements;
|
||||
ASSERT_OK(mock_key_fetcher.FetchEntitlements(request_params, &entitlements));
|
||||
|
||||
ASSERT_EQ(entitlements.size(), 2);
|
||||
EXPECT_EQ(entitlements[0].track_type, kTrackTypeSD);
|
||||
EXPECT_EQ(entitlements[1].track_type, kTrackTypeSD);
|
||||
}
|
||||
|
||||
TEST_F(WvCasKeyFetcherTest, TwoTracksOK) {
|
||||
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
|
||||
kSingingIv);
|
||||
EntitlementRequestParams request_params = CreateRequestParamsStruct(
|
||||
kContentId, kProvider, {kTrackTypeSD, kTrackTypeHD},
|
||||
/*key_rotation*/ true);
|
||||
std::vector<EntitlementKeyInfo> entitlements;
|
||||
ASSERT_OK(mock_key_fetcher.FetchEntitlements(request_params, &entitlements));
|
||||
ASSERT_EQ(entitlements.size(), 4);
|
||||
}
|
||||
|
||||
TEST_F(WvCasKeyFetcherTest, BadResponseFail) {
|
||||
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
|
||||
kSingingIv);
|
||||
std::string request;
|
||||
EntitlementRequestParams request_params = CreateRequestParamsStruct(
|
||||
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ true);
|
||||
ASSERT_OK(
|
||||
mock_key_fetcher.CreateEntitlementRequest(request_params, &request));
|
||||
std::string response;
|
||||
|
||||
mock_key_fetcher.set_report_status_ok(false);
|
||||
ASSERT_OK(mock_key_fetcher.MakeHttpRequest(request, &response));
|
||||
|
||||
std::vector<EntitlementKeyInfo> entitlements;
|
||||
EXPECT_EQ(error::INTERNAL,
|
||||
mock_key_fetcher.ParseEntitlementResponse(response, &entitlements)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -20,30 +20,6 @@ using google::protobuf::util::MessageToJsonString;
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
static const char* kWvCasStatusMessage[] = {
|
||||
"OK", // OK = 0,
|
||||
"",
|
||||
"",
|
||||
"Invalid argument", // INVALID_ARGUMENT = 3,
|
||||
"",
|
||||
"Not found", // NOT_FOUND = 5,
|
||||
"Already exists", // ALREADY_EXISTS = 6,
|
||||
"Permission denied", // PERMISSION_DENIED = 7,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"Unimplemented", // UNIMPLEMENTED = 12,
|
||||
"Internal", // INTERNAL = 13,
|
||||
"Unavailable", // UNAVAILABLE = 14,
|
||||
};
|
||||
|
||||
std::string GetWvCasStatusMessage(WvCasStatus status) {
|
||||
static_assert(arraysize(kWvCasStatusMessage) == NUM_WV_CAS_STATUS,
|
||||
"mismatching status message and status.");
|
||||
return kWvCasStatusMessage[status];
|
||||
}
|
||||
|
||||
// Numeric value of crypto mode is the index into strings array.
|
||||
static const char* kCrypoModeStrings[] = {
|
||||
"AesCbc", "AesCtr", "DvbCsa2", "DvbCsa3", "AesOfb", "AesScte",
|
||||
@@ -109,8 +85,8 @@ bool StringToScramblingLevel(const std::string& str, ScramblingLevel* mode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WvCasStatus CreateWvCasEncryptionRequestJson(
|
||||
const WvCasEncryptionRequest& request, std::string* request_json) {
|
||||
Status CreateWvCasEncryptionRequestJson(const WvCasEncryptionRequest& request,
|
||||
std::string* request_json) {
|
||||
CHECK(request_json);
|
||||
|
||||
CasEncryptionRequest request_proto;
|
||||
@@ -131,14 +107,14 @@ WvCasStatus CreateWvCasEncryptionRequestJson(
|
||||
// to base64. For example content ID '21140844' becomes 'MjExNDA4NDQ='.
|
||||
if (!MessageToJsonString(request_proto, request_json, print_options).ok()) {
|
||||
LOG(ERROR) << "Failed to convert request message to json.";
|
||||
return INTERNAL;
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
return OK;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
WvCasStatus ParseWvCasEncryptionResponseJson(
|
||||
const std::string& response_json, WvCasEncryptionResponse* response) {
|
||||
Status ParseWvCasEncryptionResponseJson(const std::string& response_json,
|
||||
WvCasEncryptionResponse* response) {
|
||||
CHECK(response);
|
||||
|
||||
CasEncryptionResponse response_proto;
|
||||
@@ -146,7 +122,7 @@ WvCasStatus ParseWvCasEncryptionResponseJson(
|
||||
// 'bytes' type fields.
|
||||
if (!JsonStringToMessage(response_json, &response_proto).ok()) {
|
||||
LOG(ERROR) << "Failed to convert response json to message.";
|
||||
return INTERNAL;
|
||||
return Status(error::INTERNAL);
|
||||
}
|
||||
|
||||
response->status =
|
||||
@@ -163,7 +139,7 @@ WvCasStatus ParseWvCasEncryptionResponseJson(
|
||||
response->entitlement_keys.push_back(key_info);
|
||||
}
|
||||
|
||||
return OK;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -12,45 +12,11 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/status.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
enum WvCasStatus {
|
||||
// Success.
|
||||
OK = 0,
|
||||
|
||||
// Client specified an invalid argument.
|
||||
INVALID_ARGUMENT = 3,
|
||||
|
||||
// Some requested entity (e.g., file or directory) was not found.
|
||||
NOT_FOUND = 5,
|
||||
|
||||
// Some entity that we attempted to create (e.g., file or directory)
|
||||
// already exists.
|
||||
ALREADY_EXISTS = 6,
|
||||
|
||||
// The caller does not have permission to execute the specified
|
||||
// operation.
|
||||
PERMISSION_DENIED = 7,
|
||||
|
||||
// Operation is not implemented or not supported/enabled in this service.
|
||||
UNIMPLEMENTED = 12,
|
||||
|
||||
// Internal errors. Means some invariants expected by underlying
|
||||
// system has been broken. If you see one of these errors,
|
||||
// something is very broken.
|
||||
INTERNAL = 13,
|
||||
|
||||
// Operation is not implemented or not supported/enabled in this service.
|
||||
UNAVAILABLE = 14,
|
||||
|
||||
// Number of errors.
|
||||
NUM_WV_CAS_STATUS,
|
||||
};
|
||||
|
||||
// Returns the message std::string for the given WvCasStatus.
|
||||
std::string GetWvCasStatusMessage(WvCasStatus status);
|
||||
|
||||
// Crypto mode for encryption / decryption. ENUM value should be consistent with
|
||||
// ECM V2 definition. Largest supported value for this CryptoMode ENUM is 15.
|
||||
enum class CryptoMode : int {
|
||||
@@ -77,6 +43,28 @@ bool ScramblingLevelToString(ScramblingLevel mode, std::string* str);
|
||||
// Returns false if str is not a valid ScramblingLevel.
|
||||
bool StringToScramblingLevel(const std::string& str, ScramblingLevel* mode);
|
||||
|
||||
// Declare Content IV size in an ECM stream.
|
||||
// Content IVs may be encoded as 8 or 16 random bytes. The receiver is
|
||||
// responsible for appending 8 zeros to an 8-byte IV. All content IVs, once the
|
||||
// size is declared, must be the same size. Wrapped key IVs are always 16 bytes.
|
||||
enum EcmIvSize { kIvSize8 = 0, kIvSize16 = 1 };
|
||||
|
||||
// Information needed for the injected entitlement keys. Used for Ecm
|
||||
// initialization.
|
||||
// Fields:
|
||||
// |track_type| the track that the key is being used to encrypt.
|
||||
// |is_even_key| if true, this entitlement is an even key. For the single key
|
||||
// case (no key rotation), is_even_key is always true.
|
||||
// |key_id| key ID for this entitlement key, must be 16 bytes.
|
||||
// |key_value| entitlement key value, must be 32 bytes. Used to decrypt
|
||||
// content keys.
|
||||
struct EntitlementKeyInfo {
|
||||
std::string track_type;
|
||||
bool is_even_key;
|
||||
std::string key_id; // must be 16 bytes.
|
||||
std::string key_value; // must be 32 bytes.
|
||||
};
|
||||
|
||||
struct WvCasEncryptionRequest {
|
||||
std::string content_id;
|
||||
std::string provider;
|
||||
@@ -124,15 +112,15 @@ struct WvCasEncryptionResponse {
|
||||
// request JSON message.
|
||||
// And that signed JSON message can be sent to Widevine license server for
|
||||
// acquiring entitlement keys.
|
||||
WvCasStatus CreateWvCasEncryptionRequestJson(
|
||||
const WvCasEncryptionRequest& request, std::string* request_json);
|
||||
Status CreateWvCasEncryptionRequestJson(const WvCasEncryptionRequest& request,
|
||||
std::string* request_json);
|
||||
|
||||
// Parses a WvCasEncryptionResponse in JSON format, returns a
|
||||
// WvCasEncryptionResponse.
|
||||
// |response_json| is supposed to be the 'response' field in the signed
|
||||
// response from Widevine license server.
|
||||
WvCasStatus ParseWvCasEncryptionResponseJson(const std::string& response_json,
|
||||
WvCasEncryptionResponse* response);
|
||||
Status ParseWvCasEncryptionResponseJson(const std::string& response_json,
|
||||
WvCasEncryptionResponse* response);
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -8,22 +8,12 @@
|
||||
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
TEST(WvCasTypesTest, GetWvCasStatusMessage) {
|
||||
EXPECT_EQ("OK", GetWvCasStatusMessage(OK));
|
||||
EXPECT_EQ("Invalid argument", GetWvCasStatusMessage(INVALID_ARGUMENT));
|
||||
EXPECT_EQ("Not found", GetWvCasStatusMessage(NOT_FOUND));
|
||||
EXPECT_EQ("Already exists", GetWvCasStatusMessage(ALREADY_EXISTS));
|
||||
EXPECT_EQ("Permission denied", GetWvCasStatusMessage(PERMISSION_DENIED));
|
||||
EXPECT_EQ("Unimplemented", GetWvCasStatusMessage(UNIMPLEMENTED));
|
||||
EXPECT_EQ("Internal", GetWvCasStatusMessage(INTERNAL));
|
||||
EXPECT_EQ("Unavailable", GetWvCasStatusMessage(UNAVAILABLE));
|
||||
}
|
||||
|
||||
TEST(WvCasTypesTest, CryptoModeToString) {
|
||||
std::string crypto_mode;
|
||||
ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesCtr, &crypto_mode));
|
||||
@@ -86,8 +76,7 @@ TEST(WvCasTypesTest, CreateWvCasEncryptionRequestJson) {
|
||||
request.key_rotation = true;
|
||||
|
||||
std::string actual_request_json;
|
||||
EXPECT_EQ(OK,
|
||||
CreateWvCasEncryptionRequestJson(request, &actual_request_json));
|
||||
EXPECT_OK(CreateWvCasEncryptionRequestJson(request, &actual_request_json));
|
||||
|
||||
// Content_id has been base64 encoded.
|
||||
std::string expected_request_json =
|
||||
@@ -107,8 +96,7 @@ TEST(WvCasTypesTest, ParseWvCasEncryptionResponseJson) {
|
||||
"\"key_slot\":\"ODD\"}]}";
|
||||
|
||||
WvCasEncryptionResponse actual_response;
|
||||
EXPECT_EQ(OK,
|
||||
ParseWvCasEncryptionResponseJson(response_json, &actual_response));
|
||||
EXPECT_OK(ParseWvCasEncryptionResponseJson(response_json, &actual_response));
|
||||
|
||||
EXPECT_EQ(WvCasEncryptionResponse::Status::OK, actual_response.status);
|
||||
// 21140844 is base64 decode of "MjExNDA4NDQ=".
|
||||
|
||||
@@ -46,11 +46,9 @@ ABSL_FLAG(int32_t, number_of_content_keys, 2,
|
||||
"key) or 2 (key rotation enabled). It also sets LeadCw as "
|
||||
"number_of_content_keys - 1.");
|
||||
ABSL_FLAG(std::string, crypto_mode, "AesCtr",
|
||||
"Encryption mode. Choices are \"AesCtr\", \"AesCbc\", "
|
||||
"\"DvbCsa2\", \"DvbCsa3\", \"AesOfb\", \"AesScte\".");
|
||||
ABSL_FLAG(bool, use_fixed_fetcher, false,
|
||||
"If set, use fixed fetcher to fetch mocked entitlement licenses when "
|
||||
"needed for testing purposes.");
|
||||
"Default encryption mode if not provided in API calls. Choices are "
|
||||
"\"AesCtr\", \"AesCbc\", \"DvbCsa2\", \"DvbCsa3\", \"AesOfb\" and "
|
||||
"\"AesScte\".");
|
||||
|
||||
#define LISTEN_QUEUE_SIZE (20)
|
||||
#define BUFFER_SIZE (1024)
|
||||
@@ -73,7 +71,6 @@ void BuildEcmgConfig(EcmgConfig* config) {
|
||||
CHECK(StringToCryptoMode(absl::GetFlag(FLAGS_crypto_mode),
|
||||
&config->crypto_mode))
|
||||
<< "Unknown crypto mode.";
|
||||
config->use_fixed_fetcher = absl::GetFlag(FLAGS_use_fixed_fetcher);
|
||||
}
|
||||
|
||||
void PrintMessage(const std::string& description, const char* const message,
|
||||
|
||||
@@ -43,6 +43,9 @@ ABSL_FLAG(int32_t, data_id, 0, "EMMG data_id.");
|
||||
ABSL_FLAG(int32_t, data_type, 1, "EMMG data_type");
|
||||
ABSL_FLAG(std::string, content_provider, "", "Content provider");
|
||||
ABSL_FLAG(std::string, content_id, "", "Content id");
|
||||
ABSL_FLAG(int32_t, bandwidth, 100, "Requested bandwidth in kbps");
|
||||
ABSL_FLAG(int32_t, max_num_message, 100,
|
||||
"Maximum number of messages that can be sent");
|
||||
|
||||
#define BUFFER_SIZE (1024)
|
||||
|
||||
@@ -56,6 +59,8 @@ void BuildEmmgConfig(widevine::cas::EmmgConfig *config) {
|
||||
config->data_type = absl::GetFlag(FLAGS_data_type);
|
||||
config->content_provider = absl::GetFlag(FLAGS_content_provider);
|
||||
config->content_id = absl::GetFlag(FLAGS_content_id);
|
||||
config->bandwidth = absl::GetFlag(FLAGS_bandwidth);
|
||||
config->max_num_message = absl::GetFlag(FLAGS_max_num_message);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "binary_release_files",
|
||||
srcs = glob(["**"]),
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "media_cas_encryption_proto",
|
||||
srcs = ["media_cas_encryption.proto"],
|
||||
|
||||
Reference in New Issue
Block a user