michael@0: // Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/basictypes.h" michael@0: #include "sandbox/win/src/crosscall_client.h" michael@0: #include "sandbox/win/src/crosscall_server.h" michael@0: #include "sandbox/win/src/sharedmem_ipc_client.h" michael@0: #include "sandbox/win/src/sharedmem_ipc_server.h" michael@0: #include "testing/gtest/include/gtest/gtest.h" michael@0: michael@0: namespace sandbox { michael@0: michael@0: // Helper function to make the fake shared memory with some michael@0: // basic elements initialized. michael@0: IPCControl* MakeChannels(size_t channel_size, size_t total_shared_size, michael@0: size_t* base_start) { michael@0: // Allocate memory michael@0: char* mem = new char[total_shared_size]; michael@0: memset(mem, 0, total_shared_size); michael@0: // Calculate how many channels we can fit in the shared memory. michael@0: total_shared_size -= offsetof(IPCControl, channels); michael@0: size_t channel_count = michael@0: total_shared_size / (sizeof(ChannelControl) + channel_size); michael@0: // Calculate the start of the first channel. michael@0: *base_start = (sizeof(ChannelControl)* channel_count) + michael@0: offsetof(IPCControl, channels); michael@0: // Setup client structure. michael@0: IPCControl* client_control = reinterpret_cast(mem); michael@0: client_control->channels_count = channel_count; michael@0: return client_control; michael@0: } michael@0: michael@0: enum TestFixMode { michael@0: FIX_NO_EVENTS, michael@0: FIX_PONG_READY, michael@0: FIX_PONG_NOT_READY michael@0: }; michael@0: michael@0: void FixChannels(IPCControl* client_control, size_t base_start, michael@0: size_t channel_size, TestFixMode mode) { michael@0: for (size_t ix = 0; ix != client_control->channels_count; ++ix) { michael@0: ChannelControl& channel = client_control->channels[ix]; michael@0: channel.channel_base = base_start; michael@0: channel.state = kFreeChannel; michael@0: if (mode != FIX_NO_EVENTS) { michael@0: BOOL signaled = (FIX_PONG_READY == mode)? TRUE : FALSE; michael@0: channel.ping_event = ::CreateEventW(NULL, FALSE, FALSE, NULL); michael@0: channel.pong_event = ::CreateEventW(NULL, FALSE, signaled, NULL); michael@0: } michael@0: base_start += channel_size; michael@0: } michael@0: } michael@0: michael@0: void CloseChannelEvents(IPCControl* client_control) { michael@0: for (size_t ix = 0; ix != client_control->channels_count; ++ix) { michael@0: ChannelControl& channel = client_control->channels[ix]; michael@0: ::CloseHandle(channel.ping_event); michael@0: ::CloseHandle(channel.pong_event); michael@0: } michael@0: } michael@0: michael@0: TEST(IPCTest, ChannelMaker) { michael@0: // Test that our testing rig is computing offsets properly. We should have michael@0: // 5 channnels and the offset to the first channel is 108 bytes in 32 bits michael@0: // and 216 in 64 bits. michael@0: size_t channel_start = 0; michael@0: IPCControl* client_control = MakeChannels(12 * 64, 4096, &channel_start); michael@0: ASSERT_TRUE(NULL != client_control); michael@0: EXPECT_EQ(5, client_control->channels_count); michael@0: #if defined(_WIN64) michael@0: EXPECT_EQ(216, channel_start); michael@0: #else michael@0: EXPECT_EQ(108, channel_start); michael@0: #endif michael@0: delete[] reinterpret_cast(client_control); michael@0: } michael@0: michael@0: TEST(IPCTest, ClientLockUnlock) { michael@0: // Make 7 channels of kIPCChannelSize (1kb) each. Test that we lock and michael@0: // unlock channels properly. michael@0: size_t base_start = 0; michael@0: IPCControl* client_control = michael@0: MakeChannels(kIPCChannelSize, 4096 * 2, &base_start); michael@0: FixChannels(client_control, base_start, kIPCChannelSize, FIX_NO_EVENTS); michael@0: michael@0: char* mem = reinterpret_cast(client_control); michael@0: SharedMemIPCClient client(mem); michael@0: michael@0: // Test that we lock the first 3 channels in sequence. michael@0: void* buff0 = client.GetBuffer(); michael@0: EXPECT_TRUE(mem + client_control->channels[0].channel_base == buff0); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[0].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[1].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[2].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[3].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[4].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[5].state); michael@0: michael@0: void* buff1 = client.GetBuffer(); michael@0: EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff1); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[0].state); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[1].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[2].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[3].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[4].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[5].state); michael@0: michael@0: void* buff2 = client.GetBuffer(); michael@0: EXPECT_TRUE(mem + client_control->channels[2].channel_base == buff2); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[0].state); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[1].state); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[2].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[3].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[4].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[5].state); michael@0: michael@0: // Test that we unlock and re-lock the right channel. michael@0: client.FreeBuffer(buff1); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[0].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[1].state); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[2].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[3].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[4].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[5].state); michael@0: michael@0: void* buff2b = client.GetBuffer(); michael@0: EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff2b); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[0].state); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[1].state); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[2].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[3].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[4].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[5].state); michael@0: michael@0: client.FreeBuffer(buff0); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[0].state); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[1].state); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[2].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[3].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[4].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[5].state); michael@0: michael@0: delete[] reinterpret_cast(client_control); michael@0: } michael@0: michael@0: TEST(IPCTest, CrossCallStrPacking) { michael@0: // This test tries the CrossCall object with null and non-null string michael@0: // combination of parameters, integer types and verifies that the unpacker michael@0: // can read them properly. michael@0: size_t base_start = 0; michael@0: IPCControl* client_control = michael@0: MakeChannels(kIPCChannelSize, 4096 * 4, &base_start); michael@0: client_control->server_alive = HANDLE(1); michael@0: FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); michael@0: michael@0: char* mem = reinterpret_cast(client_control); michael@0: SharedMemIPCClient client(mem); michael@0: michael@0: CrossCallReturn answer; michael@0: uint32 tag1 = 666; michael@0: const wchar_t text[] = L"98765 - 43210"; michael@0: std::wstring copied_text; michael@0: CrossCallParamsEx* actual_params; michael@0: michael@0: CrossCall(client, tag1, text, &answer); michael@0: actual_params = reinterpret_cast(client.GetBuffer()); michael@0: EXPECT_EQ(1, actual_params->GetParamsCount()); michael@0: EXPECT_EQ(tag1, actual_params->GetTag()); michael@0: EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); michael@0: EXPECT_STREQ(text, copied_text.c_str()); michael@0: michael@0: // Check with an empty string. michael@0: uint32 tag2 = 777; michael@0: const wchar_t* null_text = NULL; michael@0: CrossCall(client, tag2, null_text, &answer); michael@0: actual_params = reinterpret_cast(client.GetBuffer()); michael@0: EXPECT_EQ(1, actual_params->GetParamsCount()); michael@0: EXPECT_EQ(tag2, actual_params->GetTag()); michael@0: uint32 param_size = 1; michael@0: ArgType type = INVALID_TYPE; michael@0: void* param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); michael@0: EXPECT_TRUE(NULL != param_addr); michael@0: EXPECT_EQ(0, param_size); michael@0: EXPECT_EQ(WCHAR_TYPE, type); michael@0: EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); michael@0: michael@0: uint32 tag3 = 888; michael@0: param_size = 1; michael@0: copied_text.clear(); michael@0: michael@0: // Check with an empty string and a non-empty string. michael@0: CrossCall(client, tag3, null_text, text, &answer); michael@0: actual_params = reinterpret_cast(client.GetBuffer()); michael@0: EXPECT_EQ(2, actual_params->GetParamsCount()); michael@0: EXPECT_EQ(tag3, actual_params->GetTag()); michael@0: type = INVALID_TYPE; michael@0: param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); michael@0: EXPECT_TRUE(NULL != param_addr); michael@0: EXPECT_EQ(0, param_size); michael@0: EXPECT_EQ(WCHAR_TYPE, type); michael@0: EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); michael@0: EXPECT_TRUE(actual_params->GetParameterStr(1, &copied_text)); michael@0: EXPECT_STREQ(text, copied_text.c_str()); michael@0: michael@0: param_size = 1; michael@0: std::wstring copied_text_p0, copied_text_p2; michael@0: michael@0: const wchar_t text2[] = L"AeFG"; michael@0: CrossCall(client, tag1, text2, null_text, text, &answer); michael@0: actual_params = reinterpret_cast(client.GetBuffer()); michael@0: EXPECT_EQ(3, actual_params->GetParamsCount()); michael@0: EXPECT_EQ(tag1, actual_params->GetTag()); michael@0: EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text_p0)); michael@0: EXPECT_STREQ(text2, copied_text_p0.c_str()); michael@0: EXPECT_TRUE(actual_params->GetParameterStr(2, &copied_text_p2)); michael@0: EXPECT_STREQ(text, copied_text_p2.c_str()); michael@0: type = INVALID_TYPE; michael@0: param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); michael@0: EXPECT_TRUE(NULL != param_addr); michael@0: EXPECT_EQ(0, param_size); michael@0: EXPECT_EQ(WCHAR_TYPE, type); michael@0: michael@0: CloseChannelEvents(client_control); michael@0: delete[] reinterpret_cast(client_control); michael@0: } michael@0: michael@0: TEST(IPCTest, CrossCallIntPacking) { michael@0: // Check handling for regular 32 bit integers used in Windows. michael@0: size_t base_start = 0; michael@0: IPCControl* client_control = michael@0: MakeChannels(kIPCChannelSize, 4096 * 4, &base_start); michael@0: client_control->server_alive = HANDLE(1); michael@0: FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); michael@0: michael@0: uint32 tag1 = 999; michael@0: uint32 tag2 = 111; michael@0: const wchar_t text[] = L"godzilla"; michael@0: CrossCallParamsEx* actual_params; michael@0: michael@0: char* mem = reinterpret_cast(client_control); michael@0: SharedMemIPCClient client(mem); michael@0: michael@0: CrossCallReturn answer; michael@0: DWORD dw = 0xE6578; michael@0: CrossCall(client, tag2, dw, &answer); michael@0: actual_params = reinterpret_cast(client.GetBuffer()); michael@0: EXPECT_EQ(1, actual_params->GetParamsCount()); michael@0: EXPECT_EQ(tag2, actual_params->GetTag()); michael@0: ArgType type = INVALID_TYPE; michael@0: uint32 param_size = 1; michael@0: void* param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); michael@0: ASSERT_EQ(sizeof(dw), param_size); michael@0: EXPECT_EQ(ULONG_TYPE, type); michael@0: ASSERT_TRUE(NULL != param_addr); michael@0: EXPECT_EQ(0, memcmp(&dw, param_addr, param_size)); michael@0: michael@0: // Check handling for windows HANDLES. michael@0: HANDLE h = HANDLE(0x70000500); michael@0: CrossCall(client, tag1, text, h, &answer); michael@0: actual_params = reinterpret_cast(client.GetBuffer()); michael@0: EXPECT_EQ(2, actual_params->GetParamsCount()); michael@0: EXPECT_EQ(tag1, actual_params->GetTag()); michael@0: type = INVALID_TYPE; michael@0: param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); michael@0: ASSERT_EQ(sizeof(h), param_size); michael@0: EXPECT_EQ(VOIDPTR_TYPE, type); michael@0: ASSERT_TRUE(NULL != param_addr); michael@0: EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); michael@0: michael@0: // Check combination of 32 and 64 bits. michael@0: CrossCall(client, tag2, h, dw, h, &answer); michael@0: actual_params = reinterpret_cast(client.GetBuffer()); michael@0: EXPECT_EQ(3, actual_params->GetParamsCount()); michael@0: EXPECT_EQ(tag2, actual_params->GetTag()); michael@0: type = INVALID_TYPE; michael@0: param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); michael@0: ASSERT_EQ(sizeof(h), param_size); michael@0: EXPECT_EQ(VOIDPTR_TYPE, type); michael@0: ASSERT_TRUE(NULL != param_addr); michael@0: EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); michael@0: type = INVALID_TYPE; michael@0: param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); michael@0: ASSERT_EQ(sizeof(dw), param_size); michael@0: EXPECT_EQ(ULONG_TYPE, type); michael@0: ASSERT_TRUE(NULL != param_addr); michael@0: EXPECT_EQ(0, memcmp(&dw, param_addr, param_size)); michael@0: type = INVALID_TYPE; michael@0: param_addr = actual_params->GetRawParameter(2, ¶m_size, &type); michael@0: ASSERT_EQ(sizeof(h), param_size); michael@0: EXPECT_EQ(VOIDPTR_TYPE, type); michael@0: ASSERT_TRUE(NULL != param_addr); michael@0: EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); michael@0: michael@0: CloseChannelEvents(client_control); michael@0: delete[] reinterpret_cast(client_control); michael@0: } michael@0: michael@0: TEST(IPCTest, CrossCallValidation) { michael@0: // First a sanity test with a well formed parameter object. michael@0: unsigned long value = 124816; michael@0: const uint32 kTag = 33; michael@0: const uint32 kBufferSize = 256; michael@0: ActualCallParams<1, kBufferSize> params_1(kTag); michael@0: params_1.CopyParamIn(0, &value, sizeof(value), false, ULONG_TYPE); michael@0: void* buffer = const_cast(params_1.GetBuffer()); michael@0: michael@0: uint32 out_size = 0; michael@0: CrossCallParamsEx* ccp = 0; michael@0: ccp = CrossCallParamsEx::CreateFromBuffer(buffer, params_1.GetSize(), michael@0: &out_size); michael@0: ASSERT_TRUE(NULL != ccp); michael@0: EXPECT_TRUE(ccp->GetBuffer() != buffer); michael@0: EXPECT_EQ(kTag, ccp->GetTag()); michael@0: EXPECT_EQ(1, ccp->GetParamsCount()); michael@0: delete[] (reinterpret_cast(ccp)); michael@0: michael@0: #if defined(NDEBUG) michael@0: // Test hat we handle integer overflow on the number of params michael@0: // correctly. We use a test-only ctor for ActualCallParams that michael@0: // allows to create malformed cross-call buffers. michael@0: const int32 kPtrDiffSz = sizeof(ptrdiff_t); michael@0: for (int32 ix = -1; ix != 3; ++ix) { michael@0: uint32 fake_num_params = (kuint32max / kPtrDiffSz) + ix; michael@0: ActualCallParams<1, kBufferSize> params_2(kTag, fake_num_params); michael@0: params_2.CopyParamIn(0, &value, sizeof(value), false, ULONG_TYPE); michael@0: buffer = const_cast(params_2.GetBuffer()); michael@0: michael@0: EXPECT_TRUE(NULL != buffer); michael@0: ccp = CrossCallParamsEx::CreateFromBuffer(buffer, params_1.GetSize(), michael@0: &out_size); michael@0: // If the buffer is malformed the return is NULL. michael@0: EXPECT_TRUE(NULL == ccp); michael@0: } michael@0: #endif // defined(NDEBUG) michael@0: michael@0: ActualCallParams<1, kBufferSize> params_3(kTag, 1); michael@0: params_3.CopyParamIn(0, &value, sizeof(value), false, ULONG_TYPE); michael@0: buffer = const_cast(params_3.GetBuffer()); michael@0: EXPECT_TRUE(NULL != buffer); michael@0: michael@0: uint32 correct_size = params_3.OverrideSize(1); michael@0: ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); michael@0: EXPECT_TRUE(NULL == ccp); michael@0: michael@0: // The correct_size is 8 bytes aligned. michael@0: params_3.OverrideSize(correct_size - 7); michael@0: ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); michael@0: EXPECT_TRUE(NULL == ccp); michael@0: michael@0: params_3.OverrideSize(correct_size); michael@0: ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); michael@0: EXPECT_TRUE(NULL != ccp); michael@0: michael@0: // Make sure that two parameters work as expected. michael@0: ActualCallParams<2, kBufferSize> params_4(kTag, 2); michael@0: params_4.CopyParamIn(0, &value, sizeof(value), false, ULONG_TYPE); michael@0: params_4.CopyParamIn(1, buffer, sizeof(buffer), false, VOIDPTR_TYPE); michael@0: buffer = const_cast(params_4.GetBuffer()); michael@0: EXPECT_TRUE(NULL != buffer); michael@0: michael@0: ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); michael@0: EXPECT_TRUE(NULL != ccp); michael@0: michael@0: #if defined(_WIN64) michael@0: correct_size = params_4.OverrideSize(1); michael@0: params_4.OverrideSize(correct_size - 1); michael@0: ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); michael@0: EXPECT_TRUE(NULL == ccp); michael@0: #endif michael@0: } michael@0: michael@0: // This structure is passed to the mock server threads to simulate michael@0: // the server side IPC so it has the required kernel objects. michael@0: struct ServerEvents { michael@0: HANDLE ping; michael@0: HANDLE pong; michael@0: volatile LONG* state; michael@0: HANDLE mutex; michael@0: }; michael@0: michael@0: // This is the server thread that quicky answers an IPC and exits. michael@0: DWORD WINAPI QuickResponseServer(PVOID param) { michael@0: ServerEvents* events = reinterpret_cast(param); michael@0: DWORD wait_result = 0; michael@0: wait_result = ::WaitForSingleObject(events->ping, INFINITE); michael@0: ::InterlockedExchange(events->state, kAckChannel); michael@0: ::SetEvent(events->pong); michael@0: return wait_result; michael@0: } michael@0: michael@0: class CrossCallParamsMock : public CrossCallParams { michael@0: public: michael@0: CrossCallParamsMock(uint32 tag, uint32 params_count) michael@0: : CrossCallParams(tag, params_count) { michael@0: } michael@0: private: michael@0: void* params[4]; michael@0: }; michael@0: michael@0: void FakeOkAnswerInChannel(void* channel) { michael@0: CrossCallReturn* answer = reinterpret_cast(channel); michael@0: answer->call_outcome = SBOX_ALL_OK; michael@0: } michael@0: michael@0: // Create two threads that will quickly answer IPCs; the first one michael@0: // using channel 1 (channel 0 is busy) and one using channel 0. No time-out michael@0: // should occur. michael@0: TEST(IPCTest, ClientFastServer) { michael@0: const size_t channel_size = kIPCChannelSize; michael@0: size_t base_start = 0; michael@0: IPCControl* client_control = michael@0: MakeChannels(channel_size, 4096 * 2, &base_start); michael@0: FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_NOT_READY); michael@0: client_control->server_alive = ::CreateMutex(NULL, FALSE, NULL); michael@0: michael@0: char* mem = reinterpret_cast(client_control); michael@0: SharedMemIPCClient client(mem); michael@0: michael@0: ServerEvents events = {0}; michael@0: events.ping = client_control->channels[1].ping_event; michael@0: events.pong = client_control->channels[1].pong_event; michael@0: events.state = &client_control->channels[1].state; michael@0: michael@0: HANDLE t1 = ::CreateThread(NULL, 0, QuickResponseServer, &events, 0, NULL); michael@0: ASSERT_TRUE(NULL != t1); michael@0: ::CloseHandle(t1); michael@0: michael@0: void* buff0 = client.GetBuffer(); michael@0: EXPECT_TRUE(mem + client_control->channels[0].channel_base == buff0); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[0].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[1].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[2].state); michael@0: michael@0: void* buff1 = client.GetBuffer(); michael@0: EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff1); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[0].state); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[1].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[2].state); michael@0: michael@0: EXPECT_EQ(0, client_control->channels[1].ipc_tag); michael@0: michael@0: uint32 tag = 7654; michael@0: CrossCallReturn answer; michael@0: CrossCallParamsMock* params1 = new(buff1) CrossCallParamsMock(tag, 1); michael@0: FakeOkAnswerInChannel(buff1); michael@0: michael@0: ResultCode result = client.DoCall(params1, &answer); michael@0: if (SBOX_ERROR_CHANNEL_ERROR != result) michael@0: client.FreeBuffer(buff1); michael@0: michael@0: EXPECT_TRUE(SBOX_ALL_OK == result); michael@0: EXPECT_EQ(tag, client_control->channels[1].ipc_tag); michael@0: EXPECT_EQ(kBusyChannel, client_control->channels[0].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[1].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[2].state); michael@0: michael@0: HANDLE t2 = ::CreateThread(NULL, 0, QuickResponseServer, &events, 0, NULL); michael@0: ASSERT_TRUE(NULL != t2); michael@0: ::CloseHandle(t2); michael@0: michael@0: client.FreeBuffer(buff0); michael@0: events.ping = client_control->channels[0].ping_event; michael@0: events.pong = client_control->channels[0].pong_event; michael@0: events.state = &client_control->channels[0].state; michael@0: michael@0: tag = 4567; michael@0: CrossCallParamsMock* params2 = new(buff0) CrossCallParamsMock(tag, 1); michael@0: FakeOkAnswerInChannel(buff0); michael@0: michael@0: result = client.DoCall(params2, &answer); michael@0: if (SBOX_ERROR_CHANNEL_ERROR != result) michael@0: client.FreeBuffer(buff0); michael@0: michael@0: EXPECT_TRUE(SBOX_ALL_OK == result); michael@0: EXPECT_EQ(tag, client_control->channels[0].ipc_tag); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[0].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[1].state); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[2].state); michael@0: michael@0: CloseChannelEvents(client_control); michael@0: ::CloseHandle(client_control->server_alive); michael@0: michael@0: delete[] reinterpret_cast(client_control); michael@0: } michael@0: michael@0: // This is the server thread that very slowly answers an IPC and exits. Note michael@0: // that the pong event needs to be signaled twice. michael@0: DWORD WINAPI SlowResponseServer(PVOID param) { michael@0: ServerEvents* events = reinterpret_cast(param); michael@0: DWORD wait_result = 0; michael@0: wait_result = ::WaitForSingleObject(events->ping, INFINITE); michael@0: ::Sleep(kIPCWaitTimeOut1 + kIPCWaitTimeOut2 + 200); michael@0: ::InterlockedExchange(events->state, kAckChannel); michael@0: ::SetEvent(events->pong); michael@0: return wait_result; michael@0: } michael@0: michael@0: // This thread's job is to keep the mutex locked. michael@0: DWORD WINAPI MainServerThread(PVOID param) { michael@0: ServerEvents* events = reinterpret_cast(param); michael@0: DWORD wait_result = 0; michael@0: wait_result = ::WaitForSingleObject(events->mutex, INFINITE); michael@0: Sleep(kIPCWaitTimeOut1 * 20); michael@0: return wait_result; michael@0: } michael@0: michael@0: // Creates a server thread that answers the IPC so slow that is guaranteed to michael@0: // trigger the time-out code path in the client. A second thread is created michael@0: // to hold locked the server_alive mutex: this signals the client that the michael@0: // server is not dead and it retries the wait. michael@0: TEST(IPCTest, ClientSlowServer) { michael@0: size_t base_start = 0; michael@0: IPCControl* client_control = michael@0: MakeChannels(kIPCChannelSize, 4096*2, &base_start); michael@0: FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_NOT_READY); michael@0: client_control->server_alive = ::CreateMutex(NULL, FALSE, NULL); michael@0: michael@0: char* mem = reinterpret_cast(client_control); michael@0: SharedMemIPCClient client(mem); michael@0: michael@0: ServerEvents events = {0}; michael@0: events.ping = client_control->channels[0].ping_event; michael@0: events.pong = client_control->channels[0].pong_event; michael@0: events.state = &client_control->channels[0].state; michael@0: michael@0: HANDLE t1 = ::CreateThread(NULL, 0, SlowResponseServer, &events, 0, NULL); michael@0: ASSERT_TRUE(NULL != t1); michael@0: ::CloseHandle(t1); michael@0: michael@0: ServerEvents events2 = {0}; michael@0: events2.pong = events.pong; michael@0: events2.mutex = client_control->server_alive; michael@0: michael@0: HANDLE t2 = ::CreateThread(NULL, 0, MainServerThread, &events2, 0, NULL); michael@0: ASSERT_TRUE(NULL != t2); michael@0: ::CloseHandle(t2); michael@0: michael@0: ::Sleep(1); michael@0: michael@0: void* buff0 = client.GetBuffer(); michael@0: uint32 tag = 4321; michael@0: CrossCallReturn answer; michael@0: CrossCallParamsMock* params1 = new(buff0) CrossCallParamsMock(tag, 1); michael@0: FakeOkAnswerInChannel(buff0); michael@0: michael@0: ResultCode result = client.DoCall(params1, &answer); michael@0: if (SBOX_ERROR_CHANNEL_ERROR != result) michael@0: client.FreeBuffer(buff0); michael@0: michael@0: EXPECT_TRUE(SBOX_ALL_OK == result); michael@0: EXPECT_EQ(tag, client_control->channels[0].ipc_tag); michael@0: EXPECT_EQ(kFreeChannel, client_control->channels[0].state); michael@0: michael@0: CloseChannelEvents(client_control); michael@0: ::CloseHandle(client_control->server_alive); michael@0: delete[] reinterpret_cast(client_control); michael@0: } michael@0: michael@0: // This test-only IPC dispatcher has two handlers with the same signature michael@0: // but only CallOneHandler should be used. michael@0: class UnitTestIPCDispatcher : public Dispatcher { michael@0: public: michael@0: enum { michael@0: CALL_ONE_TAG = 78, michael@0: CALL_TWO_TAG = 87 michael@0: }; michael@0: michael@0: UnitTestIPCDispatcher(); michael@0: ~UnitTestIPCDispatcher() {}; michael@0: michael@0: virtual bool SetupService(InterceptionManager* manager, int service) { michael@0: return true; michael@0: } michael@0: michael@0: private: michael@0: bool CallOneHandler(IPCInfo* ipc, HANDLE p1, DWORD p2) { michael@0: ipc->return_info.extended[0].handle = p1; michael@0: ipc->return_info.extended[1].unsigned_int = p2; michael@0: return true; michael@0: } michael@0: michael@0: bool CallTwoHandler(IPCInfo* ipc, HANDLE p1, DWORD p2) { michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: UnitTestIPCDispatcher::UnitTestIPCDispatcher() { michael@0: static const IPCCall call_one = { michael@0: {CALL_ONE_TAG, VOIDPTR_TYPE, ULONG_TYPE}, michael@0: reinterpret_cast( michael@0: &UnitTestIPCDispatcher::CallOneHandler) michael@0: }; michael@0: static const IPCCall call_two = { michael@0: {CALL_TWO_TAG, VOIDPTR_TYPE, ULONG_TYPE}, michael@0: reinterpret_cast( michael@0: &UnitTestIPCDispatcher::CallTwoHandler) michael@0: }; michael@0: ipc_calls_.push_back(call_one); michael@0: ipc_calls_.push_back(call_two); michael@0: } michael@0: michael@0: // This test does most of the shared memory IPC client-server roundtrip michael@0: // and tests the packing, unpacking and call dispatching. michael@0: TEST(IPCTest, SharedMemServerTests) { michael@0: size_t base_start = 0; michael@0: IPCControl* client_control = michael@0: MakeChannels(kIPCChannelSize, 4096, &base_start); michael@0: client_control->server_alive = HANDLE(1); michael@0: FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); michael@0: michael@0: char* mem = reinterpret_cast(client_control); michael@0: SharedMemIPCClient client(mem); michael@0: michael@0: CrossCallReturn answer; michael@0: HANDLE bar = HANDLE(191919); michael@0: DWORD foo = 6767676; michael@0: CrossCall(client, UnitTestIPCDispatcher::CALL_ONE_TAG, bar, foo, &answer); michael@0: void* buff = client.GetBuffer(); michael@0: ASSERT_TRUE(NULL != buff); michael@0: michael@0: UnitTestIPCDispatcher dispatcher; michael@0: // Since we are directly calling InvokeCallback, most of this structure michael@0: // can be set to NULL. michael@0: sandbox::SharedMemIPCServer::ServerControl srv_control = { michael@0: NULL, NULL, kIPCChannelSize, NULL, michael@0: reinterpret_cast(client_control), michael@0: NULL, &dispatcher, {0} }; michael@0: michael@0: sandbox::CrossCallReturn call_return = {0}; michael@0: EXPECT_TRUE(SharedMemIPCServer::InvokeCallback(&srv_control, buff, michael@0: &call_return)); michael@0: EXPECT_EQ(SBOX_ALL_OK, call_return.call_outcome); michael@0: EXPECT_TRUE(bar == call_return.extended[0].handle); michael@0: EXPECT_EQ(foo, call_return.extended[1].unsigned_int); michael@0: michael@0: CloseChannelEvents(client_control); michael@0: delete[] reinterpret_cast(client_control); michael@0: } michael@0: michael@0: } // namespace sandbox