Add test for FullSegmentEncryptionKeyCache

Pull it into a top-level package private class at the same time

As suggested in 0ba91811d1

PiperOrigin-RevId: 275870515
This commit is contained in:
ibaker 2019-10-21 18:22:55 +01:00 committed by Oliver Woodman
parent c139281119
commit 3e6fe45885
3 changed files with 208 additions and 68 deletions

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
package com.google.android.exoplayer2.source.hls;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.util.Assertions;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* LRU cache that holds up to {@code maxSize} full-segment-encryption keys. Which each addition,
* once the cache's size exceeds {@code maxSize}, the oldest item (according to insertion order) is
* removed.
*/
/* package */ final class FullSegmentEncryptionKeyCache {
private final LinkedHashMap<Uri, byte[]> backingMap;
public FullSegmentEncryptionKeyCache(int maxSize) {
backingMap =
new LinkedHashMap<Uri, byte[]>(
/* initialCapacity= */ maxSize + 1, /* loadFactor= */ 1, /* accessOrder= */ false) {
@Override
protected boolean removeEldestEntry(Map.Entry<Uri, byte[]> eldest) {
return size() > maxSize;
}
};
}
/**
* Returns the {@code encryptionKey} cached against this {@code uri}, or null if {@code uri} is
* null or not present in the cache.
*/
@Nullable
public byte[] get(@Nullable Uri uri) {
if (uri == null) {
return null;
}
return backingMap.get(uri);
}
/**
* Inserts an entry into the cache.
*
* @throws NullPointerException if {@code uri} or {@code encryptionKey} are null.
*/
@Nullable
public byte[] put(Uri uri, byte[] encryptionKey) {
return backingMap.put(Assertions.checkNotNull(uri), Assertions.checkNotNull(encryptionKey));
}
/**
* Returns true if {@code uri} is present in the cache.
*
* @throws NullPointerException if {@code uri} is null.
*/
public boolean containsUri(Uri uri) {
return backingMap.containsKey(Assertions.checkNotNull(uri));
}
/**
* Removes {@code uri} from the cache. If {@code uri} was present in the cahce, this returns the
* corresponding {@code encryptionKey}, otherwise null.
*
* @throws NullPointerException if {@code uri} is null.
*/
@Nullable
public byte[] remove(Uri uri) {
return backingMap.remove(Assertions.checkNotNull(uri));
}
}

View File

@ -41,9 +41,7 @@ import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Source of Hls (possibly adaptive) chunks. */
@ -142,7 +140,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.playlistFormats = playlistFormats;
this.timestampAdjusterProvider = timestampAdjusterProvider;
this.muxedCaptionFormats = muxedCaptionFormats;
keyCache = new FullSegmentEncryptionKeyCache();
keyCache = new FullSegmentEncryptionKeyCache(KEY_CACHE_SIZE);
scratchSpace = Util.EMPTY_BYTE_ARRAY;
liveEdgeInPeriodTimeUs = C.TIME_UNSET;
mediaDataSource = dataSourceFactory.createDataSource(C.DATA_TYPE_MEDIA);
@ -667,69 +665,4 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return segmentStartTimeInPeriodUs + segment.durationUs;
}
}
/**
* LRU cache that holds up to {@link #KEY_CACHE_SIZE} full-segment-encryption keys. Which each
* addition, once the cache's size exceeds {@link #KEY_CACHE_SIZE}, the oldest item (according to
* insertion order) is removed.
*/
private static final class FullSegmentEncryptionKeyCache {
private final LinkedHashMap<Uri, byte[]> backingMap;
public FullSegmentEncryptionKeyCache() {
backingMap =
new LinkedHashMap<Uri, byte[]>(
/* initialCapacity= */ KEY_CACHE_SIZE + 1,
/* loadFactor= */ 1,
/* accessOrder= */ false) {
@Override
protected boolean removeEldestEntry(Map.Entry<Uri, byte[]> eldest) {
return size() > KEY_CACHE_SIZE;
}
};
}
/**
* Returns the {@code encryptionKey} cached against this {@code uri}, or null if {@code uri} is
* null or not present in the cache.
*/
@Nullable
public byte[] get(@Nullable Uri uri) {
if (uri == null) {
return null;
}
return backingMap.get(uri);
}
/**
* Inserts an entry into the cache.
*
* @throws NullPointerException if {@code uri} or {@code encryptionKey} are null.
*/
@Nullable
public byte[] put(Uri uri, byte[] encryptionKey) {
return backingMap.put(Assertions.checkNotNull(uri), Assertions.checkNotNull(encryptionKey));
}
/**
* Returns true if {@code uri} is present in the cache.
*
* @throws NullPointerException if {@code uri} is null.
*/
public boolean containsUri(Uri uri) {
return backingMap.containsKey(Assertions.checkNotNull(uri));
}
/**
* Removes {@code uri} from the cache. If {@code uri} was present in the cahce, this returns the
* corresponding {@code encryptionKey}, otherwise null.
*
* @throws NullPointerException if {@code uri} is null.
*/
@Nullable
public byte[] remove(Uri uri) {
return backingMap.remove(Assertions.checkNotNull(uri));
}
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*
*/
package com.google.android.exoplayer2.source.hls;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link FullSegmentEncryptionKeyCache}. */
@RunWith(AndroidJUnit4.class)
public class FullSegmentEncryptionKeyCacheTest {
private final Uri firstUri = Uri.parse("https://www.google.com");
private final Uri secondUri = Uri.parse("https://www.abc.xyz");
private final byte[] encryptionKey = {5, 6, 7, 8};
@Test
public void putThenGetAndContains() {
FullSegmentEncryptionKeyCache cache = new FullSegmentEncryptionKeyCache(/* maxSize= */ 5);
cache.put(firstUri, encryptionKey);
assertThat(cache.get(firstUri)).isEqualTo(encryptionKey);
assertThat(cache.get(secondUri)).isNull();
assertThat(cache.containsUri(firstUri)).isTrue();
assertThat(cache.containsUri(secondUri)).isFalse();
}
@Test
public void getNullReturnsNull() {
FullSegmentEncryptionKeyCache cache = new FullSegmentEncryptionKeyCache(/* maxSize= */ 5);
cache.put(firstUri, encryptionKey);
assertThat(cache.get(null)).isNull();
}
@Test
public void putNullKeyThrowsException() {
FullSegmentEncryptionKeyCache cache = new FullSegmentEncryptionKeyCache(/* maxSize= */ 5);
try {
cache.put(null, encryptionKey);
fail();
} catch (NullPointerException expected) {
}
}
@Test
public void putNullValueThrowsException() {
FullSegmentEncryptionKeyCache cache = new FullSegmentEncryptionKeyCache(/* maxSize= */ 5);
try {
cache.put(firstUri, null);
fail();
} catch (NullPointerException expected) {
}
}
@Test
public void containsNullThrowsException() {
FullSegmentEncryptionKeyCache cache = new FullSegmentEncryptionKeyCache(/* maxSize= */ 5);
try {
cache.containsUri(null);
fail();
} catch (NullPointerException expected) {
}
}
@Test
public void removeNullThrowsException() {
FullSegmentEncryptionKeyCache cache = new FullSegmentEncryptionKeyCache(/* maxSize= */ 5);
try {
cache.remove(null);
fail();
} catch (NullPointerException expected) {
}
}
@Test
public void oldestElementRemoved() {
FullSegmentEncryptionKeyCache cache = new FullSegmentEncryptionKeyCache(/* maxSize= */ 2);
cache.put(firstUri, encryptionKey);
cache.put(secondUri, new byte[] {1, 2, 3, 4});
cache.put(Uri.parse("www.nest.com"), new byte[] {1, 2, 3, 4});
assertThat(cache.containsUri(firstUri)).isFalse();
assertThat(cache.containsUri(secondUri)).isTrue();
}
/**
* Elements need to be removed and reinserted, rather than just updated, to change their position
* in the removal queue.
*/
@Test
public void updatingElementDoesntChangeAgeForRemoval() {
FullSegmentEncryptionKeyCache cache = new FullSegmentEncryptionKeyCache(/* maxSize= */ 2);
cache.put(firstUri, encryptionKey);
cache.put(secondUri, new byte[] {1, 2, 3, 4});
// Update firstUri element
cache.put(firstUri, new byte[] {10, 11, 12, 12});
cache.put(Uri.parse("www.nest.com"), new byte[] {1, 2, 3, 4});
// firstUri is still removed before secondUri, despite the update
assertThat(cache.containsUri(firstUri)).isFalse();
assertThat(cache.containsUri(secondUri)).isTrue();
}
}