910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/cdm/browser/media_drm_storage_impl.h"

#include <memory>
#include <utility>

#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/unguessable_token.h"
#include "components/prefs/testing_pref_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "media/mojo/services/mojo_media_drm_storage.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace cdm {

namespace {

const char kTestOrigin[] = "https://www.testorigin.com:80";
const char kTestOrigin2[] = "https://www.testorigin2.com:80";

using MediaDrmOriginId = MediaDrmStorageImpl::MediaDrmOriginId;
using ClearMatchingLicensesFilterCB =
    MediaDrmStorageImpl::ClearMatchingLicensesFilterCB;

void OnMediaDrmStorageInit(bool expected_success,
                           MediaDrmOriginId* out_origin_id,
                           bool success,
                           const MediaDrmOriginId& origin_id) {
  DCHECK(out_origin_id);
  DCHECK_EQ(success, expected_success);
  *out_origin_id = origin_id;
}

void CreateOriginId(MediaDrmStorageImpl::OriginIdObtainedCB callback) {
  std::move(callback).Run(true, base::UnguessableToken::Create());
}

void CreateEmptyOriginId(MediaDrmStorageImpl::OriginIdObtainedCB callback) {
  // |callback| has to fail in order to check if empty origin ID allowed.
  std::move(callback).Run(false, std::nullopt);
}

void CreateOriginIdAsync(MediaDrmStorageImpl::OriginIdObtainedCB callback) {
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&CreateOriginId, std::move(callback)));
}

void AllowEmptyOriginId(base::OnceCallback<void(bool)> callback) {
  std::move(callback).Run(true);
}

void DisallowEmptyOriginId(base::OnceCallback<void(bool)> callback) {
  std::move(callback).Run(false);
}

}  // namespace

class MediaDrmStorageImplTest : public content::RenderViewHostTestHarness {
 public:
  MediaDrmStorageImplTest() = default;

  void SetUp() override {
    RenderViewHostTestHarness::SetUp();

    pref_service_ = std::make_unique<TestingPrefServiceSimple>();
    PrefRegistrySimple* registry = pref_service_->registry();
    MediaDrmStorageImpl::RegisterProfilePrefs(registry);

    media_drm_storage_ =
        CreateAndInitMediaDrmStorage(GURL(kTestOrigin), &origin_id_);
  }

  void TearDown() override {
    media_drm_storage_.reset();
    base::RunLoop().RunUntilIdle();
    RenderViewHostTestHarness::TearDown();
  }

 protected:
  using SessionData = media::MediaDrmStorage::SessionData;

  std::unique_ptr<media::MediaDrmStorage> CreateMediaDrmStorage(
      content::RenderFrameHost* rfh,
      MediaDrmStorageImpl::GetOriginIdCB get_origin_id_cb,
      MediaDrmStorageImpl::AllowEmptyOriginIdCB allow_empty_cb =
          base::BindRepeating(&AllowEmptyOriginId)) {
    mojo::PendingRemote<media::mojom::MediaDrmStorage>
        pending_media_drm_storage;
    auto receiver = pending_media_drm_storage.InitWithNewPipeAndPassReceiver();

    auto media_drm_storage = std::make_unique<media::MojoMediaDrmStorage>(
        std::move(pending_media_drm_storage));

    // The created object will be destroyed on connection error.
    new MediaDrmStorageImpl(*rfh, pref_service_.get(),
                            std::move(get_origin_id_cb),
                            std::move(allow_empty_cb), std::move(receiver));

    return std::move(media_drm_storage);
  }

  std::unique_ptr<media::MediaDrmStorage> CreateAndInitMediaDrmStorage(
      const GURL& origin,
      MediaDrmOriginId* origin_id) {
    DCHECK(origin_id);

    std::unique_ptr<media::MediaDrmStorage> media_drm_storage =
        CreateMediaDrmStorage(SimulateNavigation(origin),
                              base::BindRepeating(&CreateOriginId));

    media_drm_storage->Initialize(
        base::BindOnce(OnMediaDrmStorageInit, true, origin_id));

    base::RunLoop().RunUntilIdle();

    // Verify the origin dictionary is created.
    EXPECT_TRUE(MediaDrmStorageContains(kTestOrigin));

    DCHECK(*origin_id);
    return media_drm_storage;
  }

  content::RenderFrameHost* SimulateNavigation(const GURL& url) {
    content::RenderFrameHost* rfh = web_contents()->GetPrimaryMainFrame();
    content::RenderFrameHostTester::For(rfh)->InitializeRenderFrameIfNeeded();

    auto navigation_simulator =
        content::NavigationSimulator::CreateRendererInitiated(url, rfh);
    navigation_simulator->Commit();
    return navigation_simulator->GetFinalRenderFrameHost();
  }

  void OnProvisioned() {
    media_drm_storage_->OnProvisioned(ExpectResult(true));
  }

  void SavePersistentSession(const std::string& session_id,
                             const std::vector<uint8_t>& key_set_id,
                             const std::string& mime_type,
                             bool success = true) {
    media_drm_storage_->SavePersistentSession(
        session_id,
        SessionData(key_set_id, mime_type, media::MediaDrmKeyType::OFFLINE),
        ExpectResult(success));
  }

  void LoadPersistentSession(const std::string& session_id,
                             const std::vector<uint8_t>& expected_key_set_id,
                             const std::string& expected_mime_type) {
    media_drm_storage_->LoadPersistentSession(
        session_id, ExpectResult(std::make_unique<SessionData>(
                        expected_key_set_id, expected_mime_type,
                        media::MediaDrmKeyType::OFFLINE)));
  }

  void LoadPersistentSessionAndExpectFailure(const std::string& session_id) {
    media_drm_storage_->LoadPersistentSession(
        session_id, ExpectResult(std::unique_ptr<SessionData>()));
  }

  void RemovePersistentSession(const std::string& session_id,
                               bool success = true) {
    media_drm_storage_->RemovePersistentSession(session_id,
                                                ExpectResult(success));
  }

  void SaveAndLoadPersistentSession(const std::string& session_id,
                                    const std::vector<uint8_t>& key_set_id,
                                    const std::string& mime_type) {
    SavePersistentSession(session_id, key_set_id, mime_type);
    LoadPersistentSession(session_id, key_set_id, mime_type);
  }

  media::MediaDrmStorage::ResultCB ExpectResult(bool expected_result) {
    return base::BindOnce(&MediaDrmStorageImplTest::CheckResult,
                          base::Unretained(this), expected_result);
  }

  media::MediaDrmStorage::LoadPersistentSessionCB ExpectResult(
      std::unique_ptr<SessionData> expected_session_data) {
    return base::BindOnce(&MediaDrmStorageImplTest::CheckLoadedSession,
                          base::Unretained(this),
                          std::move(expected_session_data));
  }

  void CheckResult(bool expected_result, bool result) {
    EXPECT_EQ(expected_result, result);
  }

  void CheckLoadedSession(std::unique_ptr<SessionData> expected_session_data,
                          std::unique_ptr<SessionData> session_data) {
    if (!expected_session_data) {
      EXPECT_FALSE(session_data);
      return;
    }

    EXPECT_EQ(expected_session_data->key_set_id, session_data->key_set_id);
    EXPECT_EQ(expected_session_data->mime_type, session_data->mime_type);
  }

  bool MediaDrmStorageContains(const char* origin) {
    const base::Value::Dict& storage_dict =
        pref_service_->GetDict(prefs::kMediaDrmStorage);
    return storage_dict.contains(origin);
  }

  std::unique_ptr<TestingPrefServiceSimple> pref_service_;
  std::unique_ptr<media::MediaDrmStorage> media_drm_storage_;
  MediaDrmOriginId origin_id_;
};

// ClearMatchingLicenses is only available on Android
#if BUILDFLAG(IS_ANDROID)
// MediaDrmStorageImpl should write origin ID to persistent storage when
// Initialize is called. Later call to Initialize should return the same origin
// ID. The second MediaDrmStorage won't call Initialize until the first one is
// fully initialized.
TEST_F(MediaDrmStorageImplTest, Initialize_OriginIdNotChanged) {
  MediaDrmOriginId original_origin_id = origin_id_;
  ASSERT_TRUE(original_origin_id);

  MediaDrmOriginId origin_id;
  std::unique_ptr<media::MediaDrmStorage> storage =
      CreateAndInitMediaDrmStorage(GURL(kTestOrigin), &origin_id);
  EXPECT_EQ(origin_id, original_origin_id);

  base::RunLoop loop;
  MediaDrmStorageImpl::ClearMatchingLicenses(
      pref_service_.get(), base::Time::Min(), base::Time::Max(),
      ClearMatchingLicensesFilterCB(), loop.QuitClosure());
  loop.Run();

  EXPECT_FALSE(MediaDrmStorageContains(kTestOrigin));

  MediaDrmOriginId new_origin_id;
  std::unique_ptr<media::MediaDrmStorage> new_storage =
      CreateAndInitMediaDrmStorage(GURL(kTestOrigin), &new_origin_id);

  // Origin id should be regenerated to a new value.
  EXPECT_NE(new_origin_id, original_origin_id);
}
#endif  // BUILDFLAG(IS_ANDROID)

// Two MediaDrmStorage call Initialize concurrently. The second MediaDrmStorage
// will NOT wait for the first one to be initialized. Both instances should get
// the same origin ID.
TEST_F(MediaDrmStorageImplTest, Initialize_Concurrent) {
  content::RenderFrameHost* rfh = SimulateNavigation(GURL(kTestOrigin2));

  std::unique_ptr<media::MediaDrmStorage> storage1 =
      CreateMediaDrmStorage(rfh, base::BindRepeating(&CreateOriginId));
  std::unique_ptr<media::MediaDrmStorage> storage2 =
      CreateMediaDrmStorage(rfh, base::BindRepeating(&CreateOriginId));

  MediaDrmOriginId origin_id_1;
  storage1->Initialize(
      base::BindOnce(OnMediaDrmStorageInit, true, &origin_id_1));
  MediaDrmOriginId origin_id_2;
  storage2->Initialize(
      base::BindOnce(OnMediaDrmStorageInit, true, &origin_id_2));

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(origin_id_1);
  EXPECT_TRUE(origin_id_2);
  EXPECT_EQ(origin_id_1, origin_id_2);
}

TEST_F(MediaDrmStorageImplTest, Initialize_Concurrent_Async) {
  content::RenderFrameHost* rfh = SimulateNavigation(GURL(kTestOrigin2));

  std::unique_ptr<media::MediaDrmStorage> storage1 =
      CreateMediaDrmStorage(rfh, base::BindRepeating(&CreateOriginIdAsync));
  std::unique_ptr<media::MediaDrmStorage> storage2 =
      CreateMediaDrmStorage(rfh, base::BindRepeating(&CreateOriginIdAsync));

  MediaDrmOriginId origin_id_1;
  storage1->Initialize(
      base::BindOnce(OnMediaDrmStorageInit, true, &origin_id_1));
  MediaDrmOriginId origin_id_2;
  storage2->Initialize(
      base::BindOnce(OnMediaDrmStorageInit, true, &origin_id_2));

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(origin_id_1);
  EXPECT_TRUE(origin_id_2);
  EXPECT_EQ(origin_id_1, origin_id_2);
}

TEST_F(MediaDrmStorageImplTest, Initialize_DifferentOrigins) {
  MediaDrmOriginId origin_id_1 = origin_id_;
  ASSERT_TRUE(origin_id_1);

  MediaDrmOriginId origin_id_2;
  auto storage2 =
      CreateAndInitMediaDrmStorage(GURL(kTestOrigin2), &origin_id_2);
  ASSERT_TRUE(origin_id_2);

  EXPECT_NE(origin_id_1, origin_id_2);
}

TEST_F(MediaDrmStorageImplTest, OnProvisioned) {
  OnProvisioned();
  base::RunLoop().RunUntilIdle();

  // Verify the origin dictionary is created.
  EXPECT_TRUE(MediaDrmStorageContains(kTestOrigin));
}

TEST_F(MediaDrmStorageImplTest, OnProvisioned_Twice) {
  OnProvisioned();
  SaveAndLoadPersistentSession("session_id", {1, 0}, "mime/type1");
  // Provisioning again will clear everything associated with the origin.
  OnProvisioned();
  LoadPersistentSessionAndExpectFailure("session_id");
  base::RunLoop().RunUntilIdle();
}

TEST_F(MediaDrmStorageImplTest, SaveSession_Unprovisioned) {
  SaveAndLoadPersistentSession("session_id", {1, 0}, "mime/type1");
  base::RunLoop().RunUntilIdle();
}

TEST_F(MediaDrmStorageImplTest, SaveSession_SaveTwice) {
  OnProvisioned();
  SaveAndLoadPersistentSession("session_id", {1, 0}, "mime/type1");
  SaveAndLoadPersistentSession("session_id", {2, 3}, "mime/type2");
  base::RunLoop().RunUntilIdle();
}

TEST_F(MediaDrmStorageImplTest, SaveAndLoadSession_LoadTwice) {
  OnProvisioned();
  SaveAndLoadPersistentSession("session_id", {1, 0}, "mime/type");
  LoadPersistentSession("session_id", {1, 0}, "mime/type");
  base::RunLoop().RunUntilIdle();
}

TEST_F(MediaDrmStorageImplTest, SaveAndLoadSession_SpecialCharacters) {
  OnProvisioned();
  SaveAndLoadPersistentSession("session.id", {1, 0}, "mime.type");
  SaveAndLoadPersistentSession("session/id", {1, 0}, "mime/type");
  SaveAndLoadPersistentSession("session-id", {1, 0}, "mime-type");
  SaveAndLoadPersistentSession("session_id", {1, 0}, "mime_type");
  SaveAndLoadPersistentSession("session,id", {1, 0}, "mime,type");
  base::RunLoop().RunUntilIdle();
}

TEST_F(MediaDrmStorageImplTest, LoadSession_Unprovisioned) {
  LoadPersistentSessionAndExpectFailure("session_id");
  base::RunLoop().RunUntilIdle();
}

TEST_F(MediaDrmStorageImplTest, RemoveSession_Success) {
  OnProvisioned();
  SaveAndLoadPersistentSession("session_id", {1, 0}, "mime/type");
  RemovePersistentSession("session_id", true);
  LoadPersistentSessionAndExpectFailure("session_id");
  base::RunLoop().RunUntilIdle();
}

TEST_F(MediaDrmStorageImplTest, RemoveSession_InvalidSession) {
  OnProvisioned();
  SaveAndLoadPersistentSession("session_id", {1, 0}, "mime/type");
  RemovePersistentSession("invalid_session_id", true);
  // Can still load "session_id" session.
  LoadPersistentSession("session_id", {1, 0}, "mime/type");
  base::RunLoop().RunUntilIdle();
}

TEST_F(MediaDrmStorageImplTest, GetAllOrigins) {
  OnProvisioned();
  base::RunLoop().RunUntilIdle();

  // Verify the origin is found.
  std::set<GURL> origins =
      MediaDrmStorageImpl::GetAllOrigins(pref_service_.get());
  EXPECT_EQ(1u, origins.count(GURL(kTestOrigin)));
}

TEST_F(MediaDrmStorageImplTest, GetOriginsModifiedBetween) {
  base::Time start_time = base::Time::Now();
  OnProvisioned();
  base::RunLoop().RunUntilIdle();

  // Should not be found before |start_time|.
  std::vector<GURL> origins0 = MediaDrmStorageImpl::GetOriginsModifiedBetween(
      pref_service_.get(), base::Time(), start_time);
  EXPECT_EQ(origins0, std::vector<GURL>{});

  // Verify the origin is found from all time.
  std::vector<GURL> origins1 = MediaDrmStorageImpl::GetOriginsModifiedBetween(
      pref_service_.get(), base::Time(), base::Time::Max());
  EXPECT_EQ(origins1, std::vector<GURL>{GURL(kTestOrigin)});

  // Should also be found from |start_time|.
  std::vector<GURL> origins2 = MediaDrmStorageImpl::GetOriginsModifiedBetween(
      pref_service_.get(), start_time, base::Time::Max());
  EXPECT_EQ(origins2, std::vector<GURL>{GURL(kTestOrigin)});

  // Should not be found from Now().
  base::Time check_time = base::Time::Now();
  EXPECT_GT(check_time, start_time);
  std::vector<GURL> origins3 = MediaDrmStorageImpl::GetOriginsModifiedBetween(
      pref_service_.get(), check_time, base::Time::Max());
  EXPECT_EQ(origins3, std::vector<GURL>{});

  // Save a new session.
  SavePersistentSession("session_id", {1, 0}, "mime/type");
  base::RunLoop().RunUntilIdle();

  // Now that a new session has been added, the origin should be found.
  std::vector<GURL> origins4 = MediaDrmStorageImpl::GetOriginsModifiedBetween(
      pref_service_.get(), check_time, base::Time::Max());
  EXPECT_EQ(origins4, std::vector<GURL>{GURL(kTestOrigin)});

  // Now that a new session has been added, the origin should be found between
  // two points in time.
  base::Time second_check_time = base::Time::Now();
  std::vector<GURL> origins5 = MediaDrmStorageImpl::GetOriginsModifiedBetween(
      pref_service_.get(), check_time, second_check_time);
  EXPECT_EQ(origins5, std::vector<GURL>{GURL(kTestOrigin)});
}

TEST_F(MediaDrmStorageImplTest, AllowEmptyOriginId) {
  content::RenderFrameHost* rfh = SimulateNavigation(GURL(kTestOrigin2));

  std::unique_ptr<media::MediaDrmStorage> storage =
      CreateMediaDrmStorage(rfh, base::BindRepeating(&CreateEmptyOriginId));

  MediaDrmOriginId origin_id;
  storage->Initialize(base::BindOnce(OnMediaDrmStorageInit, true, &origin_id));
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(origin_id);
}

TEST_F(MediaDrmStorageImplTest, DisallowEmptyOriginId) {
  content::RenderFrameHost* rfh = SimulateNavigation(GURL(kTestOrigin2));

  std::unique_ptr<media::MediaDrmStorage> storage =
      CreateMediaDrmStorage(rfh, base::BindRepeating(&CreateEmptyOriginId),
                            base::BindRepeating(&DisallowEmptyOriginId));

  MediaDrmOriginId origin_id;
  storage->Initialize(base::BindOnce(OnMediaDrmStorageInit, false, &origin_id));
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(origin_id);
}

// ClearMatchingLicenses is only available on Android
#if BUILDFLAG(IS_ANDROID)
TEST_F(MediaDrmStorageImplTest, TestClearLicensesMatchingDuration) {
  OnProvisioned();
  base::RunLoop().RunUntilIdle();

  // Verify the origin dictionary is created.
  EXPECT_TRUE(MediaDrmStorageContains(kTestOrigin));

  base::Time first_origin_time = base::Time::Now();

  MediaDrmOriginId origin_id;
  std::unique_ptr<media::MediaDrmStorage> storage =
      CreateAndInitMediaDrmStorage(GURL(kTestOrigin2), &origin_id);
  base::RunLoop().RunUntilIdle();

  base::RunLoop loop_one;
  MediaDrmStorageImpl::ClearMatchingLicenses(
      pref_service_.get(), base::Time::Min(), first_origin_time,
      ClearMatchingLicensesFilterCB(), loop_one.QuitClosure());

  loop_one.Run();

  EXPECT_FALSE(MediaDrmStorageContains(kTestOrigin));
  EXPECT_TRUE(MediaDrmStorageContains(kTestOrigin2));

  base::RunLoop loop_two;
  MediaDrmStorageImpl::ClearMatchingLicenses(
      pref_service_.get(), first_origin_time, base::Time::Max(),
      ClearMatchingLicensesFilterCB(), loop_two.QuitClosure());
  loop_two.Run();

  EXPECT_FALSE(MediaDrmStorageContains(kTestOrigin2));
}

TEST_F(MediaDrmStorageImplTest, TestClearLicensesMatchingFilter) {
  OnProvisioned();
  base::RunLoop().RunUntilIdle();

  // Verify the origin dictionary is created.
  EXPECT_TRUE(MediaDrmStorageContains(kTestOrigin));

  base::RunLoop loop_one;
  // With the filter returning false, the origin id should not be destroyed.
  MediaDrmStorageImpl::ClearMatchingLicenses(
      pref_service_.get(), base::Time::Min(), base::Time::Max(),
      base::BindRepeating([](const GURL& url) { return false; }),
      loop_one.QuitClosure());
  loop_one.Run();
  EXPECT_TRUE(MediaDrmStorageContains(kTestOrigin));

  base::RunLoop loop_two;
  MediaDrmStorageImpl::ClearMatchingLicenses(
      pref_service_.get(), base::Time::Min(), base::Time::Max(),
      base::BindRepeating([](const GURL& url) {
        return url.is_valid() &&
               base::ContainsOnlyChars(url.spec(), kTestOrigin);
      }),
      loop_two.QuitClosure());
  loop_two.Run();
  EXPECT_FALSE(MediaDrmStorageContains(kTestOrigin));
}
#endif  // BUILDFLAG(IS_ANDROID)

}  // namespace cdm