1) Change return type to WvCasStatus for functions in wv_cas_types.cc.

2) Add a binary wv_cas_types_example.
3) Surface wv_cas_key_fetcher *source code* to partner to serve as an example of how they would make a HTTP request to acquire an entitlement key from license server.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=229953562
This commit is contained in:
Fang Yu
2019-01-18 10:38:34 -08:00
parent 6e1f377329
commit e7173c82cd
25 changed files with 1213 additions and 173 deletions

View File

@@ -18,7 +18,9 @@ filegroup(
srcs = [
"test_ecmg_messages.h",
"wv_cas_ecm_example.cc",
"wv_cas_types_example.cc",
":wv_cas_ecm_example",
":wv_cas_types_example",
],
)
@@ -32,10 +34,16 @@ cc_library(
hdrs = ["test_ecmg_messages.h"],
)
cc_library(
name = "test_emmg_messages",
hdrs = ["test_emmg_messages.h"],
)
cc_binary(
name = "wv_cas_ecm_example",
srcs = ["wv_cas_ecm_example.cc"],
deps = [
"//base",
"@abseil_repo//absl/base:core_headers",
"//media_cas_packager_sdk/public:wv_cas_ecm",
"//media_cas_packager_sdk/public:wv_cas_types",
@@ -52,3 +60,13 @@ cc_binary(
"//protos/public:media_cas_encryption_proto",
],
)
cc_binary(
name = "wv_cas_types_example",
srcs = ["wv_cas_types_example.cc"],
deps = [
"//base",
"@abseil_repo//absl/base:core_headers",
"//media_cas_packager_sdk/public:wv_cas_types",
],
)

View File

@@ -14,7 +14,7 @@
namespace widevine {
namespace cas {
const char kTestChannelSetup[] = {
const char kTestEcmgChannelSetup[] = {
'\x03', // protocol_version
'\x00', '\x01', // message_type - Channel_setup
'\x00', '\x0e', // message_length
@@ -26,7 +26,7 @@ const char kTestChannelSetup[] = {
'\x4a', '\xd4', '\x00', '\x00' // parameter_value
};
const char kTestChannelStatus[] = {
const char kTestEcmgChannelStatus[] = {
'\x03', // protocol_version
'\x00', '\x03', // message_type - Channel_status
'\x00', '\x39', // message_length
@@ -62,7 +62,7 @@ const char kTestChannelStatus[] = {
'\x00', '\x64' // parameter_value
};
const char kTestStreamSetup[] = {
const char kTestEcmgStreamSetup[] = {
'\x03', // protocol_version
'\x01', '\x01', // message_type - Stream_setup
'\x00', '\x18', // message_length
@@ -80,7 +80,7 @@ const char kTestStreamSetup[] = {
'\x00', '\x64' // parameter_value
};
const char kTestStreamStatus[] = {
const char kTestEcmgStreamStatus[] = {
'\x03', // protocol_version
'\x01', '\x03', // message_type - Stream_status
'\x00', '\x17', // message_length
@@ -98,7 +98,7 @@ const char kTestStreamStatus[] = {
'\x01' // parameter_value
};
const char kTestCwProvision[] = {
const char kTestEcmgCwProvision[] = {
'\x03', // protocol_version
'\x02', '\x01', // message_type - CW_provision
'\x00', '\x44', // message_length
@@ -126,7 +126,7 @@ const char kTestCwProvision[] = {
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'};
// CW is encrypted using hardcoded fixed entitlement key.
const char kTestEcmResponse[] = {
const char kTestEcmgEcmResponse[] = {
'\x03', // protocol_version
'\x02', '\x02', // message_type - ECM_response
'\x00', '\xd2', // message_length
@@ -164,7 +164,7 @@ const char kTestEcmResponse[] = {
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff'};
const char kTestStreamCloseRequest[] = {
const char kTestEcmgStreamCloseRequest[] = {
'\x03', // protocol_version
'\x01', '\x04', // message_type - Stream_close_request
'\x00', '\x0c', // message_length
@@ -176,7 +176,7 @@ const char kTestStreamCloseRequest[] = {
'\x00', '\x01' // parameter_value
};
const char kTestStreamCloseResponse[] = {
const char kTestEcmgStreamCloseResponse[] = {
'\x03', // protocol_version
'\x01', '\x05', // message_type - Stream_close_response
'\x00', '\x0c', // message_length
@@ -188,7 +188,7 @@ const char kTestStreamCloseResponse[] = {
'\x00', '\x01' // parameter_value
};
const char kTestChannelClose[] = {
const char kTestEcmgChannelClose[] = {
'\x03', // protocol_version
'\x00', '\x04', // message_type - Channel_close
'\x00', '\x06', // message_length

View File

@@ -0,0 +1,146 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 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 EMMG messages used in unit tests.
#ifndef MEDIA_CAS_PACKAGER_SDK_EXAMPLE_TEST_EMMG_MESSAGES_H_
#define MEDIA_CAS_PACKAGER_SDK_EXAMPLE_TEST_EMMG_MESSAGES_H_
namespace widevine {
namespace cas {
const char kTestEmmgTsPacket[] = {
'\x47', '\x5F', '\xFF', '\x10', '\x00', '\x82', '\x70', '\x61', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x83', '\x70', '\x61',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01'};
const char kTestEmmgChannelSetup[] = {
'\x02', // protocol_version
'\x00', '\x11', // message_type - Channel_setup
'\x00', '\x13', // 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', '\x02', // parameter_type - section_TSpkt_flag
'\x00', '\x01', // parameter_length
'\x01' // parameter_value
};
const char kTestEmmgStreamSetup[] = {
'\x02', // protocol_version
'\x01', '\x11', // message_type - Stream_setup
'\x00', '\x1f', // 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', '\x08', // parameter_type - data_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
'\x00', '\x07', // parameter_type - data_type
'\x00', '\x01', // parameter_length
'\x01' // parameter_value - private data
};
const char kTestEmmgDataProvision[] = {
'\x02', // protocol_version
'\x02', '\x11', // message_type - Data_provision
'\x00', '\xda', // 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', '\x08', // parameter_type - data_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
'\x00', '\x05', // parameter_type - datagram
'\x00', '\xbc', // parameter_length
'\x47', '\x5f', '\xff', '\x10', '\x00', '\x82', '\x70', '\x61', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x83', '\x70', '\x61',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01'};
const char kTestEmmgStreamCloseRequest[] = {
'\x02', // protocol_version
'\x01', '\x14', // message_type - Stream_close_request
'\x00', '\x14', // 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
};
const char kTestEmmgChannelClose[] = {
'\x02', // protocol_version
'\x00', '\x14', // message_type - Channel_close
'\x00', '\x0e', // 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
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_EXAMPLE_TEST_EMMG_MESSAGES_H_

View File

@@ -8,12 +8,23 @@
// Example of how to use the wv_cas_ecm library.
#include <cassert>
#include <fstream>
#include <iostream>
#include <string>
#include "gflags/gflags.h"
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
DEFINE_int32(content_iv_size, 8, "Content IV size");
DEFINE_bool(key_rotation, true, "Whether key rotation is enabled");
DEFINE_string(crypto_mode, "CSA2", "Only CBC, CTR, or CSA2 is allowed");
DEFINE_int32(ecm_pid, 149, "PID for the ECM packet");
DEFINE_string(output_file, "",
"If specified, generated ECM TS packet will be written to the "
"specified output file path");
const char kCsaEvenKey[] = "even_key"; // 8 bytes
const char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes
const char kEvenEntitlementKeyId[] = "fake_key_id1...."; // 16 bytes
@@ -24,26 +35,45 @@ const char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes
const char kOddEntitlementKeyId[] = "fake_key_id2...."; // 16 bytes
const char kOddEntitlementKey[] =
"fakefakefakefakefakefakefake2..."; // 32 bytes
const size_t kTsPacketSize = 188;
widevine::cas::CryptoMode GetCryptoMode() {
if (FLAGS_crypto_mode.compare("CBC") == 0) {
return widevine::cas::CryptoMode::kAesCbc;
}
if (FLAGS_crypto_mode.compare("CTR") == 0) {
return widevine::cas::CryptoMode::kAesCtr;
}
return widevine::cas::CryptoMode::kDvbCsa2;
}
int main(int argc, char **argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
// Generate ECM.
widevine::cas::WvCasEcm wv_cas_ecm;
widevine::cas::WvCasStatus status = wv_cas_ecm.Initialize(
/* content_iv_size= */ 8, /* key_rotation_enabled= */ true,
widevine::cas::CryptoMode::kDvbCsa2);
FLAGS_content_iv_size, FLAGS_key_rotation, GetCryptoMode());
if (status != widevine::cas::OK) {
std::cerr << "Failed to initialize WV CAS ECM, error: "
<< widevine::cas::GetWvCasStatusMessage(status)
<< std::endl;
}
std::string ecm;
if (FLAGS_key_rotation) {
status = wv_cas_ecm.GenerateEcm(
kCsaEvenKey, kEvenContentIv8Bytes, kEvenEntitlementKeyId,
kEvenEntitlementKey, kCsaOddKey, kOddContentIv8Bytes,
kOddEntitlementKeyId, kOddEntitlementKey, &ecm);
} else {
status = wv_cas_ecm.GenerateSingleKeyEcm(kCsaEvenKey, kEvenContentIv8Bytes,
kEvenEntitlementKeyId,
kEvenEntitlementKey, &ecm);
}
if (status != widevine::cas::OK) {
std::cerr << "Failed to generate WV CAS ECM, error: "
<< widevine::cas::GetWvCasStatusMessage(status)
<< std::endl;
return -1;
} else {
std::cout << "ECM size: " << ecm.size() << std::endl;
std::cout << "ECM bytes: ";
@@ -52,6 +82,30 @@ int main(int argc, char **argv) {
}
std::cout << std::endl;
}
// Generate ECM TS Packet.
uint8_t packet[kTsPacketSize];
uint8_t continuity_counter; // not used.
status = wv_cas_ecm.GenerateTsPacket(ecm, FLAGS_ecm_pid,
/* table_id= */ 0x80,
&continuity_counter, packet);
if (status != widevine::cas::OK) {
std::cerr << "Failed to create ECM TS packet" << std::endl;
return -1;
} else {
std::cout << "TS packet bytes: ";
for (size_t i = 0; i < kTsPacketSize; i++) {
printf("'\\x%02x', ", static_cast<uint16_t>(packet[i]));
}
std::cout << std::endl;
}
// Write ECM TS Packet to a file.
if (!FLAGS_output_file.empty()) {
std::ofstream file;
file.open(FLAGS_output_file.c_str(), std::ios_base::binary);
assert(file.is_open());
file.write(reinterpret_cast<char *>(packet), kTsPacketSize);
file.close();
}
return 0;
}

View File

@@ -0,0 +1,37 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 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 how to use types/functions in wv_cas_types.
#include <string>
#include "gflags/gflags.h"
#include "glog/logging.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
DEFINE_string(function_to_call, "CreateWvCasEncryptionRequestJson",
"Function in wv_cas_types to exercise");
void CallCreateWvCasEncryptionRequestJson() {
widevine::cas::WvCasEncryptionRequest request;
request.content_id = "cont_id cont_id ";
request.provider = "widevine_test";
request.track_types = {"SD"};
request.key_rotation = true;
std::string request_json;
widevine::cas::CreateWvCasEncryptionRequestJson(request, &request_json);
LOG(INFO) << FLAGS_function_to_call << " returns " << request_json;
}
int main(int argc, char **argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_function_to_call.compare("CreateWvCasEncryptionRequestJson") == 0) {
CallCreateWvCasEncryptionRequestJson();
}
return 0;
}

View File

@@ -56,9 +56,10 @@ cc_library(
srcs = ["ecm_generator.cc"],
hdrs = ["ecm_generator.h"],
deps = [
":ecm",
"//base",
"@abseil_repo//absl/base:core_headers",
"//common:status",
"//media_cas_packager_sdk/internal:ecm",
],
)
@@ -81,12 +82,14 @@ cc_library(
hdrs = [
"ecmg_client_handler.h",
"ecmg_constants.h",
"simulcrypt_constants.h",
],
deps = [
":ecm",
":ecm_generator",
":fixed_key_fetcher",
":mpeg2ts",
":simulcrypt_util",
":util",
"//base",
"@abseil_repo//absl/base:core_headers",
@@ -112,6 +115,36 @@ cc_test(
],
)
cc_library(
name = "emmg",
srcs = ["emmg.cc"],
hdrs = [
"emmg.h",
"emmg_constants.h",
"simulcrypt_constants.h",
],
deps = [
":simulcrypt_util",
":util",
"//base",
"@abseil_repo//absl/base:core_headers",
"//common:status",
"//example:test_emmg_messages",
],
)
cc_test(
name = "emmg_test",
size = "small",
srcs = ["emmg_test.cc"],
deps = [
":emmg",
"//testing:gunit_main",
"@abseil_repo//absl/memory",
"//example:test_emmg_messages",
],
)
cc_library(
name = "fixed_key_fetcher",
srcs = [
@@ -146,6 +179,21 @@ cc_library(
],
)
cc_library(
name = "simulcrypt_util",
srcs = ["simulcrypt_util.cc"],
hdrs = [
"simulcrypt_constants.h",
"simulcrypt_util.h",
],
deps = [
":util",
"//base",
"@abseil_repo//absl/base:core_headers",
"//common:status",
],
)
cc_library(
name = "ts_packet",
srcs = [

View File

@@ -10,6 +10,7 @@
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <memory>
#include <utility>
@@ -19,10 +20,13 @@
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "common/crypto_util.h"
#include "common/status.h"
#include "example/constants.h"
#include "media_cas_packager_sdk/internal/ecm_generator.h"
#include "media_cas_packager_sdk/internal/ecmg_constants.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_types.h"
@@ -183,81 +187,16 @@ Status HandleParameters(const char* const request, size_t request_length,
return OkStatus();
}
// Add 'protocol_version', 'message_type', 'message_length' to the message.
// TODO(user): Per jfore@, consider pass in a pointer to a structure
// #pragma pack(push, 1) // exact fit - no padding
// struct MessageHeader{
// uint8_t protocol_version;
// uint16_t message_type;
// uint16_t message_length;
// };
// #pragma pack(pop) // restore previous pack
void BuildMessageHeader(uint16_t message_type, char* message,
size_t* message_length) {
DCHECK(message);
DCHECK(message_length);
*message_length = 0;
message[*message_length] = ECMG_SCS_PROTOCOL_VERSION;
*message_length += PROTOCOL_VERSION_SIZE;
Host16ToBigEndian(message + *message_length, &message_type);
*message_length += MESSAGE_TYPE_SIZE;
// NOTE: 'message_length' needs to be updated later after we have added all
// the params so we know the exact length of the all the params.
// Use 0 for 'total_param_length' until we know the length of the message.
uint16_t total_param_length = 0;
Host16ToBigEndian(message + *message_length, &total_param_length);
*message_length += MESSAGE_LENGTH_SIZE;
}
// Add a uint16_t parameter to the message.
void AddUint16Param(uint16_t param_type, uint16_t param_value, char* message,
size_t* message_length) {
DCHECK(message);
DCHECK(message_length);
Host16ToBigEndian(message + *message_length, &param_type);
*message_length += 2;
uint16_t param_length = 2;
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2;
Host16ToBigEndian(message + *message_length, &param_value);
*message_length += param_length;
}
// Add a uint8_t parameter to the message.
void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message,
size_t* message_length) {
DCHECK(message);
DCHECK(message_length);
Host16ToBigEndian(message + *message_length, &param_type);
*message_length += 2;
uint16_t param_length = 1;
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2;
memcpy(message + *message_length, &param_value, param_length);
*message_length += param_length;
}
// Add a param that is |param_length| bytes long.
void AddParam(uint16_t param_type, uint8_t* param_value, uint16_t param_length,
char* message, size_t* message_length) {
DCHECK(param_value);
DCHECK(message);
DCHECK(message_length);
Host16ToBigEndian(message + *message_length, &param_type);
*message_length += 2;
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2;
memcpy(message + *message_length, param_value, param_length);
*message_length += param_length;
}
void BuildChannelError(uint16_t channel_id, uint16_t error_status, char* message,
size_t* message_length) {
DCHECK(message);
DCHECK(message_length);
BuildMessageHeader(ECMG_CHANNEL_ERROR, message, message_length);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
AddUint16Param(ERROR_STATUS, error_status, message, message_length);
simulcrypt_util::BuildMessageHeader(
ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_ERROR, message, message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message,
message_length);
// No setting Error_information parameter yet.
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
@@ -268,25 +207,33 @@ void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message,
DCHECK(config);
DCHECK(message);
DCHECK(message_length);
BuildMessageHeader(ECMG_CHANNEL_STATUS, message, message_length);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
AddUint8Param(SECTION_TSPKT_FLAG, kSectionTSpktFlag, message, message_length);
simulcrypt_util::BuildMessageHeader(
ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_STATUS, message, message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint8Param(SECTION_TSPKT_FLAG, kSectionTSpktFlag, message,
message_length);
// No setting AC_delay_start parameter yet.
// No setting AC_delay_stop parameter yet.
AddUint16Param(DELAY_START, config->delay_start, message, message_length);
AddUint16Param(DELAY_STOP, config->delay_stop, message, message_length);
simulcrypt_util::AddUint16Param(DELAY_START, config->delay_start, message,
message_length);
simulcrypt_util::AddUint16Param(DELAY_STOP, config->delay_stop, message,
message_length);
// No setting transition_delay_start parameter yet.
// No setting transition_delay_stop parameter yet.
AddUint16Param(ECM_REP_PERIOD, config->ecm_rep_period, message,
simulcrypt_util::AddUint16Param(ECM_REP_PERIOD, config->ecm_rep_period,
message, message_length);
simulcrypt_util::AddUint16Param(MAX_STREAMS, kMaxStream, message,
message_length);
AddUint16Param(MAX_STREAMS, kMaxStream, message, message_length);
// min_CP_duration needs to be at least max_comp_time. So we just use
// max_comp_time here for now.
AddUint16Param(MIN_CP_DURATION, config->max_comp_time, message,
simulcrypt_util::AddUint16Param(MIN_CP_DURATION, config->max_comp_time,
message, message_length);
simulcrypt_util::AddUint8Param(LEAD_CW, kLeadCw, message, message_length);
simulcrypt_util::AddUint8Param(CW_PER_MESSAGE, kCwPerMsg, message,
message_length);
simulcrypt_util::AddUint16Param(MAX_COMP_TIME, config->max_comp_time, message,
message_length);
AddUint8Param(LEAD_CW, kLeadCw, message, message_length);
AddUint8Param(CW_PER_MESSAGE, kCwPerMsg, message, message_length);
AddUint16Param(MAX_COMP_TIME, config->max_comp_time, message, message_length);
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
}
@@ -295,10 +242,14 @@ void BuildStreamError(uint16_t channel_id, uint16_t stream_id, uint16_t error_st
char* message, size_t* message_length) {
DCHECK(message);
DCHECK(message_length);
BuildMessageHeader(ECMG_STREAM_ERROR, message, message_length);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
AddUint16Param(ERROR_STATUS, error_status, message, message_length);
simulcrypt_util::BuildMessageHeader(
ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_ERROR, message, message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message,
message_length);
simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message,
message_length);
// No setting Error_information parameter yet.
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
@@ -309,12 +260,16 @@ void BuildStreamStatus(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id,
size_t* message_length) {
DCHECK(message);
DCHECK(message_length);
BuildMessageHeader(ECMG_STREAM_STATUS, message, message_length);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
AddUint16Param(ECM_ID, ecm_id, message, message_length);
AddUint8Param(ACCESS_CRITERIA_TRANSFER_MODE, access_criteria_transfer_mode,
message, message_length);
simulcrypt_util::BuildMessageHeader(
ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_STATUS, message, message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_ID, ecm_id, message, message_length);
simulcrypt_util::AddUint8Param(ACCESS_CRITERIA_TRANSFER_MODE,
access_criteria_transfer_mode, message,
message_length);
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
}
@@ -323,9 +278,13 @@ void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id,
char* message, size_t* message_length) {
DCHECK(message);
DCHECK(message_length);
BuildMessageHeader(ECMG_STREAM_CLOSE_RESPONSE, message, message_length);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
simulcrypt_util::BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION,
ECMG_STREAM_CLOSE_RESPONSE, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message,
message_length);
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
}
@@ -336,11 +295,16 @@ void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, uint16_t cp_numbe
DCHECK(ecm_datagram);
DCHECK(message);
DCHECK(message_length);
BuildMessageHeader(ECMG_ECM_RESPONSE, message, message_length);
AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length);
AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length);
AddUint16Param(CP_NUMBER, cp_number, message, message_length);
AddParam(ECM_DATAGRAM, ecm_datagram, kTsPacketSize, message, message_length);
simulcrypt_util::BuildMessageHeader(
ECMG_SCS_PROTOCOL_VERSION, ECMG_ECM_RESPONSE, message, message_length);
simulcrypt_util::AddUint16Param(ECM_CHANNEL_ID, channel_id, message,
message_length);
simulcrypt_util::AddUint16Param(ECM_STREAM_ID, stream_id, message,
message_length);
simulcrypt_util::AddUint16Param(CP_NUMBER, cp_number, message,
message_length);
simulcrypt_util::AddParam(ECM_DATAGRAM, ecm_datagram, kTsPacketSize, message,
message_length);
uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length);
}

View File

@@ -56,27 +56,31 @@ TEST_F(EcmgClientHandlerTest, SuccessSequence) {
char response[BUFFER_SIZE];
size_t response_length;
client_handler_->HandleRequest(kTestChannelSetup, response, &response_length);
client_handler_->HandleRequest(kTestEcmgChannelSetup, response,
&response_length);
EXPECT_EQ(62, response_length);
EXPECT_EQ(0, memcmp(kTestChannelStatus, response, response_length));
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response, response_length));
client_handler_->HandleRequest(kTestStreamSetup, response, &response_length);
client_handler_->HandleRequest(kTestEcmgStreamSetup, response,
&response_length);
EXPECT_EQ(28, response_length);
EXPECT_EQ(0, memcmp(kTestStreamStatus, response, response_length));
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response, response_length));
client_handler_->HandleRequest(kTestCwProvision, response, &response_length);
client_handler_->HandleRequest(kTestEcmgCwProvision, response,
&response_length);
EXPECT_EQ(215, response_length);
// Only comparing the bytes in front of the ECM_datagram, because
// random wrapping IV is generated each time causing the ECM_datagram
// to be non-deterministic.
EXPECT_EQ(0, memcmp(kTestEcmResponse, response, 27));
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response, 27));
client_handler_->HandleRequest(kTestStreamCloseRequest, response,
client_handler_->HandleRequest(kTestEcmgStreamCloseRequest, response,
&response_length);
EXPECT_EQ(17, response_length);
EXPECT_EQ(0, memcmp(kTestStreamCloseResponse, response, response_length));
EXPECT_EQ(0, memcmp(kTestEcmgStreamCloseResponse, response, response_length));
client_handler_->HandleRequest(kTestChannelClose, response, &response_length);
client_handler_->HandleRequest(kTestEcmgChannelClose, response,
&response_length);
EXPECT_EQ(0, response_length);
}

View File

@@ -62,35 +62,30 @@
// ECMG protocol error values.
#define INVAID_MESSAGE (0x0001)
#define UNSUPPORTED_PROTOCOL_VERSION (0X0002)
#define UNKNOWN_MESSAGE_TYPE_VALUE (0X0003)
#define MESSAGE_TOO_LONG (0X0004)
#define UNKNOWN_SUPER_CAS_ID_VALUE (0X0005)
#define UNKNOWN_ECM_CHANNEL_ID_VALUE (0X0006)
#define UNKNOWN_ECM_STREAM_ID_VALUE (0X0007)
#define TOO_MANY_CHANNELS_ON_THIS_ECMG (0X0008)
#define TOO_MANY_ECM_STREAMS_ON_THIS_CHANNEL (0X0009)
#define TOO_MANY_ECM_STREAMS_ON_THIS_ECMG (0X000A)
#define NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM (0X000B)
#define ECMG_OUT_OF_STORAGE_CAPACITY (0X000C)
#define ECMG_OUT_OF_COMPUTATIONAL_RESOURCES (0X000D)
#define UNKNOWN_PARAMETER_TYPE_VALUE (0X000E)
#define INCONSISTENT_LENGTH_FOR_DVB_PARAMETER (0X000F)
#define MISSING_MANDATORY_DVB_PARAMETER (0X0010)
#define INVALID_VALUE_FOR_DVB_PARAMETER (0X0011)
#define UNKNOWN_ECM_ID_VALUE (0X0012)
#define ECM_CHANNEL_ID_VALUE_ALREADY_IN_USE (0X0013)
#define ECM_STREAM_ID_VALUE_ALREADY_IN_USE (0X0014)
#define ECM_ID_VALUE_ALREADY_IN_USE (0X0015)
#define UNKNOWN_ERROR (0X7000)
#define UNRECOVERABLE_ERROR (0X7001)
#define UNSUPPORTED_PROTOCOL_VERSION (0x0002)
#define UNKNOWN_MESSAGE_TYPE_VALUE (0x0003)
#define MESSAGE_TOO_LONG (0x0004)
#define UNKNOWN_SUPER_CAS_ID_VALUE (0x0005)
#define UNKNOWN_ECM_CHANNEL_ID_VALUE (0x0006)
#define UNKNOWN_ECM_STREAM_ID_VALUE (0x0007)
#define TOO_MANY_CHANNELS_ON_THIS_ECMG (0x0008)
#define TOO_MANY_ECM_STREAMS_ON_THIS_CHANNEL (0x0009)
#define TOO_MANY_ECM_STREAMS_ON_THIS_ECMG (0x000A)
#define NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM (0x000B)
#define ECMG_OUT_OF_STORAGE_CAPACITY (0x000C)
#define ECMG_OUT_OF_COMPUTATIONAL_RESOURCES (0x000D)
#define UNKNOWN_PARAMETER_TYPE_VALUE (0x000E)
#define INCONSISTENT_LENGTH_FOR_DVB_PARAMETER (0x000F)
#define MISSING_MANDATORY_DVB_PARAMETER (0x0010)
#define INVALID_VALUE_FOR_DVB_PARAMETER (0x0011)
#define UNKNOWN_ECM_ID_VALUE (0x0012)
#define ECM_CHANNEL_ID_VALUE_ALREADY_IN_USE (0x0013)
#define ECM_STREAM_ID_VALUE_ALREADY_IN_USE (0x0014)
#define ECM_ID_VALUE_ALREADY_IN_USE (0x0015)
#define UNKNOWN_ERROR (0x7000)
#define UNRECOVERABLE_ERROR (0x7001)
// Size (in # of bytes) of various fields.
#define PROTOCOL_VERSION_SIZE (1)
#define MESSAGE_TYPE_SIZE (2)
#define MESSAGE_LENGTH_SIZE (2)
#define PARAMETER_TYPE_SIZE (2)
#define PARAMETER_LENGTH_SIZE (2)
#define SUPER_CAS_ID_SIZE (4)
#define ECM_CHANNEL_ID_SIZE (2)
#define ECM_ID_SIZE (2)

View File

@@ -0,0 +1,247 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 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/internal/emmg.h"
#include <sys/socket.h>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <iostream>
#include "glog/logging.h"
#include "example/test_emmg_messages.h"
#include "media_cas_packager_sdk/internal/emmg_constants.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"
namespace widevine {
namespace cas {
namespace {
void Print(const char* const msg, size_t msg_size) {
for (size_t i = 0; i < msg_size; i++) {
printf("'\\x%02x', ", static_cast<uint16_t>(*(msg + i)));
}
std::cout << std::endl;
}
std::string MessageTypeToString(uint16_t message_type) {
switch (message_type) {
case EMMG_CHANNEL_SETUP:
return "EMMG_CHANNEL_SETUP";
case EMMG_CHANNEL_STATUS:
return "EMMG_CHANNEL_STATUS";
case EMMG_CHANNEL_CLOSE:
return "EMMG_CHANNEL_CLOSE";
case EMMG_CHANNEL_ERROR:
return "EMMG_CHANNEL_ERROR";
case EMMG_STREAM_SETUP:
return "EMMG_STREAM_SETUP";
case EMMG_STREAM_TEST:
return "EMMG_STREAM_TEST";
case EMMG_STREAM_STATUS:
return "EMMG_STREAM_STATUS";
case EMMG_STREAM_CLOSE_REQUEST:
return "EMMG_STREAM_CLOSE_REQUEST";
case EMMG_STREAM_CLOSE_RESPONSE:
return "EMMG_STREAM_CLOSE_RESPONSE";
case EMMG_STREAM_ERROR:
return "EMMG_STREAM_ERROR";
case EMMG_STREAM_BW_REQUEST:
return "EMMG_STREAM_BW_REQUEST";
case EMMG_STREAM_BW_ALLOCATION:
return "";
case EMMG_DATA_PROVISION:
return "EMMG_DATA_PROVISION";
default:
return "UNRECOGNIZED_MESSAGE_TYPE";
}
}
} // namespace
Emmg::Emmg(EmmgConfig* emmg_config, int server_socket_fd) {
CHECK(emmg_config);
emmg_config_ = emmg_config;
server_socket_fd_ = server_socket_fd;
}
void Emmg::Start() {
SendChannelSetup();
SendStreamSetup();
// TODO(user): Send stream_BW_request message.
// TODO(user): Send stream_BW_allocation message.
SendDataProvision();
SendStreamCloseRequest();
SendChannelClose();
}
void Emmg::BuildChannelSetup() {
bzero(request_, BUFFER_SIZE);
request_length_ = 0;
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION,
EMMG_CHANNEL_SETUP, 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::AddUint8Param(EMMG_SECTION_TSPKT_FLAG,
emmg_config_->section_tspkt_flag, request_,
&request_length_);
uint16_t total_param_length = request_length_ - 5;
Host16ToBigEndian(request_ + 3, &total_param_length);
}
void Emmg::BuildStreamSetup() {
bzero(request_, BUFFER_SIZE);
request_length_ = 0;
simulcrypt_util::BuildMessageHeader(
EMMG_MUX_PROTOCOL_VERSION, EMMG_STREAM_SETUP, 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_DATA_ID, emmg_config_->data_id, request_,
&request_length_);
simulcrypt_util::AddUint8Param(EMMG_DATA_TYPE, emmg_config_->data_type,
request_, &request_length_);
uint16_t total_param_length = request_length_ - 5;
Host16ToBigEndian(request_ + 3, &total_param_length);
}
void Emmg::BuildDataProvision() {
bzero(request_, BUFFER_SIZE);
request_length_ = 0;
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION,
EMMG_DATA_PROVISION, 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_DATA_ID, emmg_config_->data_id, request_,
&request_length_);
// Add a fake TS packet.
uint16_t datagram_type = EMMG_DATAGRAM;
Host16ToBigEndian(request_ + request_length_, &datagram_type);
request_length_ += 2;
uint16_t param_length = sizeof(kTestEmmgTsPacket); // Should be 188.
Host16ToBigEndian(request_ + request_length_, &param_length);
request_length_ += 2;
memcpy(request_ + request_length_, kTestEmmgTsPacket, param_length);
request_length_ += param_length;
uint16_t total_param_length = request_length_ - 5;
Host16ToBigEndian(request_ + 3, &total_param_length);
}
void Emmg::BuildStreamCloseRequest() {
bzero(request_, BUFFER_SIZE);
request_length_ = 0;
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION,
EMMG_STREAM_CLOSE_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_);
uint16_t total_param_length = request_length_ - 5;
Host16ToBigEndian(request_ + 3, &total_param_length);
}
void Emmg::BuildChannelClose() {
bzero(request_, BUFFER_SIZE);
request_length_ = 0;
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION,
EMMG_CHANNEL_CLOSE, 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_);
uint16_t total_param_length = request_length_ - 5;
Host16ToBigEndian(request_ + 3, &total_param_length);
}
void Emmg::SendChannelSetup() {
BuildChannelSetup();
Send(EMMG_CHANNEL_SETUP);
ReceiveResponseAndVerify(EMMG_CHANNEL_STATUS);
}
void Emmg::SendStreamSetup() {
BuildStreamSetup();
Send(EMMG_STREAM_SETUP);
ReceiveResponseAndVerify(EMMG_STREAM_STATUS);
}
void Emmg::SendDataProvision() {
BuildDataProvision();
Send(EMMG_DATA_PROVISION);
// No response for Data_provision message.
}
void Emmg::SendStreamCloseRequest() {
BuildStreamCloseRequest();
Send(EMMG_STREAM_CLOSE_REQUEST);
ReceiveResponseAndVerify(EMMG_STREAM_CLOSE_RESPONSE);
}
void Emmg::SendChannelClose() {
BuildChannelClose();
Send(EMMG_CHANNEL_CLOSE);
// No response for Channel_close message.
}
void Emmg::ReceiveResponseAndVerify(uint16_t expected_type) {
// Read response from socket.
bzero(response_, BUFFER_SIZE);
response_length_ = recv(server_socket_fd_, response_, BUFFER_SIZE, 0);
if (response_length_ == 0) {
LOG(FATAL) << "Expect a response from server but nothing was received";
}
// Verify request type (2nd and 3rd bytes) match the expected type.
uint16_t request_type;
BigEndianToHost16(&request_type, response_ + 1);
if (request_type != expected_type) {
LOG(ERROR) << "Unexpected non " << MessageTypeToString(expected_type)
<< " type message received";
LOG(ERROR) << "Response: ";
Print(response_, response_length_);
return;
}
}
void Emmg::Send(uint16_t message_type) {
if (send(server_socket_fd_, request_, request_length_, 0) < 0) {
LOG(FATAL) << "Failed to send " << MessageTypeToString(message_type)
<< " message to server";
}
}
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,80 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 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_EMMG_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_H_
#include <cstddef>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <cstdint>
#include "glog/logging.h"
#include "common/status.h"
#define BUFFER_SIZE (1024)
namespace widevine {
namespace cas {
// A struct that captures the Emmg configs.
struct EmmgConfig {
uint32_t client_id;
uint8_t section_tspkt_flag;
uint16_t data_channel_id;
uint16_t data_stream_id;
uint16_t data_id;
uint8_t data_type;
};
// A class that sends EMMG/PDG message to the MUX server.
class Emmg {
public:
Emmg(EmmgConfig* emmg_config, int server_socket_fd);
Emmg(const Emmg&) = delete;
Emmg& operator=(const Emmg&) = delete;
virtual ~Emmg() = default;
void Start();
protected:
// Protected visibility for unit testing.
void BuildChannelSetup();
void BuildStreamSetup();
void BuildDataProvision();
void BuildStreamCloseRequest();
void BuildChannelClose();
size_t request_length_ = 0;
char request_[BUFFER_SIZE];
private:
void SendChannelSetup();
void SendStreamSetup();
void SendDataProvision();
void SendStreamCloseRequest();
void SendChannelClose();
void ReceiveResponseAndVerify(uint16_t expected_type);
void Send(uint16_t message_type);
char response_[BUFFER_SIZE];
size_t response_length_ = 0;
EmmgConfig* emmg_config_;
// |server_socket_fd| is a file descriptor we can use to communicate
// with the MUX server.
int server_socket_fd_;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_H_

View File

@@ -0,0 +1,68 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 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_EMMG_CONSTANTS_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_CONSTANTS_H_
// EMMG/PDG <=> MUX protocol_version.
#define EMMG_MUX_PROTOCOL_VERSION (0x02)
// EMMG message type values.
#define EMMG_CHANNEL_SETUP (0x0011)
#define EMMG_CHANNEL_TEST (0x0012)
#define EMMG_CHANNEL_STATUS (0x0013)
#define EMMG_CHANNEL_CLOSE (0x0014)
#define EMMG_CHANNEL_ERROR (0x0015)
#define EMMG_STREAM_SETUP (0x0111)
#define EMMG_STREAM_TEST (0x0112)
#define EMMG_STREAM_STATUS (0x0113)
#define EMMG_STREAM_CLOSE_REQUEST (0x0114)
#define EMMG_STREAM_CLOSE_RESPONSE (0x0115)
#define EMMG_STREAM_ERROR (0x0116)
#define EMMG_STREAM_BW_REQUEST (0x0117)
#define EMMG_STREAM_BW_ALLOCATION (0x0118)
#define EMMG_DATA_PROVISION (0x0211)
// EMMG parameter type values.
#define EMMG_DVB_RESERVED (0x0000)
#define EMMG_CLIENT_ID (0x0001)
#define EMMG_SECTION_TSPKT_FLAG (0x0002)
#define EMMG_DATA_CHANNEL_ID (0x0003)
#define EMMG_DATA_STREAM_ID (0x0004)
#define EMMG_DATAGRAM (0x0005)
#define EMMG_BANDWIDTH (0x0006)
#define EMMG_DATA_TYPE (0x0007)
#define EMMG_DATA_ID (0x0008)
#define EMMG_ERROR_STATUS (0x7000)
#define EMMG_ERROR_INFORMATION (0x7001)
// EMMG protocol error values.
#define INVALID_MESSAGE (0x0001)
#define UNSUPPORTED_PROTOCOL_VERSION (0x0002)
#define UNKNOWN_MESSAGE_TYPE_VALUE (0x0003)
#define MESSAGE_TOO_LONG (0x0004)
#define UNKNOWN_DATA_STREAM_ID_VALUE (0x0005)
#define UNKNOWN_DATA_CHANNEL_ID_VALUE (0x0006)
#define TOO_MANY_CHANNELS_ON_THIS_MUX (0x0007)
#define TOO_MANY_DATA_STREAMS_ON_THIS_CHANNEL (0x0008)
#define TOO_MANY_DATA_STREAMS_ON_THIS_MUX (0x0009)
#define UNKNOWN_PARAMETER_TYPE (0x000A)
#define INCONSISTENT_LENGTH_FOR_DVB_PARAMETER (0x000B)
#define MISSING_MANDATORY_DVB_PARAMETER (0x000C)
#define INVALID_VALUE_FOR_DVB_PARAMETER (0x000D)
#define UNKNOWN_CLIENT_ID_VALUE (0x000E)
#define EXCEEDED_BANDWIDTH (0x000F)
#define UNKNOWN_DATA_ID_VALUE (0x0010)
#define DATA_CHANNEL_ID_VALUE_ALREADY_IN_USE (0x0011)
#define DATA_STREAM_ID_VALUE_ALREADY_IN_USE (0x0012)
#define DATA_ID_VALUE_ALREADY_IN_USE (0x0013)
#define CLIENT_ID_VALUE_ALREADY_IN_USE (0x0014)
#define UNKNOWN_ERROR (0x7000)
#define UNRECOVERABLE_ERROR (0x7001)
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_CONSTANTS_H_

View File

@@ -0,0 +1,93 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 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/internal/emmg.h"
#include "testing/gunit.h"
#include "absl/memory/memory.h"
#include "example/test_emmg_messages.h"
namespace widevine {
namespace cas {
namespace {
class TestableEmmg : public Emmg {
public:
explicit TestableEmmg(EmmgConfig* emmg_config)
: Emmg(emmg_config, /* server_socket_fd= */ 1) {}
void PublicBuildChannelSetup() { BuildChannelSetup(); }
void PublicBuildStreamSetup() { BuildStreamSetup(); }
void PublicBuildDataProvision() { BuildDataProvision(); }
void PublicBuildStreamCloseRequest() { BuildStreamCloseRequest(); }
void PublicBuildChannelClose() { BuildChannelClose(); }
char* GetRequest() { return request_; }
};
class EmmgTest : public ::testing::Test {
protected:
EmmgTest() {
config_.client_id = 0x4AD40000;
config_.section_tspkt_flag = 0x01;
config_.data_channel_id = 0x0001;
config_.data_stream_id = 0x0001;
config_.data_id = 0x0001;
config_.data_type = 0x01;
emmg_ = absl::make_unique<TestableEmmg>(&config_);
}
protected:
// Helper function for debugging the tests.
void PrintMessage(const std::string& description, const char* const message,
size_t length) {
printf("%s ", description.c_str());
fflush(stdout);
for (size_t i = 0; i < length; i++) {
printf("'\\x%02x', ", static_cast<uint16_t>(*(message + i)));
fflush(stdout);
}
printf("\n");
fflush(stdout);
}
EmmgConfig config_;
std::unique_ptr<TestableEmmg> emmg_;
};
TEST_F(EmmgTest, BuildChannelSetup) {
emmg_->PublicBuildChannelSetup();
EXPECT_EQ(0, memcmp(kTestEmmgChannelSetup, emmg_->GetRequest(),
sizeof(kTestEmmgChannelSetup)));
}
TEST_F(EmmgTest, BuildStreamSetup) {
emmg_->PublicBuildStreamSetup();
EXPECT_EQ(0, memcmp(kTestEmmgStreamSetup, emmg_->GetRequest(),
sizeof(kTestEmmgStreamSetup)));
}
TEST_F(EmmgTest, BuildDataProvision) {
emmg_->PublicBuildDataProvision();
EXPECT_EQ(0, memcmp(kTestEmmgDataProvision, emmg_->GetRequest(),
sizeof(kTestEmmgDataProvision)));
}
TEST_F(EmmgTest, BuildStreamCloseRequest) {
emmg_->PublicBuildStreamCloseRequest();
EXPECT_EQ(0, memcmp(kTestEmmgStreamCloseRequest, emmg_->GetRequest(),
sizeof(kTestEmmgStreamCloseRequest)));
}
TEST_F(EmmgTest, BuildChannelClose) {
emmg_->PublicBuildChannelClose();
EXPECT_EQ(0, memcmp(kTestEmmgChannelClose, emmg_->GetRequest(),
sizeof(kTestEmmgChannelClose)));
}
} // namespace
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,19 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 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_SIMULCRYPT_CONSTANTS_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_CONSTANTS_H_
// Size (in # of bytes) of various fields.
#define PROTOCOL_VERSION_SIZE (1)
#define MESSAGE_TYPE_SIZE (2)
#define MESSAGE_LENGTH_SIZE (2)
#define PARAMETER_TYPE_SIZE (2)
#define PARAMETER_LENGTH_SIZE (2)
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_CONSTANTS_H_

View File

@@ -0,0 +1,93 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 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/internal/simulcrypt_util.h"
#include <cstddef>
#include <cstring>
#include "glog/logging.h"
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
#include "media_cas_packager_sdk/internal/util.h"
namespace widevine {
namespace cas {
namespace simulcrypt_util {
void BuildMessageHeader(uint8_t protocol_version, uint16_t message_type,
char* message, size_t* message_length) {
DCHECK(message);
DCHECK(message_length);
*message_length = 0;
message[*message_length] = protocol_version;
*message_length += PROTOCOL_VERSION_SIZE;
Host16ToBigEndian(message + *message_length, &message_type);
*message_length += MESSAGE_TYPE_SIZE;
// NOTE: 'message_length' needs to be updated later after we have added all
// the params so we know the exact length of the all the params.
// Use 0 for 'total_param_length' until we know the length of the message.
uint16_t total_param_length = 0;
Host16ToBigEndian(message + *message_length, &total_param_length);
*message_length += MESSAGE_LENGTH_SIZE;
}
void AddUint32Param(uint16_t param_type, uint32_t param_value, char* message,
size_t* message_length) {
DCHECK(message);
DCHECK(message_length);
Host16ToBigEndian(message + *message_length, &param_type);
*message_length += 2;
uint16_t param_length = 4;
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2;
Host32ToBigEndian(message + *message_length, &param_value);
*message_length += param_length;
}
void AddUint16Param(uint16_t param_type, uint16_t param_value, char* message,
size_t* message_length) {
DCHECK(message);
DCHECK(message_length);
Host16ToBigEndian(message + *message_length, &param_type);
*message_length += 2;
uint16_t param_length = 2;
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2;
Host16ToBigEndian(message + *message_length, &param_value);
*message_length += param_length;
}
void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message,
size_t* message_length) {
DCHECK(message);
DCHECK(message_length);
Host16ToBigEndian(message + *message_length, &param_type);
*message_length += 2;
uint16_t param_length = 1;
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2;
memcpy(message + *message_length, &param_value, param_length);
*message_length += param_length;
}
void AddParam(uint16_t param_type, uint8_t* param_value, uint16_t param_length,
char* message, size_t* message_length) {
DCHECK(param_value);
DCHECK(message);
DCHECK(message_length);
Host16ToBigEndian(message + *message_length, &param_type);
*message_length += 2;
Host16ToBigEndian(message + *message_length, &param_length);
*message_length += 2;
memcpy(message + *message_length, param_value, param_length);
*message_length += param_length;
}
} // namespace simulcrypt_util
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,59 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 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.
////////////////////////////////////////////////////////////////////////////////
//
// Utility functions for building Simulcrypt messages.
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_UTIL_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_UTIL_H_
#include <cstddef>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <cstdint>
#include "common/status.h"
namespace widevine {
namespace cas {
namespace simulcrypt_util {
// Add 'protocol_version', 'message_type', 'message_length' to the message.
// TODO(user): Per jfore@, consider pass in a pointer to a structure
// #pragma pack(push, 1) // exact fit - no padding
// struct MessageHeader{
// uint8_t protocol_version;
// uint16_t message_type;
// uint16_t message_length;
// };
// #pragma pack(pop) // restore previous pack
void BuildMessageHeader(uint8_t protocol_version, uint16_t message_type,
char* message, size_t* message_length);
// Add a uint32_t parameter to the message.
void AddUint32Param(uint16_t param_type, uint32_t param_value, char* message,
size_t* message_length);
// Add a uint16_t parameter to the message.
void AddUint16Param(uint16_t param_type, uint16_t param_value, char* message,
size_t* message_length);
// Add a uint8_t parameter to the message.
void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message,
size_t* message_length);
// Add a param that is |param_length| bytes long.
void AddParam(uint16_t param_type, uint8_t* param_value, uint16_t param_length,
char* message, size_t* message_length);
} // namespace simulcrypt_util
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_UTIL_H_

View File

@@ -57,6 +57,13 @@ void Host16ToBigEndian(void* destination, const int16_t* source) {
memcpy(destination, &big_endian_number, 2);
}
void Host32ToBigEndian(void* destination, const uint32_t* source) {
DCHECK(destination);
DCHECK(source);
uint32_t big_endian_number = htonl(*source);
memcpy(destination, &big_endian_number, 4);
}
Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid, uint8_t table_id,
ContinuityCounter* cc, uint8_t* buffer,
ssize_t* bytes_modified) {

View File

@@ -33,6 +33,7 @@ void BigEndianToHost32(uint32_t* destination, const void* source);
// |destination|.
void Host16ToBigEndian(void* destination, const uint16_t* source);
void Host16ToBigEndian(void* destination, const int16_t* source);
void Host32ToBigEndian(void* destination, const uint32_t* source);
// Packages an ECM as a TS packet and inserts it into a buffer.
// Args:

View File

@@ -22,6 +22,8 @@ filegroup(
name = "binary_release_files",
srcs = glob(["*.h"]) + [
":wv_ecmg",
"wv_cas_key_fetcher.cc",
"wv_cas_types.cc",
],
)
@@ -127,7 +129,9 @@ cc_library(
"@abseil_repo//absl/base:core_headers",
"@abseil_repo//absl/strings",
"@curl_repo//:curl",
"//util:error_space",
"//common:signature_util",
"//common:status",
"//media_cas_packager_sdk/internal:key_fetcher",
"//protos/public:media_cas_encryption_proto",
],
@@ -181,3 +185,12 @@ cc_binary(
"//media_cas_packager_sdk/internal:ecmg_client_handler",
],
)
cc_binary(
name = "wv_emmg",
srcs = ["wv_emmg.cc"],
deps = [
"//base",
"//media_cas_packager_sdk/internal:emmg",
],
)

View File

@@ -103,8 +103,6 @@ Status WvCasKeyFetcher::RequestEntitlementKey(const std::string& request_string,
}
// Processes signed response.
// TODO(b/114741232): Seems we are getting CasEncryptionResponse back instead
// of SignedCasEncryptionResponse.
LOG(INFO) << "Json CasEncryptionResponse: " << http_response.response();
CasEncryptionResponse response;
if (!JsonStringToMessage(http_response.response(), &response).ok()) {

View File

@@ -79,8 +79,8 @@ bool StringToCryptoMode(const std::string& str, CryptoMode* mode) {
return false;
}
Status CreateWvCasEncryptionRequestJson(const WvCasEncryptionRequest& request,
std::string* request_json) {
WvCasStatus CreateWvCasEncryptionRequestJson(
const WvCasEncryptionRequest& request, std::string* request_json) {
CHECK(request_json);
CasEncryptionRequest request_proto;
@@ -100,23 +100,23 @@ Status CreateWvCasEncryptionRequestJson(const WvCasEncryptionRequest& request,
// NOTE: MessageToJsonString will automatically converts 'bytes' type fields
// to base64. For example content ID '21140844' becomes 'MjExNDA4NDQ='.
if (!MessageToJsonString(request_proto, request_json, print_options).ok()) {
return Status(error::INTERNAL,
"Failed to convert request message to json.");
LOG(ERROR) << "Failed to convert request message to json.";
return INTERNAL;
}
return OkStatus();
return OK;
}
Status ParseWvCasEncryptionResponseJson(const std::string& response_json,
WvCasEncryptionResponse* response) {
WvCasStatus ParseWvCasEncryptionResponseJson(
const std::string& response_json, WvCasEncryptionResponse* response) {
CHECK(response);
CasEncryptionResponse response_proto;
// NOTE: JsonStringToMessage will automatically perform base64 decode for
// 'bytes' type fields.
if (!JsonStringToMessage(response_json, &response_proto).ok()) {
return Status(error::INTERNAL,
"Failed to convert response json to message.");
LOG(ERROR) << "Failed to convert response json to message.";
return INTERNAL;
}
response->status =
@@ -133,7 +133,7 @@ Status ParseWvCasEncryptionResponseJson(const std::string& response_json,
response->entitlement_keys.push_back(key_info);
}
return OkStatus();
return OK;
}
} // namespace cas

View File

@@ -114,14 +114,14 @@ struct WvCasEncryptionResponse {
// request JSON message.
// And that signed JSON message can be sent to Widevine license server for
// aquiring entitlement keys.
Status CreateWvCasEncryptionRequestJson(const WvCasEncryptionRequest& request,
std::string* request_json);
WvCasStatus 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.
Status ParseWvCasEncryptionResponseJson(const std::string& response_json,
WvCasStatus ParseWvCasEncryptionResponseJson(const std::string& response_json,
WvCasEncryptionResponse* response);
} // namespace cas

View File

@@ -56,7 +56,8 @@ TEST(WvCasTypesTest, CreateWvCasEncryptionRequestJson) {
request.key_rotation = true;
std::string actual_request_json;
EXPECT_OK(CreateWvCasEncryptionRequestJson(request, &actual_request_json));
EXPECT_EQ(OK,
CreateWvCasEncryptionRequestJson(request, &actual_request_json));
// Content_id has been base64 encoded.
std::string expected_request_json =
@@ -76,7 +77,8 @@ TEST(WvCasTypesTest, ParseWvCasEncryptionResponseJson) {
"\"key_slot\":\"ODD\"}]}";
WvCasEncryptionResponse actual_response;
EXPECT_OK(ParseWvCasEncryptionResponseJson(response_json, &actual_response));
EXPECT_EQ(OK,
ParseWvCasEncryptionResponseJson(response_json, &actual_response));
EXPECT_EQ(WvCasEncryptionResponse::Status::OK, actual_response.status);
// 21140844 is base64 decode of "MjExNDA4NDQ=".

View File

@@ -6,15 +6,17 @@
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
// Example server that listens on a port for Simulcrypt API messages.
// Widevine MediaCAS ECMG server.
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#include "gflags/gflags.h"
#include "glog/logging.h"

View File

@@ -0,0 +1,92 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 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.
////////////////////////////////////////////////////////////////////////////////
// Widevine MediaCAS EMMG server.
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cstring>
#include "gflags/gflags.h"
#include "glog/logging.h"
#include "media_cas_packager_sdk/internal/emmg.h"
DEFINE_string(mux_address, "", "Mux server address");
DEFINE_int32(mux_port, 0, "Mux server port number");
// EMMG specfic configs.
DEFINE_int32(client_id, 0, "EMMG client_id");
DEFINE_int32(section_tspkt_flag, 1, "EMMG section_tspkt_flag");
DEFINE_int32(data_channel_id, 0, "EMMG data_channel_id");
DEFINE_int32(data_stream_id, 0, "EMMG data_stream_id");
DEFINE_int32(data_id, 0, "EMMG data_id");
// data_type: type of data carried in the datagram in the stream:
// 0x00: EMM;
// 0x01: private data;
// 0x02: DVB reserved (ECM);
// other values: DVB reserved.
DEFINE_int32(data_type, 0, "EMMG data_type");
#define BUFFER_SIZE (1024)
void BuildEmmgConfig(widevine::cas::EmmgConfig *config) {
CHECK(config);
config->client_id = FLAGS_client_id;
config->section_tspkt_flag = FLAGS_section_tspkt_flag;
config->data_channel_id = FLAGS_data_channel_id;
config->data_stream_id = FLAGS_data_stream_id;
config->data_id = FLAGS_data_id;
config->data_type = FLAGS_data_type;
}
int main(int argc, char **argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
CHECK(!FLAGS_mux_address.empty()) << "flag --mux_address is required";
CHECK(FLAGS_mux_port != 0) << "flag --mux_port is required";
// Create server address.
struct hostent ret;
struct hostent *server;
char buffer[BUFFER_SIZE];
int h_errnop = 0;
int return_val = gethostbyname_r(FLAGS_mux_address.c_str(), &ret, buffer,
sizeof(buffer), &server, &h_errnop);
if (return_val != 0 || server == nullptr) {
LOG(FATAL) << "gethostbyname_r failed for " << FLAGS_mux_address;
}
struct sockaddr_in server_address;
bzero(reinterpret_cast<char *>(&server_address), sizeof(server_address));
server_address.sin_family = AF_INET;
bcopy(server->h_addr,
reinterpret_cast<char *>(&server_address.sin_addr.s_addr),
server->h_length);
server_address.sin_port = htons(FLAGS_mux_port);
// Connect to server.
int server_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket_fd < 0) {
LOG(FATAL) << "Failed to create socket.";
}
if (connect(server_socket_fd,
reinterpret_cast<struct sockaddr *>(&server_address),
sizeof(server_address)) < 0) {
LOG(FATAL) << "Failed to connect to server.";
}
// Send EMMG messages.
widevine::cas::EmmgConfig emmg_config;
BuildEmmgConfig(&emmg_config);
widevine::cas::Emmg emmg(&emmg_config, server_socket_fd);
emmg.Start();
close(server_socket_fd);
return 0;
}