Add custom key fetcher callback to Simulcrypt ECMG
This commit is contained in:
@@ -79,3 +79,13 @@ cc_binary(
|
||||
"//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;
|
||||
}
|
||||
Reference in New Issue
Block a user