mirror of
https://github.com/samsonjs/media.git
synced 2026-03-31 10:25:48 +00:00
Fix ClearKey response conversion pre O-MR1
Issue: #4075 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=191872512
This commit is contained in:
parent
02bc2d7ce1
commit
9a507db171
3 changed files with 125 additions and 58 deletions
|
|
@ -53,6 +53,8 @@
|
|||
`BaseRenderer.onStreamChanged`.
|
||||
* HLS: Fix playlist loading error propagation when the current selection does
|
||||
not include all of the playlist's variants.
|
||||
* Fix ClearKey decryption error if the key contains a forward slash
|
||||
([#4075](https://github.com/google/ExoPlayer/issues/4075)).
|
||||
* Fix IllegalStateException when switching surface on Huawei P9 Lite
|
||||
([#4084](https://github.com/google/ExoPlayer/issues/4084)).
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ package com.google.android.exoplayer2.drm;
|
|||
|
||||
import android.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
|
@ -29,7 +27,6 @@ import org.json.JSONObject;
|
|||
/* package */ final class ClearKeyUtil {
|
||||
|
||||
private static final String TAG = "ClearKeyUtil";
|
||||
private static final Pattern REQUEST_KIDS_PATTERN = Pattern.compile("\"kids\":\\[\"(.*?)\"]");
|
||||
|
||||
private ClearKeyUtil() {}
|
||||
|
||||
|
|
@ -43,21 +40,12 @@ import org.json.JSONObject;
|
|||
if (Util.SDK_INT >= 27) {
|
||||
return request;
|
||||
}
|
||||
// Prior to O-MR1 the ClearKey CDM encoded the values in the "kids" array using Base64 rather
|
||||
// than Base64Url. See [Internal: b/64388098]. Any "/" characters that ended up in the request
|
||||
// as a result were not escaped as "\/". We know the exact request format from the platform's
|
||||
// InitDataParser.cpp, so we can use a regexp rather than parsing the JSON.
|
||||
// Prior to O-MR1 the ClearKey CDM encoded the values in the "kids" array using Base64 encoding
|
||||
// rather than Base64Url encoding. See [Internal: b/64388098]. We know the exact request format
|
||||
// from the platform's InitDataParser.cpp. Since there aren't any "+" or "/" symbols elsewhere
|
||||
// in the request, it's safe to fix the encoding by replacement through the whole request.
|
||||
String requestString = Util.fromUtf8Bytes(request);
|
||||
Matcher requestKidsMatcher = REQUEST_KIDS_PATTERN.matcher(requestString);
|
||||
if (!requestKidsMatcher.find()) {
|
||||
Log.e(TAG, "Failed to adjust request data: " + requestString);
|
||||
return request;
|
||||
}
|
||||
int kidsStartIndex = requestKidsMatcher.start(1);
|
||||
int kidsEndIndex = requestKidsMatcher.end(1);
|
||||
StringBuilder adjustedRequestBuilder = new StringBuilder(requestString);
|
||||
base64ToBase64Url(adjustedRequestBuilder, kidsStartIndex, kidsEndIndex);
|
||||
return Util.getUtf8Bytes(adjustedRequestBuilder.toString());
|
||||
return Util.getUtf8Bytes(base64ToBase64Url(requestString));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -71,39 +59,39 @@ import org.json.JSONObject;
|
|||
return response;
|
||||
}
|
||||
// Prior to O-MR1 the ClearKey CDM expected Base64 encoding rather than Base64Url encoding for
|
||||
// the "k" and "kid" strings. See [Internal: b/64388098].
|
||||
// the "k" and "kid" strings. See [Internal: b/64388098]. We know that the ClearKey CDM only
|
||||
// looks at the k, kid and kty parameters in each key, so can ignore the rest of the response.
|
||||
try {
|
||||
JSONObject responseJson = new JSONObject(Util.fromUtf8Bytes(response));
|
||||
StringBuilder adjustedResponseBuilder = new StringBuilder("{\"keys\":[");
|
||||
JSONArray keysArray = responseJson.getJSONArray("keys");
|
||||
for (int i = 0; i < keysArray.length(); i++) {
|
||||
if (i != 0) {
|
||||
adjustedResponseBuilder.append(",");
|
||||
}
|
||||
JSONObject key = keysArray.getJSONObject(i);
|
||||
key.put("k", base64UrlToBase64(key.getString("k")));
|
||||
key.put("kid", base64UrlToBase64(key.getString("kid")));
|
||||
adjustedResponseBuilder.append("{\"k\":\"");
|
||||
adjustedResponseBuilder.append(base64UrlToBase64(key.getString("k")));
|
||||
adjustedResponseBuilder.append("\",\"kid\":\"");
|
||||
adjustedResponseBuilder.append(base64UrlToBase64(key.getString("kid")));
|
||||
adjustedResponseBuilder.append("\",\"kty\":\"");
|
||||
adjustedResponseBuilder.append(key.getString("kty"));
|
||||
adjustedResponseBuilder.append("\"}");
|
||||
}
|
||||
return Util.getUtf8Bytes(responseJson.toString());
|
||||
adjustedResponseBuilder.append("]}");
|
||||
return Util.getUtf8Bytes(adjustedResponseBuilder.toString());
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Failed to adjust response data: " + Util.fromUtf8Bytes(response), e);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
private static void base64ToBase64Url(StringBuilder base64, int startIndex, int endIndex) {
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
switch (base64.charAt(i)) {
|
||||
case '+':
|
||||
base64.setCharAt(i, '-');
|
||||
break;
|
||||
case '/':
|
||||
base64.setCharAt(i, '_');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
private static String base64ToBase64Url(String base64) {
|
||||
return base64.replace('+', '-').replace('/', '_');
|
||||
}
|
||||
|
||||
private static String base64UrlToBase64(String base64) {
|
||||
return base64.replace('-', '+').replace('_', '/');
|
||||
private static String base64UrlToBase64(String base64Url) {
|
||||
return base64Url.replace('-', '+').replace('_', '/');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ package com.google.android.exoplayer2.drm;
|
|||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import java.nio.charset.Charset;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
|
@ -27,37 +26,115 @@ import org.robolectric.annotation.Config;
|
|||
/**
|
||||
* Unit test for {@link ClearKeyUtil}.
|
||||
*/
|
||||
// TODO: When API level 27 is supported, add tests that check the adjust methods are no-ops.
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public final class ClearKeyUtilTest {
|
||||
|
||||
private static final byte[] SINGLE_KEY_RESPONSE =
|
||||
Util.getUtf8Bytes(
|
||||
"{"
|
||||
+ "\"keys\":["
|
||||
+ "{"
|
||||
+ "\"k\":\"abc_def-\","
|
||||
+ "\"kid\":\"ab_cde-f\","
|
||||
+ "\"kty\":\"o_c-t\","
|
||||
+ "\"ignored\":\"ignored\""
|
||||
+ "}"
|
||||
+ "],"
|
||||
+ "\"ignored\":\"ignored\""
|
||||
+ "}");
|
||||
private static final byte[] MULTI_KEY_RESPONSE =
|
||||
Util.getUtf8Bytes(
|
||||
"{"
|
||||
+ "\"keys\":["
|
||||
+ "{"
|
||||
+ "\"k\":\"abc_def-\","
|
||||
+ "\"kid\":\"ab_cde-f\","
|
||||
+ "\"kty\":\"oct\","
|
||||
+ "\"ignored\":\"ignored\""
|
||||
+ "},{"
|
||||
+ "\"k\":\"ghi_jkl-\","
|
||||
+ "\"kid\":\"gh_ijk-l\","
|
||||
+ "\"kty\":\"oct\""
|
||||
+ "}"
|
||||
+ "],"
|
||||
+ "\"ignored\":\"ignored\""
|
||||
+ "}");
|
||||
private static final byte[] KEY_REQUEST =
|
||||
Util.getUtf8Bytes(
|
||||
"{"
|
||||
+ "\"kids\":["
|
||||
+ "\"abc+def/\","
|
||||
+ "\"ab+cde/f\""
|
||||
+ "],"
|
||||
+ "\"type\":\"temporary\""
|
||||
+ "}");
|
||||
|
||||
@Config(sdk = 26)
|
||||
@Test
|
||||
public void testAdjustResponseDataV26() {
|
||||
byte[] data = ("{\"keys\":[{"
|
||||
+ "\"k\":\"abc_def-\","
|
||||
+ "\"kid\":\"ab_cde-f\"}],"
|
||||
+ "\"type\":\"abc_def-"
|
||||
+ "\"}").getBytes(Charset.forName(C.UTF8_NAME));
|
||||
// We expect "-" and "_" to be replaced with "+" and "\/" (forward slashes need to be escaped in
|
||||
// JSON respectively, for "k" and "kid" only.
|
||||
byte[] expected = ("{\"keys\":[{"
|
||||
+ "\"k\":\"abc\\/def+\","
|
||||
+ "\"kid\":\"ab\\/cde+f\"}],"
|
||||
+ "\"type\":\"abc_def-"
|
||||
+ "\"}").getBytes(Charset.forName(C.UTF8_NAME));
|
||||
assertThat(ClearKeyUtil.adjustResponseData(data)).isEqualTo(expected);
|
||||
public void testAdjustSingleKeyResponseDataV26() {
|
||||
// Everything but the keys should be removed. Within each key only the k, kid and kty parameters
|
||||
// should remain. Any "-" and "_" characters in the k and kid values should be replaced with "+"
|
||||
// and "/".
|
||||
byte[] expected =
|
||||
Util.getUtf8Bytes(
|
||||
"{"
|
||||
+ "\"keys\":["
|
||||
+ "{"
|
||||
+ "\"k\":\"abc/def+\",\"kid\":\"ab/cde+f\",\"kty\":\"o_c-t\""
|
||||
+ "}"
|
||||
+ "]"
|
||||
+ "}");
|
||||
assertThat(ClearKeyUtil.adjustResponseData(SINGLE_KEY_RESPONSE)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Config(sdk = 26)
|
||||
@Test
|
||||
public void testAdjustMultiKeyResponseDataV26() {
|
||||
// Everything but the keys should be removed. Within each key only the k, kid and kty parameters
|
||||
// should remain. Any "-" and "_" characters in the k and kid values should be replaced with "+"
|
||||
// and "/".
|
||||
byte[] expected =
|
||||
Util.getUtf8Bytes(
|
||||
"{"
|
||||
+ "\"keys\":["
|
||||
+ "{"
|
||||
+ "\"k\":\"abc/def+\",\"kid\":\"ab/cde+f\",\"kty\":\"oct\""
|
||||
+ "},{"
|
||||
+ "\"k\":\"ghi/jkl+\",\"kid\":\"gh/ijk+l\",\"kty\":\"oct\""
|
||||
+ "}"
|
||||
+ "]"
|
||||
+ "}");
|
||||
assertThat(ClearKeyUtil.adjustResponseData(MULTI_KEY_RESPONSE)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Config(sdk = 27)
|
||||
@Test
|
||||
public void testAdjustResponseDataV27() {
|
||||
// Response should be unchanged.
|
||||
assertThat(ClearKeyUtil.adjustResponseData(SINGLE_KEY_RESPONSE)).isEqualTo(SINGLE_KEY_RESPONSE);
|
||||
}
|
||||
|
||||
@Config(sdk = 26)
|
||||
@Test
|
||||
public void testAdjustRequestDataV26() {
|
||||
byte[] data = "{\"kids\":[\"abc+def/\",\"ab+cde/f\"],\"type\":\"abc+def/\"}"
|
||||
.getBytes(Charset.forName(C.UTF8_NAME));
|
||||
// We expect "+" and "/" to be replaced with "-" and "_" respectively, for "kids".
|
||||
byte[] expected = "{\"kids\":[\"abc-def_\",\"ab-cde_f\"],\"type\":\"abc+def/\"}"
|
||||
.getBytes(Charset.forName(C.UTF8_NAME));
|
||||
assertThat(ClearKeyUtil.adjustRequestData(data)).isEqualTo(expected);
|
||||
byte[] expected =
|
||||
Util.getUtf8Bytes(
|
||||
"{"
|
||||
+ "\"kids\":["
|
||||
+ "\"abc-def_\","
|
||||
+ "\"ab-cde_f\""
|
||||
+ "],"
|
||||
+ "\"type\":\"temporary\""
|
||||
+ "}");
|
||||
assertThat(ClearKeyUtil.adjustRequestData(KEY_REQUEST)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Config(sdk = 27)
|
||||
@Test
|
||||
public void testAdjustRequestDataV27() {
|
||||
// Request should be unchanged.
|
||||
assertThat(ClearKeyUtil.adjustRequestData(KEY_REQUEST)).isEqualTo(KEY_REQUEST);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue