// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/browser_io_surface_manager_mac.h"

#include "testing/gtest/include/gtest/gtest.h"

namespace content {

class BrowserIOSurfaceManagerTest : public testing::Test {
 public:
  void EnsureRunning() { io_surface_manager_.EnsureRunning(); }

  IOSurfaceManagerToken GenerateGpuProcessToken() {
    return io_surface_manager_.GenerateGpuProcessToken();
  }

  void InvalidateGpuProcessToken() {
    io_surface_manager_.InvalidateGpuProcessToken();
  }

  IOSurfaceManagerToken GenerateChildProcessToken(int child_process_id) {
    return io_surface_manager_.GenerateChildProcessToken(child_process_id);
  }

  void InvalidateChildProcessToken(const IOSurfaceManagerToken& token) {
    io_surface_manager_.InvalidateChildProcessToken(token);
  }

  bool HandleRegisterIOSurfaceRequest(
      const IOSurfaceManagerHostMsg_RegisterIOSurface& request,
      IOSurfaceManagerMsg_RegisterIOSurfaceReply* reply) {
    return io_surface_manager_.HandleRegisterIOSurfaceRequest(request, reply);
  }

  bool HandleUnregisterIOSurfaceRequest(
      const IOSurfaceManagerHostMsg_UnregisterIOSurface& request) {
    return io_surface_manager_.HandleUnregisterIOSurfaceRequest(request);
  }

  bool HandleAcquireIOSurfaceRequest(
      const IOSurfaceManagerHostMsg_AcquireIOSurface& request,
      IOSurfaceManagerMsg_AcquireIOSurfaceReply* reply) {
    return io_surface_manager_.HandleAcquireIOSurfaceRequest(request, reply);
  }

  // Helper function used to register an IOSurface for testing.
  void RegisterIOSurface(const IOSurfaceManagerToken& gpu_process_token,
                         int io_surface_id,
                         int client_id,
                         mach_port_t io_surface_port) {
    IOSurfaceManagerHostMsg_RegisterIOSurface request = {{0}};
    IOSurfaceManagerMsg_RegisterIOSurfaceReply reply = {{0}};
    request.io_surface_id = io_surface_id;
    request.client_id = client_id;
    request.io_surface_port.name = io_surface_port;
    memcpy(request.token_name, gpu_process_token.name,
           sizeof(gpu_process_token.name));
    ASSERT_TRUE(HandleRegisterIOSurfaceRequest(request, &reply));
    ASSERT_TRUE(reply.result);
  }

 private:
  BrowserIOSurfaceManager io_surface_manager_;
};

TEST_F(BrowserIOSurfaceManagerTest, EnsureRunning) {
  EnsureRunning();
  base::mac::ScopedMachSendRight service_port =
      BrowserIOSurfaceManager::LookupServicePort(getpid());
  EXPECT_TRUE(service_port.is_valid());
}

TEST_F(BrowserIOSurfaceManagerTest, RegisterIOSurfaceSucceeds) {
  const int kIOSurfaceId = 1;
  const int kClientId = 1;

  IOSurfaceManagerToken gpu_process_token = GenerateGpuProcessToken();

  IOSurfaceManagerHostMsg_RegisterIOSurface request = {{0}};
  IOSurfaceManagerMsg_RegisterIOSurfaceReply reply = {{0}};

  request.io_surface_id = kIOSurfaceId;
  request.client_id = kClientId;
  memcpy(request.token_name, gpu_process_token.name,
         sizeof(gpu_process_token.name));
  EXPECT_TRUE(HandleRegisterIOSurfaceRequest(request, &reply));
  EXPECT_TRUE(reply.result);
}

TEST_F(BrowserIOSurfaceManagerTest, RegisterIOSurfaceFailsWithInvalidToken) {
  const int kIOSurfaceId = 1;
  const int kClientId = 1;

  IOSurfaceManagerHostMsg_RegisterIOSurface request = {{0}};
  IOSurfaceManagerMsg_RegisterIOSurfaceReply reply = {{0}};
  request.io_surface_id = kIOSurfaceId;
  request.client_id = kClientId;
  // Fails as request doesn't contain a valid token.
  EXPECT_FALSE(HandleRegisterIOSurfaceRequest(request, &reply));
}

TEST_F(BrowserIOSurfaceManagerTest,
       RegisterIOSurfaceFailsWithChildProcessToken) {
  const int kIOSurfaceId = 1;
  const int kClientId = 1;

  IOSurfaceManagerToken child_process_token =
      GenerateChildProcessToken(kClientId);

  IOSurfaceManagerHostMsg_RegisterIOSurface request = {{0}};
  IOSurfaceManagerMsg_RegisterIOSurfaceReply reply = {{0}};
  request.io_surface_id = kIOSurfaceId;
  request.client_id = kClientId;
  memcpy(request.token_name, child_process_token.name,
         sizeof(child_process_token.name));
  // Fails as child process is not allowed to register IOSurfaces.
  EXPECT_FALSE(HandleRegisterIOSurfaceRequest(request, &reply));
}

TEST_F(BrowserIOSurfaceManagerTest, UnregisterIOSurfaceSucceeds) {
  const int kIOSurfaceId = 1;
  const int kClientId = 1;

  IOSurfaceManagerToken gpu_process_token = GenerateGpuProcessToken();

  RegisterIOSurface(gpu_process_token, kIOSurfaceId, kClientId, 0);

  IOSurfaceManagerHostMsg_UnregisterIOSurface request = {{0}};
  request.io_surface_id = kIOSurfaceId;
  request.client_id = kClientId;
  memcpy(request.token_name, gpu_process_token.name,
         sizeof(gpu_process_token.name));
  EXPECT_TRUE(HandleUnregisterIOSurfaceRequest(request));
}

TEST_F(BrowserIOSurfaceManagerTest, UnregisterIOSurfaceFailsWithInvalidToken) {
  const int kIOSurfaceId = 1;
  const int kClientId = 1;

  IOSurfaceManagerToken gpu_process_token = GenerateGpuProcessToken();

  RegisterIOSurface(gpu_process_token, kIOSurfaceId, kClientId, 0);

  IOSurfaceManagerHostMsg_UnregisterIOSurface request = {{0}};
  request.io_surface_id = kIOSurfaceId;
  request.client_id = kClientId;
  // Fails as request doesn't contain valid GPU token.
  EXPECT_FALSE(HandleUnregisterIOSurfaceRequest(request));
}

TEST_F(BrowserIOSurfaceManagerTest,
       UnregisterIOSurfaceFailsWithChildProcessToken) {
  const int kIOSurfaceId = 1;
  const int kClientId = 1;

  IOSurfaceManagerToken gpu_process_token = GenerateGpuProcessToken();
  IOSurfaceManagerToken child_process_token =
      GenerateChildProcessToken(kClientId);

  RegisterIOSurface(gpu_process_token, kIOSurfaceId, kClientId, 0);

  IOSurfaceManagerHostMsg_UnregisterIOSurface request = {{0}};
  request.io_surface_id = kIOSurfaceId;
  request.client_id = kClientId;
  memcpy(request.token_name, child_process_token.name,
         sizeof(child_process_token.name));
  // Fails as child process is not allowed to unregister IOSurfaces.
  EXPECT_FALSE(HandleUnregisterIOSurfaceRequest(request));
}

TEST_F(BrowserIOSurfaceManagerTest, AcquireIOSurfaceSucceeds) {
  const int kIOSurfaceId = 10;
  const int kClientId = 1;
  const mach_port_t kIOSurfacePortId = 100u;

  IOSurfaceManagerToken gpu_process_token = GenerateGpuProcessToken();
  IOSurfaceManagerToken child_process_token =
      GenerateChildProcessToken(kClientId);

  RegisterIOSurface(gpu_process_token, kIOSurfaceId, kClientId,
                    kIOSurfacePortId);

  IOSurfaceManagerHostMsg_AcquireIOSurface request = {{0}};
  IOSurfaceManagerMsg_AcquireIOSurfaceReply reply = {{0}};
  request.io_surface_id = kIOSurfaceId;
  memcpy(request.token_name, child_process_token.name,
         sizeof(child_process_token.name));
  EXPECT_TRUE(HandleAcquireIOSurfaceRequest(request, &reply));
  EXPECT_EQ(kIOSurfacePortId, reply.io_surface_port.name);
}

TEST_F(BrowserIOSurfaceManagerTest, AcquireIOSurfaceFailsWithInvalidToken) {
  const int kIOSurfaceId = 10;
  const int kClientId = 1;
  const mach_port_t kIOSurfacePortId = 100u;

  IOSurfaceManagerToken gpu_process_token = GenerateGpuProcessToken();

  RegisterIOSurface(gpu_process_token, kIOSurfaceId, kClientId,
                    kIOSurfacePortId);

  IOSurfaceManagerHostMsg_AcquireIOSurface request = {{0}};
  IOSurfaceManagerMsg_AcquireIOSurfaceReply reply = {{0}};
  request.io_surface_id = kIOSurfaceId;
  // Fails as request doesn't contain valid token.
  EXPECT_FALSE(HandleAcquireIOSurfaceRequest(request, &reply));
}

TEST_F(BrowserIOSurfaceManagerTest,
       AcquireIOSurfaceFailsWithWrongChildProcessToken) {
  const int kIOSurfaceId = 10;
  const int kClientId1 = 1;
  const int kClientId2 = 2;
  const mach_port_t kIOSurfacePortId = 100u;

  IOSurfaceManagerToken gpu_process_token = GenerateGpuProcessToken();
  IOSurfaceManagerToken child_process_token2 =
      GenerateChildProcessToken(kClientId2);

  RegisterIOSurface(gpu_process_token, kIOSurfaceId, kClientId1,
                    kIOSurfacePortId);

  IOSurfaceManagerHostMsg_AcquireIOSurface request = {{0}};
  IOSurfaceManagerMsg_AcquireIOSurfaceReply reply = {{0}};
  request.io_surface_id = kIOSurfaceId;
  memcpy(request.token_name, child_process_token2.name,
         sizeof(child_process_token2.name));
  EXPECT_TRUE(HandleAcquireIOSurfaceRequest(request, &reply));
  // Fails as child process 2 is not allowed to acquire this IOSurface.
  EXPECT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL),
            reply.io_surface_port.name);
}

TEST_F(BrowserIOSurfaceManagerTest,
       AcquireIOSurfaceFailsAfterInvalidatingChildProcessToken) {
  const int kIOSurfaceId = 10;
  const int kClientId = 1;
  const mach_port_t kIOSurfacePortId = 100u;

  IOSurfaceManagerToken gpu_process_token = GenerateGpuProcessToken();
  IOSurfaceManagerToken child_process_token =
      GenerateChildProcessToken(kClientId);

  RegisterIOSurface(gpu_process_token, kIOSurfaceId, kClientId,
                    kIOSurfacePortId);

  InvalidateChildProcessToken(child_process_token);

  IOSurfaceManagerHostMsg_AcquireIOSurface request = {{0}};
  IOSurfaceManagerMsg_AcquireIOSurfaceReply reply = {{0}};
  request.io_surface_id = kIOSurfaceId;
  memcpy(request.token_name, child_process_token.name,
         sizeof(child_process_token.name));
  // Fails as token is no longer valid.
  EXPECT_FALSE(HandleAcquireIOSurfaceRequest(request, &reply));
}

TEST_F(BrowserIOSurfaceManagerTest,
       AcquireIOSurfaceAfterInvalidatingGpuProcessToken) {
  const int kIOSurfaceId = 10;
  const int kClientId = 1;
  const mach_port_t kIOSurfacePortId = 100u;

  IOSurfaceManagerToken gpu_process_token = GenerateGpuProcessToken();
  IOSurfaceManagerToken child_process_token =
      GenerateChildProcessToken(kClientId);

  RegisterIOSurface(gpu_process_token, kIOSurfaceId, kClientId,
                    kIOSurfacePortId);

  InvalidateGpuProcessToken();

  IOSurfaceManagerHostMsg_AcquireIOSurface request = {{0}};
  IOSurfaceManagerMsg_AcquireIOSurfaceReply reply = {{0}};
  request.io_surface_id = kIOSurfaceId;
  memcpy(request.token_name, child_process_token.name,
         sizeof(child_process_token.name));
  EXPECT_TRUE(HandleAcquireIOSurfaceRequest(request, &reply));
  // Should return invalid port.
  EXPECT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL),
            reply.io_surface_port.name);
}

}  // namespace content
