From f702e5091979714d93ffc3f79f8d40af13442a6a Mon Sep 17 00:00:00 2001 From: Jeff Tinker Date: Thu, 25 Apr 2013 05:34:39 -0700 Subject: [PATCH] Update java MediaDrm API test to cover more of the API bug: 8620943 Change-Id: I72b690cfcd838064a470042e0943a711cc536207 --- .../src/com/widevine/test/KeyRequester.java | 137 +++++++ .../com/widevine/test/MediaDrmAPITest.java | 335 ++++++++---------- .../com/widevine/test/ProvisionRequester.java | 99 ++++++ 3 files changed, 391 insertions(+), 180 deletions(-) create mode 100644 libwvdrmengine/test/java/src/com/widevine/test/KeyRequester.java create mode 100644 libwvdrmengine/test/java/src/com/widevine/test/ProvisionRequester.java diff --git a/libwvdrmengine/test/java/src/com/widevine/test/KeyRequester.java b/libwvdrmengine/test/java/src/com/widevine/test/KeyRequester.java new file mode 100644 index 00000000..56e99367 --- /dev/null +++ b/libwvdrmengine/test/java/src/com/widevine/test/KeyRequester.java @@ -0,0 +1,137 @@ +package com.widevine.test; + +import android.media.MediaDrm; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.HttpClient; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.HttpResponse; +import org.apache.http.util.EntityUtils; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import android.os.AsyncTask; +import android.util.Log; + +public class KeyRequester { + private final String TAG = "KeyRequester"; + + private byte[] mPssh; + private String mServerUrl; + + public KeyRequester(byte[] pssh, String url) { + mPssh = pssh; + mServerUrl = url; + } + + public void doTransact(MediaDrm drm, byte[] sessionId) { + MediaDrm.KeyRequest drmRequest; + drmRequest = drm.getKeyRequest(sessionId, mPssh, "video/avc", + MediaDrm.KEY_TYPE_STREAMING, null); + + PostRequestTask postTask = new PostRequestTask(drmRequest.getData()); + postTask.execute(mServerUrl); + + // wait for post task to complete + byte[] responseBody; + long startTime = System.currentTimeMillis(); + do { + responseBody = postTask.getResponseBody(); + } while (responseBody == null && System.currentTimeMillis() - startTime < 5000); + + if (responseBody == null) { + Log.d(TAG, "No response from license server!"); + } else { + byte[] drmResponse = parseResponseBody(responseBody); + + drm.provideKeyResponse(sessionId, drmResponse); + } + } + + private class PostRequestTask extends AsyncTask { + private final String TAG = "PostRequestTask"; + + private byte[] mDrmRequest; + private byte[] mResponseBody; + + public PostRequestTask(byte[] drmRequest) { + mDrmRequest = drmRequest; + } + + protected Void doInBackground(String... urls) { + mResponseBody = postRequest(urls[0], mDrmRequest); + Log.d(TAG, "response length=" + mResponseBody.length); + return null; + } + + public byte[] getResponseBody() { + return mResponseBody; + } + + private byte[] postRequest(String url, byte[] drmRequest) { + Log.d(TAG, "PostRequest url=" + url); + HttpClient httpclient = new DefaultHttpClient(); + HttpPost httppost = new HttpPost(url); + + try { + // Add data + ByteArrayEntity entity = new ByteArrayEntity(drmRequest); + entity.setChunked(true); + httppost.setEntity(entity); + httppost.setHeader("User-Agent", "Widevine CDM v1.0"); + + // Execute HTTP Post Request + HttpResponse response = httpclient.execute(httppost); + + byte[] responseBody; + int responseCode = response.getStatusLine().getStatusCode(); + if (responseCode == 200) { + responseBody = EntityUtils.toByteArray(response.getEntity()); + } else { + Log.d(TAG, "Server returned HTTP error code " + responseCode); + return null; + } + return responseBody; + + } catch (ClientProtocolException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + } + + + // validate the response body and return the drmResponse blob + private byte[] parseResponseBody(byte[] responseBody) { + String bodyString = null; + try { + bodyString = new String(responseBody, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + if (bodyString == null) { + return null; + } + + if (!bodyString.startsWith("GLS/")) { + Log.e(TAG, "Invalid response from server, expected GLS/"); + return null; + } + if (!bodyString.startsWith("GLS/1.")) { + Log.e(TAG, "Invalid server version, expected 1.x"); + return null; + } + int drmMessageOffset = bodyString.indexOf("\r\n\r\n"); + if (drmMessageOffset == -1) { + Log.e(TAG, "Invalid server response, could not locate drm message"); + return null; + } + return Arrays.copyOfRange(responseBody, drmMessageOffset + 4, responseBody.length); + } + +} + diff --git a/libwvdrmengine/test/java/src/com/widevine/test/MediaDrmAPITest.java b/libwvdrmengine/test/java/src/com/widevine/test/MediaDrmAPITest.java index 30bd5cdc..9d2cd339 100644 --- a/libwvdrmengine/test/java/src/com/widevine/test/MediaDrmAPITest.java +++ b/libwvdrmengine/test/java/src/com/widevine/test/MediaDrmAPITest.java @@ -6,141 +6,42 @@ package com.widevine.test; import android.app.TabActivity; import android.os.Bundle; -import android.os.AsyncTask; import android.media.MediaDrm; +import android.media.MediaDrmException; import android.media.MediaCrypto; import android.media.MediaCodec; +import android.media.MediaCryptoException; +import android.media.MediaCodec.CryptoException; import android.media.MediaCodecList; import android.media.MediaCodec.CryptoInfo; import android.media.MediaCodecInfo; import android.media.MediaFormat; -import android.media.MediaCryptoException; -import android.media.MediaCodec.CryptoException; import android.util.Log; -import java.util.concurrent.TimeUnit; import java.util.UUID; import java.util.Arrays; import java.util.LinkedList; import java.util.ListIterator; +import java.util.Iterator; +import java.util.HashMap; import java.util.Random; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.lang.Exception; +import java.lang.InterruptedException; import java.security.MessageDigest; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.HttpClient; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.HttpResponse; -import org.apache.http.util.EntityUtils; +import java.security.NoSuchAlgorithmException; public class MediaDrmAPITest extends TabActivity { private final String TAG = "MediaDrmAPITest"; - private class PostRequestTask extends AsyncTask { - private byte[] mDrmRequest; - private byte[] mResponseBody; - - public PostRequestTask(byte[] drmRequest) { - mDrmRequest = drmRequest; - } - - protected Void doInBackground(String... urls) { - mResponseBody = postRequest(urls[0], mDrmRequest); - Log.d(TAG, "response length=" + mResponseBody.length); - return null; - } - - public byte[] getResponseBody() { - return mResponseBody; - } - } - - private byte[] postRequest(String url, byte[] drmRequest) { - Log.d(TAG, "PostRequest url=" + url); - HttpClient httpclient = new DefaultHttpClient(); - HttpPost httppost = new HttpPost(url); - - try { - // Add data - ByteArrayEntity entity = new ByteArrayEntity(drmRequest); - entity.setChunked(true); - httppost.setEntity(entity); - httppost.setHeader("User-Agent", "Widevine CDM v1.0"); - - // Execute HTTP Post Request - HttpResponse response = httpclient.execute(httppost); - - byte[] responseBody; - int responseCode = response.getStatusLine().getStatusCode(); - if (responseCode == 200) { - responseBody = EntityUtils.toByteArray(response.getEntity()); - } else { - Log.d(TAG, "Server returned HTTP error code " + responseCode); - return null; - } - return responseBody; - - } catch (ClientProtocolException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - private static byte[] hex2ba(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i+1), 16)); - } - return data; - } - - // validate the response body and return the drmResponse blob - private byte[] parseResponseBody(byte[] responseBody) { - String bodyString = null; - try { - bodyString = new String(responseBody, "UTF-8"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - - if (bodyString == null) { - return null; - } - - if (!bodyString.startsWith("GLS/")) { - Log.e(TAG, "Invalid response from server, expected GLS/"); - return null; - } - if (!bodyString.startsWith("GLS/1.")) { - Log.e(TAG, "Invalid server version, expected 1.x"); - return null; - } - int drmMessageOffset = bodyString.indexOf("\r\n\r\n"); - if (drmMessageOffset == -1) { - Log.e(TAG, "Invalid server response, could not locate drm message"); - return null; - } - return Arrays.copyOfRange(responseBody, drmMessageOffset + 4, responseBody.length); - } - - - static final UUID kWidevineScheme = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); static final String kClientAuth = "?source=YOUTUBE&video_id=EGHC6OHNbOo&oauth=ya.gtsqawidevine"; - static final String kServerUrl = "https://jmt17.google.com/video-dev/license/GetCencLicense"; + static final String kKeyServerUrl = "https://jmt17.google.com/video-dev/license/GetCencLicense"; static final String kPort = "80"; - static final byte[] kKeyId = hex2ba(// pssh data - "08011210e02562e04cd55351b14b3d748d36ed8e"); + static final byte[] kPssh = hex2ba(// pssh data + "08011210e02562e04cd55351b14b3d748d36ed8e"); + + private boolean mTestFailed; /** Called when the activity is first created. */ @Override @@ -149,64 +50,120 @@ public class MediaDrmAPITest extends TabActivity { super.onCreate(savedInstanceState); setContentView(R.layout.main); - listDecoders(); + mTestFailed = false; + testWidevineSchemeSupported(); + testProvisioning(); + testProperties(); + testQueryKeyStatus(); testClearContentNoKeys(); testEncryptedContent(); + + if (mTestFailed) { + Log.e(TAG, "TEST FAILED!"); + } else { + Log.e(TAG, "TEST SUCCESS!"); + } + } + + private void testWidevineSchemeSupported() { + if (!MediaDrm.isCryptoSchemeSupported(kWidevineScheme)) { + Log.e(TAG, "testWidevineSchemeSupported failed"); + finish(); + } + } + + private void testProperties() { + MediaDrm drm = null; + try { + drm = new MediaDrm(kWidevineScheme); + } catch (MediaDrmException e) { + Log.e(TAG, "test failed due to exception: " + e.getMessage()); + e.printStackTrace(); + finish(); + } + + Log.i(TAG, "vendor: " + drm.getPropertyString(MediaDrm.PROPERTY_VENDOR)); + Log.i(TAG, "version: " + drm.getPropertyString(MediaDrm.PROPERTY_VERSION)); + Log.i(TAG, "description: " + drm.getPropertyString(MediaDrm.PROPERTY_DESCRIPTION)); + Log.i(TAG, "deviceId: " + Arrays.toString(drm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID))); + Log.i(TAG, "algorithms: " + drm.getPropertyString(MediaDrm.PROPERTY_ALGORITHMS)); + + // widevine-specific properties + Log.i(TAG, "security level: " + drm.getPropertyString("securityLevel")); + Log.i(TAG, "system ID: " + drm.getPropertyString("systemId")); + } + + private void testProvisioning() { + MediaDrm drm = null; + try { + drm = new MediaDrm(kWidevineScheme); + } catch (MediaDrmException e) { + Log.e(TAG, "test failed due to exception: " + e.getMessage()); + e.printStackTrace(); + finish(); + } + + ProvisionRequester provisionRequester = new ProvisionRequester(); + provisionRequester.doTransact(drm); + } + + private void testQueryKeyStatus() { + MediaDrm drm = null; + try { + drm = new MediaDrm(kWidevineScheme); + } catch (MediaDrmException e) { + Log.e(TAG, "test failed due to exception: " + e.getMessage()); + e.printStackTrace(); + finish(); + } + + byte[] sessionId = drm.openSession(); + KeyRequester keyRequester = new KeyRequester(kPssh, kKeyServerUrl + ":" + kPort + kClientAuth); + keyRequester.doTransact(drm, sessionId); + + Log.i(TAG, "Query Key Status:"); + HashMap keyStatus = drm.queryKeyStatus(sessionId); + Iterator iterator = keyStatus.keySet().iterator(); + while (iterator.hasNext()) { + String name = iterator.next(); + Log.i(TAG, "\t" + name + " = " + keyStatus.get(name)); + } + + drm.closeSession(sessionId); } private void testEncryptedContent() { + MediaDrm drm = null; try { - MediaDrm drm = new MediaDrm(kWidevineScheme); - byte[] sessionId = drm.openSession(); - - doLicenseExchange(drm, sessionId); - testDecrypt(sessionId); - - drm.closeSession(sessionId); - } catch (Exception e) { + drm = new MediaDrm(kWidevineScheme); + } catch (MediaDrmException e) { + Log.e(TAG, "test failed due to exception: " + e.getMessage()); e.printStackTrace(); + finish(); } + + byte[] sessionId = drm.openSession(); + KeyRequester keyRequester = new KeyRequester(kPssh, kKeyServerUrl + ":" + kPort + kClientAuth); + keyRequester.doTransact(drm, sessionId); + testDecrypt(sessionId); + drm.closeSession(sessionId); } private void testClearContentNoKeys() { + MediaDrm drm = null; try { - MediaDrm drm = new MediaDrm(kWidevineScheme); - byte[] sessionId = drm.openSession(); - - testClear(sessionId); - - drm.closeSession(sessionId); - } catch (Exception e) { + drm = new MediaDrm(kWidevineScheme); + } catch (MediaDrmException e) { + Log.e(TAG, "test failed due to exception: " + e.getMessage()); e.printStackTrace(); + finish(); } + byte[] sessionId = drm.openSession(); + testClear(sessionId); + drm.closeSession(sessionId); } - public void doLicenseExchange(MediaDrm drm, byte[] sessionId) throws Exception { - MediaDrm.KeyRequest drmRequest; - drmRequest = drm.getKeyRequest(sessionId, kKeyId, "video/avc", - MediaDrm.KEY_TYPE_STREAMING, null); - - PostRequestTask postTask = new PostRequestTask(drmRequest.getData()); - postTask.execute(kServerUrl + ":" + kPort + kClientAuth); - - // wait for post task to complete - byte[] responseBody; - long startTime = System.currentTimeMillis(); - do { - responseBody = postTask.getResponseBody(); - } while (responseBody == null && System.currentTimeMillis() - startTime < 5000); - - if (responseBody == null) { - Log.d(TAG, "No response from license server!"); - } else { - byte[] drmResponse = parseResponseBody(responseBody); - - drm.provideKeyResponse(sessionId, drmResponse); - } - } - - private byte[] getTestModeSessionId(byte[] sessionId) { String testMode = new String("test_mode"); byte[] testModeSessionId = new byte[sessionId.length + testMode.length()]; @@ -219,16 +176,25 @@ public class MediaDrmAPITest extends TabActivity { return testModeSessionId; } + private void sleep(int msec) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + } + // do minimal codec setup to pass an encrypted buffer down the stack to see if it gets // decrypted correctly. - public void testDecrypt(byte[] sessionId) throws Exception { + public void testDecrypt(byte[] sessionId) { Log.i(TAG, "testDecrypt"); - MediaCrypto crypto; + MediaCrypto crypto = null; try { crypto = new MediaCrypto(kWidevineScheme, getTestModeSessionId(sessionId)); } catch (MediaCryptoException e) { - throw e; + Log.e(TAG, "test failed due to exception: " + e.getMessage()); + e.printStackTrace(); + finish(); } String mime = "video/avc"; @@ -251,13 +217,13 @@ public class MediaDrmAPITest extends TabActivity { int index; Log.i(TAG, "waiting for buffer..."); while ((index = codec.dequeueInputBuffer(0 /* timeoutUs */)) < 0) { - Thread.sleep(10); + sleep(10); } Log.i(TAG, "Got index " + index); final int kMaxSubsamplesPerSample = 10; final int kMaxSampleSize = 128 * 1024; -; + int clearSizes[] = new int[kMaxSubsamplesPerSample]; int encryptedSizes[] = new int[kMaxSubsamplesPerSample]; @@ -286,16 +252,22 @@ public class MediaDrmAPITest extends TabActivity { MediaCodec.CRYPTO_MODE_AES_CTR); try { - Log.i(TAG,"Sending " + sampleSize + " bytes, numSubSamples=" + numSubSamples); + // Log.i(TAG,"Sending " + sampleSize + " bytes, numSubSamples=" + numSubSamples); codec.queueSecureInputBuffer(index, 0 /* offset */, info, 0 /* sampleTime */, 0 /* flags */); } catch (CryptoException e) { - Log.i(TAG,"Checking " + sampleSize + " bytes"); + // Log.i(TAG,"Checking " + sampleSize + " bytes"); // in test mode, the WV CryptoPlugin throws a CryptoException where the // message string contains a SHA256 hash of the decrypted data, for verification // purposes. - MessageDigest digest = MessageDigest.getInstance("SHA-256"); + MessageDigest digest = null; + try { + digest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException ex) { + ex.printStackTrace(); + finish(); + } byte buf[] = Arrays.copyOf(refBuffer.array(), sampleSize); byte[] sha256 = digest.digest(buf); if (Arrays.equals(sha256, hex2ba(e.getMessage()))) { @@ -306,6 +278,7 @@ public class MediaDrmAPITest extends TabActivity { for (int i = 0; i < sha256.length; i++) { System.out.printf("%02x", sha256[i]); } + mTestFailed = true; } } @@ -345,14 +318,16 @@ public class MediaDrmAPITest extends TabActivity { // do minimal codec setup to pass a clear buffer down the stack to see if it gets // passed through correctly. - public void testClear(byte[] sessionId) throws Exception { + public void testClear(byte[] sessionId) { Log.i(TAG, "testClear"); - MediaCrypto crypto; + MediaCrypto crypto = null; try { crypto = new MediaCrypto(kWidevineScheme, getTestModeSessionId(sessionId)); } catch (MediaCryptoException e) { - throw e; + Log.e(TAG, "test failed due to exception: " + e.getMessage()); + e.printStackTrace(); + finish(); } String mime = "video/avc"; @@ -375,7 +350,7 @@ public class MediaDrmAPITest extends TabActivity { int index; Log.i(TAG, "waiting for buffer..."); while ((index = codec.dequeueInputBuffer(0 /* timeoutUs */)) < 0) { - Thread.sleep(10); + sleep(10); } Log.i(TAG, "Got index " + index); @@ -397,7 +372,13 @@ public class MediaDrmAPITest extends TabActivity { // in test mode, the WV CryptoPlugin throws a CryptoException where the // message string contains a SHA256 hash of the decrypted data, for verification // purposes. - MessageDigest digest = MessageDigest.getInstance("SHA-256"); + MessageDigest digest = null; + try { + digest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException ex) { + ex.printStackTrace(); + finish(); + } byte[] sha256 = digest.digest(refBuffer.array()); if (Arrays.equals(sha256, hex2ba(e.getMessage()))) { Log.i(TAG, "sha256: " + e.getMessage() + " matches OK"); @@ -407,6 +388,7 @@ public class MediaDrmAPITest extends TabActivity { for (int i = 0; i < sha256.length; i++) { System.out.printf("%02x", sha256[i]); } + mTestFailed = true; } } } @@ -437,21 +419,14 @@ public class MediaDrmAPITest extends TabActivity { return null; } - private void listDecoders() { - int n = MediaCodecList.getCodecCount(); - for (int i = 0; i < n; ++i) { - MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); - - if (info.isEncoder()) { - continue; - } - - String[] supportedTypes = info.getSupportedTypes(); - - Log.i(TAG, "codec: " + info.getName()); - for (int j = 0; j < supportedTypes.length; ++j) { - Log.i(TAG, " type: " + supportedTypes[j]); - } + private static byte[] hex2ba(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); } + return data; } + } diff --git a/libwvdrmengine/test/java/src/com/widevine/test/ProvisionRequester.java b/libwvdrmengine/test/java/src/com/widevine/test/ProvisionRequester.java new file mode 100644 index 00000000..97428a86 --- /dev/null +++ b/libwvdrmengine/test/java/src/com/widevine/test/ProvisionRequester.java @@ -0,0 +1,99 @@ +package com.widevine.test; + +import android.media.MediaDrm; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.HttpClient; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.HttpResponse; +import org.apache.http.util.EntityUtils; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import android.os.AsyncTask; +import android.util.Log; + +public class ProvisionRequester { + private final String TAG = "ProvisionRequester"; + + public ProvisionRequester() { + } + + public void doTransact(MediaDrm drm) { + MediaDrm.ProvisionRequest drmRequest; + drmRequest = drm.getProvisionRequest(); + + PostRequestTask postTask = new PostRequestTask(drmRequest.getData()); + Log.i(TAG, "Attempting to provision from server '" + drmRequest.getDefaultUrl() + "'"); + postTask.execute(drmRequest.getDefaultUrl()); + + // wait for post task to complete + byte[] responseBody; + long startTime = System.currentTimeMillis(); + do { + responseBody = postTask.getResponseBody(); + } while (responseBody == null && System.currentTimeMillis() - startTime < 5000); + + if (responseBody == null) { + Log.d(TAG, "No response from provisioning server!"); + } else { + drm.provideProvisionResponse(responseBody); + } + } + + private class PostRequestTask extends AsyncTask { + private final String TAG = "PostRequestTask"; + + private byte[] mDrmRequest; + private byte[] mResponseBody; + + public PostRequestTask(byte[] drmRequest) { + mDrmRequest = drmRequest; + } + + protected Void doInBackground(String... urls) { + mResponseBody = postRequest(urls[0], mDrmRequest); + Log.d(TAG, "response length=" + mResponseBody.length); + return null; + } + + public byte[] getResponseBody() { + return mResponseBody; + } + + private byte[] postRequest(String url, byte[] drmRequest) { + Log.d(TAG, "PostRequest url=" + url); + HttpClient httpclient = new DefaultHttpClient(); + HttpPost httppost = new HttpPost(url); + + try { + // Add data + ByteArrayEntity entity = new ByteArrayEntity(drmRequest); + httppost.setEntity(entity); + httppost.setHeader("User-Agent", "Widevine CDM v1.0"); + httppost.setHeader("Content-Type", "application/json"); + + // Execute HTTP Post Request + HttpResponse response = httpclient.execute(httppost); + + byte[] responseBody; + int responseCode = response.getStatusLine().getStatusCode(); + if (responseCode == 200) { + responseBody = EntityUtils.toByteArray(response.getEntity()); + } else { + Log.d(TAG, "Server returned HTTP error code " + responseCode); + return null; + } + return responseBody; + + } catch (ClientProtocolException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + } +} +