Commit 2945468b authored by Xiaohan Wang's avatar Xiaohan Wang Committed by Commit Bot

media: Disallow parallel provisioning in MediaDrmBridge

This CL adds MediaCryptoDeferrer to ensure there will be only one
provisioning flow at any time. If NotProvisionedException is thrown
when another provisioning flow already started, the MediaCrypto creation
process will be deferred and resumed when the previous provisoning
process finishes. If creating MediaCrypto doesn't trigger provisioning,
it'll not be affected.

Note that when we resume the MediaCrypto creation, we may trigger
provisioning again, which is also handled in MediaCryptoDeferrer. This
could happen when per-origin provisioning is enabled and we have
different origin IDs.

MediaCryptoDeferrer is only used when mRequiresMediaCrypto is true.

Bug: 834965
Change-Id: I4e9ce60acbb6132b8170b9f47bc85ef5fc946f4c
Reviewed-on: https://chromium-review.googlesource.com/1071103
Commit-Queue: Xiaohan Wang <xhwang@chromium.org>
Reviewed-by: default avatarYuchen Liu <yucliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#561658}
parent aab8cfea
......@@ -19,9 +19,11 @@ import org.chromium.base.annotations.MainDex;
import org.chromium.media.MediaDrmSessionManager.SessionId;
import org.chromium.media.MediaDrmSessionManager.SessionInfo;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
// Implementation Notes of MediaDrmBridge:
......@@ -50,8 +52,8 @@ import java.util.UUID;
// should check mMediaBridge to make sure release() hasn't been called.
/**
* A wrapper of the android MediaDrm class. Each MediaDrmBridge manages multiple
* sessions for AndroidVideoDecodeAccelerators and MediaCodecAudioDecoders.
* A wrapper of the android MediaDrm class. Each MediaDrmBridge manages multiple sessions for
* MediaCodecAudioDecoders, and AndroidVideoDecodeAccelerators or MediaCodecVideoDecoders.
*/
@JNINamespace("media")
@MainDex
......@@ -99,15 +101,69 @@ public class MediaDrmBridge {
private boolean mResetDeviceCredentialsPending;
// MediaDrmBridge is waiting for provisioning response from the server.
// Whether the current MediaDrmBridge instance is waiting for provisioning response.
private boolean mProvisioningPending;
// Boolean to track if 'ORIGIN' is set in MediaDrm.
private boolean mOriginSet = false;
// Delay the MediaDrm event handle if present.
private SessionEventDeferrer mSessionEventDeferrer = null;
// Defer the creation of MediaCryptor creation. Only used when mRequiresMediaCrypto is true.
private static final MediaCryptoDeferrer sMediaCryptoDeferrer = new MediaCryptoDeferrer();
private static class MediaCryptoDeferrer {
// Whether any MediaDrmBridge instance is waiting for provisioning response.
private boolean mIsProvisioning;
// Pending events to fire after provisioning is finished.
private final Queue<Runnable> mEventHandlers;
MediaCryptoDeferrer() {
mIsProvisioning = false;
mEventHandlers = new ArrayDeque<Runnable>();
}
boolean isProvisioning() {
return mIsProvisioning;
}
void onProvisionStarted() {
assert !mIsProvisioning;
mIsProvisioning = true;
}
void defer(Runnable handler) {
assert mIsProvisioning;
mEventHandlers.add(handler);
}
void onProvisionDone() {
assert mIsProvisioning;
mIsProvisioning = false;
// This will cause createMediaCrypto() on another MediaDrmBridge object and could cause
// reentrance into the shared static sMediaCryptoDeferrer. For example, during
// createMediaCrypto(), we could hit NotProvisionedException again, and call
// isProvisioning() to check whether it can start provisioning or not. If so, it'll
// call onProvisionStarted(). To avoid the case where we call createMediaCrypto() and
// then immediately call defer(), we'll return early whenever mIsProvisioning becomes
// true.
while (!mEventHandlers.isEmpty()) {
Log.d(TAG, "run deferred CreateMediaCrypto() calls");
Runnable r = mEventHandlers.element();
mEventHandlers.remove();
r.run();
if (mIsProvisioning) {
Log.d(TAG, "provision triggerred while running deferred CreateMediaCrypto()");
return;
}
}
}
}
// Block MediaDrm event for |mSessionId|. MediaDrm may fire event before the
// functions return. This may break Chromium CDM API's assumption. For
// example, when loading session, 'restoreKeys' will trigger key status
......@@ -247,8 +303,22 @@ public class MediaDrmBridge {
try {
mediaCryptoSessionDrmId = openSession();
} catch (android.media.NotProvisionedException e) {
Log.d(TAG, "Device not provisioned", e);
startProvisioning();
Log.d(TAG, "Not provisioned during openSession()");
if (!sMediaCryptoDeferrer.isProvisioning()) {
startProvisioning();
return true;
}
// Cannot provision. Defer MediaCrypto creation and try again later.
Log.d(TAG, "defer CreateMediaCrypto() calls");
sMediaCryptoDeferrer.defer(new Runnable() {
@Override
public void run() {
createMediaCrypto();
}
});
return true;
}
......@@ -348,7 +418,6 @@ public class MediaDrmBridge {
try {
mediaDrmBridge = new MediaDrmBridge(cryptoScheme, requiresMediaCrypto,
nativeMediaDrmBridge, nativeMediaDrmStorageBridge);
Log.d(TAG, "MediaDrmBridge successfully created.");
} catch (android.media.UnsupportedSchemeException e) {
Log.e(TAG, "Unsupported DRM scheme", e);
return null;
......@@ -982,12 +1051,18 @@ public class MediaDrmBridge {
assert !mProvisioningPending;
mProvisioningPending = true;
assert mMediaDrm != null;
MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest();
if (isNativeMediaDrmBridgeValid()) {
nativeOnStartProvisioning(
mNativeMediaDrmBridge, request.getDefaultUrl(), request.getData());
if (!isNativeMediaDrmBridgeValid()) {
return;
}
if (mRequiresMediaCrypto) {
sMediaCryptoDeferrer.onProvisionStarted();
}
MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest();
nativeOnStartProvisioning(
mNativeMediaDrmBridge, request.getDefaultUrl(), request.getData());
}
/**
......@@ -1002,15 +1077,15 @@ public class MediaDrmBridge {
Log.d(TAG, "processProvisionResponse()");
assert mMediaCryptoSession == null;
// If |mMediaDrm| is released, there is no need to callback native.
if (mMediaDrm == null) {
return;
}
assert mProvisioningPending;
mProvisioningPending = false;
boolean success = isResponseReceived ? provideProvisionResponse(response) : false;
boolean success = false;
// If |mMediaDrm| is released, there is no need to callback native.
if (mMediaDrm != null) {
success = isResponseReceived ? provideProvisionResponse(response) : false;
}
if (mResetDeviceCredentialsPending) {
assert !mRequiresMediaCrypto;
......@@ -1019,13 +1094,51 @@ public class MediaDrmBridge {
return;
}
assert mRequiresMediaCrypto;
// This may call release() internally. However, sMediaCryptoDeferrer.onProvisionDone() will
// still be called below to ensure provisioning failure here doesn't block other
// MediaDrmBridge instances from proceeding.
onProvisioned(success);
if (mRequiresMediaCrypto) {
sMediaCryptoDeferrer.onProvisionDone();
}
}
/**
* Provides the provision response to MediaDrm.
*
* @returns false if the response is invalid or on error, true otherwise.
*/
boolean provideProvisionResponse(byte[] response) {
if (response == null || response.length == 0) {
Log.e(TAG, "Invalid provision response.");
return false;
}
try {
mMediaDrm.provideProvisionResponse(response);
return true;
} catch (android.media.DeniedByServerException e) {
Log.e(TAG, "failed to provide provision response", e);
} catch (java.lang.IllegalStateException e) {
Log.e(TAG, "failed to provide provision response", e);
}
return false;
}
/*
* Continue to createMediaCrypto() after provisioning.
*
* @param success Whether provisioning has succeeded or not.
*/
void onProvisioned(boolean success) {
if (!success) {
release();
return;
}
assert mRequiresMediaCrypto;
if (!mOriginSet) {
createMediaCrypto();
return;
......@@ -1049,28 +1162,6 @@ public class MediaDrmBridge {
});
}
/**
* Provides the provision response to MediaDrm.
*
* @returns false if the response is invalid or on error, true otherwise.
*/
boolean provideProvisionResponse(byte[] response) {
if (response == null || response.length == 0) {
Log.e(TAG, "Invalid provision response.");
return false;
}
try {
mMediaDrm.provideProvisionResponse(response);
return true;
} catch (android.media.DeniedByServerException e) {
Log.e(TAG, "failed to provide provision response", e);
} catch (java.lang.IllegalStateException e) {
Log.e(TAG, "failed to provide provision response", e);
}
return false;
}
/**
* Delay session event handler if |mSessionEventDeferrer| exists and
* matches |sessionId|. Otherwise run the handler immediately.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment