Add custom key fetcher callback to Simulcrypt ECMG
This commit is contained in:
@@ -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(
|
||||
name = "wv_cas_types_test",
|
||||
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(
|
||||
name = "wv_ecmg",
|
||||
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.
|
||||
};
|
||||
|
||||
// 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 {
|
||||
std::string content_id;
|
||||
std::string provider;
|
||||
|
||||
Reference in New Issue
Block a user