blob: f66bf48d127c89ac960d481a061949c7afb67fa5 [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 "cobalt/dom/eme/media_key_session.h"
#include "cobalt/dom/array_buffer.h"
#include "cobalt/dom/array_buffer_view.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/dom/eme/media_key_message_event.h"
#include "cobalt/dom/eme/media_key_message_event_init.h"
#include "cobalt/dom/eme/media_keys.h"
#include "cobalt/script/script_value_factory.h"
namespace cobalt {
namespace dom {
namespace eme {
// See step 3.1 of
// https://www.w3.org/TR/encrypted-media/#dom-mediakeys-createsession.
MediaKeySession::MediaKeySession(
scoped_ptr<media::DrmSystem::Session> drm_system_session,
script::ScriptValueFactory* script_value_factory,
const ClosedCallback& closed_callback)
: drm_system_session_(drm_system_session.Pass()),
script_value_factory_(script_value_factory),
uninitialized_(true),
callable_(false),
closed_callback_(closed_callback),
ALLOW_THIS_IN_INITIALIZER_LIST(closed_promise_reference_(
this, script_value_factory->CreateBasicPromise<void>())),
initiated_by_generate_request_(false) {}
// Session ID should be empty for uninitialized sessions according to step 3.1
// of https://www.w3.org/TR/encrypted-media/#dom-mediakeys-createsession.
std::string MediaKeySession::session_id() const {
return drm_system_session_->id().value_or("");
}
MediaKeySession::~MediaKeySession() {}
const MediaKeySession::VoidPromiseValue* MediaKeySession::closed() const {
return &closed_promise_reference_.referenced_value();
}
const EventTarget::EventListenerScriptValue* MediaKeySession::onmessage()
const {
return GetAttributeEventListener(base::Tokens::message());
}
void MediaKeySession::set_onmessage(
const EventListenerScriptValue& event_listener) {
SetAttributeEventListener(base::Tokens::message(), event_listener);
}
namespace {
void GetBufferAndSize(const BufferSource& buffer_source, const uint8** buffer,
int* buffer_size) {
if (buffer_source.IsType<scoped_refptr<ArrayBufferView> >()) {
scoped_refptr<ArrayBufferView> array_buffer_view =
buffer_source.AsType<scoped_refptr<ArrayBufferView> >();
*buffer = static_cast<const uint8*>(array_buffer_view->base_address());
*buffer_size = array_buffer_view->byte_length();
} else if (buffer_source.IsType<scoped_refptr<ArrayBuffer> >()) {
scoped_refptr<ArrayBuffer> array_buffer =
buffer_source.AsType<scoped_refptr<ArrayBuffer> >();
*buffer = array_buffer->data();
*buffer_size = array_buffer->byte_length();
} else {
NOTREACHED();
}
}
} // namespace
// See
// https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-generaterequest.
scoped_ptr<MediaKeySession::VoidPromiseValue> MediaKeySession::GenerateRequest(
const std::string& init_data_type, const BufferSource& init_data) {
scoped_ptr<VoidPromiseValue> promise =
script_value_factory_->CreateBasicPromise<void>();
VoidPromiseValue::StrongReference promise_reference(*promise);
// 1. If this object is closed, return a promise rejected with
// an InvalidStateError.
// 2. If this object's uninitialized value is false, return a promise rejected
// with an InvalidStateError.
if (drm_system_session_->is_closed() || !uninitialized_) {
promise_reference.value().Reject(
new DOMException(DOMException::kInvalidStateErr));
return promise.Pass();
}
// 3. Let this object's uninitialized value be false.
uninitialized_ = false;
const uint8* init_data_buffer;
int init_data_buffer_size;
GetBufferAndSize(init_data, &init_data_buffer, &init_data_buffer_size);
// 4. If initDataType is the empty string, return a promise rejected with
// a newly created TypeError.
// 5. If initData is an empty array, return a promise rejected with a newly
// created TypeError.
if (init_data_type.empty() || init_data_buffer_size == 0) {
promise_reference.value().Reject(script::kTypeError);
return promise.Pass();
}
// 10.2. The user agent must thoroughly validate the initialization data
// before passing it to the CDM.
//
// Sanitation is the responsibility of Starboard implementers.
// 10.9. Use the CDM.
initiated_by_generate_request_ = true;
drm_system_session_->GenerateUpdateRequest(
init_data_type, init_data_buffer, init_data_buffer_size,
base::Bind(&MediaKeySession::OnSessionUpdateRequestGenerated,
base::AsWeakPtr(this),
base::Owned(new VoidPromiseValue::Reference(this, *promise))),
base::Bind(&MediaKeySession::OnSessionUpdateRequestDidNotGenerate,
base::AsWeakPtr(this),
base::Owned(new VoidPromiseValue::Reference(this, *promise))));
// 11. Return promise.
return promise.Pass();
}
// See https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-update.
scoped_ptr<MediaKeySession::VoidPromiseValue> MediaKeySession::Update(
const BufferSource& response) {
scoped_ptr<VoidPromiseValue> promise =
script_value_factory_->CreateBasicPromise<void>();
VoidPromiseValue::StrongReference promise_reference(*promise);
// 1. If this object is closed, return a promise rejected with
// an InvalidStateError.
// 2. If this object's callable value is false, return a promise rejected
// with an InvalidStateError.
if (drm_system_session_->is_closed() || !callable_) {
promise_reference.value().Reject(
new DOMException(DOMException::kInvalidStateErr));
return promise.Pass();
}
const uint8* response_buffer;
int response_buffer_size;
GetBufferAndSize(response, &response_buffer, &response_buffer_size);
// 3. If response is an empty array, return a promise rejected with a newly
// created TypeError.
if (response_buffer_size == 0) {
promise_reference.value().Reject(script::kTypeError);
return promise.Pass();
}
// 6.1. Let sanitized response be a validated and/or sanitized version of
// response copy.
//
// Sanitation is the responsibility of Starboard implementers.
// 6.7. Use the CDM.
drm_system_session_->Update(
response_buffer, response_buffer_size,
base::Bind(&MediaKeySession::OnSessionUpdated, base::AsWeakPtr(this),
base::Owned(new VoidPromiseValue::Reference(this, *promise))),
base::Bind(&MediaKeySession::OnSessionDidNotUpdate, base::AsWeakPtr(this),
base::Owned(new VoidPromiseValue::Reference(this, *promise))));
// 7. Return promise.
return promise.Pass();
}
// See https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-close.
scoped_ptr<MediaKeySession::VoidPromiseValue> MediaKeySession::Close() {
scoped_ptr<VoidPromiseValue> promise =
script_value_factory_->CreateBasicPromise<void>();
VoidPromiseValue::StrongReference promise_reference(*promise);
// 2. If session is closed, return a resolved promise.
if (drm_system_session_->is_closed()) {
promise_reference.value().Resolve();
return promise.Pass();
}
// 3. If session's callable value is false, return a promise rejected with
// an InvalidStateError.
if (!callable_) {
promise_reference.value().Reject(
new DOMException(DOMException::kInvalidStateErr));
return promise.Pass();
}
// 5.2. Use CDM to close the key session associated with session.
drm_system_session_->Close();
// Let |MediaKeys| know that the session should be removed from the list
// of open sessions.
closed_callback_.Run(this);
// 5.3.1. Run the Session Closed algorithm on the session.
OnClosed();
// 5.3.2. Resolve promise.
promise_reference.value().Resolve();
return promise.Pass();
}
// See
// https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-generaterequest.
void MediaKeySession::OnSessionUpdateRequestGenerated(
VoidPromiseValue::Reference* promise_reference, scoped_array<uint8> message,
int message_size) {
MediaKeyMessageEventInit media_key_message_event_init;
// 10.9.4. If a license request for the requested license type can be
// generated based on the sanitized init data:
// 10.9.4.1. Let message be a license request for the requested license type
// generated based on the sanitized init data interpreted
// per initDataType.
// Otherwise:
// 10.9.4.1. Let message be the request that needs to be processed before
// a license request request for the requested license type can be
// generated based on the sanitized init data.
media_key_message_event_init.set_message(new ArrayBuffer(
NULL, ArrayBuffer::kFromHeap, message.Pass(), message_size));
// 10.9.4.2. Let message type reflect the type of message, either
// "license-request" or "individualization-request".
//
// TODO: Introduce message type parameter to |SbDrmSessionUpdateRequestFunc|
// and stop pretending that all messages are license requests.
media_key_message_event_init.set_message_type(
kMediaKeyMessageTypeLicenseRequest);
// 10.3. Let this object's callable value be true.
callable_ = true;
// 10.4. Run the Queue a "message" Event algorithm on the session.
//
// TODO: Implement Event.isTrusted as per
// https://www.w3.org/TR/dom/#dom-event-istrusted and set it to true.
DispatchEvent(
new MediaKeyMessageEvent("message", media_key_message_event_init));
// 10.5. Resolve promise.
//
// If the request was generated spontaneously by the underlying DRM system,
// we shouldn't resolve the promise returned by |GenerateRequest|. The promise
// was resolved in the first invocation of this method and now is simply
// hanging around.
if (initiated_by_generate_request_) {
initiated_by_generate_request_ = false;
promise_reference->value().Resolve();
}
}
// See
// https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-generaterequest.
void MediaKeySession::OnSessionUpdateRequestDidNotGenerate(
VoidPromiseValue::Reference* promise_reference) {
// 10.10.1. If any of the preceding steps failed, reject promise with a new
// DOMException whose name is the appropriate error name.
//
// TODO: Introduce Starboard API that allows CDM to propagate error codes.
promise_reference->value().Reject(new DOMException(DOMException::kNone));
}
// See https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-update.
void MediaKeySession::OnSessionUpdated(
VoidPromiseValue::Reference* promise_reference) {
// 8.1.1. If the set of keys known to the CDM for this object changed or
// the status of any key(s) changed, run the Update Key Statuses
// algorithm on the session.
//
// TODO: Implement key statuses.
// 8.1.2. If the expiration time for the session changed, run the Update
// Expiration algorithm on the session.
//
// TODO: Implement expiration.
// 8.2. Resolve promise.
promise_reference->value().Resolve();
}
// See https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-update.
void MediaKeySession::OnSessionDidNotUpdate(
VoidPromiseValue::Reference* promise_reference) {
// 8.1.3. If any of the preceding steps failed, reject promise with a new
// DOMException whose name is the appropriate error name.
//
// TODO: Introduce Starboard API that allows CDM to propagate error codes.
promise_reference->value().Reject(new DOMException(DOMException::kNone));
}
// See https://www.w3.org/TR/encrypted-media/#session-closed.
void MediaKeySession::OnClosed() {
// 2. Run the Update Key Statuses algorithm on the session, providing an empty
// sequence.
//
// TODO: Implement key statuses.
// 3. Run the Update Expiration algorithm on the session, providing NaN.
//
// TODO: Implement expiration.
// 4. Let promise be the closed attribute of the session.
// 5. Resolve promise.
closed_promise_reference_.value().Resolve();
}
} // namespace eme
} // namespace dom
} // namespace cobalt