Skip to content

Instantly share code, notes, and snippets.

@rprichard
Last active July 31, 2022 08:34
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rprichard/8dd8ca134b39534b7da2733994aa07ba to your computer and use it in GitHub Desktop.
Save rprichard/8dd8ca134b39534b7da2733994aa07ba to your computer and use it in GitHub Desktop.
Windows Named Pipe Test (Connection and Disconnection)
// 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