Add custom key fetcher callback to Simulcrypt ECMG
This commit is contained in:
@@ -1,9 +1,15 @@
|
|||||||
// Copyright 2017 Google LLC. All rights reserved.
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright 2017 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 VIDEO_WIDEVINE_EXPORT_COMMON_CERTIFICATE_TYPE_H_
|
#ifndef COMMON_CERTIFICATE_TYPE_H_
|
||||||
#define VIDEO_WIDEVINE_EXPORT_COMMON_CERTIFICATE_TYPE_H_
|
#define COMMON_CERTIFICATE_TYPE_H_
|
||||||
|
|
||||||
namespace video_widevine {
|
namespace widevine {
|
||||||
|
|
||||||
enum CertificateType {
|
enum CertificateType {
|
||||||
kCertificateTypeTesting,
|
kCertificateTypeTesting,
|
||||||
@@ -11,6 +17,6 @@ enum CertificateType {
|
|||||||
kCertificateTypeProduction,
|
kCertificateTypeProduction,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace video_widevine
|
} // namespace widevine
|
||||||
|
|
||||||
#endif // VIDEO_WIDEVINE_EXPORT_COMMON_CERTIFICATE_TYPE_H_
|
#endif // COMMON_CERTIFICATE_TYPE_H_
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
// Copyright 2020 Google LLC. All rights reserved.
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Description:
|
// Description:
|
||||||
// Container of Widevine default security profiless.
|
// Container of Widevine default security profiless.
|
||||||
|
|
||||||
#ifndef VIDEO_WIDEVINE_EXPORT_COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_
|
#ifndef COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_
|
||||||
#define VIDEO_WIDEVINE_EXPORT_COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_
|
#define COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_
|
||||||
|
|
||||||
#include "video/widevine/export/common/security_profile_list.h"
|
#include "common/security_profile_list.h"
|
||||||
|
|
||||||
namespace video_widevine {
|
namespace widevine {
|
||||||
|
|
||||||
class DefaultDeviceSecurityProfileList : public SecurityProfileList {
|
class DefaultDeviceSecurityProfileList : public SecurityProfileList {
|
||||||
public:
|
public:
|
||||||
@@ -28,6 +34,6 @@ class DefaultDeviceSecurityProfileList : public SecurityProfileList {
|
|||||||
std::vector<std::string>* default_profile_strings) const;
|
std::vector<std::string>* default_profile_strings) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace video_widevine
|
} // namespace widevine
|
||||||
|
|
||||||
#endif // VIDEO_WIDEVINE_EXPORT_COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_
|
#endif // COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
// Copyright 2020 Google LLC. All rights reserved.
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Description:
|
// Description:
|
||||||
// Container of device security profiles. Security profiles indicate rules
|
// Container of device security profiles. Security profiles indicate rules
|
||||||
// to allow using the profile. The rules are based on DRM capabilities of a
|
// to allow using the profile. The rules are based on DRM capabilities of a
|
||||||
// device.
|
// device.
|
||||||
|
|
||||||
#ifndef VIDEO_WIDEVINE_EXPORT_COMMON_SECURITY_PROFILE_LIST_H_
|
#ifndef COMMON_SECURITY_PROFILE_LIST_H_
|
||||||
#define VIDEO_WIDEVINE_EXPORT_COMMON_SECURITY_PROFILE_LIST_H_
|
#define COMMON_SECURITY_PROFILE_LIST_H_
|
||||||
|
|
||||||
#include "third_party/absl/synchronization/mutex.h"
|
#include "absl/synchronization/mutex.h"
|
||||||
#include "video/widevine/protos/public/client_identification.proto.h"
|
#include "protos/public/client_identification.pb.h"
|
||||||
#include "video/widevine/protos/public/device_security_profile_data.proto.h"
|
#include "protos/public/device_security_profile_data.pb.h"
|
||||||
#include "video/widevine/protos/public/provisioned_device_info.proto.h"
|
#include "protos/public/provisioned_device_info.pb.h"
|
||||||
#include "video/widevine/protos/public/security_profile.proto.h"
|
#include "protos/public/security_profile.pb.h"
|
||||||
|
|
||||||
namespace video_widevine {
|
namespace widevine {
|
||||||
using ClientCapabilities = ClientIdentification::ClientCapabilities;
|
using ClientCapabilities = ClientIdentification::ClientCapabilities;
|
||||||
|
|
||||||
// The SecurityProfileList will hold all security profiles. During license
|
// The SecurityProfileList will hold all security profiles. During license
|
||||||
@@ -76,10 +82,10 @@ class SecurityProfileList {
|
|||||||
const ClientIdentification& client_id,
|
const ClientIdentification& client_id,
|
||||||
const ProvisionedDeviceInfo& device_info) const;
|
const ProvisionedDeviceInfo& device_info) const;
|
||||||
|
|
||||||
int64 GetCurrentTimeSeconds() const;
|
int64_t GetCurrentTimeSeconds() const;
|
||||||
|
|
||||||
bool IsProfileActive(const SecurityProfile& profile,
|
bool IsProfileActive(const SecurityProfile& profile,
|
||||||
int64 current_time_seconds) const;
|
int64_t current_time_seconds) const;
|
||||||
|
|
||||||
mutable absl::Mutex mutex_;
|
mutable absl::Mutex mutex_;
|
||||||
// Security profiles
|
// Security profiles
|
||||||
@@ -87,5 +93,5 @@ class SecurityProfileList {
|
|||||||
std::vector<SecurityProfile> security_profiles_ ABSL_GUARDED_BY(mutex_);
|
std::vector<SecurityProfile> security_profiles_ ABSL_GUARDED_BY(mutex_);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace video_widevine
|
} // namespace widevine
|
||||||
#endif // VIDEO_WIDEVINE_EXPORT_COMMON_SECURITY_PROFILE_LIST_H_
|
#endif // COMMON_SECURITY_PROFILE_LIST_H_
|
||||||
|
|||||||
@@ -79,3 +79,13 @@ cc_binary(
|
|||||||
"//media_cas_packager_sdk/public:wv_cas_emm",
|
"//media_cas_packager_sdk/public:wv_cas_emm",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cc_binary(
|
||||||
|
name = "wv_ecmg_example",
|
||||||
|
srcs = ["wv_ecmg_example.cc"],
|
||||||
|
deps = [
|
||||||
|
"//base",
|
||||||
|
"//media_cas_packager_sdk/public:wv_cas_ecmg_client_handler",
|
||||||
|
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|||||||
201
example/wv_ecmg_example.cc
Normal file
201
example/wv_ecmg_example.cc
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Example of Simulcrypt ECMG with custom entitlement key fetcher.
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include "media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h"
|
||||||
|
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using widevine::cas::EcmgConfig;
|
||||||
|
using widevine::cas::EntitlementKeyInfo;
|
||||||
|
using widevine::cas::WvCasEcmgClientHandler;
|
||||||
|
|
||||||
|
constexpr int kServerPortNumber = 1234;
|
||||||
|
constexpr int kListenQueueSize = 20;
|
||||||
|
constexpr int kBufferSizeBytes = 2048;
|
||||||
|
|
||||||
|
void BuildEcmgConfig(EcmgConfig* config) {
|
||||||
|
config->delay_start = 200; // in milliseconds.
|
||||||
|
config->delay_stop = 200; // in milliseconds.
|
||||||
|
config->ecm_rep_period = 100; // in milliseconds.
|
||||||
|
config->max_comp_time = 100; // in milliseconds.
|
||||||
|
config->access_criteria_transfer_mode = 0;
|
||||||
|
config->number_of_content_keys = 2;
|
||||||
|
config->crypto_mode = widevine::cas::CryptoMode::kAesCtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintMessage(const std::string& description, const char* const message,
|
||||||
|
size_t length) {
|
||||||
|
std::cout << description << std::endl;
|
||||||
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
printf("'\\x%02x', ", static_cast<uint16_t>(*(message + i)));
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServeClient(int socket_fd, WvCasEcmgClientHandler* ecmg) {
|
||||||
|
char request[kBufferSizeBytes];
|
||||||
|
char response[kBufferSizeBytes];
|
||||||
|
while (true) {
|
||||||
|
bzero(request, kBufferSizeBytes);
|
||||||
|
bzero(response, kBufferSizeBytes);
|
||||||
|
size_t response_length = 0;
|
||||||
|
size_t request_length = recv(socket_fd, request, kBufferSizeBytes, 0);
|
||||||
|
if (request_length == 0) {
|
||||||
|
std::cout << "No more request from client." << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (request_length < 0) {
|
||||||
|
std::cerr << "Failed to receive request from client." << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PrintMessage("Request", request, request_length);
|
||||||
|
ecmg->HandleRequest(kBufferSizeBytes, request, kBufferSizeBytes, response,
|
||||||
|
&response_length);
|
||||||
|
PrintMessage("Response", response, response_length);
|
||||||
|
if (send(socket_fd, response, response_length, 0) < 0) {
|
||||||
|
std::cerr << "Failed to send response to client." << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<EntitlementKeyInfo> MyEntitlementKeyFetcherFun(uint16_t channel_id,
|
||||||
|
uint16_t stream_id,
|
||||||
|
uint16_t ecm_id) {
|
||||||
|
// Fill the function to get entitlement keys based on the input parameters.
|
||||||
|
// Potential step 1: Convert the input {channel_id, stream_id, ecm_id} to
|
||||||
|
// EntitlementRequestParams defined in wv_cas_key_fetcher.h, such as
|
||||||
|
// content_id, content provider, etc.
|
||||||
|
// Potential step 2: Make the key fetch request. Refer to wv_cas_key_fetcher
|
||||||
|
// and wv_cas_curl_key_fetcher for APIs that can fetch keys from Widevine
|
||||||
|
// server with EntitlementRequestParams.
|
||||||
|
|
||||||
|
// The size of the returned vector must not exceed 2. When the vector size is
|
||||||
|
// 2, one of them must be specified as even key and the other must be odd key.
|
||||||
|
constexpr char track_types_hd[] = "HD";
|
||||||
|
constexpr char entitlement_key_id_even[] = "fake_key_id1....";
|
||||||
|
constexpr char entitlement_key_value_even[] =
|
||||||
|
"fakefakefakefakefakefakefake1...";
|
||||||
|
constexpr char entitlement_key_id_odd[] = "fake_key_id2....";
|
||||||
|
constexpr char entitlement_key_value_odd[] =
|
||||||
|
"fakefakefakefakefakefakefake2...";
|
||||||
|
return {{track_types_hd, /*is_even_key=*/true, entitlement_key_id_even,
|
||||||
|
entitlement_key_value_even},
|
||||||
|
{track_types_hd, /*is_even_key=*/false, entitlement_key_id_odd,
|
||||||
|
entitlement_key_value_odd}};
|
||||||
|
}
|
||||||
|
|
||||||
|
int create_listen_socket_fd() {
|
||||||
|
// Server address.
|
||||||
|
struct sockaddr_in server_address;
|
||||||
|
bzero(reinterpret_cast<char*>(&server_address), sizeof(server_address));
|
||||||
|
server_address.sin_family = AF_INET;
|
||||||
|
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
server_address.sin_port = htons(kServerPortNumber);
|
||||||
|
|
||||||
|
// Create a listening socket.
|
||||||
|
int listen_socket_fd = socket(AF_INET, SOCK_STREAM, /* protocol= */ 0);
|
||||||
|
if (listen_socket_fd < 0) {
|
||||||
|
std::cerr << "Failed to open listening socket" << std::endl;
|
||||||
|
}
|
||||||
|
// Set SO_REUSEADDR on a socket to true (1).
|
||||||
|
int optval = 1;
|
||||||
|
setsockopt(listen_socket_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
|
||||||
|
sizeof(optval));
|
||||||
|
// Bind address.
|
||||||
|
if (bind(listen_socket_fd,
|
||||||
|
reinterpret_cast<struct sockaddr*>(&server_address),
|
||||||
|
sizeof(server_address)) < 0) {
|
||||||
|
std::cerr << "Failed to bind on server socket" << std::endl;
|
||||||
|
}
|
||||||
|
return listen_socket_fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
EcmgConfig ecmg_config;
|
||||||
|
BuildEcmgConfig(&ecmg_config);
|
||||||
|
|
||||||
|
int listen_socket_fd = create_listen_socket_fd();
|
||||||
|
// Listen for connection from clients.
|
||||||
|
std::cout << "Server listening ..." << std::endl << std::flush;
|
||||||
|
int return_val = listen(listen_socket_fd, kListenQueueSize);
|
||||||
|
switch (return_val) {
|
||||||
|
case EADDRINUSE:
|
||||||
|
std::cerr << "Another socket is already listening on the same port."
|
||||||
|
<< std::endl;
|
||||||
|
break;
|
||||||
|
case EBADF:
|
||||||
|
std::cerr << "The argument sockfd is not a valid descriptor."
|
||||||
|
<< std::endl;
|
||||||
|
break;
|
||||||
|
case ENOTSOCK:
|
||||||
|
std::cerr << "The argument sockfd is not a socket." << std::endl;
|
||||||
|
break;
|
||||||
|
case EOPNOTSUPP:
|
||||||
|
std::cerr << "The socket is not of a type that supports the listen() "
|
||||||
|
"operation."
|
||||||
|
<< std::endl;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// While loop to serve different client connections.
|
||||||
|
while (true) {
|
||||||
|
struct sockaddr_in client_address;
|
||||||
|
socklen_t client_address_size = sizeof(client_address);
|
||||||
|
int client_socket_fd = accept(
|
||||||
|
listen_socket_fd, reinterpret_cast<struct sockaddr*>(&client_address),
|
||||||
|
&client_address_size);
|
||||||
|
std::cout << "\nTCP connection " << client_socket_fd << " start"
|
||||||
|
<< std::endl;
|
||||||
|
if (client_socket_fd < 0) {
|
||||||
|
std::cerr << "Failed to accept connection request from client"
|
||||||
|
<< std::endl;
|
||||||
|
} else {
|
||||||
|
if (fork() == 0) {
|
||||||
|
// Reaching here, I am in the child process.
|
||||||
|
close(listen_socket_fd);
|
||||||
|
WvCasEcmgClientHandler client_handler(ecmg_config);
|
||||||
|
client_handler.SetCustomEntitlementKeyFetcherFunc(
|
||||||
|
MyEntitlementKeyFetcherFun);
|
||||||
|
ServeClient(client_socket_fd, &client_handler);
|
||||||
|
std::cout << "\nTCP connection " << client_socket_fd << " closed"
|
||||||
|
<< std::endl;
|
||||||
|
close(client_socket_fd);
|
||||||
|
// Terminate child process.
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
// Reaching here, I am in parent process.
|
||||||
|
close(client_socket_fd);
|
||||||
|
}
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -111,6 +111,7 @@ cc_test(
|
|||||||
"@abseil_repo//absl/memory",
|
"@abseil_repo//absl/memory",
|
||||||
"@abseil_repo//absl/strings",
|
"@abseil_repo//absl/strings",
|
||||||
"@abseil_repo//absl/strings:str_format",
|
"@abseil_repo//absl/strings:str_format",
|
||||||
|
"@abseil_repo//absl/types:span",
|
||||||
"//example:test_ecmg_messages",
|
"//example:test_ecmg_messages",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -420,10 +420,10 @@ void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, uint16_t cp_numbe
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config) {
|
EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config)
|
||||||
|
: channel_id_set_(false), custom_key_fetcher_(nullptr) {
|
||||||
DCHECK(ecmg_config);
|
DCHECK(ecmg_config);
|
||||||
ecmg_config_ = ecmg_config;
|
ecmg_config_ = ecmg_config;
|
||||||
channel_id_set_ = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||||
@@ -608,6 +608,36 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to fetch entitlement keys using custom key fetcher if none is received.
|
||||||
|
std::vector<EntitlementIdKeyComb>& entitlements =
|
||||||
|
streams_info_[params.ecm_stream_id]->entitlement_comb;
|
||||||
|
if (entitlements.empty() && custom_key_fetcher_ != nullptr) {
|
||||||
|
const std::vector<EntitlementKeyInfo>& fetched_entitlements =
|
||||||
|
custom_key_fetcher_(params.ecm_channel_id, params.ecm_stream_id,
|
||||||
|
params.ecm_id);
|
||||||
|
// Only process if the fetched size is expected (1 or 2 entries).
|
||||||
|
if (fetched_entitlements.empty()) {
|
||||||
|
LOG(WARNING) << "EntitlementKeyFetcherFunc returned empty result.";
|
||||||
|
} else if (fetched_entitlements.size() > 2) {
|
||||||
|
LOG(ERROR)
|
||||||
|
<< "EntitlementKeyFetcherFunc should return at most 2 entries, but "
|
||||||
|
<< fetched_entitlements.size() << " entries are returned.";
|
||||||
|
} else {
|
||||||
|
entitlements.reserve(fetched_entitlements.size());
|
||||||
|
for (auto const& fetched_entitlement : fetched_entitlements) {
|
||||||
|
EntitlementIdKeyComb entitlement_comb;
|
||||||
|
entitlement_comb.key_id = fetched_entitlement.key_id;
|
||||||
|
entitlement_comb.key_value = fetched_entitlement.key_value;
|
||||||
|
// In the case of two entitlement keys, the first in |entitlements| must
|
||||||
|
// be even key followed by the odd key.
|
||||||
|
entitlements.insert(fetched_entitlement.is_even_key
|
||||||
|
? entitlements.begin()
|
||||||
|
: entitlements.end(),
|
||||||
|
entitlement_comb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BuildStreamStatus(params.ecm_channel_id, params.ecm_stream_id, params.ecm_id,
|
BuildStreamStatus(params.ecm_channel_id, params.ecm_stream_id, params.ecm_id,
|
||||||
ecmg_config_->access_criteria_transfer_mode, response,
|
ecmg_config_->access_criteria_transfer_mode, response,
|
||||||
response_length);
|
response_length);
|
||||||
|
|||||||
@@ -24,17 +24,6 @@
|
|||||||
namespace widevine {
|
namespace widevine {
|
||||||
namespace cas {
|
namespace cas {
|
||||||
|
|
||||||
// A struct that captures the Ecmg configs.
|
|
||||||
struct EcmgConfig {
|
|
||||||
int16_t delay_start;
|
|
||||||
int16_t delay_stop;
|
|
||||||
uint16_t ecm_rep_period;
|
|
||||||
uint16_t max_comp_time;
|
|
||||||
uint8_t access_criteria_transfer_mode;
|
|
||||||
uint8_t number_of_content_keys;
|
|
||||||
CryptoMode crypto_mode;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A struct that represent a CP_CW_Combination.
|
// A struct that represent a CP_CW_Combination.
|
||||||
struct EcmgCpCwCombination {
|
struct EcmgCpCwCombination {
|
||||||
uint16_t cp; // crypto period
|
uint16_t cp; // crypto period
|
||||||
@@ -91,8 +80,17 @@ class EcmgClientHandler {
|
|||||||
// Handle a |request| from the client.
|
// Handle a |request| from the client.
|
||||||
// If any response is generated, it would returned via |response|
|
// If any response is generated, it would returned via |response|
|
||||||
// and |response_length|.
|
// and |response_length|.
|
||||||
void HandleRequest(const char* const request, char* response,
|
virtual void HandleRequest(const char* const request, char* response,
|
||||||
size_t* response_length);
|
size_t* response_length);
|
||||||
|
|
||||||
|
// Sets the custom entitlement key fetching function used by ECMG to fetch
|
||||||
|
// entitlement keys. If entitlement key information is not received in
|
||||||
|
// Simulcrypt ECMG from SCS, ECMG will try to fetch entitlement keys using
|
||||||
|
// this function.
|
||||||
|
virtual void SetCustomEntitlementKeyFetcherFunc(
|
||||||
|
EntitlementKeyFetcherFunc fetcher) {
|
||||||
|
custom_key_fetcher_ = fetcher;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void HandleChannelSetup(const EcmgParameters& params, char* response,
|
void HandleChannelSetup(const EcmgParameters& params, char* response,
|
||||||
@@ -133,7 +131,7 @@ class EcmgClientHandler {
|
|||||||
|
|
||||||
EcmgConfig* ecmg_config_;
|
EcmgConfig* ecmg_config_;
|
||||||
// Per spec, "There is always one (and only one) channel per TCP connection".
|
// Per spec, "There is always one (and only one) channel per TCP connection".
|
||||||
bool channel_id_set_;
|
bool channel_id_set_ = false;
|
||||||
uint16_t channel_id_;
|
uint16_t channel_id_;
|
||||||
|
|
||||||
// Channel specific information.
|
// Channel specific information.
|
||||||
@@ -142,6 +140,8 @@ class EcmgClientHandler {
|
|||||||
|
|
||||||
// Map from ECM_stream_id to EcmgStreamInfo.
|
// Map from ECM_stream_id to EcmgStreamInfo.
|
||||||
absl::flat_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_;
|
absl::flat_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_;
|
||||||
|
// Used to fetch entitlement keys if none are received from SCS.
|
||||||
|
EntitlementKeyFetcherFunc custom_key_fetcher_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cas
|
} // namespace cas
|
||||||
|
|||||||
@@ -12,11 +12,14 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "testing/gmock.h"
|
#include "testing/gmock.h"
|
||||||
#include "testing/gunit.h"
|
#include "testing/gunit.h"
|
||||||
#include "absl/memory/memory.h"
|
#include "absl/memory/memory.h"
|
||||||
#include "absl/strings/str_cat.h"
|
#include "absl/strings/str_cat.h"
|
||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/types/span.h"
|
||||||
#include "example/test_ecmg_messages.h"
|
#include "example/test_ecmg_messages.h"
|
||||||
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
||||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||||
@@ -32,6 +35,7 @@ using simulcrypt_util::AddUint16Param;
|
|||||||
using simulcrypt_util::AddUint32Param;
|
using simulcrypt_util::AddUint32Param;
|
||||||
using simulcrypt_util::AddUint8Param;
|
using simulcrypt_util::AddUint8Param;
|
||||||
using simulcrypt_util::BuildMessageHeader;
|
using simulcrypt_util::BuildMessageHeader;
|
||||||
|
using ::testing::ElementsAreArray;
|
||||||
|
|
||||||
constexpr size_t kBufferSize = 1024;
|
constexpr size_t kBufferSize = 1024;
|
||||||
constexpr uint16_t kWidevineSystemId = 0x4AD4;
|
constexpr uint16_t kWidevineSystemId = 0x4AD4;
|
||||||
@@ -44,8 +48,10 @@ constexpr size_t kEcmId = 2;
|
|||||||
constexpr size_t kNominalCpDuration = 0x64;
|
constexpr size_t kNominalCpDuration = 0x64;
|
||||||
constexpr size_t kCpNumber = 0;
|
constexpr size_t kCpNumber = 0;
|
||||||
constexpr char kContentKeyEven[] = "0123456701234567";
|
constexpr char kContentKeyEven[] = "0123456701234567";
|
||||||
|
constexpr char kContentKeyIvEven[] = "0123456701234567";
|
||||||
constexpr char kContentKeyEven8Bytes[] = "01234567";
|
constexpr char kContentKeyEven8Bytes[] = "01234567";
|
||||||
constexpr char kContentKeyOdd[] = "abcdefghabcdefgh";
|
constexpr char kContentKeyOdd[] = "abcdefghabcdefgh";
|
||||||
|
constexpr char kContentKeyIvOdd[] = "abcdefghabcdefgh";
|
||||||
constexpr char kContentKeyOdd8Bytes[] = "abcdefgh";
|
constexpr char kContentKeyOdd8Bytes[] = "abcdefgh";
|
||||||
constexpr char kEntitlementKeyIdEven[] = "0123456701234567";
|
constexpr char kEntitlementKeyIdEven[] = "0123456701234567";
|
||||||
constexpr char kEntitlementKeyValueEven[] = "01234567012345670123456701234567";
|
constexpr char kEntitlementKeyValueEven[] = "01234567012345670123456701234567";
|
||||||
@@ -58,6 +64,14 @@ constexpr char kTrackTypesSD[] = "SD";
|
|||||||
constexpr char kTrackTypesHD[] = "HD";
|
constexpr char kTrackTypesHD[] = "HD";
|
||||||
constexpr absl::string_view kWrappedKeyIv = "0123456701234567";
|
constexpr absl::string_view kWrappedKeyIv = "0123456701234567";
|
||||||
|
|
||||||
|
std::vector<EntitlementKeyInfo> FakeEntitlementKeyFetcherFunc(
|
||||||
|
uint16_t /*channel_id*/, uint16_t /*stream_id*/, uint16_t /*ecm_id*/) {
|
||||||
|
return {{kTrackTypesSD, /*is_even_key*/ false, kEntitlementKeyIdOdd,
|
||||||
|
kEntitlementKeyValueOdd},
|
||||||
|
{kTrackTypesSD, /*is_even_key*/ true, kEntitlementKeyIdEven,
|
||||||
|
kEntitlementKeyValueEven}};
|
||||||
|
}
|
||||||
|
|
||||||
class MockEcmgClientHandler : public EcmgClientHandler {
|
class MockEcmgClientHandler : public EcmgClientHandler {
|
||||||
public:
|
public:
|
||||||
explicit MockEcmgClientHandler(EcmgConfig* ecmg_config)
|
explicit MockEcmgClientHandler(EcmgConfig* ecmg_config)
|
||||||
@@ -339,6 +353,33 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
|
|||||||
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
|
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(EcmgClientHandlerTest, SuccessSequenceFetchedEntitlements) {
|
||||||
|
handler_->SetCustomEntitlementKeyFetcherFunc(FakeEntitlementKeyFetcherFunc);
|
||||||
|
BuildChannelSetupRequest(kChannelId, kSuperCasId, /*age_restriction=*/0,
|
||||||
|
/*crypto_mode=*/"", request_, &request_len_);
|
||||||
|
handler_->HandleRequest(request_, response_, &response_len_);
|
||||||
|
EXPECT_EQ(response_len_, sizeof(kTestEcmgChannelStatus));
|
||||||
|
EXPECT_THAT(absl::MakeSpan(response_, response_len_),
|
||||||
|
ElementsAreArray(kTestEcmgChannelStatus));
|
||||||
|
|
||||||
|
// No entitlement keys are injected.
|
||||||
|
BuildStreamSetupRequest(
|
||||||
|
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kTrackTypesSD,
|
||||||
|
/*entitlements=*/{}, {kContentKeyIvEven, kContentKeyIvOdd}, request_,
|
||||||
|
&request_len_);
|
||||||
|
handler_->HandleRequest(request_, response_, &response_len_);
|
||||||
|
EXPECT_EQ(response_len_, sizeof(kTestEcmgStreamStatus));
|
||||||
|
|
||||||
|
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||||
|
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||||
|
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||||
|
request_, &request_len_);
|
||||||
|
EXPECT_THAT(absl::MakeSpan(response_, response_len_),
|
||||||
|
ElementsAreArray(kTestEcmgStreamStatus));
|
||||||
|
handler_->HandleRequest(request_, response_, &response_len_);
|
||||||
|
EXPECT_EQ(response_len_, sizeof(kTestEcmgEcmResponse));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(EcmgClientHandlerTest, SuccessSequenceCsa2) {
|
TEST_F(EcmgClientHandlerTest, SuccessSequenceCsa2) {
|
||||||
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
|
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
|
||||||
kCryptoModeCsa2, request_, &request_len_);
|
kCryptoModeCsa2, request_, &request_len_);
|
||||||
|
|||||||
@@ -189,6 +189,20 @@ cc_library(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "wv_cas_ecmg_client_handler",
|
||||||
|
srcs = ["wv_cas_ecmg_client_handler.cc"],
|
||||||
|
hdrs = ["wv_cas_ecmg_client_handler.h"],
|
||||||
|
deps = [
|
||||||
|
":wv_cas_types",
|
||||||
|
"//base",
|
||||||
|
"@abseil_repo//absl/memory",
|
||||||
|
"@abseil_repo//absl/strings",
|
||||||
|
"//common:status",
|
||||||
|
"//media_cas_packager_sdk/internal:ecmg_client_handler",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
cc_test(
|
cc_test(
|
||||||
name = "wv_cas_types_test",
|
name = "wv_cas_types_test",
|
||||||
size = "small",
|
size = "small",
|
||||||
@@ -210,6 +224,19 @@ cc_test(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "wv_cas_ecmg_client_handler_test",
|
||||||
|
srcs = ["wv_cas_ecmg_client_handler_test.cc"],
|
||||||
|
deps = [
|
||||||
|
":wv_cas_ecmg_client_handler",
|
||||||
|
":wv_cas_types",
|
||||||
|
"//testing:gunit_main",
|
||||||
|
"@abseil_repo//absl/memory",
|
||||||
|
"//common:status",
|
||||||
|
"//media_cas_packager_sdk/internal:ecmg_client_handler",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
cc_binary(
|
cc_binary(
|
||||||
name = "wv_ecmg",
|
name = "wv_ecmg",
|
||||||
srcs = ["wv_ecmg.cc"],
|
srcs = ["wv_ecmg.cc"],
|
||||||
|
|||||||
74
media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.cc
Normal file
74
media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.cc
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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_ecmg_client_handler.h"
|
||||||
|
|
||||||
|
#include "glog/logging.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
|
||||||
|
|
||||||
|
namespace widevine {
|
||||||
|
namespace cas {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr size_t kSimulcryptMessageHeaderSizeBytes = 5;
|
||||||
|
constexpr size_t kMinimumResponseBufferSizeBytes = 2048;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
WvCasEcmgClientHandler::WvCasEcmgClientHandler(const EcmgConfig& ecmg_config)
|
||||||
|
: ecmg_config_(ecmg_config),
|
||||||
|
inner_handler_(absl::make_unique<EcmgClientHandler>(&ecmg_config_)) {}
|
||||||
|
|
||||||
|
WvCasEcmgClientHandler::~WvCasEcmgClientHandler() = default;
|
||||||
|
|
||||||
|
WvCasEcmgClientHandler::WvCasEcmgClientHandler(
|
||||||
|
std::unique_ptr<EcmgClientHandler> inner_handler)
|
||||||
|
: inner_handler_(std::move(inner_handler)) {}
|
||||||
|
|
||||||
|
void WvCasEcmgClientHandler::SetCustomEntitlementKeyFetcherFunc(
|
||||||
|
EntitlementKeyFetcherFunc fetcher) {
|
||||||
|
return inner_handler_->SetCustomEntitlementKeyFetcherFunc(fetcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size,
|
||||||
|
const char* const request_buffer,
|
||||||
|
size_t response_buffer_size,
|
||||||
|
char* response_buffer,
|
||||||
|
size_t* response_length) {
|
||||||
|
if (request_buffer == nullptr || response_buffer == nullptr ||
|
||||||
|
response_length == nullptr) {
|
||||||
|
std::string error_message =
|
||||||
|
"request_buffer, response_buffer and response_length must "
|
||||||
|
"not be null pointer.";
|
||||||
|
LOG(ERROR) << error_message;
|
||||||
|
return {error::INVALID_ARGUMENT, error_message};
|
||||||
|
}
|
||||||
|
if (request_buffer_size < kSimulcryptMessageHeaderSizeBytes) {
|
||||||
|
std::string error_message =
|
||||||
|
absl::StrCat("request_buffer_size is too small. Minimum required is ",
|
||||||
|
kSimulcryptMessageHeaderSizeBytes, " bytes.");
|
||||||
|
LOG(ERROR) << error_message;
|
||||||
|
*response_length = 0;
|
||||||
|
return {error::INVALID_ARGUMENT, error_message};
|
||||||
|
}
|
||||||
|
if (response_buffer_size < kMinimumResponseBufferSizeBytes) {
|
||||||
|
std::string error_message =
|
||||||
|
absl::StrCat("response_buffer_size is too small. Minimum required is ",
|
||||||
|
kMinimumResponseBufferSizeBytes, " bytes.");
|
||||||
|
LOG(ERROR) << error_message;
|
||||||
|
*response_length = 0;
|
||||||
|
return {error::INVALID_ARGUMENT, error_message};
|
||||||
|
}
|
||||||
|
inner_handler_->HandleRequest(request_buffer, response_buffer,
|
||||||
|
response_length);
|
||||||
|
return OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cas
|
||||||
|
} // namespace widevine
|
||||||
67
media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h
Normal file
67
media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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_ECMG_CLIENT_HANDLER_H_
|
||||||
|
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECMG_CLIENT_HANDLER_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "common/status.h"
|
||||||
|
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||||
|
|
||||||
|
namespace widevine {
|
||||||
|
namespace cas {
|
||||||
|
|
||||||
|
// Forward declaration to avoid including internal headers.
|
||||||
|
class EcmgClientHandler;
|
||||||
|
|
||||||
|
// Handles requests from one and only one SCS client.
|
||||||
|
// WvCasEcmgClientHandler is NOT thread-safe.
|
||||||
|
class WvCasEcmgClientHandler {
|
||||||
|
public:
|
||||||
|
explicit WvCasEcmgClientHandler(const EcmgConfig& ecmg_config);
|
||||||
|
WvCasEcmgClientHandler(const WvCasEcmgClientHandler&) = delete;
|
||||||
|
WvCasEcmgClientHandler& operator=(const WvCasEcmgClientHandler&) = delete;
|
||||||
|
virtual ~WvCasEcmgClientHandler();
|
||||||
|
|
||||||
|
// Sets the custom entitlement key fetching function used by ECMG to fetch
|
||||||
|
// entitlement keys. If entitlement key information is not received in
|
||||||
|
// Simulcrypt ECMG from SCS, ECMG will try to fetch entitlement keys using
|
||||||
|
// this function.
|
||||||
|
// Calling this function is optional.
|
||||||
|
void SetCustomEntitlementKeyFetcherFunc(EntitlementKeyFetcherFunc fetcher);
|
||||||
|
|
||||||
|
// Handles a |request| from the SCS client. If any response is generated, it
|
||||||
|
// will return the response via |response_buffer| and |response_length|.
|
||||||
|
// Args:
|
||||||
|
// - |request_buffer_size| is the size of |request_buffer|.
|
||||||
|
// - |request_buffer| is the buffer that holds the request message.
|
||||||
|
// - |response_buffer_size| is the size of |response_buffer|. Must be at large
|
||||||
|
// enough to hold the max possible response message (at least 2048 bytes).
|
||||||
|
// - |response_buffer| is the buffer that holds the response message.
|
||||||
|
// - |response_length| is the actual length of the response message.
|
||||||
|
Status HandleRequest(size_t request_buffer_size,
|
||||||
|
const char* const request_buffer,
|
||||||
|
size_t response_buffer_size, char* response_buffer,
|
||||||
|
size_t* response_length);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// For unit test only.
|
||||||
|
explicit WvCasEcmgClientHandler(
|
||||||
|
std::unique_ptr<EcmgClientHandler> inner_handler);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Serves as storage for the |inner_handler_| below.
|
||||||
|
EcmgConfig ecmg_config_;
|
||||||
|
std::unique_ptr<EcmgClientHandler> inner_handler_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cas
|
||||||
|
} // namespace widevine
|
||||||
|
|
||||||
|
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECMG_CLIENT_HANDLER_H_
|
||||||
147
media_cas_packager_sdk/public/wv_cas_ecmg_client_handler_test.cc
Normal file
147
media_cas_packager_sdk/public/wv_cas_ecmg_client_handler_test.cc
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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_ecmg_client_handler.h"
|
||||||
|
|
||||||
|
#include "testing/gmock.h"
|
||||||
|
#include "testing/gunit.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "common/status.h"
|
||||||
|
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
|
||||||
|
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||||
|
|
||||||
|
namespace widevine {
|
||||||
|
namespace cas {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr size_t kBufferSize = 2048;
|
||||||
|
|
||||||
|
class MockEcmgClientHandler : public EcmgClientHandler {
|
||||||
|
public:
|
||||||
|
MockEcmgClientHandler() : EcmgClientHandler(&config_) {}
|
||||||
|
MOCK_METHOD(void, HandleRequest,
|
||||||
|
(const char* const request, char* response,
|
||||||
|
size_t* response_length),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
private:
|
||||||
|
EcmgConfig config_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Changes protected methods to public.
|
||||||
|
class TestWvCasEcmgClientHandler : public WvCasEcmgClientHandler {
|
||||||
|
public:
|
||||||
|
explicit TestWvCasEcmgClientHandler(
|
||||||
|
std::unique_ptr<EcmgClientHandler> inner_handler)
|
||||||
|
: WvCasEcmgClientHandler(std::move(inner_handler)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(WvCasEcmgClientHandlerTest, HandleRequestSuccess) {
|
||||||
|
size_t request_buffer_size = kBufferSize;
|
||||||
|
char request_buffer[kBufferSize];
|
||||||
|
size_t response_buffer_size = kBufferSize;
|
||||||
|
char response_buffer[kBufferSize];
|
||||||
|
size_t response_length = 0;
|
||||||
|
auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>();
|
||||||
|
EXPECT_CALL(*mock_internal_handler,
|
||||||
|
HandleRequest(request_buffer, response_buffer, &response_length));
|
||||||
|
|
||||||
|
auto handler = absl::make_unique<TestWvCasEcmgClientHandler>(
|
||||||
|
std::move(mock_internal_handler));
|
||||||
|
EXPECT_OK(handler->HandleRequest(request_buffer_size, request_buffer,
|
||||||
|
response_buffer_size, response_buffer,
|
||||||
|
&response_length));
|
||||||
|
}
|
||||||
|
|
||||||
|
class WvCasEcmgClientHandlerInvalidInputTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
size_t request_buffer_size_ = kBufferSize;
|
||||||
|
char request_buffer_[kBufferSize];
|
||||||
|
size_t response_buffer_size_ = kBufferSize;
|
||||||
|
char response_buffer_[kBufferSize];
|
||||||
|
size_t response_length_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(WvCasEcmgClientHandlerInvalidInputTest, RequestBufferTooSmallFail) {
|
||||||
|
auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>();
|
||||||
|
EXPECT_CALL(*mock_internal_handler, HandleRequest).Times(0);
|
||||||
|
auto handler = absl::make_unique<TestWvCasEcmgClientHandler>(
|
||||||
|
std::move(mock_internal_handler));
|
||||||
|
|
||||||
|
EXPECT_EQ(handler
|
||||||
|
->HandleRequest(/*request_buffer_size=*/1, request_buffer_,
|
||||||
|
response_buffer_size_, response_buffer_,
|
||||||
|
&response_length_)
|
||||||
|
.error_code(),
|
||||||
|
error::INVALID_ARGUMENT);
|
||||||
|
EXPECT_EQ(response_length_, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WvCasEcmgClientHandlerInvalidInputTest, ResponseBufferTooSmallFail) {
|
||||||
|
auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>();
|
||||||
|
EXPECT_CALL(*mock_internal_handler, HandleRequest).Times(0);
|
||||||
|
auto handler = absl::make_unique<TestWvCasEcmgClientHandler>(
|
||||||
|
std::move(mock_internal_handler));
|
||||||
|
|
||||||
|
EXPECT_EQ(handler
|
||||||
|
->HandleRequest(request_buffer_size_, request_buffer_,
|
||||||
|
/*response_buffer_size=*/1, response_buffer_,
|
||||||
|
&response_length_)
|
||||||
|
.error_code(),
|
||||||
|
error::INVALID_ARGUMENT);
|
||||||
|
EXPECT_EQ(response_length_, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WvCasEcmgClientHandlerInvalidInputTest, RequestBufferNullFail) {
|
||||||
|
auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>();
|
||||||
|
EXPECT_CALL(*mock_internal_handler, HandleRequest).Times(0);
|
||||||
|
auto handler = absl::make_unique<TestWvCasEcmgClientHandler>(
|
||||||
|
std::move(mock_internal_handler));
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
handler
|
||||||
|
->HandleRequest(request_buffer_size_,
|
||||||
|
/*request_buffer=*/nullptr, response_buffer_size_,
|
||||||
|
response_buffer_, &response_length_)
|
||||||
|
.error_code(),
|
||||||
|
error::INVALID_ARGUMENT);
|
||||||
|
EXPECT_EQ(response_length_, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WvCasEcmgClientHandlerInvalidInputTest, ResponseBufferNullFail) {
|
||||||
|
auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>();
|
||||||
|
EXPECT_CALL(*mock_internal_handler, HandleRequest).Times(0);
|
||||||
|
auto handler = absl::make_unique<TestWvCasEcmgClientHandler>(
|
||||||
|
std::move(mock_internal_handler));
|
||||||
|
|
||||||
|
EXPECT_EQ(handler
|
||||||
|
->HandleRequest(request_buffer_size_, request_buffer_,
|
||||||
|
response_buffer_size_,
|
||||||
|
/*response_buffer=*/nullptr, &response_length_)
|
||||||
|
.error_code(),
|
||||||
|
error::INVALID_ARGUMENT);
|
||||||
|
EXPECT_EQ(response_length_, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WvCasEcmgClientHandlerInvalidInputTest, ResponseLengthNullFail) {
|
||||||
|
auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>();
|
||||||
|
EXPECT_CALL(*mock_internal_handler, HandleRequest).Times(0);
|
||||||
|
auto handler = absl::make_unique<TestWvCasEcmgClientHandler>(
|
||||||
|
std::move(mock_internal_handler));
|
||||||
|
|
||||||
|
EXPECT_EQ(handler
|
||||||
|
->HandleRequest(request_buffer_size_, request_buffer_,
|
||||||
|
response_buffer_size_, response_buffer_,
|
||||||
|
/*response_length=*/nullptr)
|
||||||
|
.error_code(),
|
||||||
|
error::INVALID_ARGUMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace cas
|
||||||
|
} // namespace widevine
|
||||||
@@ -65,6 +65,68 @@ struct EntitlementKeyInfo {
|
|||||||
std::string key_value; // must be 32 bytes.
|
std::string key_value; // must be 32 bytes.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A struct that captures the Simulcrypt ECMG configurations. Most fields are
|
||||||
|
// Simulcrypt standard fields (See ETSI TS 103 197 V1.5.1 (2008-10)
|
||||||
|
// Section 5.3).
|
||||||
|
struct EcmgConfig {
|
||||||
|
// |delay_start| is a signed integer represents the amount of time between the
|
||||||
|
// start of a Crypto Period, and the start of the broadcasting of the ECM
|
||||||
|
// attached to this period. If it is positive, it means that the ECM shall
|
||||||
|
// be delayed with respect to the start of the Crypto Period. If negative,
|
||||||
|
// it means that the ECM shall be broadcast ahead of this time. This
|
||||||
|
// parameter is communicated by the ECMG to the SCS during the channel
|
||||||
|
// setup.
|
||||||
|
int16_t delay_start;
|
||||||
|
// |delay_stop| is a signed integer represents the amount of time between the
|
||||||
|
// end of a Crypto Period, and the end of the broadcasting of the ECM
|
||||||
|
// attached to this period. If it is positive, it means that the end of the
|
||||||
|
// ECM broadcast shall be delayed with respect to the end of the Crypto
|
||||||
|
// Period. If negative, it means that the ECM broadcast shall be ended ahead
|
||||||
|
// of time. This parameter is communicated by the ECMG to the SCS during the
|
||||||
|
// channel setup.
|
||||||
|
int16_t delay_stop;
|
||||||
|
// |ecm_rep_period| is an integer represents the period in milliseconds for
|
||||||
|
// the repetition of data (e.g. ECMs).
|
||||||
|
uint16_t ecm_rep_period;
|
||||||
|
// |max_comp_time| this parameter is communicated by the ECMG to the SCS
|
||||||
|
// during channel setup. It is the worst case time needed by an ECMG to
|
||||||
|
// compute an ECM when all the streams in a channel are being used. This
|
||||||
|
// time is typically used by the SCS to decide when to time-out on the
|
||||||
|
// ECM_response message. This value shall be lower than the min_CP_duration
|
||||||
|
// parameter of the same channel_status message.
|
||||||
|
uint16_t max_comp_time;
|
||||||
|
// |access_criteria_transfer_mode| this 1-byte parameter is a flag. If it
|
||||||
|
// equals 0, it indicates that the access_criteria parameter is required in
|
||||||
|
// the CW_provision message only when the contents of this parameter change.
|
||||||
|
// If it equals 1, it indicates that the ECMG requires the access_criteria
|
||||||
|
// parameter be present in each CW_provision message.
|
||||||
|
uint8_t access_criteria_transfer_mode;
|
||||||
|
// |number_of_content_keys| is the number of content keys in an ECM. If
|
||||||
|
// content key rotation is enabled, number_of_content_keys should be set to 2;
|
||||||
|
// otherwise it should be set to 1.
|
||||||
|
uint8_t number_of_content_keys;
|
||||||
|
// |crypto_mode| indicates the crypto mode used to encrypt the content. It
|
||||||
|
// will be included in the ECM. If new crypto_mode is received from SCS, ECM
|
||||||
|
// will use the new one.
|
||||||
|
CryptoMode crypto_mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A custom entitlement key fetching function used by ECMG to fetch entitlement
|
||||||
|
// keys. If entitlement key information is not received in Simulcrypt ECMG from
|
||||||
|
// SCS, ECMG will try to fetch entitlement keys using this function.
|
||||||
|
// Args:
|
||||||
|
// |channel_id| is the channel id received at ECMG from SCS when setting up
|
||||||
|
// the channel.
|
||||||
|
// |stream_id| is the stream id received at ECMG from SCS when setting up
|
||||||
|
// the stream.
|
||||||
|
// |ecm_id| is the ecm id received at ECMG from SCS when setting up the
|
||||||
|
// stream.
|
||||||
|
// Returns a vector of EntitlementKeyInfo. The size of the returned vector
|
||||||
|
// must not exceed 2. When the vector size is 2, one of them must be specified
|
||||||
|
// as even key and the other must be odd key (sequence does not matter).
|
||||||
|
using EntitlementKeyFetcherFunc = std::vector<EntitlementKeyInfo> (*)(
|
||||||
|
uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id);
|
||||||
|
|
||||||
struct WvCasEncryptionRequest {
|
struct WvCasEncryptionRequest {
|
||||||
std::string content_id;
|
std::string content_id;
|
||||||
std::string provider;
|
std::string provider;
|
||||||
|
|||||||
Reference in New Issue
Block a user