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