diff --git a/example/wv_ecmg_example b/example/wv_ecmg_example new file mode 100644 index 0000000..eefb85b Binary files /dev/null and b/example/wv_ecmg_example differ diff --git a/example/wv_ecmg_example.cc b/example/wv_ecmg_example.cc new file mode 100644 index 0000000..f0cfd6f --- /dev/null +++ b/example/wv_ecmg_example.cc @@ -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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#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(*(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 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(&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(&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(&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; +}