chromium/mojo/core/quota_unittest.cc

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

#include <string>

#include "mojo/core/embedder/embedder.h"
#include "mojo/core/test/mojo_test_base.h"
#include "mojo/public/c/system/quota.h"

namespace mojo {
namespace core {
namespace {

using QuotaTest = test::MojoTestBase;

void QuotaExceededEventHandler(const MojoTrapEvent* event) {
  // Always treat trigger context as the address of a bool to set to |true|.
  if (event->result == MOJO_RESULT_OK)
    *reinterpret_cast<bool*>(event->trigger_context) = true;
}

TEST_F(QuotaTest, InvalidArguments) {
  if (IsMojoIpczEnabled()) {
    GTEST_SKIP() << "Mojo quota APIs are not supported by MojoIpcz.";
  }

  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoSetQuota(MOJO_HANDLE_INVALID,
                         MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, 2, nullptr));

  const MojoQuotaType kInvalidQuotaType = 0xfffffffful;
  MojoHandle message_pipe0, message_pipe1;
  CreateMessagePipe(&message_pipe0, &message_pipe1);
  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoSetQuota(message_pipe0, kInvalidQuotaType, 0, nullptr));

  const MojoSetQuotaOptions kInvalidSetQuotaOptions = {0};
  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoSetQuota(message_pipe0, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, 0,
                         &kInvalidSetQuotaOptions));

  uint64_t limit = 0;
  uint64_t usage = 0;
  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoQueryQuota(message_pipe0, kInvalidQuotaType, nullptr, &limit,
                           &usage));

  const MojoQueryQuotaOptions kInvalidQueryQuotaOptions = {0};
  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoQueryQuota(message_pipe0, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH,
                           &kInvalidQueryQuotaOptions, &limit, &usage));

  MojoClose(message_pipe0);
  MojoClose(message_pipe1);

  MojoHandle producer, consumer;
  CreateDataPipe(&producer, &consumer, 1);
  EXPECT_EQ(
      MOJO_RESULT_INVALID_ARGUMENT,
      MojoSetQuota(producer, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, 0, nullptr));
  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoSetQuota(producer, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE, 0,
                         nullptr));
  EXPECT_EQ(
      MOJO_RESULT_INVALID_ARGUMENT,
      MojoSetQuota(consumer, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, 0, nullptr));
  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoSetQuota(consumer, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE, 0,
                         nullptr));
  MojoClose(producer);
  MojoClose(consumer);
}

TEST_F(QuotaTest, BasicReceiveQueueLength) {
  if (IsMojoIpczEnabled()) {
    GTEST_SKIP() << "Mojo quota APIs are not supported by MojoIpcz.";
  }

  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  uint64_t limit = 0;
  uint64_t usage = 0;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
                           &limit, &usage));
  EXPECT_EQ(MOJO_QUOTA_LIMIT_NONE, limit);
  EXPECT_EQ(0u, usage);

  const uint64_t kTestLimit = 42;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoSetQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, kTestLimit,
                         nullptr));

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
                           &limit, &usage));
  EXPECT_EQ(kTestLimit, limit);
  EXPECT_EQ(0u, usage);

  const std::string kTestMessage = "doot";
  WriteMessage(b, kTestMessage);
  WaitForSignals(a, MOJO_HANDLE_SIGNAL_READABLE);

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
                           &limit, &usage));
  EXPECT_EQ(kTestLimit, limit);
  EXPECT_EQ(1u, usage);
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
}

TEST_F(QuotaTest, BasicReceiveQueueMemorySize) {
  if (IsMojoIpczEnabled()) {
    GTEST_SKIP() << "Mojo quota APIs are not supported by MojoIpcz.";
  }

  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  uint64_t limit = 0;
  uint64_t usage = 0;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
                           nullptr, &limit, &usage));
  EXPECT_EQ(MOJO_QUOTA_LIMIT_NONE, limit);
  EXPECT_EQ(0u, usage);

  const uint64_t kTestLimit = 42;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoSetQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
                         kTestLimit, nullptr));

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
                           nullptr, &limit, &usage));
  EXPECT_EQ(kTestLimit, limit);
  EXPECT_EQ(0u, usage);

  const std::string kTestMessage = "doot";
  WriteMessage(b, kTestMessage);
  WaitForSignals(a, MOJO_HANDLE_SIGNAL_READABLE);

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
                           nullptr, &limit, &usage));
  EXPECT_EQ(kTestLimit, limit);
  EXPECT_EQ(usage, kTestMessage.size());

  MojoClose(a);
  MojoClose(b);
}

TEST_F(QuotaTest, ReceiveQueueLengthLimitExceeded) {
  if (IsMojoIpczEnabled()) {
    GTEST_SKIP() << "Mojo quota APIs are not supported by MojoIpcz.";
  }

  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  const uint64_t kMaxMessages = 1;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoSetQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, kMaxMessages,
                         nullptr));

  MojoHandleSignalsState signals;
  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
  EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  const std::string kTestMessage = "this message is lit, fam";
  WriteMessage(a, kTestMessage);
  WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE);

  uint64_t limit = 0;
  uint64_t usage = 0;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
                           &limit, &usage));
  EXPECT_EQ(kMaxMessages, limit);
  EXPECT_EQ(1u, usage);

  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
  EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  // Push the endpoint over quota and ensure that it signals accordingly.
  WriteMessage(a, kTestMessage);
  WaitForSignals(b, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
  EXPECT_TRUE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
                           &limit, &usage));
  EXPECT_EQ(kMaxMessages, limit);
  EXPECT_EQ(2u, usage);

  // Read a message and wait for QUOTA_EXCEEDED to go back low.
  EXPECT_EQ(kTestMessage, ReadMessage(b));
  WaitForSignals(b, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
                 MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED);

  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
  EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
                           &limit, &usage));
  EXPECT_EQ(kMaxMessages, limit);
  EXPECT_EQ(1u, usage);

  MojoClose(a);
  MojoClose(b);
}

TEST_F(QuotaTest, ReceiveQueueMemorySizeLimitExceeded) {
  if (IsMojoIpczEnabled()) {
    GTEST_SKIP() << "Mojo quota APIs are not supported by MojoIpcz.";
  }

  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  const uint64_t kMaxMessageBytes = 6;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoSetQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
                         kMaxMessageBytes, nullptr));

  MojoHandleSignalsState signals;
  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
  EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  const std::string kTestMessage = "four";
  WriteMessage(a, kTestMessage);
  WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE);

  uint64_t limit = 0;
  uint64_t usage = 0;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
                           nullptr, &limit, &usage));
  EXPECT_EQ(kMaxMessageBytes, limit);
  EXPECT_EQ(kTestMessage.size(), usage);

  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
  EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  // Push the endpoint over quota and ensure that it signals accordingly.
  WriteMessage(a, kTestMessage);
  WaitForSignals(b, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
  EXPECT_TRUE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
                           nullptr, &limit, &usage));
  EXPECT_EQ(kMaxMessageBytes, limit);
  EXPECT_EQ(kTestMessage.size() * 2, usage);

  // Read a message and wait for QUOTA_EXCEEDED to go back low.
  EXPECT_EQ(kTestMessage, ReadMessage(b));
  WaitForSignals(b, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
                 MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED);

  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
  EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
                           nullptr, &limit, &usage));
  EXPECT_EQ(kMaxMessageBytes, limit);
  EXPECT_EQ(kTestMessage.size(), usage);

  MojoClose(a);
  MojoClose(b);
}

TEST_F(QuotaTest, BasicUnreadMessageCount) {
  if (IsMojoIpczEnabled()) {
    GTEST_SKIP() << "Mojo quota APIs are not supported by MojoIpcz.";
  }

  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  uint64_t limit = 0;
  uint64_t usage = 0;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_UNREAD_MESSAGE_COUNT, nullptr,
                           &limit, &usage));
  EXPECT_EQ(MOJO_QUOTA_LIMIT_NONE, limit);
  EXPECT_EQ(0u, usage);

  const uint64_t kTestLimit = 42;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoSetQuota(a, MOJO_QUOTA_TYPE_UNREAD_MESSAGE_COUNT, kTestLimit,
                         nullptr));

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_UNREAD_MESSAGE_COUNT, nullptr,
                           &limit, &usage));
  EXPECT_EQ(kTestLimit, limit);
  EXPECT_EQ(0u, usage);

  const std::string kTestMessage = "doot";
  WriteMessage(a, kTestMessage);

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_UNREAD_MESSAGE_COUNT, nullptr,
                           &limit, &usage));
  EXPECT_EQ(kTestLimit, limit);
  EXPECT_EQ(usage, 1u);

  MojoClose(a);
  MojoClose(b);
}

TEST_F(QuotaTest, UnreadMessageCountLimitExceeded) {
  if (IsMojoIpczEnabled()) {
    GTEST_SKIP() << "Mojo quota APIs are not supported by MojoIpcz.";
  }

  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  const uint64_t kMaxUnreadMessageCount = 4;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoSetQuota(a, MOJO_QUOTA_TYPE_UNREAD_MESSAGE_COUNT,
                         kMaxUnreadMessageCount, nullptr));

  MojoHandleSignalsState signals;
  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &signals));
  EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  const std::string kTestMessage = "msg";
  WriteMessage(a, kTestMessage);

  uint64_t limit = 0;
  uint64_t usage = 0;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_UNREAD_MESSAGE_COUNT, nullptr,
                           &limit, &usage));
  EXPECT_EQ(kMaxUnreadMessageCount, limit);
  EXPECT_EQ(1u, usage);

  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &signals));
  EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  // Push the endpoint over quota and ensure that it signals accordingly.
  WriteMessage(a, kTestMessage);
  WriteMessage(a, kTestMessage);
  WriteMessage(a, kTestMessage);
  WriteMessage(a, kTestMessage);
  WaitForSignals(a, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &signals));
  EXPECT_TRUE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_UNREAD_MESSAGE_COUNT, nullptr,
                           &limit, &usage));
  EXPECT_EQ(kMaxUnreadMessageCount, limit);
  EXPECT_EQ(5u, usage);

  // Read the messages and wait for QUOTA_EXCEEDED on the other end to go back
  // low. There's some hysteresis in the signaling, so it's not sufficient to
  // read a single packet, but reading below the quota size should work.
  EXPECT_EQ(kTestMessage, ReadMessage(b));
  EXPECT_EQ(kTestMessage, ReadMessage(b));
  EXPECT_EQ(kTestMessage, ReadMessage(b));
  EXPECT_EQ(kTestMessage, ReadMessage(b));
  WaitForSignals(a, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
                 MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED);

  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &signals));
  EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(a, MOJO_QUOTA_TYPE_UNREAD_MESSAGE_COUNT, nullptr,
                           &limit, &usage));
  EXPECT_EQ(kMaxUnreadMessageCount, limit);
  EXPECT_LE(1u, usage);

  MojoClose(a);
  MojoClose(b);
}

TEST_F(QuotaTest, TrapQuotaExceeded) {
  if (IsMojoIpczEnabled()) {
    GTEST_SKIP() << "Mojo quota APIs are not supported by MojoIpcz.";
  }

  // Simple sanity check to verify that QUOTA_EXCEEDED signals can be trapped
  // like any other signals.

  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  const uint64_t kMaxMessages = 42;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoSetQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, kMaxMessages,
                         nullptr));

  bool signal_event_fired = false;
  MojoHandle quota_trap;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoCreateTrap(&QuotaExceededEventHandler, nullptr, &quota_trap));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(quota_trap, b, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           reinterpret_cast<uintptr_t>(&signal_event_fired),
                           nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(quota_trap, nullptr, nullptr, nullptr));

  const std::string kTestMessage("sup");
  for (uint64_t i = 0; i < kMaxMessages; ++i)
    WriteMessage(a, kTestMessage);

  // We're at quota but not yet over.
  MojoHandleSignalsState signals;
  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
  EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
  EXPECT_FALSE(signal_event_fired);

  // Push over quota. The event handler should be invoked before this returns.
  WriteMessage(a, kTestMessage);
  EXPECT_TRUE(signal_event_fired);

  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
  EXPECT_TRUE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);

  uint64_t limit = 0;
  uint64_t usage = 0;
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
                           &limit, &usage));
  EXPECT_EQ(kMaxMessages, limit);
  EXPECT_EQ(kMaxMessages + 1, usage);

  MojoClose(quota_trap);
  MojoClose(a);
  MojoClose(b);
}

}  // namespace
}  // namespace core
}  // namespace mojo