Add custom key fetcher callback to Simulcrypt ECMG

This commit is contained in:
Lu Chen
2020-09-18 18:34:38 -07:00
parent 02c1c8adf5
commit 90bbcb4b4d
14 changed files with 720 additions and 42 deletions

View File

@@ -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",
],
)

View File

@@ -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);

View File

@@ -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

View File

@@ -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_);