blob: c3c7f21b05220da9f27d7d8eabfc47efa337078c [file] [log] [blame]
// Copyright 2017 Google Inc. 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/drm_system_playready.h"
#include <cctype>
#include <sstream>
#include "starboard/configuration.h"
#include "starboard/log.h"
#include "starboard/memory.h"
#include "starboard/string.h"
namespace starboard {
namespace shared {
namespace win32 {
namespace {
const bool kLogPlayreadyChallengeResponse = false;
std::string GetHexRepresentation(const void* data, size_t size) {
const char kHex[] = "0123456789ABCDEF";
std::stringstream representation;
std::stringstream ascii;
const uint8_t* binary = static_cast<const uint8_t*>(data);
bool new_line = true;
for (size_t i = 0; i < size; ++i) {
if (new_line) {
new_line = false;
} else {
representation << ' ';
}
ascii << (std::isprint(binary[i]) ? static_cast<char>(binary[i]) : '?');
representation << kHex[binary[i] / 16] << kHex[binary[i] % 16];
if (i % 16 == 15 && i != size - 1) {
representation << " (" << ascii.str() << ')' << std::endl;
std::stringstream empty;
ascii.swap(empty); // Clear the ascii stream
new_line = true;
}
}
if (!ascii.str().empty()) {
representation << '(' << ascii.str() << ')' << std::endl;
}
return representation.str();
}
template <typename T>
std::string GetHexRepresentation(const T& value) {
return GetHexRepresentation(&value, sizeof(T));
}
} // namespace
SbDrmSystemPlayready::SbDrmSystemPlayready(
void* context,
SbDrmSessionUpdateRequestFunc session_update_request_callback,
SbDrmSessionUpdatedFunc session_updated_callback,
SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback)
: context_(context),
session_update_request_callback_(session_update_request_callback),
session_updated_callback_(session_updated_callback),
key_statuses_changed_callback_(key_statuses_changed_callback),
current_session_id_(1) {
SB_DCHECK(session_update_request_callback);
SB_DCHECK(session_updated_callback);
SB_DCHECK(key_statuses_changed_callback);
}
SbDrmSystemPlayready::~SbDrmSystemPlayready() {
SB_DCHECK(thread_checker_.CalledOnValidThread());
}
void SbDrmSystemPlayready::GenerateSessionUpdateRequest(
int ticket,
const char* type,
const void* initialization_data,
int initialization_data_size) {
SB_DCHECK(thread_checker_.CalledOnValidThread());
if (SbStringCompareAll("cenc", type) != 0) {
SB_NOTREACHED() << "Invalid initialization data type " << type;
return;
}
std::string session_id = GenerateAndAdvanceSessionId();
scoped_refptr<License> license =
License::Create(initialization_data, initialization_data_size);
const std::string& challenge = license->license_challenge();
if (challenge.empty()) {
SB_NOTREACHED();
return;
}
SB_LOG(INFO) << "Send challenge for key id "
<< GetHexRepresentation(license->key_id()) << " in session "
<< session_id;
SB_LOG_IF(INFO, kLogPlayreadyChallengeResponse)
<< GetHexRepresentation(challenge.data(), challenge.size());
session_update_request_callback_(this, context_, ticket, session_id.c_str(),
static_cast<int>(session_id.size()),
challenge.c_str(),
static_cast<int>(challenge.size()), NULL);
pending_requests_[session_id] = license;
}
void SbDrmSystemPlayready::UpdateSession(int ticket,
const void* key,
int key_size,
const void* session_id,
int session_id_size) {
SB_DCHECK(thread_checker_.CalledOnValidThread());
std::string session_id_copy(static_cast<const char*>(session_id),
session_id_size);
auto iter = pending_requests_.find(session_id_copy);
SB_DCHECK(iter != pending_requests_.end());
if (iter == pending_requests_.end()) {
SB_NOTREACHED() << "Invalid session id " << session_id_copy;
return;
}
scoped_refptr<License> license = iter->second;
SB_LOG(INFO) << "Adding playready response for key id "
<< GetHexRepresentation(license->key_id());
SB_LOG_IF(INFO, kLogPlayreadyChallengeResponse)
<< GetHexRepresentation(key, key_size);
license->UpdateLicense(key, key_size);
if (license->usable()) {
SB_LOG(INFO) << "Successfully add key for key id "
<< GetHexRepresentation(license->key_id()) << " in session "
<< session_id_copy;
{
ScopedLock lock(mutex_);
successful_requests_[iter->first] = license;
}
session_updated_callback_(this, context_, ticket, session_id,
session_id_size, true);
GUID key_id = license->key_id();
SbDrmKeyId drm_key_id;
SB_DCHECK(sizeof(drm_key_id.identifier) >= sizeof(key_id));
SbMemoryCopy(&drm_key_id, &key_id, sizeof(key_id));
drm_key_id.identifier_size = sizeof(key_id);
SbDrmKeyStatus key_status = kSbDrmKeyStatusUsable;
key_statuses_changed_callback_(this, context_, session_id, session_id_size,
1, &drm_key_id, &key_status);
pending_requests_.erase(iter);
} else {
SB_LOG(INFO) << "Failed to add key for session " << session_id_copy;
// Don't report it as a failure as otherwise the JS player is going to
// terminate the video.
session_updated_callback_(this, context_, ticket, session_id,
session_id_size, true);
// When UpdateLicense() fails, the |license| must have generated a new
// challenge internally. Send this challenge again.
const std::string& challenge = license->license_challenge();
if (challenge.empty()) {
SB_NOTREACHED();
return;
}
SB_LOG(INFO) << "Send challenge again for key id "
<< GetHexRepresentation(license->key_id()) << " in session "
<< session_id;
SB_LOG_IF(INFO, kLogPlayreadyChallengeResponse)
<< GetHexRepresentation(challenge.data(), challenge.size());
// We have use |kSbDrmTicketInvalid| as the license challenge is not a
// result of GenerateSessionUpdateRequest().
session_update_request_callback_(
this, context_, kSbDrmTicketInvalid, session_id, session_id_size,
challenge.c_str(), static_cast<int>(challenge.size()), NULL);
}
}
void SbDrmSystemPlayready::CloseSession(const void* session_id,
int session_id_size) {
SB_DCHECK(thread_checker_.CalledOnValidThread());
key_statuses_changed_callback_(this, context_, session_id, session_id_size, 0,
nullptr, nullptr);
std::string session_id_copy(static_cast<const char*>(session_id),
session_id_size);
pending_requests_.erase(session_id_copy);
ScopedLock lock(mutex_);
successful_requests_.erase(session_id_copy);
}
SbDrmSystemPrivate::DecryptStatus SbDrmSystemPlayready::Decrypt(
InputBuffer* buffer) {
const SbDrmSampleInfo* drm_info = buffer->drm_info();
if (drm_info == NULL || drm_info->initialization_vector_size == 0) {
return kSuccess;
}
GUID key_id;
if (drm_info->identifier_size != sizeof(key_id)) {
return kRetry;
}
key_id = *reinterpret_cast<const GUID*>(drm_info->identifier);
ScopedLock lock(mutex_);
for (auto& item : successful_requests_) {
if (item.second->key_id() == key_id) {
if (buffer->sample_type() == kSbMediaTypeAudio) {
return kSuccess;
}
if (item.second->IsHDCPRequired()) {
if (!SbMediaSetOutputProtection(true)) {
return kFailure;
}
}
return kSuccess;
}
}
return kRetry;
}
scoped_refptr<SbDrmSystemPlayready::License> SbDrmSystemPlayready::GetLicense(
const uint8_t* key_id,
int key_id_size) {
GUID key_id_copy;
if (key_id_size != sizeof(key_id_copy)) {
return NULL;
}
key_id_copy = *reinterpret_cast<const GUID*>(key_id);
ScopedLock lock(mutex_);
for (auto& item : successful_requests_) {
if (item.second->key_id() == key_id_copy) {
return item.second;
}
}
return NULL;
}
std::string SbDrmSystemPlayready::GenerateAndAdvanceSessionId() {
SB_DCHECK(thread_checker_.CalledOnValidThread());
std::stringstream ss;
ss << current_session_id_;
++current_session_id_;
return ss.str();
}
} // namespace win32
} // namespace shared
} // namespace starboard