/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "TestCommon.h" #include "gtest/gtest.h" #include #include "mozilla/Atomics.h" #include "mozilla/gtest/MozAssertions.h" #include "mozilla/Monitor.h" #include "nsNamedPipeService.h" #include "nsNetCID.h" #define PIPE_NAME L"\\\\.\\pipe\\TestNPS" #define TEST_STR "Hello World" using namespace mozilla; /** * Unlike a monitor, an event allows a thread to wait on another thread * completing an action without regard to ordering of the wait and the notify. */ class Event { public: explicit Event(const char* aName) : mMonitor(aName) {} ~Event() = default; void Set() { MonitorAutoLock lock(mMonitor); MOZ_RELEASE_ASSERT(!mSignaled); mSignaled = true; mMonitor.Notify(); } void Wait() { MonitorAutoLock lock(mMonitor); while (!mSignaled) { lock.Wait(); } mSignaled = false; } private: Monitor mMonitor MOZ_UNANNOTATED; bool mSignaled = false; }; class nsNamedPipeDataObserver final : public nsINamedPipeDataObserver { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSINAMEDPIPEDATAOBSERVER explicit nsNamedPipeDataObserver(HANDLE aPipe); int Read(void* aBuffer, uint32_t aSize); int Write(const void* aBuffer, uint32_t aSize); uint32_t Transferred() const { return mBytesTransferred; } private: ~nsNamedPipeDataObserver() = default; HANDLE mPipe; OVERLAPPED mOverlapped; Atomic mBytesTransferred; Event mEvent; }; NS_IMPL_ISUPPORTS(nsNamedPipeDataObserver, nsINamedPipeDataObserver) nsNamedPipeDataObserver::nsNamedPipeDataObserver(HANDLE aPipe) : mPipe(aPipe), mOverlapped(), mBytesTransferred(0), mEvent("named-pipe") { mOverlapped.hEvent = CreateEventA(nullptr, TRUE, TRUE, "named-pipe"); } int nsNamedPipeDataObserver::Read(void* aBuffer, uint32_t aSize) { DWORD bytesRead = 0; if (!ReadFile(mPipe, aBuffer, aSize, &bytesRead, &mOverlapped)) { switch (GetLastError()) { case ERROR_IO_PENDING: { mEvent.Wait(); } if (!GetOverlappedResult(mPipe, &mOverlapped, &bytesRead, FALSE)) { ADD_FAILURE() << "GetOverlappedResult failed"; return -1; } if (mBytesTransferred != bytesRead) { ADD_FAILURE() << "GetOverlappedResult mismatch"; return -1; } break; default: ADD_FAILURE() << "ReadFile error " << GetLastError(); return -1; } } else { mEvent.Wait(); if (mBytesTransferred != bytesRead) { ADD_FAILURE() << "GetOverlappedResult mismatch"; return -1; } } mBytesTransferred = 0; return bytesRead; } int nsNamedPipeDataObserver::Write(const void* aBuffer, uint32_t aSize) { DWORD bytesWritten = 0; if (!WriteFile(mPipe, aBuffer, aSize, &bytesWritten, &mOverlapped)) { switch (GetLastError()) { case ERROR_IO_PENDING: { mEvent.Wait(); } if (!GetOverlappedResult(mPipe, &mOverlapped, &bytesWritten, FALSE)) { ADD_FAILURE() << "GetOverlappedResult failed"; return -1; } if (mBytesTransferred != bytesWritten) { ADD_FAILURE() << "GetOverlappedResult mismatch"; return -1; } break; default: ADD_FAILURE() << "WriteFile error " << GetLastError(); return -1; } } else { mEvent.Wait(); if (mBytesTransferred != bytesWritten) { ADD_FAILURE() << "GetOverlappedResult mismatch"; return -1; } } mBytesTransferred = 0; return bytesWritten; } NS_IMETHODIMP nsNamedPipeDataObserver::OnDataAvailable(uint32_t aBytesTransferred, void* aOverlapped) { if (aOverlapped != &mOverlapped) { ADD_FAILURE() << "invalid overlapped object"; return NS_ERROR_FAILURE; } DWORD bytesTransferred = 0; BOOL ret = GetOverlappedResult(mPipe, reinterpret_cast(aOverlapped), &bytesTransferred, FALSE); if (!ret) { ADD_FAILURE() << "GetOverlappedResult failed"; return NS_ERROR_FAILURE; } if (bytesTransferred != aBytesTransferred) { ADD_FAILURE() << "GetOverlappedResult mismatch"; return NS_ERROR_FAILURE; } mBytesTransferred += aBytesTransferred; mEvent.Set(); return NS_OK; } NS_IMETHODIMP nsNamedPipeDataObserver::OnError(uint32_t aError, void* aOverlapped) { return NS_ERROR_NOT_IMPLEMENTED; } BOOL CreateAndConnectInstance(LPOVERLAPPED aOverlapped, LPHANDLE aPipe); BOOL ConnectToNewClient(HANDLE aPipe, LPOVERLAPPED aOverlapped); BOOL CreateAndConnectInstance(LPOVERLAPPED aOverlapped, LPHANDLE aPipe) { // FIXME: adjust parameters *aPipe = CreateNamedPipeW(PIPE_NAME, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 65536, 65536, 3000, NULL); if (*aPipe == INVALID_HANDLE_VALUE) { ADD_FAILURE() << "CreateNamedPipe failed " << GetLastError(); return FALSE; } return ConnectToNewClient(*aPipe, aOverlapped); } BOOL ConnectToNewClient(HANDLE aPipe, LPOVERLAPPED aOverlapped) { if (ConnectNamedPipe(aPipe, aOverlapped)) { ADD_FAILURE() << "Unexpected, overlapped ConnectNamedPipe() always returns 0."; return FALSE; } switch (GetLastError()) { case ERROR_IO_PENDING: return TRUE; case ERROR_PIPE_CONNECTED: if (SetEvent(aOverlapped->hEvent)) break; [[fallthrough]]; default: // error ADD_FAILURE() << "ConnectNamedPipe failed " << GetLastError(); break; } return FALSE; } static nsresult CreateNamedPipe(LPHANDLE aServer, LPHANDLE aClient) { OVERLAPPED overlapped; overlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL); BOOL ret; ret = CreateAndConnectInstance(&overlapped, aServer); if (!ret) { ADD_FAILURE() << "pipe server should be pending"; return NS_ERROR_FAILURE; } *aClient = CreateFileW(PIPE_NAME, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr); if (*aClient == INVALID_HANDLE_VALUE) { ADD_FAILURE() << "Unable to create pipe client"; CloseHandle(*aServer); return NS_ERROR_FAILURE; } DWORD pipeMode = PIPE_READMODE_MESSAGE; if (!SetNamedPipeHandleState(*aClient, &pipeMode, nullptr, nullptr)) { ADD_FAILURE() << "SetNamedPipeHandleState error " << GetLastError(); CloseHandle(*aServer); CloseHandle(*aClient); return NS_ERROR_FAILURE; } WaitForSingleObjectEx(overlapped.hEvent, INFINITE, TRUE); return NS_OK; } TEST(TestNamedPipeService, Test) { nsCOMPtr svc = net::NamedPipeService::GetOrCreate(); HANDLE readPipe, writePipe; nsresult rv = CreateNamedPipe(&readPipe, &writePipe); ASSERT_NS_SUCCEEDED(rv); RefPtr readObserver = new nsNamedPipeDataObserver(readPipe); RefPtr writeObserver = new nsNamedPipeDataObserver(writePipe); ASSERT_NS_SUCCEEDED(svc->AddDataObserver(readPipe, readObserver)); ASSERT_NS_SUCCEEDED(svc->AddDataObserver(writePipe, writeObserver)); ASSERT_EQ(std::size_t(writeObserver->Write(TEST_STR, sizeof(TEST_STR))), sizeof(TEST_STR)); char buffer[sizeof(TEST_STR)]; ASSERT_EQ(std::size_t(readObserver->Read(buffer, sizeof(buffer))), sizeof(TEST_STR)); ASSERT_STREQ(buffer, TEST_STR) << "I/O mismatch"; ASSERT_NS_SUCCEEDED(svc->RemoveDataObserver(readPipe, readObserver)); ASSERT_NS_SUCCEEDED(svc->RemoveDataObserver(writePipe, writeObserver)); }