Change order of loading certificates from pk7 cert
------------- Add libcurl to media_cas_packager_sdk. libcurl will later be used by a key fetcher to retrieve entitlement key from License Server using a HTTP request. ------------- Add a function named parsehelper to parse DCSL from the key smith response. ------------- Move wv_cas_key_fetcher to media_cas_packager_sdk so partners can use it request entitlement keys from License Server. ------------- Add pkcs7 write method to x509_cert.cc ------------- Update boringssl_repo to latest in master-with-bazel ------------- Add a TsPacket class to media_cas_packager_sdk to allow the construction of a ECM TS packet in the SDK. ------------- Move InsertEcm() from our internal CAS directory to the media_cas_packager_sdk, to be used to build a ECM TS packet by the SDK. ------------- Add METADATA in common folder ------------- Refactoring of certificate verification into DrmRootCertificate. ------------- Extend the default duration of leaf certificates. ------------- Fix moe_test ------------- Add a new method to WvCasEcm to allow partner to create a TS packet carrying the generated ECM. ------------- Change from SHA1 to SHA256 for Cast certificates ------------- Update crypto mode enumeration to match WV ECM document ------------- Fix the way we set the validity dates ------------- Move exported_root/util/status to common/ to prepare for util::Status migration Also added constructor/operator to copy from/to util::Status. ------------- Add GenerateDCSLrequest function to certificate_util.h. ------------- Fix build break ------------- Allow 'table_id' (in the section header) be specified by caller of SDK method WvCasEcm::GenerateTsPacket(). ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=224535399
This commit is contained in:
@@ -120,6 +120,17 @@ cc_library(
|
||||
deps = ["//util:status"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "mpeg2ts",
|
||||
hdrs = [
|
||||
"mpeg2ts.h",
|
||||
],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "simulcrypt",
|
||||
srcs = ["simulcrypt.cc"],
|
||||
@@ -149,11 +160,60 @@ cc_test(
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "util",
|
||||
srcs = ["util.cc"],
|
||||
hdrs = ["util.h"],
|
||||
name = "ts_packet",
|
||||
srcs = [
|
||||
"ts_packet.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"mpeg2ts.h",
|
||||
"ts_packet.h",
|
||||
],
|
||||
deps = [
|
||||
":mpeg2ts",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//util:status",
|
||||
"//common:string_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ts_packet_test",
|
||||
size = "small",
|
||||
srcs = ["ts_packet_test.cc"],
|
||||
deps = [
|
||||
":mpeg2ts",
|
||||
":ts_packet",
|
||||
"//base",
|
||||
"//testing:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "util",
|
||||
srcs = ["util.cc"],
|
||||
hdrs = [
|
||||
"mpeg2ts.h",
|
||||
"util.h",
|
||||
],
|
||||
deps = [
|
||||
":mpeg2ts",
|
||||
":ts_packet",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"//util:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "util_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"util_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":util",
|
||||
"//testing:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -140,12 +140,7 @@ util::Status CasEcm::Initialize(const std::string& content_id,
|
||||
"Parameter content_iv_size must be kIvSize8 or kIvSize16."};
|
||||
}
|
||||
|
||||
if (ecm_init_parameters.crypto_mode == CryptoMode::kCryptoModeUnspecified) {
|
||||
return {util::error::INVALID_ARGUMENT, "Invalid crypto mode."};
|
||||
} else {
|
||||
crypto_mode_ = ecm_init_parameters.crypto_mode;
|
||||
}
|
||||
|
||||
crypto_mode_ = ecm_init_parameters.crypto_mode;
|
||||
content_id_ = content_id;
|
||||
content_provider_ = content_provider;
|
||||
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
|
||||
@@ -414,9 +409,6 @@ std::string CasEcm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
generation());
|
||||
std::bitset<kNumBitsDecryptModeField> decrypt_mode(
|
||||
static_cast<int>(crypto_mode()));
|
||||
if (decrypt_mode.to_string() == "00") {
|
||||
LOG(FATAL) << "Invalid decrypt mode \"00\"";
|
||||
}
|
||||
std::bitset<kNumBitsRotationEnabledField> rotation_enabled(
|
||||
RotationFieldValue(paired_keys_required()));
|
||||
std::bitset<kNumBitsWrappedKeyIvSizeField> wrapped_key_iv_size(
|
||||
|
||||
40
media_cas_packager_sdk/internal/mpeg2ts.h
Normal file
40
media_cas_packager_sdk/internal/mpeg2ts.h
Normal file
@@ -0,0 +1,40 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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_MPEG2TS_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <cstdint>
|
||||
#include "base/macros.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// MPEG2 TS Program ID (13 bits).
|
||||
typedef uint16_t ProgramId;
|
||||
// MPEG2 TS Continuity Counter (4 bits).
|
||||
typedef uint8_t ContinuityCounter;
|
||||
|
||||
constexpr ProgramId kInvalidPid = 0x2000;
|
||||
|
||||
constexpr size_t kTsPacketSize = 188;
|
||||
constexpr size_t kTsPacketHeaderSize = 4;
|
||||
constexpr size_t kMaxTsPayloadSize = kTsPacketSize - kTsPacketHeaderSize;
|
||||
|
||||
constexpr uint8_t kTsPacketSyncByte = 0x47;
|
||||
|
||||
constexpr uint8_t kTsPacketTableId80 = 0x80;
|
||||
constexpr uint8_t kTsPacketTableId81 = 0x81;
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_
|
||||
77
media_cas_packager_sdk/internal/ts_packet.cc
Normal file
77
media_cas_packager_sdk/internal/ts_packet.cc
Normal file
@@ -0,0 +1,77 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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/ts_packet.h"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "util/status.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
util::Status TsPacket::Write(std::string* output) const {
|
||||
DCHECK(output);
|
||||
output->resize(kTsPacketSize);
|
||||
|
||||
// TsPacket header.
|
||||
std::bitset<8> sync_byte(kTsPacketSyncByte);
|
||||
std::bitset<1> transport_error_indication(transport_error_indication_);
|
||||
std::bitset<1> payload_unit_start_indicator(payload_unit_start_indicator_);
|
||||
std::bitset<1> transport_priority(transport_priority_);
|
||||
std::bitset<13> pid(pid_);
|
||||
std::bitset<2> transport_scrambling_control(transport_scrambling_control_);
|
||||
std::bitset<2> adaptation_field_control(adaptation_field_control_);
|
||||
std::bitset<4> continuity_counter(continuity_counter_);
|
||||
if (adaptation_field_control_ & kAdaptationFieldOnly) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"TsPacket does NOT handle adaptation field yet");
|
||||
}
|
||||
|
||||
// Converts header bitset to string.
|
||||
std::string header_bitset = absl::StrCat(
|
||||
sync_byte.to_string(), transport_error_indication.to_string(),
|
||||
payload_unit_start_indicator.to_string(), transport_priority.to_string(),
|
||||
pid.to_string(), transport_scrambling_control.to_string(),
|
||||
adaptation_field_control.to_string(), continuity_counter.to_string());
|
||||
if (header_bitset.size() != 4 * 8) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
absl::StrCat("TS packet header bitset incorret size: ",
|
||||
header_bitset.size()));
|
||||
}
|
||||
std::string serialized_header;
|
||||
util::Status status = string_util::BitsetStringToBinaryString(
|
||||
header_bitset, &serialized_header);
|
||||
if (!status.ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert TS packet header bitset to std::string");
|
||||
}
|
||||
|
||||
// Write TsPacket payload.
|
||||
if (payload_.size() != CalculatePayloadSize()) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Incorrect payload size: ", payload_.size()));
|
||||
}
|
||||
|
||||
// Return header + payload as a TS packet.
|
||||
*output = serialized_header + payload_;
|
||||
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
int32_t TsPacket::CalculatePayloadSize() const {
|
||||
int32_t adaptation_length = 0;
|
||||
return kMaxTsPayloadSize - adaptation_length;
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
117
media_cas_packager_sdk/internal/ts_packet.h
Normal file
117
media_cas_packager_sdk/internal/ts_packet.h
Normal file
@@ -0,0 +1,117 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TS Packet is the basic unit of data in a transport stream, and a transport
|
||||
// stream is merely a sequence of packets, without any global header. Each
|
||||
// packet starts with a sync byte and a header, that may be followed with
|
||||
// optional additional headers; the rest of the packet consists of payload.
|
||||
// All header fields are read as big-endian. Packets are 188 bytes in length,
|
||||
// but the communication medium may add additional information:
|
||||
// Forward error correction is added by ISDB & DVB (16 bytes) and ATSC
|
||||
// (20 bytes),[4] while the M2TS format prefixes packets with a 4-byte
|
||||
// copyright and timestamp tag.
|
||||
//
|
||||
// TsPacket class is used to read data from the input stream or serialize data
|
||||
// into the output stream. In contrast to other classes/interfaces in
|
||||
// video/file, TsPacket does not use bidirectional BinaryIO interface, because
|
||||
// BinaryIO is 10-15% slower than BitReader/BitWriter, and for entity like
|
||||
// TsPacket this difference is tangible.
|
||||
//
|
||||
// TsPacket is a simple class. It does not know how to automatically update
|
||||
// AdaptationField::length and AdaptationFieldExtension::length. In order
|
||||
// to initialize TsPacket correctly, user has to set all its fields including
|
||||
// length. Length calculation helpers are provided, though:
|
||||
// * AdaptationFieldExtension::CalculateLength()
|
||||
// * AdaptationField::CalculateLength()
|
||||
// * TsPacket::PayloadSize()
|
||||
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include "base/macros.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "util/status.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// NOTE(user): This class does not handle "adaptation field" or
|
||||
// "adaptation field extension" yet.
|
||||
// NOTE(user): This class does not support reading a TS packet yet.
|
||||
class TsPacket {
|
||||
public:
|
||||
enum AdaptationFieldControl {
|
||||
kAFCReserved = 0x00,
|
||||
kPayloadOnly = 0x01,
|
||||
kAdaptationFieldOnly = 0x02,
|
||||
kAdaptationFieldAndPayload = 0x03
|
||||
};
|
||||
|
||||
TsPacket() = default;
|
||||
TsPacket(const TsPacket&) = delete;
|
||||
TsPacket& operator=(const TsPacket&) = delete;
|
||||
virtual ~TsPacket() = default;
|
||||
|
||||
// Writes the packet into the provided output. Returns kOk on success.
|
||||
virtual util::Status Write(std::string* output) const;
|
||||
|
||||
// Returns the size of payload data for the current TS packet configuration.
|
||||
int32_t CalculatePayloadSize() const;
|
||||
|
||||
// Getters.
|
||||
bool transport_error_indication() const {
|
||||
return transport_error_indication_;
|
||||
}
|
||||
bool payload_unit_start_indicator() const {
|
||||
return payload_unit_start_indicator_;
|
||||
}
|
||||
bool transport_priority() const { return transport_priority_; }
|
||||
uint32_t pid() const { return pid_; }
|
||||
uint32_t transport_scrambling_control() const {
|
||||
return transport_scrambling_control_;
|
||||
}
|
||||
uint32_t adaptation_field_control() const { return adaptation_field_control_; }
|
||||
uint32_t continuity_counter() const { return continuity_counter_; }
|
||||
const std::string& payload() const { return payload_; }
|
||||
|
||||
// Setters.
|
||||
void set_transport_error_indication(bool v) {
|
||||
transport_error_indication_ = v;
|
||||
}
|
||||
void set_payload_unit_start_indicator(bool v) {
|
||||
payload_unit_start_indicator_ = v;
|
||||
}
|
||||
void set_transport_priority(bool v) { transport_priority_ = v; }
|
||||
void set_pid(uint32_t v) { pid_ = v; }
|
||||
void set_transport_scrambling_control(uint32_t v) {
|
||||
transport_scrambling_control_ = v;
|
||||
}
|
||||
void set_adaptation_field_control(uint32_t v) { adaptation_field_control_ = v; }
|
||||
void set_continuity_counter(uint32_t v) { continuity_counter_ = v; }
|
||||
void set_payload(const std::string& p) { payload_ = p; }
|
||||
|
||||
private:
|
||||
bool transport_error_indication_ = false;
|
||||
bool payload_unit_start_indicator_ = false;
|
||||
bool transport_priority_ = false;
|
||||
uint32_t pid_ = kInvalidPid;
|
||||
uint32_t transport_scrambling_control_ = 0;
|
||||
uint32_t adaptation_field_control_ = 0;
|
||||
uint32_t continuity_counter_ = 0;
|
||||
std::string payload_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_
|
||||
84
media_cas_packager_sdk/internal/ts_packet_test.cc
Normal file
84
media_cas_packager_sdk/internal/ts_packet_test.cc
Normal file
@@ -0,0 +1,84 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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/ts_packet.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
class TsPacketTest : public ::testing::Test {
|
||||
public:
|
||||
TsPacketTest() = default;
|
||||
|
||||
protected:
|
||||
void AddStuffing(std::string* bytes, int payload_size) {
|
||||
*bytes += std::string(kTsPacketSize - bytes->size() - payload_size, 0xFF);
|
||||
}
|
||||
|
||||
void ExpectWriteMatches(TsPacket* packet, const std::string& expected) {
|
||||
std::string bytes;
|
||||
ASSERT_OK(packet->Write(&bytes));
|
||||
EXPECT_EQ(expected, bytes);
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(TsPacketTest);
|
||||
};
|
||||
|
||||
TEST_F(TsPacketTest, Packet1) {
|
||||
TsPacket ts;
|
||||
ts.set_transport_error_indication(true);
|
||||
ts.set_payload_unit_start_indicator(false);
|
||||
ts.set_transport_priority(true);
|
||||
ts.set_pid(0x123);
|
||||
ts.set_transport_scrambling_control(3);
|
||||
ts.set_adaptation_field_control(TsPacket::kPayloadOnly);
|
||||
ts.set_continuity_counter(0x7);
|
||||
const std::string payload(ts.CalculatePayloadSize(), 0xFF);
|
||||
ts.set_payload(payload);
|
||||
std::string bytes = std::string("\x47\xA1\x23\xD7", 4) + payload;
|
||||
ExpectWriteMatches(&ts, bytes);
|
||||
EXPECT_EQ(kMaxTsPayloadSize, ts.CalculatePayloadSize());
|
||||
}
|
||||
|
||||
TEST_F(TsPacketTest, Packet2) {
|
||||
TsPacket ts;
|
||||
ts.set_transport_error_indication(false);
|
||||
ts.set_payload_unit_start_indicator(true);
|
||||
ts.set_transport_priority(false);
|
||||
ts.set_pid(0x345);
|
||||
ts.set_transport_scrambling_control(1);
|
||||
ts.set_adaptation_field_control(TsPacket::kPayloadOnly);
|
||||
ts.set_continuity_counter(0x4);
|
||||
const std::string payload(ts.CalculatePayloadSize(), 0xFF);
|
||||
ts.set_payload(payload);
|
||||
std::string bytes = std::string("\x47\x43\x45\x54", 4) + payload;
|
||||
ExpectWriteMatches(&ts, bytes);
|
||||
EXPECT_EQ(184, ts.CalculatePayloadSize());
|
||||
}
|
||||
|
||||
TEST_F(TsPacketTest, BlankPacket184BytePayload) {
|
||||
TsPacket ts;
|
||||
ts.set_pid(0x0);
|
||||
ts.set_adaptation_field_control(TsPacket::kPayloadOnly);
|
||||
const std::string payload(ts.CalculatePayloadSize(), 0xFF);
|
||||
ts.set_payload(payload);
|
||||
std::string bytes = std::string("\x47\x00\x00\x10", 4) + payload;
|
||||
ExpectWriteMatches(&ts, bytes);
|
||||
EXPECT_EQ(184, ts.CalculatePayloadSize());
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
@@ -9,12 +9,23 @@
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "media_cas_packager_sdk/internal/ts_packet.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
namespace {
|
||||
|
||||
constexpr size_t kSectionHeaderSize = 4;
|
||||
constexpr size_t kTsFillValue = 0xFF;
|
||||
|
||||
ContinuityCounter Increment(ContinuityCounter continuity_counter) {
|
||||
return ++continuity_counter & 0xf;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void BigEndianToHost16(uint16_t* destination, const void* source) {
|
||||
DCHECK(destination);
|
||||
@@ -24,5 +35,56 @@ void BigEndianToHost16(uint16_t* destination, const void* source) {
|
||||
*destination = ntohs(big_endian_number);
|
||||
}
|
||||
|
||||
util::Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
|
||||
uint8_t table_id, ContinuityCounter* cc,
|
||||
uint8_t* buffer, ssize_t* bytes_modified) {
|
||||
DCHECK(cc);
|
||||
DCHECK(buffer);
|
||||
DCHECK(bytes_modified);
|
||||
DCHECK_EQ(*bytes_modified % kTsPacketSize, 0);
|
||||
|
||||
// Create a TS payload of 184 bytes (Max TS size - ts header length).
|
||||
CHECK_LE(ecm.size(), kMaxTsPayloadSize);
|
||||
std::string payload(kMaxTsPayloadSize, kTsFillValue);
|
||||
// Reference https://en.wikipedia.org/wiki/Program-specific_information
|
||||
static constexpr uint8_t kPointerField = '\x00';
|
||||
// Here are the 8 bits covers multiple fields from
|
||||
// "section syntax indicator" to the first two bits of "section length".
|
||||
// We always set the first two bits of "section length" to 0 because
|
||||
// the data follows (i.e. the ECM) cannot be more than 180 bytes, so
|
||||
// the remaining 8 bits in "section length" is sufficient to store its
|
||||
// length.
|
||||
static constexpr uint8_t kSyntaxtIndicatorToLength = '\x70';
|
||||
uint8_t ecm_length = ecm.size();
|
||||
// Section header.
|
||||
memcpy(&payload.at(0), &kPointerField, 1);
|
||||
memcpy(&payload.at(1), &table_id, 1);
|
||||
memcpy(&payload.at(2), &kSyntaxtIndicatorToLength, 1);
|
||||
memcpy(&payload.at(3), &ecm_length, 1);
|
||||
// ECM follows the header.
|
||||
memcpy(&payload.at(kSectionHeaderSize), ecm.data(), ecm.size());
|
||||
|
||||
// Wrap the data with a TS header.
|
||||
TsPacket ecm_packet;
|
||||
ecm_packet.set_payload_unit_start_indicator(true);
|
||||
ecm_packet.set_pid(pid);
|
||||
ecm_packet.set_payload(payload);
|
||||
ecm_packet.set_adaptation_field_control(0x1);
|
||||
ecm_packet.set_continuity_counter(*cc);
|
||||
*cc = Increment(*cc);
|
||||
|
||||
// And write the packet.
|
||||
std::string ecm_ts_packet;
|
||||
util::Status status = ecm_packet.Write(&ecm_ts_packet);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
memcpy(buffer + *bytes_modified, ecm_ts_packet.data(), ecm_ts_packet.size());
|
||||
*bytes_modified += ecm_ts_packet.size();
|
||||
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/status.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
@@ -19,6 +24,28 @@ namespace cas {
|
||||
// the result in |destination|.
|
||||
void BigEndianToHost16(uint16_t* destination, const void* source);
|
||||
|
||||
// Packages an ECM as a TS packet and inserts it into a buffer.
|
||||
// Args:
|
||||
// - |ecm| is the serialized ECM.
|
||||
// - |pid| is the PID used for ECMs in the TS header.
|
||||
// - |table_id| is the table ID byte put in the section header, it should be
|
||||
// either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or
|
||||
// 0x81 to 0x80 is used to singal to the client that the key contained
|
||||
// in the ECM has changed. In other words, if you are building an ECM
|
||||
// with a new key that was not in any previous ECM, you should flip the
|
||||
// table ID so the client knows this is an important ECM it should process.
|
||||
// - |cc| is the continuity counter to use in the header, which will also be
|
||||
// incremented by this function.
|
||||
// - |buffer| is the output buffer. Must be big enough to hold an additional
|
||||
// 188 bytes.
|
||||
// - |bytes_modified| the number of bytes which have already been modified in
|
||||
// the |buffer| and is used as an offset.
|
||||
// |bytes_modified| will be incremented by 188 if insertion of ECM into
|
||||
// |buffer| is successful.
|
||||
util::Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
|
||||
uint8_t table_id, ContinuityCounter* cc,
|
||||
uint8_t* buffer, ssize_t* bytes_modified);
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
|
||||
77
media_cas_packager_sdk/internal/util_test.cc
Normal file
77
media_cas_packager_sdk/internal/util_test.cc
Normal file
@@ -0,0 +1,77 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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/util.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// ECM payload data taken from a CETS encrypted file at Google Fiber
|
||||
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
|
||||
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
|
||||
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4',
|
||||
'\xC6', '\x6D', '\x57', '\xDC'};
|
||||
// ECM packet for Video PID (TSheader + payload) taken from a CETS encrypted
|
||||
// file, payload_unit_start = 1, adaptation_field_control = 1, cc = 0
|
||||
constexpr char kExpectedEcmPacket[] = {
|
||||
// TS header.
|
||||
'\x47', '\x5F', '\xFD', '\x10',
|
||||
// Section header.
|
||||
'\x00', '\x80', '\x70', '\x1C',
|
||||
// ECM.
|
||||
'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
|
||||
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4', '\xC6', '\x6D', '\x57',
|
||||
'\xDC',
|
||||
// Padding.
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF'};
|
||||
} // namespace
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
TEST(InsertEcmAsTsPacketTest, BasicHappyPath) {
|
||||
// declare variables used by InsertEcmAsTsPacket()
|
||||
ContinuityCounter ecm_cc_ = 0;
|
||||
ProgramId video_ecm_pid_ = 0x1FFD;
|
||||
uint8_t buffer[188]; // output ts packet size
|
||||
ssize_t output_bytes_modified = 0;
|
||||
// convert kEcmPayload[] into a std::string to be accepted by the method
|
||||
std::string ecm_data(kEcmPayload);
|
||||
EXPECT_OK(InsertEcmAsTsPacket(ecm_data, video_ecm_pid_, kTsPacketTableId80,
|
||||
&ecm_cc_, buffer, &output_bytes_modified));
|
||||
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, buffer, sizeof(buffer)));
|
||||
EXPECT_EQ(1, ecm_cc_);
|
||||
EXPECT_EQ(188, output_bytes_modified);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
@@ -43,6 +43,7 @@ cc_library(
|
||||
"//util:status",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
"//media_cas_packager_sdk/internal:ecm_generator",
|
||||
"//media_cas_packager_sdk/internal:key_fetcher",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
"//protos/public:media_cas_proto",
|
||||
],
|
||||
@@ -93,15 +94,17 @@ cc_library(
|
||||
deps = [
|
||||
":wv_cas_types",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/base:core_headers", # buildcleaner: keep
|
||||
"@abseil_repo//absl/memory", # buildcleaner: keep
|
||||
"@abseil_repo//absl/strings", # buildcleaner: keep
|
||||
"//util:status",
|
||||
"//common:crypto_util",
|
||||
"//example:constants",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
"//media_cas_packager_sdk/internal:ecm_generator",
|
||||
"//media_cas_packager_sdk/internal:fixed_key_fetcher",
|
||||
"//media_cas_packager_sdk/internal:mpeg2ts",
|
||||
"//media_cas_packager_sdk/internal:util",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -114,6 +117,44 @@ cc_test(
|
||||
":wv_cas_types",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//media_cas_packager_sdk/internal:mpeg2ts",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "wv_cas_key_fetcher",
|
||||
srcs = [
|
||||
"wv_cas_key_fetcher.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"wv_cas_key_fetcher.h",
|
||||
],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//third_party/curl",
|
||||
"//util:status",
|
||||
"//common:signature_util",
|
||||
"//media_cas_packager_sdk/internal:key_fetcher",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "wv_cas_key_fetcher_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"wv_cas_key_fetcher_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":wv_cas_key_fetcher",
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//util:status",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_generator.h"
|
||||
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
namespace widevine {
|
||||
@@ -312,5 +314,21 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm(
|
||||
return OK;
|
||||
}
|
||||
|
||||
WvCasStatus WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id,
|
||||
uint8_t* continuity_counter,
|
||||
uint8_t* packet) {
|
||||
ssize_t bytes_modified = 0;
|
||||
util::Status status = InsertEcmAsTsPacket(
|
||||
ecm, pid, table_id, continuity_counter, packet, &bytes_modified);
|
||||
if (!status.ok() || bytes_modified != kTsPacketSize) {
|
||||
memset(packet, 0, kTsPacketSize);
|
||||
LOG(ERROR) << "Failed to generate TS packet: " << status;
|
||||
return INTERNAL;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <cstddef>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -111,6 +111,29 @@ class WvCasEcm {
|
||||
const char* const even_entitlement_key_id,
|
||||
const char* const even_entitlement_key, std::string* ecm) const;
|
||||
|
||||
// Generate a TS packet with the given |ecm| as payload.
|
||||
//
|
||||
// Args (all pointer parameters must be not nullptr):
|
||||
// - |ecm| serialized ECM, e.g., generated by GenerateEcm() method above
|
||||
// - |pid| program ID for the ECM stream
|
||||
// - |table_id| is the table ID byte put in the section header, it should be
|
||||
// either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or
|
||||
// 0x81 to 0x80 is used to singal to the client that the key contained
|
||||
// in the ECM has changed. In other words, if you are building an ECM
|
||||
// with a new key that was not in any previous ECM, you should flip the
|
||||
// table ID so the client knows this is an important ECM it should process.
|
||||
// - |continuity_counter| continuity_counter for the ECM packet,
|
||||
// it will be incremented, only last 4 bits are used
|
||||
// - |packet| a buffer of size at least 188 bytes to be used to return
|
||||
// the generated TS packet
|
||||
//
|
||||
// Returns:
|
||||
// - A status indicating whether there was any error during processing
|
||||
virtual WvCasStatus GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id,
|
||||
uint8_t* continuity_counter,
|
||||
uint8_t* packet);
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
int content_iv_size_;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
using ::testing::Test;
|
||||
@@ -18,6 +19,7 @@ using ::testing::Test;
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
const char kEvenKey[] = "even_key........"; // 16 bytes
|
||||
const char kCsaEvenKey[] = "even_key"; // 8 bytes
|
||||
const char kCsaEvenKeyWithNul[] = {'\x01', '\x00', '\x00', '\x00',
|
||||
@@ -26,9 +28,9 @@ const char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes
|
||||
const char kEvenContentIv16Bytes[] = "even_iv........."; // 16 bytes
|
||||
const char kEvenEntitlementKeyId[] = "even_ent_key_id."; // 16 bytes
|
||||
const char kEvenEntitlementKey[] =
|
||||
"even_entitlement_key............"; // 32 bytes
|
||||
const char kOddKey[] = "odd_key........."; // 16 bytes
|
||||
const char kCsaOddKey[] = "odd_key."; // 8 bytes
|
||||
"even_entitlement_key............"; // 32 bytes
|
||||
const char kOddKey[] = "odd_key........."; // 16 bytes
|
||||
const char kCsaOddKey[] = "odd_key."; // 8 bytes
|
||||
const char kCsaOddKeyWithNul[] = {'\x00', '\x02', '\x00', '\x00',
|
||||
'\x00', '\x00', '\x02', '\x00'};
|
||||
const char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes
|
||||
@@ -37,6 +39,46 @@ const char kOddEntitlementKeyId[] = "odd_ent_key_id.."; // 16 bytes
|
||||
const char kOddEntitlementKey[] =
|
||||
"odd_entitlement_key............."; // 32 bytes
|
||||
|
||||
// ECM payload data taken from a CETS encrypted file at Google Fiber
|
||||
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
|
||||
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
|
||||
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4',
|
||||
'\xC6', '\x6D', '\x57', '\xDC'};
|
||||
// ECM packet for Video PID (TSheader + payload) taken from a CETS encrypted
|
||||
// file, payload_unit_start = 1, adaptation_field_control = 1, cc = 0
|
||||
constexpr char kExpectedEcmPacket[] = {
|
||||
// TS header.
|
||||
'\x47', '\x5F', '\xFD', '\x10',
|
||||
// Section header.
|
||||
'\x00', '\x80', '\x70', '\x1C',
|
||||
// ECM.
|
||||
'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
|
||||
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
|
||||
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4', '\xC6', '\x6D', '\x57',
|
||||
'\xDC',
|
||||
// Padding.
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF'};
|
||||
} // namespace
|
||||
|
||||
class WvCasEcmTest : public Test {
|
||||
protected:
|
||||
WvCasEcmTest() {}
|
||||
@@ -193,7 +235,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_16BytesContentIv_Cbc_Success) {
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40105c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"4ad40101c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e2e2e2e2e2e2e2e2e6f64645f656e745f6b65795f69642e2e34cd74b6b99888"
|
||||
"9aad0e71b44bdd8c0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3e"
|
||||
@@ -216,7 +258,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_16BytesContentIv_Cbc_Success) {
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40104c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"4ad40100c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
|
||||
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
|
||||
"6e5f69762e2e2e2e2e2e2e2e2e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
@@ -242,7 +284,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_Success) {
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40107806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
|
||||
"4ad40105806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
|
||||
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
|
||||
"6e5f69762e6f64645f656e745f6b65795f69642e2e7f655fe61e99e89e03ac23df98cc02"
|
||||
"1cf21dfe9637c72c3480727ab18332d4ee219e81b8f34c9df2704b0595501832736f6464"
|
||||
@@ -265,7 +307,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_8BytesContentIv_Csa_Success) {
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40106806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
|
||||
"4ad40104806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
|
||||
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
|
||||
"6e5f69762e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
@@ -291,7 +333,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_NulCharInKey_Success) {
|
||||
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40107806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
|
||||
"4ad40105806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
|
||||
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
|
||||
"6e5f69762e6f64645f656e745f6b65795f69642e2e874aab870ffba640875a4521d3cd57"
|
||||
"02f26d0f9c7e9c69d7059c9ad42b091ec1f151aaa190536f4f330edebe84fe5a786f6464"
|
||||
@@ -315,11 +357,36 @@ TEST_F(WvCasEcmTest,
|
||||
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
|
||||
|
||||
EXPECT_EQ(
|
||||
"4ad40106806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
|
||||
"4ad40104806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
|
||||
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
|
||||
"6e5f69762e",
|
||||
absl::BytesToHexString(actual_ecm));
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateTsPacket_TableId80) {
|
||||
std::string ecm(kEcmPayload);
|
||||
ProgramId pid = 0x1FFD;
|
||||
ContinuityCounter cc = 0;
|
||||
uint8_t packet[188];
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.GenerateTsPacket(ecm, pid, kTsPacketTableId80, &cc,
|
||||
packet));
|
||||
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, packet, sizeof(packet)));
|
||||
EXPECT_EQ(1, cc);
|
||||
}
|
||||
|
||||
TEST_F(WvCasEcmTest, GenerateTsPacket_TableId81) {
|
||||
std::string ecm(kEcmPayload);
|
||||
ProgramId pid = 0x1FFD;
|
||||
ContinuityCounter cc = 0;
|
||||
uint8_t packet[188];
|
||||
EXPECT_EQ(OK, wv_cas_ecm_.GenerateTsPacket(ecm, pid, kTsPacketTableId81, &cc,
|
||||
packet));
|
||||
char expected_ecm[188];
|
||||
memcpy(expected_ecm, kExpectedEcmPacket, 188);
|
||||
expected_ecm[5] = '\x81';
|
||||
EXPECT_EQ(0, memcmp(expected_ecm, packet, sizeof(packet)));
|
||||
EXPECT_EQ(1, cc);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
160
media_cas_packager_sdk/public/wv_cas_key_fetcher.cc
Normal file
160
media_cas_packager_sdk/public/wv_cas_key_fetcher.cc
Normal file
@@ -0,0 +1,160 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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_key_fetcher.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "glog/logging.h"
|
||||
#include "net/proto2/util/public/json_util.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "curl/curl.h"
|
||||
#include "curl/easy.h"
|
||||
#include "util/status.h"
|
||||
#include "common/signature_util.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
using google::protobuf::util::JsonPrintOptions;
|
||||
using google::protobuf::util::JsonStringToMessage;
|
||||
using google::protobuf::util::MessageToJsonString;
|
||||
|
||||
DEFINE_string(
|
||||
license_server, "",
|
||||
"HTTP URL to the license server for making CAS encryption request");
|
||||
DEFINE_string(signing_provider, "",
|
||||
"Name of the provider signing the CAS encryption request");
|
||||
DEFINE_string(signing_key, "",
|
||||
"AES key (in hex) for signing the CAS encryption request");
|
||||
DEFINE_string(signing_iv, "",
|
||||
"AES iv (in hex) for signing the CAS encryption request");
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
util::Status WvCasKeyFetcher::RequestEntitlementKey(
|
||||
const std::string& request_string, std::string* signed_response_string) {
|
||||
if (FLAGS_signing_provider.empty() || FLAGS_signing_key.empty() ||
|
||||
FLAGS_signing_iv.empty()) {
|
||||
return util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
"Flag 'signing_provider', 'signing_key' or 'signing_iv' is empty");
|
||||
}
|
||||
|
||||
// Processes request.
|
||||
CasEncryptionRequest request;
|
||||
request.ParseFromString(request_string);
|
||||
std::string request_json;
|
||||
JsonPrintOptions print_options;
|
||||
// Set this option so that the json output is
|
||||
// {"content_id":"MjExNDA4NDQ=", ...
|
||||
// instead of
|
||||
// {"contentId":"MjExNDA4NDQ=", ...
|
||||
print_options.preserve_proto_field_names = true;
|
||||
// NOTE: MessageToJsonString will automatically converts 'bytes' type fields
|
||||
// to base64. For example content ID '21140844' becomes 'MjExNDA4NDQ='.
|
||||
if (!MessageToJsonString(request, &request_json, print_options).ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert request message to json.");
|
||||
}
|
||||
LOG(INFO) << "Json CasEncryptionRequest: " << request_json;
|
||||
|
||||
// Creates signed request.
|
||||
SignedCasEncryptionRequest signed_request;
|
||||
signed_request.set_request(request_json);
|
||||
std::string signature;
|
||||
if (!signature_util::GenerateAesSignature(
|
||||
request_json, absl::HexStringToBytes(FLAGS_signing_key),
|
||||
absl::HexStringToBytes(FLAGS_signing_iv), &signature)
|
||||
.ok()) {
|
||||
return util::Status(util::error::INTERNAL, "Failed to sign the request.");
|
||||
}
|
||||
signed_request.set_signature(signature);
|
||||
signed_request.set_signer(FLAGS_signing_provider);
|
||||
std::string signed_request_json;
|
||||
// NOTE: MessageToJsonString will automatically converts the 'request' and
|
||||
// 'signature' fields in SignedCasEncryptionRequest to base64, because they
|
||||
// are of type 'bytes'.
|
||||
if (!MessageToJsonString(signed_request, &signed_request_json).ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert signed request message to json.");
|
||||
}
|
||||
LOG(INFO) << "Json SignedCasEncryptionRequest: " << signed_request_json;
|
||||
|
||||
// Makes HTTP request against License Server.
|
||||
std::string http_response_json;
|
||||
util::Status status =
|
||||
MakeHttpRequest(signed_request_json, &http_response_json);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
LOG(INFO) << "Json HTTP response: " << http_response_json;
|
||||
HttpResponse http_response;
|
||||
if (!JsonStringToMessage(http_response_json, &http_response).ok()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert http response json to message.");
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Failed to convert response json to message.");
|
||||
}
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response.SerializeAsString());
|
||||
signed_response.SerializeToString(signed_response_string);
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
size_t AppendToString(void* ptr, size_t size, size_t count, std::string* output) {
|
||||
const absl::string_view data(static_cast<char*>(ptr), size * count);
|
||||
absl::StrAppend(output, data);
|
||||
return data.size();
|
||||
}
|
||||
|
||||
util::Status WvCasKeyFetcher::MakeHttpRequest(
|
||||
const std::string& signed_request_json, std::string* http_response_json) const {
|
||||
CHECK(http_response_json);
|
||||
if (FLAGS_license_server.empty()) {
|
||||
return util::Status(util::error::INVALID_ARGUMENT,
|
||||
"Flag 'license_server' is empty");
|
||||
}
|
||||
CURL* curl;
|
||||
CURLcode curl_code;
|
||||
curl = curl_easy_init();
|
||||
if (curl) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, FLAGS_license_server.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, signed_request_json.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, http_response_json);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &AppendToString);
|
||||
// If we don't provide POSTFIELDSIZE, libcurl will strlen() by itself.
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
|
||||
(int64_t)strlen(signed_request_json.c_str()));
|
||||
curl_code = curl_easy_perform(curl);
|
||||
if (curl_code != CURLE_OK) {
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"curl_easy_perform() failed: " +
|
||||
std::string(curl_easy_strerror(curl_code)));
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
} else {
|
||||
return util::Status(util::error::INTERNAL, "curl_easy_init() failed");
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
58
media_cas_packager_sdk/public/wv_cas_key_fetcher.h
Normal file
58
media_cas_packager_sdk/public/wv_cas_key_fetcher.h
Normal file
@@ -0,0 +1,58 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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_KEY_FETCHER_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_KEY_FETCHER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/commandlineflags_declare.h"
|
||||
#include "util/status.h"
|
||||
#include "media_cas_packager_sdk/internal/key_fetcher.h"
|
||||
|
||||
DECLARE_string(license_server);
|
||||
DECLARE_string(signing_provider);
|
||||
DECLARE_string(signing_key);
|
||||
DECLARE_string(signing_iv);
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// WV CAS KeyFetcher. Performs the communication with the Widevine License
|
||||
// Server to get entitlement keys on behalf of a WvCasEcm object.
|
||||
class WvCasKeyFetcher : public KeyFetcher {
|
||||
public:
|
||||
WvCasKeyFetcher() = default;
|
||||
WvCasKeyFetcher(const WvCasKeyFetcher&) = delete;
|
||||
WvCasKeyFetcher& operator=(const WvCasKeyFetcher&) = delete;
|
||||
virtual ~WvCasKeyFetcher() = default;
|
||||
|
||||
// Get entitlement keys from the license server. Send a
|
||||
// SignedCasEncryptionRequest message to the license server, receive a
|
||||
// SignedCasEncryptionResponse and return it to the caller.
|
||||
// Args:
|
||||
// |request_string| a serialized CasEncryptionRequest message, produced
|
||||
// by WvCasEcm::Initialize().
|
||||
// |signed_response_string| a serialized SignedCasEncryptionResponse
|
||||
// message. It should be passed into
|
||||
// widevine::cas::Ecm::ProcessCasEncryptionResponse().
|
||||
virtual util::Status RequestEntitlementKey(const std::string& request_string,
|
||||
std::string* signed_response_string);
|
||||
|
||||
protected:
|
||||
// Makes a HTTP request to License Server for entitlement key(s).
|
||||
// Returns the HTTP response in Json format in |http_response_json|.
|
||||
// Protected visibility to support unit testing.
|
||||
virtual util::Status MakeHttpRequest(const std::string& signed_request_json,
|
||||
std::string* http_response_json) const;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_KEY_FETCHER_H_
|
||||
109
media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc
Normal file
109
media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc
Normal file
@@ -0,0 +1,109 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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_key_fetcher.h"
|
||||
|
||||
#include "base/commandlineflags_declare.h"
|
||||
#include "glog/logging.h"
|
||||
#include "google/protobuf/text_format.h"
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "util/status.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
using testing::_;
|
||||
using testing::DoAll;
|
||||
using testing::Return;
|
||||
using testing::SetArgumentPointee;
|
||||
|
||||
namespace {
|
||||
|
||||
const char kSigningProvider[] = "widevine_test";
|
||||
const char kSingingKey[] =
|
||||
"1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9";
|
||||
const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249";
|
||||
const char kCasEncryptionRequest[] =
|
||||
"content_id: \"21140844\" "
|
||||
"provider: \"widevine\" "
|
||||
"track_types: \"SD\" "
|
||||
"key_rotation: false";
|
||||
const char kSignedCasEncryptionRequest[] =
|
||||
"{\"request\":"
|
||||
"\"eyJjb250ZW50X2lkIjoiTWpFeE5EQTRORFE9IiwicHJvdmlkZXIiOiJ3aWRldmluZSIsIn"
|
||||
"RyYWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2V9\",\"signature\":"
|
||||
"\"JyTnKEy1w98HRP1lL78+OIEiqtIXyfCN8iudvNXIWhw=\",\"signer\":\"widevine_"
|
||||
"test\"}";
|
||||
const char kHttpResponse[] =
|
||||
"{\"response\":"
|
||||
"\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu"
|
||||
"dF9rZXlzIjpbeyJrZXlfaWQiOiJNUGFndXhNb1hNNkUxUzhEOUF3RkNBPT0iLCJrZXkiOiJoZ1"
|
||||
"JycmdqeUg4NjQycjY3VHd0OHJ1cU5MUGNMRmtKcWRVSUROdm5GZDBNPSIsInRyYWNrX3R5cGUi"
|
||||
"OiJTRCIsImtleV9zbG90IjoiU0lOR0xFIn1dfQ==\"}";
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
class MockWvCasKeyFetcher : public WvCasKeyFetcher {
|
||||
public:
|
||||
MockWvCasKeyFetcher() : WvCasKeyFetcher() {}
|
||||
~MockWvCasKeyFetcher() override {}
|
||||
MOCK_CONST_METHOD2(MakeHttpRequest,
|
||||
util::Status(const std::string& signed_request_json,
|
||||
std::string* http_response_json));
|
||||
};
|
||||
|
||||
class WvCasKeyFetcherTest : public ::testing::Test {
|
||||
public:
|
||||
WvCasKeyFetcherTest() {}
|
||||
void SetUp() override {
|
||||
FLAGS_signing_provider = kSigningProvider;
|
||||
FLAGS_signing_key = kSingingKey;
|
||||
FLAGS_signing_iv = kSingingIv;
|
||||
|
||||
CHECK(
|
||||
google::protobuf::TextFormat::ParseFromString(kCasEncryptionRequest, &request_));
|
||||
|
||||
response_.set_status(CasEncryptionResponse::OK);
|
||||
response_.set_content_id("21140844");
|
||||
CasEncryptionResponse::KeyInfo* entitlement_keys =
|
||||
response_.add_entitlement_keys();
|
||||
CHECK(absl::Base64Unescape("MPaguxMoXM6E1S8D9AwFCA==",
|
||||
entitlement_keys->mutable_key_id()));
|
||||
CHECK(absl::Base64Unescape("hgRrrgjyH8642r67Twt8ruqNLPcLFkJqdUIDNvnFd0M=",
|
||||
entitlement_keys->mutable_key()));
|
||||
entitlement_keys->set_track_type("SD");
|
||||
entitlement_keys->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
|
||||
}
|
||||
|
||||
protected:
|
||||
MockWvCasKeyFetcher mock_key_fetcher_;
|
||||
CasEncryptionRequest request_;
|
||||
CasEncryptionResponse response_;
|
||||
};
|
||||
|
||||
TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
|
||||
EXPECT_CALL(mock_key_fetcher_,
|
||||
MakeHttpRequest(kSignedCasEncryptionRequest, _))
|
||||
.WillOnce(DoAll(SetArgumentPointee<1>(std::string(kHttpResponse)),
|
||||
Return(util::OkStatus())));
|
||||
|
||||
std::string actual_signed_response;
|
||||
EXPECT_OK(mock_key_fetcher_.RequestEntitlementKey(
|
||||
request_.SerializeAsString(), &actual_signed_response));
|
||||
|
||||
SignedCasEncryptionResponse expected_signed_response;
|
||||
expected_signed_response.set_response(response_.SerializeAsString());
|
||||
EXPECT_EQ(expected_signed_response.SerializeAsString(),
|
||||
actual_signed_response);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "base/macros.h"
|
||||
|
||||
namespace widevine {
|
||||
@@ -37,34 +38,38 @@ std::string GetWvCasStatusMessage(WvCasStatus status) {
|
||||
return kWvCasStatusMessage[status];
|
||||
}
|
||||
|
||||
std::string CryptoModeToString(CryptoMode mode) {
|
||||
switch (mode) {
|
||||
case CryptoMode::kAesCtr: {
|
||||
return "kAesCtr";
|
||||
}
|
||||
case CryptoMode::kAesCbc: {
|
||||
return "kAesCbc";
|
||||
}
|
||||
case CryptoMode::kDvbCsa2: {
|
||||
return "kDvbCsa2";
|
||||
}
|
||||
default: {
|
||||
return "kCryptoModeUnspecified";
|
||||
}
|
||||
// Numeric value of crypto mode is the index into strings array.
|
||||
static const char* kCrypoModeStrings[] = {
|
||||
"AesCbc",
|
||||
"AesCtr",
|
||||
"DvbCsa2",
|
||||
};
|
||||
|
||||
bool CryptoModeToString(CryptoMode mode, std::string* str) {
|
||||
if (str == nullptr) {
|
||||
return false;
|
||||
}
|
||||
int mode_idx = static_cast<int>(mode);
|
||||
if (mode_idx >= 0 && mode_idx < arraysize(kCrypoModeStrings)) {
|
||||
*str = kCrypoModeStrings[mode_idx];
|
||||
return true;
|
||||
}
|
||||
LOG(ERROR) << "Invalid crypto mode: " << mode_idx;
|
||||
return false;
|
||||
}
|
||||
|
||||
CryptoMode StringToCryptoMode(std::string str) {
|
||||
if (str == "kAesCtr") {
|
||||
return CryptoMode::kAesCtr;
|
||||
bool StringToCryptoMode(const std::string& str, CryptoMode* mode) {
|
||||
if (mode == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (str == "kAesCbc") {
|
||||
return CryptoMode::kAesCbc;
|
||||
for (int i = 0; i < arraysize(kCrypoModeStrings); ++i) {
|
||||
if (str.compare(kCrypoModeStrings[i]) == 0) {
|
||||
*mode = static_cast<CryptoMode>(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (str == "kDvbCsa2") {
|
||||
return CryptoMode::kDvbCsa2;
|
||||
}
|
||||
return CryptoMode::kCryptoModeUnspecified;
|
||||
LOG(ERROR) << "Invalid crypto mode: " << str;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -50,17 +50,19 @@ enum WvCasStatus {
|
||||
// Returns the message std::string for the given WvCasStatus.
|
||||
std::string GetWvCasStatusMessage(WvCasStatus status);
|
||||
|
||||
// Crypto mode for encryption / decryption.
|
||||
// Crypto mode for encryption / decryption. ENUM value should be consistent with
|
||||
// https://docs.google.com/document/d/1A5vflf8tbKyUheV-xsvfxFqB6YyNLNdsGXYx8ZnhjfY/edit#heading=h.ej4ts3lifoio
|
||||
enum class CryptoMode : int {
|
||||
kCryptoModeUnspecified = 0,
|
||||
kAesCbc = 0,
|
||||
kAesCtr = 1,
|
||||
kAesCbc = 2,
|
||||
kDvbCsa2 = 3
|
||||
kDvbCsa2 = 2,
|
||||
};
|
||||
|
||||
std::string CryptoModeToString(CryptoMode mode);
|
||||
// Returns false if mode is not a valid CryptoMode.
|
||||
bool CryptoModeToString(CryptoMode mode, std::string* str);
|
||||
|
||||
CryptoMode StringToCryptoMode(std::string str);
|
||||
// Returns false if str is not a valid CryptoMode.
|
||||
bool StringToCryptoMode(const std::string& str, CryptoMode* mode);
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -25,19 +25,25 @@ TEST(WvCasTypesTest, GetWvCasStatusMessage) {
|
||||
}
|
||||
|
||||
TEST(WvCasTypesTest, CryptoModeToString) {
|
||||
EXPECT_EQ("kAesCtr", CryptoModeToString(CryptoMode::kAesCtr));
|
||||
EXPECT_EQ("kAesCbc", CryptoModeToString(CryptoMode::kAesCbc));
|
||||
EXPECT_EQ("kDvbCsa2", CryptoModeToString(CryptoMode::kDvbCsa2));
|
||||
EXPECT_EQ("kCryptoModeUnspecified",
|
||||
CryptoModeToString(CryptoMode::kCryptoModeUnspecified));
|
||||
std::string crypto_mode;
|
||||
ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesCtr, &crypto_mode));
|
||||
EXPECT_EQ("AesCtr", crypto_mode);
|
||||
ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesCbc, &crypto_mode));
|
||||
EXPECT_EQ("AesCbc", crypto_mode);
|
||||
ASSERT_TRUE(CryptoModeToString(CryptoMode::kDvbCsa2, &crypto_mode));
|
||||
EXPECT_EQ("DvbCsa2", crypto_mode);
|
||||
EXPECT_FALSE(CryptoModeToString(static_cast<CryptoMode>(-1), &crypto_mode));
|
||||
}
|
||||
|
||||
TEST(WvCasTypesTest, StringToCryptoMode) {
|
||||
EXPECT_EQ(CryptoMode::kAesCtr, StringToCryptoMode("kAesCtr"));
|
||||
EXPECT_EQ(CryptoMode::kAesCbc, StringToCryptoMode("kAesCbc"));
|
||||
EXPECT_EQ(CryptoMode::kDvbCsa2, StringToCryptoMode("kDvbCsa2"));
|
||||
EXPECT_EQ(CryptoMode::kCryptoModeUnspecified,
|
||||
StringToCryptoMode("wrong crypto mode"));
|
||||
CryptoMode crypto_mode;
|
||||
ASSERT_TRUE(StringToCryptoMode("AesCtr", &crypto_mode));
|
||||
EXPECT_EQ(CryptoMode::kAesCtr, crypto_mode);
|
||||
ASSERT_TRUE(StringToCryptoMode("AesCbc", &crypto_mode));
|
||||
EXPECT_EQ(CryptoMode::kAesCbc, crypto_mode);
|
||||
ASSERT_TRUE(StringToCryptoMode("DvbCsa2", &crypto_mode));
|
||||
EXPECT_EQ(CryptoMode::kDvbCsa2, crypto_mode);
|
||||
EXPECT_FALSE(StringToCryptoMode("invalid crypto mode", &crypto_mode));
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
Reference in New Issue
Block a user