Last active
July 31, 2022 08:34
-
-
Save rprichard/8dd8ca134b39534b7da2733994aa07ba to your computer and use it in GitHub Desktop.
Windows Named Pipe Test (Connection and Disconnection)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Dedicated to the public domain as per the Creative Commons CC0 | |
// (https://creativecommons.org/about/cc0/). | |
// Compile with C++11 (i.e. -std=c++11) | |
// Tests the behavior of Windows named pipes regarding the lifecycle of | |
// connections. | |
// | |
// This test demonstrates various ways that Wine (as of 1.9.12) differs from | |
// Windows: | |
// | |
// - With Windows, there can be multiple concurrent ConnectNamedPipe | |
// operations on a pipe. With Wine, the second ConnectNamedPipe call fails | |
// with ERROR_INVALID_HANDLE. | |
// | |
// - With Windows, DisconnectNamedPipe aborts ConnectNamedPipe operations with | |
// the error ERROR_PIPE_NOT_CONNECTED. The DisconnectNamedPipe call | |
// succeeds, and the pipe is no longer listening. With Wine, the | |
// DisconnectNamedPipe call itself fails with ERROR_PIPE_LISTENING, and the | |
// pipe is still listening. | |
// | |
// - With Windows, if the server breaks the pipe by closing its handle, the | |
// client can finish reading the remaining data in the pipe. Once the data | |
// is gone, reading from a broken pipe fails with ERROR_BROKEN_PIPE. | |
// Writing to a broken pipe fails with ERROR_NO_DATA. With Wine, once the | |
// server breaks the pipe, the client cannot read the remaining data, and | |
// I/O on a broken pipe fails with ERROR_PIPE_NOT_CONNECTED. (Exception: | |
// ReadFile on a server pipe still returns ERROR_BROKEN_PIPE.) | |
// | |
// - Calling ConnectNamedPipe on a broken server pipe fails with ERROR_NO_DATA | |
// on Windows, but ERROR_NO_DATA_DETECTED on Wine. | |
// | |
#include <windows.h> | |
#include <assert.h> | |
#include <stdio.h> | |
#include <string> | |
#define CHECK(cond) \ | |
do { \ | |
if (!(cond)) { \ | |
printf("%s,%d: %s: CHECK failed: `%s`\n", \ | |
__FILE__, __LINE__, __FUNCTION__, #cond); \ | |
} \ | |
} while (false) | |
#define CHECK_SUCCESS(cond) \ | |
do { \ | |
if (!(cond)) { \ | |
printf("%s,%d: %s: API call unexpectedly failed: `%s`: " \ | |
"LastError=%u%s\n", \ | |
__FILE__, __LINE__, __FUNCTION__, #cond, \ | |
static_cast<unsigned>(GetLastError()), \ | |
stringFromError(GetLastError())); \ | |
} \ | |
} while (false) | |
#define CHECK_FAILURE(cond, expectedErr) \ | |
do { \ | |
if (cond) { \ | |
printf("%s,%d: %s: API call unexpectedly succeeded: `%s`: " \ | |
"expected error %u%s\n", \ | |
__FILE__, __LINE__, __FUNCTION__, #cond, \ | |
static_cast<unsigned>(expectedErr), \ | |
stringFromError(expectedErr)); \ | |
} else if (GetLastError() != expectedErr) { \ | |
printf("%s,%d: %s: API call failed with wrong error: `%s`: " \ | |
"actual %u%s != expected %u%s\n", \ | |
__FILE__, __LINE__, __FUNCTION__, #cond, \ | |
static_cast<unsigned>(GetLastError()), \ | |
stringFromError(GetLastError()), \ | |
static_cast<unsigned>(expectedErr), \ | |
stringFromError(expectedErr)); \ | |
} \ | |
} while (false) | |
static const char *stringFromError(DWORD err) { | |
switch (err) { | |
case ERROR_INVALID_HANDLE: return "(ERROR_INVALID_HANDLE)"; // 6 | |
case ERROR_BROKEN_PIPE: return "(ERROR_BROKEN_PIPE)"; // 109 | |
case ERROR_NO_DATA: return "(ERROR_NO_DATA)"; // 232 | |
case ERROR_PIPE_NOT_CONNECTED: return "(ERROR_PIPE_NOT_CONNECTED)"; // 233 | |
case ERROR_PIPE_CONNECTED: return "(ERROR_PIPE_CONNECTED)"; // 535 | |
case ERROR_PIPE_LISTENING: return "(ERROR_PIPE_LISTENING)"; // 536 | |
case ERROR_IO_INCOMPLETE: return "(ERROR_IO_INCOMPLETE)"; // 996 | |
case ERROR_IO_PENDING: return "(ERROR_IO_PENDING)"; // 997 | |
case ERROR_NO_DATA_DETECTED: return "(ERROR_NO_DATA_DETECTED)"; // 1104 | |
default: return ""; | |
} | |
} | |
static std::string name(int idx) { | |
return "\\\\.\\pipe\\NamedPipeTest" + std::to_string(idx); | |
} | |
static HANDLE openServer(const std::string &name, int maxInstances=1) { | |
return CreateNamedPipeA( | |
name.c_str(), | |
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, | |
0, | |
/*nMaxInstances=*/maxInstances, | |
256, | |
256, | |
0, | |
nullptr); | |
} | |
static HANDLE createFile(const std::string &name) { | |
return CreateFileA( | |
name.c_str(), | |
GENERIC_READ | GENERIC_WRITE, | |
0, | |
nullptr, | |
OPEN_EXISTING, | |
0, | |
nullptr); | |
} | |
static bool connectToServerAndClose(const std::string &name) { | |
HANDLE handle = createFile(name); | |
if (handle == INVALID_HANDLE_VALUE) { | |
return false; | |
} | |
CloseHandle(handle); | |
return true; | |
} | |
static HANDLE assertValid(HANDLE handle) { | |
assert(handle != INVALID_HANDLE_VALUE); | |
assert(handle != nullptr); | |
return handle; | |
} | |
class Over : public OVERLAPPED { | |
public: | |
Over() { | |
*(OVERLAPPED*)this = {}; | |
hEvent = m_event = CreateEvent(nullptr, TRUE, FALSE, nullptr); | |
assert(hEvent != nullptr); | |
} | |
~Over() { | |
assert(hEvent == m_event); | |
CloseHandle(m_event); | |
} | |
private: | |
HANDLE m_event; | |
}; | |
// A new server pipe can be connected to without first calling | |
// ConnectNamedPipe, because a new pipe starts out in a "Listening" | |
// state. | |
static void testImplicitConnect() { | |
const HANDLE server = assertValid(openServer(name(0))); | |
const HANDLE client = assertValid(createFile(name(0))); | |
Over over; | |
CHECK_FAILURE(ConnectNamedPipe(server, &over), ERROR_PIPE_CONNECTED); | |
CHECK_SUCCESS(CloseHandle(client)); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
// Calling DisconnectNamedPipe on a new pipe successfully transitions it to a | |
// "Not Connected" state. The second DisconnectNamedPipe call fails, and the | |
// client can't connect. | |
static void testDisconnectAtStartup() { | |
const HANDLE server = assertValid(openServer(name(0))); | |
CHECK_SUCCESS(DisconnectNamedPipe(server)); | |
CHECK(createFile(name(0)) == INVALID_HANDLE_VALUE); | |
CHECK_FAILURE(DisconnectNamedPipe(server), ERROR_PIPE_NOT_CONNECTED); | |
CHECK(createFile(name(0)) == INVALID_HANDLE_VALUE); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
// A client can't connect after the server has called DisconnectNamedPipe, | |
// because the pipe is in a "Not Connected" state. | |
static void testConnectAfterDisconnect() { | |
const HANDLE server = assertValid(openServer(name(0))); | |
const HANDLE client = assertValid(createFile(name(0))); | |
CHECK_SUCCESS(DisconnectNamedPipe(server)); | |
CHECK(createFile(name(0)) == INVALID_HANDLE_VALUE); | |
CHECK_SUCCESS(CloseHandle(server)); | |
CHECK_SUCCESS(CloseHandle(client)); | |
} | |
// Trying to connect after the client breaks the connection fails, because the | |
// pipe is in some kind of "Broken / No Data" state. | |
static void testReconnectAfterBreak() { | |
const HANDLE server = assertValid(openServer(name(0))); | |
const HANDLE client = assertValid(createFile(name(0))); | |
CHECK_SUCCESS(CloseHandle(client)); | |
Over over; | |
CHECK_FAILURE(ConnectNamedPipe(server, &over), ERROR_NO_DATA); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
// Start connecting, asynchronously. Have a client connect, then immediately | |
// break the connection, before the server checks the result of the async | |
// operation. The operation should still have succeeded. | |
static void testBreakBeforeAsyncCleanup() { | |
DWORD dummyDW = 0; | |
Over over; | |
const HANDLE server = assertValid(openServer(name(0))); | |
CHECK_FAILURE(ConnectNamedPipe(server, &over), ERROR_IO_PENDING); | |
const HANDLE client = assertValid(createFile(name(0))); | |
CHECK_SUCCESS(CloseHandle(client)); | |
CHECK_SUCCESS(GetOverlappedResult(server, &over, &dummyDW, FALSE)); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
static void testMultipleAyncConnectOpsStillSucceed() { | |
DWORD dummyDW = 0; | |
Over over1; | |
Over over2; | |
const HANDLE server = assertValid(openServer(name(0))); | |
// Two connect operations, both pending | |
CHECK_FAILURE(ConnectNamedPipe(server, &over1), ERROR_IO_PENDING); | |
CHECK_FAILURE(ConnectNamedPipe(server, &over2), ERROR_IO_PENDING); | |
const HANDLE client = assertValid(createFile(name(0))); | |
// Now they've both succeeded. | |
CHECK_SUCCESS(GetOverlappedResult(server, &over1, &dummyDW, FALSE)); | |
CHECK_SUCCESS(GetOverlappedResult(server, &over2, &dummyDW, FALSE)); | |
CHECK_SUCCESS(CloseHandle(client)); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
static void testDisconnectAbortsConnectionAttempt() { | |
DWORD dummyDW = 0; | |
Over over; | |
const HANDLE server = assertValid(openServer(name(0))); | |
CHECK_FAILURE(ConnectNamedPipe(server, &over), ERROR_IO_PENDING); | |
CHECK_SUCCESS(DisconnectNamedPipe(server)); | |
CHECK(!connectToServerAndClose(name(0))); | |
CHECK_FAILURE(GetOverlappedResult(server, &over, &dummyDW, TRUE), ERROR_PIPE_NOT_CONNECTED); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
static void testDisconnectAbortsReconnectionAttempt() { | |
DWORD dummyDW = 0; | |
Over over; | |
// First connection. | |
const HANDLE server = assertValid(openServer(name(0))); | |
const HANDLE client = assertValid(createFile(name(0))); | |
CHECK_SUCCESS(DisconnectNamedPipe(server)); | |
CHECK_SUCCESS(CloseHandle(client)); | |
// Reconnection attempt. | |
CHECK_FAILURE(ConnectNamedPipe(server, &over), ERROR_IO_PENDING); | |
CHECK_SUCCESS(DisconnectNamedPipe(server)); | |
CHECK(!connectToServerAndClose(name(0))); | |
CHECK_FAILURE(GetOverlappedResult(server, &over, &dummyDW, TRUE), ERROR_PIPE_NOT_CONNECTED); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
static void testDisconnectAbortsMultipleConnectionAttempts() { | |
DWORD dummyDW = 0; | |
Over over1; | |
Over over2; | |
const HANDLE server = assertValid(openServer(name(0))); | |
CHECK_FAILURE(ConnectNamedPipe(server, &over1), ERROR_IO_PENDING); | |
CHECK_FAILURE(ConnectNamedPipe(server, &over2), ERROR_IO_PENDING); | |
CHECK_SUCCESS(DisconnectNamedPipe(server)); | |
CHECK(!connectToServerAndClose(name(0))); | |
CHECK_FAILURE(GetOverlappedResult(server, &over1, &dummyDW, FALSE), ERROR_PIPE_NOT_CONNECTED); | |
CHECK_FAILURE(GetOverlappedResult(server, &over2, &dummyDW, FALSE), ERROR_PIPE_NOT_CONNECTED); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
static void testIoOnInitiallyUnconnectedServerPipe() { | |
DWORD dummyDW = 0; | |
Over over; | |
char buf[1] = {}; | |
const HANDLE server = assertValid(openServer(name(0))); | |
CHECK_FAILURE(WriteFile(server, buf, 1, &dummyDW, &over), ERROR_PIPE_LISTENING); | |
CHECK_FAILURE(ReadFile(server, buf, 1, &dummyDW, &over), ERROR_PIPE_LISTENING); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
static void testIoOnBrokenServerPipe() { | |
DWORD dummyDW = 0; | |
Over over; | |
char buf[1] = {}; | |
const HANDLE server = assertValid(openServer(name(0))); | |
const HANDLE client = assertValid(createFile(name(0))); | |
CHECK_SUCCESS(CloseHandle(client)); | |
CHECK_FAILURE(WriteFile(server, buf, 1, &dummyDW, &over), ERROR_NO_DATA); | |
CHECK_FAILURE(ReadFile(server, buf, 1, &dummyDW, &over), ERROR_BROKEN_PIPE); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
static void testIoOnBrokenClientPipe() { | |
DWORD dummyDW = 0; | |
Over over; | |
char buf[1] = {}; | |
const HANDLE server = assertValid(openServer(name(0))); | |
const HANDLE client = assertValid(createFile(name(0))); | |
CHECK_SUCCESS(CloseHandle(server)); | |
CHECK_FAILURE(WriteFile(client, buf, 1, &dummyDW, &over), ERROR_NO_DATA); | |
CHECK_FAILURE(ReadFile(client, buf, 1, &dummyDW, &over), ERROR_BROKEN_PIPE); | |
CHECK_SUCCESS(CloseHandle(client)); | |
} | |
static void testIoOnDisconnectedClientPipe() { | |
DWORD dummyDW = 0; | |
Over over; | |
char buf[1] = {}; | |
const HANDLE server = assertValid(openServer(name(0))); | |
const HANDLE client = assertValid(createFile(name(0))); | |
CHECK_SUCCESS(DisconnectNamedPipe(server)); | |
CHECK_FAILURE(WriteFile(client, buf, 1, &dummyDW, &over), ERROR_PIPE_NOT_CONNECTED); | |
CHECK_FAILURE(ReadFile(client, buf, 1, &dummyDW, &over), ERROR_PIPE_NOT_CONNECTED); | |
CHECK_SUCCESS(CloseHandle(client)); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
// Windows creates I/O buffers for each pipe instance. DisconnectNamedPipe | |
// kills the connection but keeps the I/O buffers for the next connection. | |
// Therefore, any existing data is lost. | |
static void testReadFromDisconnectedPipe() { | |
DWORD dummyDW = 0; | |
Over over; | |
char buf[1] = {}; | |
const HANDLE server = assertValid(openServer(name(0))); | |
const HANDLE client = assertValid(createFile(name(0))); | |
CHECK_SUCCESS(WriteFile(server, buf, 1, &dummyDW, &over)); | |
DisconnectNamedPipe(server); | |
CHECK_FAILURE(ReadFile(client, buf, 1, &dummyDW, &over), ERROR_PIPE_NOT_CONNECTED); | |
CHECK_SUCCESS(CloseHandle(client)); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
// If a server pipe is broken, the remaining data can still be read. | |
static void testReadFromBrokenServerPipe() { | |
DWORD actual = 0; | |
Over over; | |
char buf[2] = {}; | |
const HANDLE server = assertValid(openServer(name(0))); | |
const HANDLE client = assertValid(createFile(name(0))); | |
CHECK_SUCCESS(WriteFile(client, buf, 1, &actual, &over)); | |
CHECK_SUCCESS(CloseHandle(client)); | |
CHECK_SUCCESS(ReadFile(server, buf, 2, &actual, &over)); | |
CHECK(actual == 1); | |
CHECK_FAILURE(ReadFile(server, buf, 2, &actual, &over), ERROR_BROKEN_PIPE); | |
CHECK_SUCCESS(CloseHandle(server)); | |
} | |
// If a client pipe is broken, rather than disconnected, the remaining data can | |
// still be read. | |
static void testReadFromBrokenClientPipe() { | |
DWORD actual = 0; | |
Over over; | |
char buf[2] = {}; | |
const HANDLE server = assertValid(openServer(name(0))); | |
const HANDLE client = assertValid(createFile(name(0))); | |
CHECK_SUCCESS(WriteFile(server, buf, 1, &actual, &over)); | |
CHECK_SUCCESS(CloseHandle(server)); | |
CHECK_SUCCESS(ReadFile(client, buf, 2, &actual, &over)); | |
CHECK(actual == 1); | |
CHECK_FAILURE(ReadFile(client, buf, 2, &actual, &over), ERROR_BROKEN_PIPE); | |
CHECK_SUCCESS(CloseHandle(client)); | |
} | |
// | |
// XXX: This test fails on XP, but it passes on Vista, Win7, and Win10. | |
// | |
static void testChangeMaxInstancesAfterClosingLastServerHandle() { | |
// Use a different pipe number here so that lingering handles can't | |
// interfere with the other tests. | |
const HANDLE server = assertValid(openServer(name(1))); | |
const HANDLE server2 = openServer(name(1), 2); | |
CHECK(server2 == INVALID_HANDLE_VALUE); | |
const HANDLE client = assertValid(createFile(name(1))); | |
CHECK_SUCCESS(CloseHandle(server)); | |
// There are no handles to the server pipe left open. We can change | |
// the number of max instances. | |
const HANDLE server3 = openServer(name(1), 2); | |
const HANDLE server4 = openServer(name(1), 2); | |
CHECK(server3 != INVALID_HANDLE_VALUE); | |
CHECK(server4 != INVALID_HANDLE_VALUE); | |
if (server2 != INVALID_HANDLE_VALUE) { CHECK_SUCCESS(CloseHandle(server2)); } | |
if (server3 != INVALID_HANDLE_VALUE) { CHECK_SUCCESS(CloseHandle(server3)); } | |
if (server4 != INVALID_HANDLE_VALUE) { CHECK_SUCCESS(CloseHandle(server4)); } | |
CHECK_SUCCESS(CloseHandle(client)); | |
} | |
int main() { | |
testImplicitConnect(); | |
testDisconnectAtStartup(); | |
testConnectAfterDisconnect(); | |
testReconnectAfterBreak(); | |
testBreakBeforeAsyncCleanup(); | |
testMultipleAyncConnectOpsStillSucceed(); | |
testDisconnectAbortsConnectionAttempt(); | |
testDisconnectAbortsReconnectionAttempt(); | |
testDisconnectAbortsMultipleConnectionAttempts(); | |
testIoOnInitiallyUnconnectedServerPipe(); | |
testIoOnBrokenServerPipe(); | |
testIoOnBrokenClientPipe(); | |
testIoOnDisconnectedClientPipe(); | |
testReadFromDisconnectedPipe(); | |
testReadFromBrokenServerPipe(); | |
testReadFromBrokenClientPipe(); | |
testChangeMaxInstancesAfterClosingLastServerHandle(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment