Cas Client repo update.
-Parse EMM in Cas plugin -Entitlement key rotation support -Multi_content_license support
This commit is contained in:
49
plugin/include/emm_parser.h
Normal file
49
plugin/include/emm_parser.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef EMM_PARSER_H
|
||||
#define EMM_PARSER_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
using video_widevine::EmmPayload;
|
||||
|
||||
class EmmParser {
|
||||
public:
|
||||
EmmParser(const EmmParser&) = delete;
|
||||
EmmParser& operator=(const EmmParser&) = delete;
|
||||
virtual ~EmmParser() = default;
|
||||
|
||||
// The EmmParser factory method.
|
||||
// The methods validates the passed in |emm|. If validation is successful, it
|
||||
// constructs and returns an EmmParser. Otherwise, nullptr is returned.
|
||||
static std::unique_ptr<const EmmParser> Create(const CasEmm& emm);
|
||||
|
||||
// Accessor methods.
|
||||
virtual uint64_t timestamp() const { return timestamp_; }
|
||||
virtual std::string signature() const { return signature_; }
|
||||
virtual EmmPayload emm_payload() const { return emm_payload_; };
|
||||
|
||||
protected:
|
||||
// Called by the factory create and unit test.
|
||||
EmmParser() = default;
|
||||
|
||||
private:
|
||||
bool Parse(int start_index, const CasEmm& emm);
|
||||
|
||||
uint8_t version_;
|
||||
uint64_t timestamp_;
|
||||
EmmPayload emm_payload_;
|
||||
std::string signature_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // EMM_PARSER_H
|
||||
75
plugin/src/emm_parser.cpp
Normal file
75
plugin/src/emm_parser.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "emm_parser.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
// ETSI ETR 289 specifies table ids 0x82 to 0x8F are for CA System private
|
||||
// usage, which are typically used by EMM, with one table id for each EMM type.
|
||||
constexpr uint16_t kSectionHeader = 0x82;
|
||||
constexpr size_t kSectionHeaderSize = 3;
|
||||
constexpr size_t kSectionHeaderWithPointerSize = 4;
|
||||
constexpr uint8_t kPointerFieldZero = 0x00;
|
||||
|
||||
// Returns the possible starting index of EMM. -1 will be returned in case of
|
||||
// error. It assumes the pointer field will always set to 0, if present.
|
||||
int find_emm_start_index(const CasEmm& cas_emm) {
|
||||
if (cas_emm.empty()) {
|
||||
return -1;
|
||||
}
|
||||
// Case 1: Pointer field (always set to 0); section header; EMM.
|
||||
if (cas_emm[0] == kPointerFieldZero) {
|
||||
return kSectionHeaderWithPointerSize < cas_emm.size()
|
||||
? kSectionHeaderWithPointerSize
|
||||
: -1;
|
||||
}
|
||||
// Case 2: Section header (3 bytes), EMM.
|
||||
if (cas_emm[0] == kSectionHeader) {
|
||||
return kSectionHeaderSize < cas_emm.size() ? kSectionHeaderSize : -1;
|
||||
}
|
||||
// Case 3: EMM.
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<const EmmParser> EmmParser::Create(const CasEmm& emm) {
|
||||
auto parser = std::unique_ptr<EmmParser>(new EmmParser());
|
||||
if (!parser->Parse(find_emm_start_index(emm), emm)) {
|
||||
return nullptr;
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
|
||||
bool EmmParser::Parse(int start_index, const CasEmm& emm) {
|
||||
if (start_index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
video_widevine::SignedEmmPayload signed_emm;
|
||||
if (!signed_emm.ParseFromArray(emm.data() + start_index,
|
||||
emm.size() - start_index)) {
|
||||
LOGE("Failed to parse signed EMM.");
|
||||
return false;
|
||||
}
|
||||
|
||||
signature_ = signed_emm.signature();
|
||||
if (signature_.empty()) {
|
||||
LOGE("No signature in the EMM.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!emm_payload_.ParseFromString(signed_emm.serialized_payload())) {
|
||||
LOGE("Failed to parse EMM payload.");
|
||||
return false;
|
||||
}
|
||||
timestamp_ = emm_payload_.timestamp_secs();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
133
tests/src/emm_parser_test.cpp
Normal file
133
tests/src/emm_parser_test.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "emm_parser.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
using video_widevine::EmmPayload;
|
||||
using video_widevine::SignedEmmPayload;
|
||||
|
||||
constexpr uint8_t kSectionHeader = 0x82;
|
||||
constexpr int64_t kDefaultTimestamp = 1598905921;
|
||||
constexpr char kDefaultSignature[] = "signature";
|
||||
|
||||
class EmmParserTest : public testing::Test {
|
||||
protected:
|
||||
EmmParserTest()
|
||||
: timestamp_(kDefaultTimestamp), signature_(kDefaultSignature) {}
|
||||
void SetSectionHeader(const std::vector<uint8_t> section_header) {
|
||||
section_header_.assign(section_header.begin(), section_header.end());
|
||||
}
|
||||
void SetTimestamp(uint64_t timestamp) { timestamp_ = timestamp; }
|
||||
void SetSignedEmm(const std::string& signed_emm) {
|
||||
serialized_signed_emm_ = signed_emm;
|
||||
}
|
||||
void SetEmmPayload(const std::string& serialized_payload) {
|
||||
serialized_emm_payload_ = serialized_payload;
|
||||
}
|
||||
void SetSignature(const std::string& signature) { signature_ = signature; }
|
||||
|
||||
std::vector<uint8_t> BuildEmm() const {
|
||||
std::vector<uint8_t> emm_data(section_header_.begin(),
|
||||
section_header_.end());
|
||||
if (!serialized_signed_emm_.empty()) {
|
||||
emm_data.insert(emm_data.end(), serialized_signed_emm_.begin(),
|
||||
serialized_signed_emm_.end());
|
||||
return emm_data;
|
||||
}
|
||||
|
||||
SignedEmmPayload signed_emm;
|
||||
if (serialized_emm_payload_.empty()) {
|
||||
EmmPayload emm_payload;
|
||||
emm_payload.set_timestamp_secs(timestamp_);
|
||||
emm_payload.SerializeToString(signed_emm.mutable_serialized_payload());
|
||||
} else {
|
||||
signed_emm.set_serialized_payload(serialized_emm_payload_);
|
||||
}
|
||||
signed_emm.set_signature(signature_);
|
||||
|
||||
emm_data.resize(emm_data.size() + signed_emm.ByteSizeLong());
|
||||
signed_emm.SerializeToArray(&emm_data[section_header_.size()],
|
||||
emm_data.size());
|
||||
return emm_data;
|
||||
}
|
||||
|
||||
void ValidateParserAgainstDefault(const EmmParser* const parser) {
|
||||
ASSERT_NE(parser, nullptr);
|
||||
EXPECT_EQ(parser->timestamp(), kDefaultTimestamp);
|
||||
EmmPayload expected_emm_payload;
|
||||
expected_emm_payload.set_timestamp_secs(timestamp_);
|
||||
EXPECT_EQ(parser->emm_payload().SerializeAsString(),
|
||||
expected_emm_payload.SerializeAsString());
|
||||
EXPECT_EQ(parser->signature(), kDefaultSignature);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> section_header_;
|
||||
uint64_t timestamp_;
|
||||
std::string signature_;
|
||||
std::string serialized_signed_emm_;
|
||||
std::string serialized_emm_payload_;
|
||||
};
|
||||
|
||||
TEST_F(EmmParserTest, ParseDefaultSuccess) {
|
||||
auto parser = EmmParser::Create(BuildEmm());
|
||||
ValidateParserAgainstDefault(parser.get());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithSectionHeaderSuccess) {
|
||||
SetSectionHeader({kSectionHeader, 0, 0});
|
||||
auto parser = EmmParser::Create(BuildEmm());
|
||||
ValidateParserAgainstDefault(parser.get());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithSectionHeaderAndPointerFieldSuccess) {
|
||||
SetSectionHeader({0, kSectionHeader, 0, 0});
|
||||
auto parser = EmmParser::Create(BuildEmm());
|
||||
ValidateParserAgainstDefault(parser.get());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithMalformedEmmCreateFail) {
|
||||
SetSignedEmm("some emm");
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithMalformedPayloadCreateFail) {
|
||||
SetEmmPayload("some payload");
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithNoSignatureCreateFail) {
|
||||
SetSignature("");
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
class EmmParserWrongPrefixTest
|
||||
: public EmmParserTest,
|
||||
public ::testing::WithParamInterface<std::vector<uint8_t>> {};
|
||||
|
||||
TEST_P(EmmParserWrongPrefixTest, EmmWithWrongPrefixCreateFail) {
|
||||
SetSectionHeader(GetParam());
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EmmParserWrongPrefixes, EmmParserWrongPrefixTest,
|
||||
::testing::Values(std::vector<uint8_t>({0}),
|
||||
std::vector<uint8_t>({1, 0, 0}),
|
||||
std::vector<uint8_t>({kSectionHeader, 0}),
|
||||
std::vector<uint8_t>({1, kSectionHeader, 0, 0})));
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
45
tests/src/mock_ecm_parser.h
Normal file
45
tests/src/mock_ecm_parser.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef MOCK_ECM_PARSER_H
|
||||
#define MOCK_ECM_PARSER_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "ecm_parser.h"
|
||||
|
||||
class MockEcmParser : public wvcas::EcmParser {
|
||||
public:
|
||||
MOCK_METHOD(uint8_t, version, (), (const, override));
|
||||
MOCK_METHOD(uint8_t, age_restriction, (), (const, override));
|
||||
MOCK_METHOD(wvcas::CryptoMode, crypto_mode, (), (const, override));
|
||||
MOCK_METHOD(bool, rotation_enabled, (), (const, override));
|
||||
MOCK_METHOD(size_t, content_iv_size, (), (const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, entitlement_key_id, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, content_key_id, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, wrapped_key_data, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, wrapped_key_iv, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, content_iv, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(bool, set_group_id, (const std::string& group_id), (override));
|
||||
MOCK_METHOD(bool, has_fingerprinting, (), (const, override));
|
||||
MOCK_METHOD(video_widevine::Fingerprinting, fingerprinting, (),
|
||||
(const, override));
|
||||
MOCK_METHOD(bool, has_service_blocking, (), (const, override));
|
||||
MOCK_METHOD(video_widevine::ServiceBlocking, service_blocking, (),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::string, ecm_serialized_payload, (), (const, override));
|
||||
MOCK_METHOD(std::string, signature, (), (const, override));
|
||||
MOCK_METHOD(bool, is_entitlement_rotation_enabled, (), (const, override));
|
||||
MOCK_METHOD(uint32_t, entitlement_period_index, (), (const, override));
|
||||
MOCK_METHOD(uint32_t, entitlement_rotation_window_left, (),
|
||||
(const, override));
|
||||
};
|
||||
|
||||
#endif // MOCK_ECM_PARSER_H
|
||||
47
tests/src/mock_event_listener.h
Normal file
47
tests/src/mock_event_listener.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef MOCK_EVENT_LISTENER_H
|
||||
#define MOCK_EVENT_LISTENER_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cas_types.h"
|
||||
|
||||
class MockEventListener : public wvcas::CasEventListener {
|
||||
public:
|
||||
MockEventListener() {}
|
||||
virtual ~MockEventListener() {}
|
||||
|
||||
MOCK_METHOD(void, OnSessionRenewalNeeded, (), (override));
|
||||
MOCK_METHOD(void, OnSessionKeysChange,
|
||||
(const wvcas::KeyStatusMap& keys_status, bool has_new_usable_key),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnExpirationUpdate, (int64_t new_expiry_time_seconds),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnNewRenewalServerUrl,
|
||||
(const std::string& renewal_server_url), (override));
|
||||
MOCK_METHOD(void, OnLicenseExpiration, (), (override));
|
||||
MOCK_METHOD(void, OnAgeRestrictionUpdated,
|
||||
(const wvcas::WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionFingerprintingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& fingerprinting),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionServiceBlockingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& service_blocking),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnFingerprintingUpdated,
|
||||
(const std::vector<uint8_t>& fingerprinting), (override));
|
||||
MOCK_METHOD(void, OnServiceBlockingUpdated,
|
||||
(const std::vector<uint8_t>& service_blocking), (override));
|
||||
MOCK_METHOD(void, OnEntitlementPeriodUpdateNeeded,
|
||||
(const std::string& signed_license_request), (override));
|
||||
};
|
||||
|
||||
#endif // MOCK_EVENT_LISTENER_H
|
||||
Reference in New Issue
Block a user