/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <gtest/gtest.h>
#include <mfidl.h>
#include <string>
#include <windows.h>
#include <wrl.h>

#include "RefCounted.h"
#include "content_decryption_module.h"

// Define the include guard of WMFClearKeyCDM.h before including
// WMFClearKeySession.cpp so the preprocessor skips the real header when
// WMFClearKeySession.cpp includes it from its own directory. This allows the
// session source to compile without pulling in the full CDM stack.
#define DOM_MEDIA_PLATFORM_WMF_CLEARKEY_WMFCLEARKEYCDM_H

namespace mozilla {

class WMFClearKeySession;

class SessionManagerWrapper : public RefCounted {
 public:
  HRESULT GenerateRequest(cdm::InitDataType, const BYTE*, DWORD,
                          cdm::SessionType, WMFClearKeySession*, std::string&) {
    return E_NOTIMPL;
  }
  HRESULT UpdateSession(const std::string&, const BYTE*, DWORD) {
    return E_NOTIMPL;
  }
  HRESULT CloseSession(const std::string&) { return E_NOTIMPL; }
  HRESULT RemoveSession(const std::string&) { return E_NOTIMPL; }
  void Shutdown() {}
  bool IsShutdown() { return false; }

 private:
  ~SessionManagerWrapper() override = default;
};

}  // namespace mozilla

#include "../WMFClearKeySession.cpp"

using namespace Microsoft::WRL;
using namespace mozilla;

namespace {

class WMFClearKeySessionTest : public ::testing::Test {
 protected:
  void SetUp() override {
    ASSERT_HRESULT_SUCCEEDED(
        MakeAndInitialize<MockSessionCallbacks>(&mCallbacks));
    // Assign from a raw pointer so RefPtr::operator=(T*) runs and correctly
    // AddRefs the object (avoids a UAF with this RefPtr impl, as this is not
    // Mozilla's standard RefPtr).
    SessionManagerWrapper* mgr = new SessionManagerWrapper();
    mMgr = mgr;
    ASSERT_HRESULT_SUCCEEDED(MakeAndInitialize<WMFClearKeySession>(
        &mSession, MF_MEDIAKEYSESSION_TYPE_TEMPORARY, mCallbacks.Get(), mgr));
    mSession->SetSessionIdForTesting("test-session");
  }

  class MockSessionCallbacks
      : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
                            IMFContentDecryptionModuleSessionCallbacks> {
   public:
    STDMETHODIMP KeyMessage(MF_MEDIAKEYSESSION_MESSAGETYPE, const BYTE*, DWORD,
                            LPCWSTR) override {
      return S_OK;
    }
    STDMETHODIMP KeyStatusChanged() override { return S_OK; }
  };

  static void ExpectSingleKeyStatusEquals(WMFClearKeySession* session,
                                          const uint8_t* keyId,
                                          size_t keyIdSize) {
    cdm::KeyInformation keyInfo;
    keyInfo.key_id = keyId;
    keyInfo.key_id_size = static_cast<uint32_t>(keyIdSize);
    keyInfo.status = cdm::KeyStatus::kUsable;
    keyInfo.system_code = 0;

    session->OnKeyStatusChanged(&keyInfo, 1);

    MFMediaKeyStatus* statuses = nullptr;
    UINT count = 0;
    ASSERT_HRESULT_SUCCEEDED(session->GetKeyStatuses(&statuses, &count));
    ASSERT_NE(statuses, nullptr);

    ASSERT_EQ(count, 1u);
    EXPECT_EQ(statuses[0].cbKeyId, keyIdSize);
    ASSERT_NE(statuses[0].pbKeyId, nullptr);
    EXPECT_EQ(memcmp(statuses[0].pbKeyId, keyId, keyIdSize), 0);

    CoTaskMemFree(statuses[0].pbKeyId);
    CoTaskMemFree(statuses);
  }

  ComPtr<MockSessionCallbacks> mCallbacks;
  RefPtr<SessionManagerWrapper> mMgr;
  ComPtr<WMFClearKeySession> mSession;
};

}  // namespace

TEST_F(WMFClearKeySessionTest, GetKeyStatusesSixteenByteKeyId) {
  const uint8_t kKeyId[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                              0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};

  ExpectSingleKeyStatusEquals(mSession.Get(), kKeyId, sizeof(kKeyId));
}

TEST_F(WMFClearKeySessionTest, GetKeyStatusesEightByteKeyId) {
  const uint8_t kKeyId[8] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};

  ExpectSingleKeyStatusEquals(mSession.Get(), kKeyId, sizeof(kKeyId));
}

TEST_F(WMFClearKeySessionTest, GetKeyStatusesThirtyTwoByteKeyId) {
  const uint8_t kKeyId[32] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                              0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
                              0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
                              0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f};

  ExpectSingleKeyStatusEquals(mSession.Get(), kKeyId, sizeof(kKeyId));
}
