Back to Repositories

Testing AES-128 Data Source Encryption in SmartTube

This test suite validates the functionality of AES-128 data source encryption in ExoPlayer’s HLS module. It focuses on verifying proper handling of encrypted data streams and upstream data source interactions.

Test Coverage Overview

The test suite provides comprehensive coverage of the Aes128DataSource class functionality, focusing on data source lifecycle management and encryption handling. Key test areas include:

  • Upstream data source open/close operations
  • Error handling during stream operations
  • Encryption initialization and cipher configuration
  • Resource cleanup validation

Implementation Analysis

The testing approach utilizes JUnit with AndroidJUnit4 runner, implementing mock data sources for isolation. The tests employ a combination of behavioral verification and state assertion to validate the AES-128 encryption implementation.

Notable patterns include mock object implementation through UpstreamDataSource and exception handling verification for encryption setup.

Technical Details

Testing infrastructure includes:

  • JUnit 4 test framework
  • AndroidJUnit4 test runner
  • Truth assertion library
  • Mock data source implementations
  • AES/CBC/PKCS7Padding cipher configuration

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases with clear setup and teardown
  • Comprehensive error case handling
  • Mock object usage for controlled testing
  • Platform-specific cipher fallback handling
  • Clear test method naming conventions

yuliskov/smarttube

exoplayer-amzn-2.10.6/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/Aes128DataSourceTest.java

            
/*
 * Copyright (C) 2018 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 android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import org.junit.Test;
import org.junit.runner.RunWith;

/** Test for {@link Aes128DataSource}. */
@RunWith(AndroidJUnit4.class)
public class Aes128DataSourceTest {

  @Test
  public void test_OpenCallsUpstreamOpen_CloseCallsUpstreamClose() throws IOException {
    UpstreamDataSource upstream = new UpstreamDataSource();
    Aes128DataSource testInstance = new TestAes123DataSource(upstream, new byte[16], new byte[16]);
    assertThat(upstream.opened).isFalse();

    Uri uri = Uri.parse("http.abc.com/def");
    testInstance.open(new DataSpec(uri));
    assertThat(upstream.opened).isTrue();

    testInstance.close();
    assertThat(upstream.opened).isFalse();
  }

  @Test
  public void test_OpenCallsUpstreamThrowingOpen_CloseCallsUpstreamClose() throws IOException {
    UpstreamDataSource upstream =
        new UpstreamDataSource() {
          @Override
          public long open(DataSpec dataSpec) throws IOException {
            throw new IOException();
          }
        };
    Aes128DataSource testInstance = new TestAes123DataSource(upstream, new byte[16], new byte[16]);
    assertThat(upstream.opened).isFalse();

    Uri uri = Uri.parse("http.abc.com/def");
    try {
      testInstance.open(new DataSpec(uri));
    } catch (IOException e) {
      // Expected.
    }
    assertThat(upstream.opened).isFalse();
    assertThat(upstream.closedCalled).isFalse();

    // Even though the upstream open call failed, close should still call close on the upstream as
    // per the contract of DataSource.
    testInstance.close();
    assertThat(upstream.closedCalled).isTrue();
  }

  private static class TestAes123DataSource extends Aes128DataSource {

    public TestAes123DataSource(DataSource upstream, byte[] encryptionKey, byte[] encryptionIv) {
      super(upstream, encryptionKey, encryptionIv);
    }

    @Override
    protected Cipher getCipherInstance() throws NoSuchPaddingException, NoSuchAlgorithmException {
      try {
        return super.getCipherInstance();
      } catch (NoSuchAlgorithmException e) {
        // Some host machines may not provide an algorithm for "AES/CBC/PKCS7Padding", however on
        // such machines it's possible to get a functionally identical algorithm by requesting
        // "AES/CBC/PKCS5Padding".
        return Cipher.getInstance("AES/CBC/PKCS5Padding");
      }
    }
  }

  private static class UpstreamDataSource implements DataSource {

    public boolean opened;
    public boolean closedCalled;

    @Override
    public void addTransferListener(TransferListener transferListener) {}

    @Override
    public long open(DataSpec dataSpec) throws IOException {
      opened = true;
      return C.LENGTH_UNSET;
    }

    @Override
    public int read(byte[] buffer, int offset, int readLength) {
      return C.RESULT_END_OF_INPUT;
    }

    @Override
    public Uri getUri() {
      return null;
    }

    @Override
    public void close() {
      opened = false;
      closedCalled = true;
    }
  }
}