Add custom key fetcher callback to Simulcrypt ECMG
This commit is contained in:
@@ -111,6 +111,7 @@ cc_test(
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/strings:str_format",
|
||||
"@abseil_repo//absl/types:span",
|
||||
"//example:test_ecmg_messages",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -420,10 +420,10 @@ void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, uint16_t cp_numbe
|
||||
|
||||
} // namespace
|
||||
|
||||
EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config) {
|
||||
EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config)
|
||||
: channel_id_set_(false), custom_key_fetcher_(nullptr) {
|
||||
DCHECK(ecmg_config);
|
||||
ecmg_config_ = ecmg_config;
|
||||
channel_id_set_ = false;
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
@@ -608,6 +608,36 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to fetch entitlement keys using custom key fetcher if none is received.
|
||||
std::vector<EntitlementIdKeyComb>& entitlements =
|
||||
streams_info_[params.ecm_stream_id]->entitlement_comb;
|
||||
if (entitlements.empty() && custom_key_fetcher_ != nullptr) {
|
||||
const std::vector<EntitlementKeyInfo>& fetched_entitlements =
|
||||
custom_key_fetcher_(params.ecm_channel_id, params.ecm_stream_id,
|
||||
params.ecm_id);
|
||||
// Only process if the fetched size is expected (1 or 2 entries).
|
||||
if (fetched_entitlements.empty()) {
|
||||
LOG(WARNING) << "EntitlementKeyFetcherFunc returned empty result.";
|
||||
} else if (fetched_entitlements.size() > 2) {
|
||||
LOG(ERROR)
|
||||
<< "EntitlementKeyFetcherFunc should return at most 2 entries, but "
|
||||
<< fetched_entitlements.size() << " entries are returned.";
|
||||
} else {
|
||||
entitlements.reserve(fetched_entitlements.size());
|
||||
for (auto const& fetched_entitlement : fetched_entitlements) {
|
||||
EntitlementIdKeyComb entitlement_comb;
|
||||
entitlement_comb.key_id = fetched_entitlement.key_id;
|
||||
entitlement_comb.key_value = fetched_entitlement.key_value;
|
||||
// In the case of two entitlement keys, the first in |entitlements| must
|
||||
// be even key followed by the odd key.
|
||||
entitlements.insert(fetched_entitlement.is_even_key
|
||||
? entitlements.begin()
|
||||
: entitlements.end(),
|
||||
entitlement_comb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BuildStreamStatus(params.ecm_channel_id, params.ecm_stream_id, params.ecm_id,
|
||||
ecmg_config_->access_criteria_transfer_mode, response,
|
||||
response_length);
|
||||
|
||||
@@ -24,17 +24,6 @@
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// A struct that captures the Ecmg configs.
|
||||
struct EcmgConfig {
|
||||
int16_t delay_start;
|
||||
int16_t delay_stop;
|
||||
uint16_t ecm_rep_period;
|
||||
uint16_t max_comp_time;
|
||||
uint8_t access_criteria_transfer_mode;
|
||||
uint8_t number_of_content_keys;
|
||||
CryptoMode crypto_mode;
|
||||
};
|
||||
|
||||
// A struct that represent a CP_CW_Combination.
|
||||
struct EcmgCpCwCombination {
|
||||
uint16_t cp; // crypto period
|
||||
@@ -91,8 +80,17 @@ class EcmgClientHandler {
|
||||
// Handle a |request| from the client.
|
||||
// If any response is generated, it would returned via |response|
|
||||
// and |response_length|.
|
||||
void HandleRequest(const char* const request, char* response,
|
||||
size_t* response_length);
|
||||
virtual void HandleRequest(const char* const request, char* response,
|
||||
size_t* response_length);
|
||||
|
||||
// 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.
|
||||
virtual void SetCustomEntitlementKeyFetcherFunc(
|
||||
EntitlementKeyFetcherFunc fetcher) {
|
||||
custom_key_fetcher_ = fetcher;
|
||||
}
|
||||
|
||||
private:
|
||||
void HandleChannelSetup(const EcmgParameters& params, char* response,
|
||||
@@ -133,7 +131,7 @@ class EcmgClientHandler {
|
||||
|
||||
EcmgConfig* ecmg_config_;
|
||||
// Per spec, "There is always one (and only one) channel per TCP connection".
|
||||
bool channel_id_set_;
|
||||
bool channel_id_set_ = false;
|
||||
uint16_t channel_id_;
|
||||
|
||||
// Channel specific information.
|
||||
@@ -142,6 +140,8 @@ class EcmgClientHandler {
|
||||
|
||||
// Map from ECM_stream_id to EcmgStreamInfo.
|
||||
absl::flat_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_;
|
||||
// Used to fetch entitlement keys if none are received from SCS.
|
||||
EntitlementKeyFetcherFunc custom_key_fetcher_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -12,11 +12,14 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "example/test_ecmg_messages.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
@@ -32,6 +35,7 @@ using simulcrypt_util::AddUint16Param;
|
||||
using simulcrypt_util::AddUint32Param;
|
||||
using simulcrypt_util::AddUint8Param;
|
||||
using simulcrypt_util::BuildMessageHeader;
|
||||
using ::testing::ElementsAreArray;
|
||||
|
||||
constexpr size_t kBufferSize = 1024;
|
||||
constexpr uint16_t kWidevineSystemId = 0x4AD4;
|
||||
@@ -44,8 +48,10 @@ constexpr size_t kEcmId = 2;
|
||||
constexpr size_t kNominalCpDuration = 0x64;
|
||||
constexpr size_t kCpNumber = 0;
|
||||
constexpr char kContentKeyEven[] = "0123456701234567";
|
||||
constexpr char kContentKeyIvEven[] = "0123456701234567";
|
||||
constexpr char kContentKeyEven8Bytes[] = "01234567";
|
||||
constexpr char kContentKeyOdd[] = "abcdefghabcdefgh";
|
||||
constexpr char kContentKeyIvOdd[] = "abcdefghabcdefgh";
|
||||
constexpr char kContentKeyOdd8Bytes[] = "abcdefgh";
|
||||
constexpr char kEntitlementKeyIdEven[] = "0123456701234567";
|
||||
constexpr char kEntitlementKeyValueEven[] = "01234567012345670123456701234567";
|
||||
@@ -58,6 +64,14 @@ constexpr char kTrackTypesSD[] = "SD";
|
||||
constexpr char kTrackTypesHD[] = "HD";
|
||||
constexpr absl::string_view kWrappedKeyIv = "0123456701234567";
|
||||
|
||||
std::vector<EntitlementKeyInfo> FakeEntitlementKeyFetcherFunc(
|
||||
uint16_t /*channel_id*/, uint16_t /*stream_id*/, uint16_t /*ecm_id*/) {
|
||||
return {{kTrackTypesSD, /*is_even_key*/ false, kEntitlementKeyIdOdd,
|
||||
kEntitlementKeyValueOdd},
|
||||
{kTrackTypesSD, /*is_even_key*/ true, kEntitlementKeyIdEven,
|
||||
kEntitlementKeyValueEven}};
|
||||
}
|
||||
|
||||
class MockEcmgClientHandler : public EcmgClientHandler {
|
||||
public:
|
||||
explicit MockEcmgClientHandler(EcmgConfig* ecmg_config)
|
||||
@@ -339,6 +353,33 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
|
||||
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessSequenceFetchedEntitlements) {
|
||||
handler_->SetCustomEntitlementKeyFetcherFunc(FakeEntitlementKeyFetcherFunc);
|
||||
BuildChannelSetupRequest(kChannelId, kSuperCasId, /*age_restriction=*/0,
|
||||
/*crypto_mode=*/"", request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(response_len_, sizeof(kTestEcmgChannelStatus));
|
||||
EXPECT_THAT(absl::MakeSpan(response_, response_len_),
|
||||
ElementsAreArray(kTestEcmgChannelStatus));
|
||||
|
||||
// No entitlement keys are injected.
|
||||
BuildStreamSetupRequest(
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kTrackTypesSD,
|
||||
/*entitlements=*/{}, {kContentKeyIvEven, kContentKeyIvOdd}, request_,
|
||||
&request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(response_len_, sizeof(kTestEcmgStreamStatus));
|
||||
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
request_, &request_len_);
|
||||
EXPECT_THAT(absl::MakeSpan(response_, response_len_),
|
||||
ElementsAreArray(kTestEcmgStreamStatus));
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(response_len_, sizeof(kTestEcmgEcmResponse));
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessSequenceCsa2) {
|
||||
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
|
||||
kCryptoModeCsa2, request_, &request_len_);
|
||||
|
||||
Reference in New Issue
Block a user