Files
media_cas_packager_sdk/example/wv_ecmg_example.cc
Widevine Buildbot 810ceaf1a1 Add support for Widevine ECM v3
Widevine ECM v3 is redesigned mainly based on protobuf, and supports new features including carrying fingerprinting and service blocking information. Existing clients must upgrade the Widevine CAS plugin to use the new ECM v3.
2020-12-14 18:02:09 +00:00

217 lines
8.0 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// 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;
constexpr unsigned char kTestECPrivateKey2Secp256r1[] = {
0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0x34, 0x9a, 0xf2, 0x95,
0x94, 0xd4, 0xca, 0xb9, 0xa0, 0x81, 0xe4, 0x1c, 0xf5, 0xde, 0x8d,
0x23, 0xf6, 0x79, 0xba, 0x3c, 0x6e, 0xc9, 0x0b, 0x56, 0x0f, 0x07,
0x5e, 0x9f, 0xe9, 0x38, 0x18, 0xfc, 0xa0, 0x0a, 0x06, 0x08, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0xa1, 0x44, 0x03, 0x42,
0x00, 0x04, 0x7b, 0x2a, 0x61, 0x59, 0xe5, 0x1b, 0xb6, 0x30, 0x7b,
0x59, 0x98, 0x42, 0x59, 0x37, 0xfb, 0x46, 0xfe, 0x53, 0xfe, 0x32,
0xa1, 0x5c, 0x93, 0x36, 0x11, 0xb0, 0x5a, 0xae, 0xa4, 0x48, 0xe3,
0x20, 0x12, 0xce, 0x78, 0xa7, 0x7f, 0xfd, 0x73, 0x5e, 0x09, 0x77,
0x53, 0x77, 0x8f, 0xd6, 0x1b, 0x26, 0xfa, 0xc4, 0x2c, 0xc4, 0x11,
0xfa, 0x72, 0x6a, 0xbe, 0x94, 0x78, 0x4d, 0x74, 0x20, 0x27, 0x86};
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;
config->ecc_private_signing_key.assign(
std::begin(kTestECPrivateKey2Secp256r1),
std::end(kTestECPrivateKey2Secp256r1));
}
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;
}