// Copyright 2018 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 <string> #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) { 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) { 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); } TEST_F(QuotaTest, BasicReceiveQueueMemorySize) { 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) { 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) { 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, TrapQuotaExceeded) { // 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, "a_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