blob: 59f32127ff391fe9c9807c521393d963d9411167 [file] [log] [blame]
// Copyright 2017 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/win32/wrm_header.h"
#include <guiddef.h>
#include <initguid.h>
#include <stdlib.h>
#include <algorithm>
#include "starboard/memory.h"
#include "starboard/shared/win32/error_utils.h"
namespace starboard {
namespace shared {
namespace win32 {
using Microsoft::WRL::ComPtr;
namespace {
// The playready system id used to create the content header.
DEFINE_GUID(kPlayreadyContentHeaderCLSID,
0xF4637010,
0x03C3,
0x42CD,
0xB9,
0x32,
0xB4,
0x8A,
0xDF,
0x3A,
0x6A,
0x54);
// The playready system id used to identify the wrm header in the
// initialization data.
DEFINE_GUID(kPlayreadyInitializationDataCLSID,
0x79f0049a,
0x4098,
0x8642,
0xab,
0x92,
0xe6,
0x5b,
0xe0,
0x88,
0x5f,
0x95);
const uint16_t kPlayreadyWRMTag = 0x0001;
class Reader {
public:
Reader(const uint8_t* data, size_t length)
: start_(data), curr_(data), end_(data + length) {}
size_t GetPosition() const { return curr_ - start_; }
size_t GetRemaining() const { return end_ - curr_; }
const uint8_t* curr() const { curr_; }
bool Skip(size_t distance) {
if (distance > GetRemaining())
return false;
curr_ += distance;
return true;
}
bool ReadGUID(GUID* guid) {
if (sizeof(*guid) > GetRemaining()) {
return false;
}
memcpy(guid, curr_, sizeof(*guid));
curr_ += sizeof(*guid);
return true;
}
bool ReadBigEndianU32(uint32_t* i) {
if (sizeof(*i) > GetRemaining())
return false;
*i =
curr_[0] * 0x1000000 + curr_[1] * 0x10000 + curr_[2] * 0x100 + curr_[3];
curr_ += sizeof(*i);
return true;
}
bool ReadLittleEndianU16(uint16_t* i) {
if (sizeof(*i) > GetRemaining())
return false;
*i = curr_[0] + curr_[1] * 0x100;
curr_ += sizeof(*i);
return true;
}
private:
const uint8_t* const start_;
const uint8_t* curr_;
const uint8_t* const end_;
};
std::vector<uint8_t> ParseWrmHeaderFromInitializationData(
const void* initialization_data,
int initialization_data_size) {
if (initialization_data_size == 0) {
SB_NOTIMPLEMENTED();
return std::vector<uint8_t>();
}
std::vector<uint8_t> output;
const uint8_t* data = static_cast<const uint8_t*>(initialization_data);
Reader reader(data, initialization_data_size);
while (reader.GetRemaining() > 0) {
// Parse pssh atom (big endian)
//
// 4 bytes -- size
// 4 bytes -- "pssh"
// 4 bytes -- flags
// 16 bytes -- guid
uint32_t pssh_size;
if (!reader.ReadBigEndianU32(&pssh_size)) {
return output;
}
// Skipping pssh and flags
if (!reader.Skip(8)) {
return output;
}
GUID system_id;
if (!reader.ReadGUID(&system_id)) {
return output;
}
if (system_id != kPlayreadyInitializationDataCLSID) {
// Skip entire pssh atom
if (!reader.Skip(pssh_size - 28)) {
return output;
}
continue;
}
// 4 bytes -- size of PlayreadyObject
// followed by PlayreadyObject
// Skip size, and continue parsing
if (!reader.Skip(4)) {
return output;
}
// Parse Playready object (little endian)
// 4 bytes -- size
// 2 bytes -- record count
//
// Playready Record
// 2 bytes -- type
// 2 bytes -- size of record
// n bytes -- record
if (!reader.Skip(4)) {
return output;
}
uint16_t num_records;
if (!reader.ReadLittleEndianU16(&num_records)) {
return output;
}
for (int i = 0; i < num_records; i++) {
uint16_t record_type;
if (!reader.ReadLittleEndianU16(&record_type)) {
return output;
}
uint16_t record_size;
if (!reader.ReadLittleEndianU16(&record_size)) {
return output;
}
if ((record_type & kPlayreadyWRMTag) == kPlayreadyWRMTag) {
std::copy(data + reader.GetPosition(),
data + reader.GetPosition() + record_size,
std::back_inserter(output));
return output;
}
if (!reader.Skip(record_size)) {
return output;
}
}
}
return output;
}
uint32_t Base64ToValue(uint8_t byte) {
if (byte >= 'A' && byte <= 'Z') {
return byte - 'A';
}
if (byte >= 'a' && byte <= 'z') {
return byte - 'a' + 26;
}
if (byte >= '0' && byte <= '9') {
return byte - '0' + 52;
}
if (byte == '+') {
return 62;
}
if (byte == '/') {
return 63;
}
SB_DCHECK(byte == '=');
return 0;
}
std::string Base64Decode(const std::wstring& input) {
SB_DCHECK(input.size() % 4 == 0);
std::string output;
output.reserve(input.size() / 4 * 3);
for (size_t i = 0; i < input.size() - 3; i += 4) {
uint32_t decoded =
Base64ToValue(input[i]) * 4 + Base64ToValue(input[i + 1]) / 16;
output += static_cast<char>(decoded);
if (input[i + 2] != '=') {
decoded = Base64ToValue(input[i + 1]) % 16 * 16 +
Base64ToValue(input[i + 2]) / 4;
output += static_cast<char>(decoded);
if (input[i + 3] != '=') {
decoded =
Base64ToValue(input[i + 2]) % 4 * 64 + Base64ToValue(input[i + 3]);
output += static_cast<char>(decoded);
}
}
}
return output;
}
GUID ParseKeyIdFromWrmHeader(const std::vector<uint8_t>& wrm_header) {
// The wrm_header is an XML in wchar_t that contains the base64 encoded key
// id in <KID> node in base64. The original key id should be 16 characters,
// so it is 24 characters after based64 encoded.
const size_t kEncodedKeyIdLength = 24;
SB_DCHECK(wrm_header.size() % 2 == 0);
std::wstring wrm_header_copy(
reinterpret_cast<const wchar_t*>(wrm_header.data()),
wrm_header.size() / 2);
std::wstring::size_type begin = wrm_header_copy.find(L"<KID>");
if (begin == wrm_header_copy.npos) {
return WrmHeader::kInvalidKeyId;
}
std::wstring::size_type end = wrm_header_copy.find(L"</KID>", begin);
if (end == wrm_header_copy.npos) {
return WrmHeader::kInvalidKeyId;
}
begin += 5;
if (end - begin != kEncodedKeyIdLength) {
return WrmHeader::kInvalidKeyId;
}
std::string key_id_in_string =
Base64Decode(wrm_header_copy.substr(begin, kEncodedKeyIdLength));
if (key_id_in_string.size() != sizeof(GUID)) {
return WrmHeader::kInvalidKeyId;
}
GUID key_id = *reinterpret_cast<const GUID*>(key_id_in_string.data());
key_id.Data1 = _byteswap_ulong(key_id.Data1);
key_id.Data2 = _byteswap_ushort(key_id.Data2);
key_id.Data3 = _byteswap_ushort(key_id.Data3);
return key_id;
}
ComPtr<IStream> CreateWrmHeaderStream(const std::vector<uint8_t>& wrm_header) {
ComPtr<IStream> stream;
HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &stream);
if (FAILED(hr)) {
return NULL;
}
DWORD wrm_header_size = static_cast<DWORD>(wrm_header.size());
ULONG bytes_written = 0;
if (wrm_header_size != 0) {
hr = stream->Write(wrm_header.data(), wrm_header_size, &bytes_written);
if (FAILED(hr)) {
return NULL;
}
}
return stream;
}
ComPtr<IStream> CreateContentHeaderFromWrmHeader(
const std::vector<uint8_t>& wrm_header) {
ComPtr<IStream> content_header;
// Assume we use one license for one stream.
const DWORD kNumStreams = 1;
const DWORD kNextStreamId = static_cast<DWORD>(-1);
HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &content_header);
if (FAILED(hr)) {
return NULL;
}
// Initialize spInitStm with the required data
// Format: (All DWORD values are serialized in little-endian order)
// [GUID (content protection system guid, see msprita.idl)]
// [DWORD (stream count, use the actual stream count even if all streams are
// encrypted using the same data, note that zero is invalid)] [DWORD (next
// stream ID, use -1 if all remaining streams are encrypted using the same
// data)] [DWORD (next stream's binary data size)] [BYTE* (next stream's
// binary data)] { Repeat from "next stream ID" above for each stream }
DWORD wrm_header_size = static_cast<DWORD>(wrm_header.size());
ULONG bytes_written = 0;
hr = content_header->Write(&kPlayreadyContentHeaderCLSID,
sizeof(kPlayreadyContentHeaderCLSID),
&bytes_written);
if (FAILED(hr)) {
return NULL;
}
hr = content_header->Write(&kNumStreams, sizeof(kNumStreams), &bytes_written);
if (FAILED(hr)) {
return NULL;
}
hr = content_header->Write(&kNextStreamId, sizeof(kNextStreamId),
&bytes_written);
if (FAILED(hr)) {
return NULL;
}
hr = content_header->Write(&wrm_header_size, sizeof(wrm_header_size),
&bytes_written);
if (FAILED(hr)) {
return NULL;
}
if (0 != wrm_header_size) {
hr = content_header->Write(wrm_header.data(), wrm_header_size,
&bytes_written);
if (FAILED(hr)) {
return NULL;
}
}
return content_header;
}
ComPtr<IStream> ResetStreamPosition(const ComPtr<IStream>& stream) {
if (stream == NULL) {
return NULL;
}
LARGE_INTEGER seek_position = {0};
HRESULT hr = stream->Seek(seek_position, STREAM_SEEK_SET, NULL);
CheckResult(hr);
return stream;
}
} // namespace.
// static
GUID WrmHeader::kInvalidKeyId;
WrmHeader::WrmHeader(const void* initialization_data,
int initialization_data_size) {
wrm_header_ = ParseWrmHeaderFromInitializationData(initialization_data,
initialization_data_size);
key_id_ = ParseKeyIdFromWrmHeader(wrm_header_);
}
ComPtr<IStream> WrmHeader::content_header() const {
SB_DCHECK(is_valid());
if (key_id_ == kInvalidKeyId) {
return NULL;
}
return ResetStreamPosition(CreateContentHeaderFromWrmHeader(wrm_header_));
}
} // namespace win32
} // namespace shared
} // namespace starboard