[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [dferry] /: Windows support! (and select() event polling on Unix)
From:       Andreas Hartmetz <ahartmetz () gmail ! com>
Date:       2015-12-31 14:23:56
Message-ID: E1aEe8y-0003kS-2d () scm ! kde ! org
[Download RAW message or body]

Git commit 37d3f78a093f0c8245d4517fe5a41497458c216e by Andreas Hartmetz.
Committed on 31/12/2015 at 14:22.
Pushed by ahartmetz into branch 'master'.

Windows support! (and select() event polling on Unix)

- Add separate Windows and Unix select() based event pollers. They are
  just different enough that a universal version turns into a maze of
  ifdefs. The Unix version is a byproduct of the Windows version and
  passes all the tests when used on Linux with some hacks to choose it
  instead of epoll.
- Make existing TCP/IP sockets Windows compatible
- Genericize some data types
- DLL import / export stuff for public API
- Bus discovery and authentication on Windows - that part is
  still highly dodgy(ly implemented). It also seems like "official
  D-Bus" on Windows with default options doesn't use authentication
  that actually checks anything, which is very dodgy, too.
  (it doesn't use tcp-nonce, just EXTERNAL auth which does nothing
   on Windows)
  tcp-nonce is also missing from this implementation.
  This is really just the minimum to connect to a test session bus.
- Leave in some debug output that turned out to be useful during
  debugging. It should be silent when things work.

Note: the timer test doesn't pass. I still need to figure out exactly
how timer precision is relaxed on Windows and how to make the test more
tolerant without making it useless.

M  +41   -9    CMakeLists.txt
M  +178  -33   buslogic/connectioninfo.cpp
M  +5    -1    buslogic/connectioninfo.h
M  +3    -0    buslogic/transceiver.cpp
M  +34   -7    connection/authnegotiator.cpp
M  +6    -1    connection/iconnection.cpp
M  +2    -0    connection/iconnection.h
M  +33   -15   connection/ipserver.cpp
M  +2    -1    connection/ipserver.h
M  +52   -26   connection/ipsocket.cpp
M  +3    -3    connection/ipsocket.h
M  +4    -8    connection/iserver.cpp
M  +1    -3    connection/iserver.h
M  +7    -0    connection/localserver.cpp
M  +3    -0    connection/localserver.h
M  +1    -1    connection/localsocket.h
M  +31   -2    connection/platform.h
M  +14   -2    connection/stringtools.cpp
M  +1    -0    connection/stringtools.h
M  +1    -1    events/event.h
M  +22   -8    events/eventdispatcher.cpp
M  +1    -1    events/eventdispatcher_p.h
M  +11   -1    events/platformtime.cpp
A  +163  -0    events/selecteventpoller_unix.cpp     [License: LGPL (v2+)]
A  +72   -0    events/selecteventpoller_unix.h     [License: LGPL (v2+)]
A  +254  -0    events/selecteventpoller_win32.cpp     [License: LGPL (v2+)]
A  +86   -0    events/selecteventpoller_win32.h     [License: LGPL (v2+)]
M  +2    -2    serialization/arguments.h
M  +2    -0    serialization/basictypeio.h
M  +5    -1    serialization/message.cpp
M  +1    -0    tests/CMakeLists.txt
M  +3    -1    tests/buslogic/CMakeLists.txt
M  +3    -0    tests/serialization/tst_message.cpp
M  +19   -3    tests/testutil.h
M  +18   -0    util/export.h
A  +313  -0    util/sha1.c     [License: Public Domain]
M  +7    -0    util/types.h
A  +96   -0    util/winutil.cpp     [License: LGPL (v2+)]
C  +7    -7    util/winutil.h [from: connection/platform.h - 079% similarity]

http://commits.kde.org/dferry/37d3f78a093f0c8245d4517fe5a41497458c216e

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 86ab0d6..c1b236b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,20 +5,33 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} \
${CMAKE_CURRENT_SOURCE_DIR}/cmake)  
 if (CMAKE_COMPILER_IS_GNUCXX)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Wextra -Werror \
-Wno-error=unused-result") +   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
-fvisibility=hidden -fvisibility-inlines-hidden")  endif()
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden \
-fvisibility-inlines-hidden")  set(CMAKE_CXX_STANDARD 11)
 
+if (WIN32 AND CMAKE_SYSTEM_VERSION VERSION_LESS 6.0)
+    message(FATAL_ERROR "Windows Vista or later is required.")
+endif()
+
 include(TestBigEndian)
-if(${BIGENDIAN})
+if (BIGENDIAN)
     add_definitions(-DBIGENDIAN)
 endif()
+if (UNIX)
+    add_definitions(-D__unix__) # help for platforms that don't define this standard \
macro +endif()
 
 option(DFERRY_BUILD_ANALYZER "Build the dfer-analyzer bus analyzer GUI" TRUE)
 
 include(GNUInstallDirs)
 
-set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
+if (WIN32)
+    # Windows doesn't have an RPATH equivalent, so just make sure that all .dll and \
.exe files +    # are located together, so that the .exes find the .dlls at runtime
+    set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+else()
+    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
+endif()
 
 include_directories(${CMAKE_SOURCE_DIR}/buslogic
                     ${CMAKE_SOURCE_DIR}/client
@@ -38,10 +51,7 @@ set(DFER_SOURCES
     connection/ipserver.cpp
     connection/ipsocket.cpp
     connection/iserver.cpp
-    connection/localserver.cpp
-    connection/localsocket.cpp
     connection/stringtools.cpp
-    events/epolleventpoller.cpp
     events/event.cpp
     events/eventdispatcher.cpp
     events/ieventpoller.cpp
@@ -53,6 +63,11 @@ set(DFER_SOURCES
     util/error.cpp
     util/icompletionclient.cpp
     util/types.cpp)
+if (UNIX)
+    set(DFER_SOURCES ${DFER_SOURCES}
+        connection/localserver.cpp
+        connection/localsocket.cpp)
+endif()
 
 set(DFER_PUBLIC_HEADERS
     buslogic/connectioninfo.h
@@ -79,22 +94,39 @@ set(DFER_PRIVATE_HEADERS
     connection/ipserver.h
     connection/ipsocket.h
     connection/iserver.h
-    connection/localserver.h
-    connection/localsocket.h
     connection/platform.h
     connection/stringtools.h
     events/event.h
-    events/epolleventpoller.h
     events/ieventpoller.h
     events/iioeventclient.h
     events/platformtime.h
     serialization/basictypeio.h)
+if (UNIX)
+    set(DFER_PRIVATE_HEADERS ${DFER_PRIVATE_HEADERS}
+        connection/localserver.h
+        connection/localsocket.h)
+endif()
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    set(DFER_SOURCES ${DFER_SOURCES} events/epolleventpoller.cpp)
+    set(DFER_PRIVATE_HEADERS ${DFER_PRIVATE_HEADERS} events/epolleventpoller.h)
+elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+    set(DFER_PRIVATE_HEADERS ${DFER_PRIVATE_HEADERS} \
events/selecteventpoller_win32.h util/winutil.h) +    set(DFER_SOURCES \
${DFER_SOURCES} events/selecteventpoller_win32.cpp util/winutil.cpp) +elseif(UNIX)
+    set(DFER_PRIVATE_HEADERS ${DFER_PRIVATE_HEADERS} \
events/selecteventpoller_unix.h) +    set(DFER_SOURCES ${DFER_SOURCES} \
events/selecteventpoller_unix.cpp) +else()
+    message(FATAL_ERROR "This operating system is not supported.")
+endif()
 
 set(DFER_HEADERS ${DFER_PUBLIC_HEADERS} ${DFER_PRIVATE_HEADERS})
 
 add_library(dfer SHARED ${DFER_SOURCES} ${DFER_HEADERS})
 target_include_directories(dfer INTERFACE "$<INSTALL_INTERFACE:include/dferry>")
 target_compile_definitions(dfer PRIVATE -DBUILDING_LIBDFER)
+if (WIN32)
+    target_link_libraries(dfer PRIVATE ws2_32)
+endif()
 
 find_package(LibTinyxml2 REQUIRED) # for the introspection parser in dferclient
 include_directories(${LIBTINYXML2_INCLUDE_DIRS})
diff --git a/buslogic/connectioninfo.cpp b/buslogic/connectioninfo.cpp
index 4ba92ef..cbbc5ce 100644
--- a/buslogic/connectioninfo.cpp
+++ b/buslogic/connectioninfo.cpp
@@ -33,11 +33,24 @@
 
 #include <sys/stat.h>
 #include <sys/types.h>
+
+#ifdef __unix__
 #include <pwd.h>
 #include <unistd.h>
+#endif
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <sddl.h>
+#include <mbstring.h>
+#include "winutil.h"
+#endif
+
 
 using namespace std;
 
+#ifdef __unix__
 static string homeDir()
 {
     const char *home = getenv("HOME"); // this overrides the entry in /etc/passwd
@@ -88,6 +101,121 @@ static string sessionInfoFile()
     string ret = homeDir() + pathInHome + uuid + '-' + display;
     return ret;
 }
+#endif
+
+#ifdef _WIN32
+/*
+What do on Windows:
+- Get the machine UUID (just for completeness or necessary is yet to be seen)
+- Get the server bus address, which includes "noncefile=", the path to the nonce \
file. +  It is obtained from a shared memory segment with a well-known name, and the \
liveness +  of the server process is checked using a mutex AFAIU.
+- Read 16 bytes from the nonce file, the credentials for the planned TCP connection
+- Implement the nonce-data dialog in AuthNegotiator / something it can use
+*/
+static string hashOfInstallRoot()
+{
+    // Using the non-Unicode API for bug compatibility with libdbus pathname hashes, \
for now. +    // This requires us to be installed to the same folder, which is a \
little weird, so maybe +    // drop this compatibility later.
+    string ret;
+#if 1
+    char path[MAX_PATH * 2] = "C:\\Program Files \
(x86)\\D-Bus\\bin\\dbus-monitor.exe)"; +    size_t pathLength = 0;
+#else
+    char path[MAX_PATH * 2]; // * 2 because of multibyte (limited to double byte \
really) charsets +    HMODULE hm = nullptr;
+    GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+                        GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+                    reinterpret_cast<LPCSTR>(&hashOfInstallRoot), &hm);
+    DWORD pathLength = GetModuleFileNameA(hm, path, sizeof(path));
+    if (!pathLength) {
+        // blah
+        return ret;
+    }
+#endif
+    // remove binary name to obtain the path
+    char *lastBackslash = reinterpret_cast<char *>(
+                            _mbsrchr(reinterpret_cast<unsigned char *>(path), \
'\\')); +    if (!lastBackslash) {
+        return ret;
+    }
+    pathLength = lastBackslash - path + 1;
+
+    // remove possible "\bin", "\bin\debug", "\bin\release"
+    if (pathLength >= 4 && _strnicmp(lastBackslash - 4, "\\bin", 4) == 0) {
+        pathLength -= 4;
+    } else if (pathLength >= 10 && _strnicmp(lastBackslash - 10, "\\bin\\debug", 10) \
== 0) { +        pathLength -= 10;
+    } else if (pathLength >= 12 && _strnicmp(lastBackslash - 12, "\\bin\\release", \
12) == 0) { +        pathLength -= 12;
+    }
+
+    // super crappy tolower(), also known as _dbus_string_tolower_ascii()
+    for (size_t i = 0; i < pathLength; i++) {
+        if (path[i] >= 'A' && path[i] <= 'Z') {
+            path[i] += 'a' - 'A';
+        }
+    }
+
+    string pathString(path, pathLength);
+    return sha1Hex(pathString);
+}
+
+// Returns something like:
+// "tcp:host=localhost,port=52933,family=ipv4,guid=0fcf91a66520469005539fb2000001a7"
+static string sessionBusAddressFromShm()
+{
+    string ret;
+    string shmNamePostfix;
+
+    if (true /* scope == "*install-path" */) {
+        shmNamePostfix = hashOfInstallRoot();
+    } else {
+        // scope == "*user"
+        shmNamePostfix = fetchWindowsSid();
+    }
+
+    // the SID corresponds to the "*user" "autolaunch method" or whatever \
apparently; +    // the default seems to be "install-path", for with the shm name \
postfix comes from +    // _dbus_get_install_root_as_hash - reimplement that one!
+
+    // TODO check that the daemon is available using the mutex
+
+    HANDLE sharedMem;
+    string shmName = "DBusDaemonAddressInfo-";
+    shmName += shmNamePostfix;
+    // full shm name looks something like \
"DBusDaemonAddressInfo-395c81f0c8140cfdeab22831b0faf4ec0ebcaae5" +    // full mutex \
name looks something like "DBusDaemonMutex-395c81f0c8140cfdeab22831b0faf4ec0ebcaae5" \
+ +    // read shm
+    for (int i = 0 ; i < 20; i++) {
+        // we know that dbus-daemon is available, so we wait until shm is available
+        sharedMem = OpenFileMappingA(FILE_MAP_READ, FALSE, shmName.c_str());
+        if (sharedMem != 0) {
+            break;
+        }
+        cerr << "Retrying OpenFileMappingA\n";
+        Sleep(100);
+    }
+
+    if (sharedMem == 0)
+        return ret;
+
+    const void *addrView = MapViewOfFile(sharedMem, FILE_MAP_READ, 0, 0, 0);
+    if (!addrView) {
+        return ret;
+    }
+    ret = static_cast<const char *>(addrView);
+
+    // cleanup
+    UnmapViewOfFile(addrView);
+    CloseHandle(sharedMem);
+
+    return ret;
+}
+#endif
+
 
 class ConnectionInfo::Private
 {
@@ -124,9 +252,14 @@ ConnectionInfo::ConnectionInfo(Bus bus)
     if (bus == Bus::Session) {
         d->fetchSessionBusInfo();
     } else if (bus == Bus::System) {
-        // TODO non-Linux
+#ifdef __unix__
+        // ### does the __unix__ version actually apply to non-Linux?
         d->m_socketType = SocketType::Unix;
         d->m_path = "/var/run/dbus/system_bus_socket";
+#else
+        // Windows... it doesn't really have a system bus
+        d->m_socketType = SocketType::None;
+#endif
     } else {
         assert(bus <= Bus::PeerToPeer);
     }
@@ -210,7 +343,7 @@ string ConnectionInfo::guid() const
 void ConnectionInfo::Private::fetchSessionBusInfo()
 {
     string line;
-
+#ifdef __unix__
     // TODO: on X, the spec requires a special way to find the session bus
     //       (but nobody seems to use it?)
 
@@ -230,57 +363,69 @@ void ConnectionInfo::Private::fetchSessionBusInfo()
             }
         }
     }
-
+#elif defined(_WIN32)
+    line = sessionBusAddressFromShm();
+//#error see dbus-sysdeps-win.c, _dbus_get_autolaunch_shm and CreateMutexA / \
WaitForSingleObject in its callers +#endif // no #else <some error>, some platform \
might not have a session bus  parseSessionBusInfo(line);
 }
 
 void ConnectionInfo::Private::parseSessionBusInfo(string info)
 {
-    SocketType provisionalType = SocketType::None;
-
-    string unixAddressLiteral = "unix:";
-    string guidLiteral = "guid=";
-
-    if (info.find(unixAddressLiteral) == 0) {
-        provisionalType = SocketType::Unix;
-        info.erase(0, unixAddressLiteral.length());
-    }
+    // typical input on Linux: \
"unix:abstract=/tmp/dbus-BrYfzr7UIv,guid=6c79b601925e949a9fe0c9ea565d80e8" +    // \
Windows: "tcp:host=localhost,port=64707,family=ipv4,guid=11ec225ce5f514366eec72f10000011d"
  
     // TODO is there any escaping?
+    // ### well-formed input is assumed - this may produce nonsensical results with \
bad input  const vector<string> parts = split(info, ',');
 
-    if (provisionalType == SocketType::Unix) {
-        string pathLiteral = "path=";
-        string abstractLiteral = "abstract=";
-        // TODO what about "tmpdir=..."?
-
-        for (const string &part : parts) {
-            if (part.find(pathLiteral) == 0) {
-                if (m_socketType != SocketType::None) {
-                    goto invalid; // error - duplicate path specification
-                }
-                m_socketType = SocketType::Unix;
-                m_path = part.substr(pathLiteral.length());
-            } else if (part.find(abstractLiteral) == 0) {
-                if (m_socketType != SocketType::None) {
-                    goto invalid;
-                }
-                m_socketType = SocketType::AbstractUnix;
-                m_path = part.substr(abstractLiteral.length());
+    const string guidLiteral = "guid=";
+    const string tcpHostLiteral = "tcp:host=";
+    const string portLiteral = "port=";
+    // const string familyLiteral = "family="; // ### ignored for now (we assume \
"ipv4") +#ifdef __unix__
+    const string unixPathLiteral = "unix:path=";
+    const string unixAbstractLiteral = "unix:abstract=";
+    // TODO what about "tmpdir=..."?
+
+    for (const string &part : parts) {
+        if (part.find(unixPathLiteral) == 0) {
+            if (m_socketType != SocketType::None) {
+                goto invalid; // error - duplicate path specification
             }
+            m_socketType = SocketType::Unix;
+            m_path = part.substr(unixPathLiteral.length());
+        } else if (part.find(unixAbstractLiteral) == 0) {
+            if (m_socketType != SocketType::None) {
+                goto invalid;
+            }
+            m_socketType = SocketType::AbstractUnix;
+            m_path = part.substr(unixAbstractLiteral.length());
         }
-    } else {
-        // TODO
     }
-
+#endif
     for (const string &part : parts) {
         if (part.find(guidLiteral) == 0) {
             m_guid = part.substr(guidLiteral.length());
+        } else if (part.find(tcpHostLiteral) == 0) {
+            if (part.substr(tcpHostLiteral.length()) != "localhost") {
+                // only local connections are currently supported!
+                goto invalid;
+            }
+            m_socketType = SocketType::Ip;
+        } else if (part.find(portLiteral) == 0) {
+            string portStr = part.substr(portLiteral.length());
+            errno = 0;
+            m_port = strtol(portStr.c_str(), nullptr, 10);
+            if (errno) {
+                goto invalid;
+            }
         }
     }
 
     return;
 invalid:
+    // TODO introduce and call a clear() method
     m_socketType = SocketType::None;
     m_path.clear();
 }
diff --git a/buslogic/connectioninfo.h b/buslogic/connectioninfo.h
index 51f85ed..391f7e4 100644
--- a/buslogic/connectioninfo.h
+++ b/buslogic/connectioninfo.h
@@ -45,9 +45,13 @@ public:
     enum SocketType : unsigned char
     {
         None = 0,
+#ifdef __unix__
         Unix,
+#ifdef __linux__
         AbstractUnix,
-        Ip
+#endif
+#endif
+        Ip = 3
     };
 
     enum class Role : unsigned char
diff --git a/buslogic/transceiver.cpp b/buslogic/transceiver.cpp
index 966c203..f02096f 100644
--- a/buslogic/transceiver.cpp
+++ b/buslogic/transceiver.cpp
@@ -99,6 +99,7 @@ Transceiver::Transceiver(EventDispatcher *dispatcher, const \
ConnectionInfo &ci)  
     if (ci.bus() == ConnectionInfo::Bus::None || ci.socketType() == \
ConnectionInfo::SocketType::None ||  ci.role() == ConnectionInfo::Role::None) {
+        cerr << "\nTransceiver: connection constructor Exit A\n\n";
         return;
     }
 
@@ -136,9 +137,11 @@ Transceiver::Transceiver(EventDispatcher *dispatcher, CommRef \
mainTransceiverRef  
     d->m_mainThreadLink = std::move(mainTransceiverRef.commutex);
     CommutexLocker locker(&d->m_mainThreadLink);
+    assert(locker.hasLock());
     Commutex *const id = d->m_mainThreadLink.id();
     if (!id) {
         assert(false);
+        cerr << "\nTransceiver: slave constructor Exit A\n\n";
         return; // stay in Unconnected state
     }
 
diff --git a/connection/authnegotiator.cpp b/connection/authnegotiator.cpp
index 4946c44..dcaa297 100644
--- a/connection/authnegotiator.cpp
+++ b/connection/authnegotiator.cpp
@@ -30,25 +30,49 @@
 #include <cassert>
 #include <iostream>
 #include <sstream>
+
+#ifdef __unix__
 #include <sys/types.h>
 #include <unistd.h>
+#endif
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <sddl.h>
+#include "winutil.h"
+#endif
 
 using namespace std;
 
 AuthNegotiator::AuthNegotiator(IConnection *connection)
    : m_state(InitialState),
-     m_completionClient(0)
+     m_completionClient(nullptr)
 {
+    cerr << "AuthNegotiator constructing\n";
     connection->addClient(this);
     setReadNotificationEnabled(true);
     byte nullBuf[1] = { 0 };
     connection->write(chunk(nullBuf, 1));
 
-    // no idea why the uid is first encoded to ascii and the ascii to hex...
-    uid_t uid = geteuid();
-    stringstream uidDecimal;
-    uidDecimal << uid;
-    string extLine = "AUTH EXTERNAL " + hexEncode(uidDecimal.str()) + "\r\n";
+    stringstream uidEncoded;
+#ifdef _WIN32
+    // Most common (or rather... actually used) authentication method on Windows:
+    // - Server publishes address of a nonce file; the file name is in a shared \
memory segment +    // - Client reads nonce file
+    // - Client connects and sends the nonce data, TODO: before or after the null \
byte / is there a null byte? +    // - Client uses EXTERNAL auth and says which \
Windows security ID (SID) it intends to have +    uidEncoded << fetchWindowsSid();
+#else
+    // Most common (or rather... actually used) authentication method on Unix \
derivatives: +    // - Client sends a null byte so the server has something to \
receive with recvmsg() +    // - Server checks UID using SCM_CREDENTIALS, a mechanism \
of Unix local sockets +    // - Client uses EXTERNAL auth and says which Unix user ID \
it intends to have +
+    // The numeric UID is first encoded to ASCII ("1000") and the ASCII to hex... \
because. +    uidEncoded << geteuid();
+#endif
+    string extLine = "AUTH EXTERNAL " + hexEncode(uidEncoded.str()) + "\r\n";
     cout << extLine;
     connection->write(chunk(extLine.c_str(), extLine.length()));
     m_state = ExpectOkState;
@@ -107,7 +131,8 @@ bool AuthNegotiator::isEndOfLine() const
 
 void AuthNegotiator::advanceState()
 {
-    // TODO authentication ping-pong
+    // TODO authentication ping-pong done *properly* (grammar / some simple state \
machine), +    //      but hey, this works for now!
     // some findings:
     // - the string after the server OK is its UUID that also appears in the address \
string  
@@ -116,12 +141,14 @@ void AuthNegotiator::advanceState()
     switch (m_state) {
     case ExpectOkState: {
         // TODO check the OK
+#ifdef __unix__
         cstring negotiateLine("NEGOTIATE_UNIX_FD\r\n");
         cout << negotiateLine.ptr;
         connection()->write(chunk(negotiateLine.ptr, negotiateLine.length));
         m_state = ExpectUnixFdResponseState;
         break; }
     case ExpectUnixFdResponseState: {
+#endif
         // TODO check the response
         cstring beginLine("BEGIN\r\n");
         cout << beginLine.ptr;
diff --git a/connection/iconnection.cpp b/connection/iconnection.cpp
index bda5bd9..afe6bce 100644
--- a/connection/iconnection.cpp
+++ b/connection/iconnection.cpp
@@ -27,9 +27,12 @@
 #include "eventdispatcher_p.h"
 #include "iconnectionclient.h"
 #include "ipsocket.h"
-#include "localsocket.h"
 #include "connectioninfo.h"
 
+#ifdef __unix__
+#include "localsocket.h"
+#endif
+
 #include <algorithm>
 #include <cassert>
 
@@ -145,10 +148,12 @@ void IConnection::notifyWrite()
 IConnection *IConnection::create(const ConnectionInfo &ci)
 {
     switch (ci.socketType()) {
+#ifdef __unix__
         case ConnectionInfo::SocketType::Unix:
         return new LocalSocket(ci.path());
     case ConnectionInfo::SocketType::AbstractUnix:
         return new LocalSocket(string(1, '\0') + ci.path());
+#endif
     case ConnectionInfo::SocketType::Ip:
         return new IpSocket(ci);
     default:
diff --git a/connection/iconnection.h b/connection/iconnection.h
index b8dc95a..de7308a 100644
--- a/connection/iconnection.h
+++ b/connection/iconnection.h
@@ -33,6 +33,7 @@
 class ConnectionInfo;
 class EventDispatcher;
 class IConnectionClient;
+class SelectEventPoller;
 
 class IConnection : public IioEventClient
 {
@@ -68,6 +69,7 @@ protected:
 
 private:
     friend class IConnectionClient;
+    friend class SelectEventPoller;
     void updateReadWriteInterest(); // called internally and from IConnectionClient
 
     EventDispatcher *m_eventDispatcher;
diff --git a/connection/ipserver.cpp b/connection/ipserver.cpp
index 795ab69..87bf985 100644
--- a/connection/ipserver.cpp
+++ b/connection/ipserver.cpp
@@ -26,28 +26,37 @@
 #include "connectioninfo.h"
 
 #include "icompletionclient.h"
-#include "localsocket.h"
+#include "ipsocket.h"
 
-#include <fcntl.h>
-#include <sys/socket.h>
+#ifdef __unix__
 #include <netinet/in.h>
+#include <sys/socket.h>
+#include <fcntl.h>
 #include <unistd.h>
+#endif
+#ifdef _WIN32
+#include <winsock2.h>
+#endif
 
 #include <cassert>
 #include <cstring>
 
+#include <iostream>
+
 IpServer::IpServer(const ConnectionInfo &ci)
    : m_listenFd(-1)
 {
     assert(ci.socketType() == ConnectionInfo::SocketType::Ip);
 
-    const int fd = socket(AF_INET, SOCK_STREAM, 0);
-    if (fd < 0) {
+    const FileDescriptor fd = socket(AF_INET, SOCK_STREAM, 0);
+    if (!isValidFileDescriptor(fd)) {
+        std::cerr << "IpServer contruction failed A.\n";
         return;
     }
+#ifdef __unix__
     // don't let forks inherit the file descriptor - just in case
     fcntl(fd, F_SETFD, FD_CLOEXEC);
-
+#endif
     struct sockaddr_in addr;
     addr.sin_family = AF_INET;
     addr.sin_port = htons(ci.port());
@@ -59,7 +68,8 @@ IpServer::IpServer(const ConnectionInfo &ci)
     if (ok) {
         m_listenFd = fd;
     } else {
-#ifdef WINDOWS
+        std::cerr << "IpServer contruction failed B.\n";
+#ifdef _WIN32
         closesocket(fd);
 #else
         ::close(fd);
@@ -75,32 +85,40 @@ IpServer::~IpServer()
 void IpServer::notifyRead()
 {
     setEventDispatcher(nullptr);
-    int connFd = accept(m_listenFd, nullptr, nullptr);
-    if (connFd < 0) {
+    FileDescriptor connFd = accept(m_listenFd, nullptr, nullptr);
+    if (!isValidFileDescriptor(connFd)) {
+        std::cerr << "\nIpServer::notifyRead(): accept() failed.\n\n";
         return;
     }
+#ifdef __unix__
     fcntl(connFd, F_SETFD, FD_CLOEXEC);
-
-    m_incomingConnections.push_back(new LocalSocket(connFd));
+#endif
+    m_incomingConnections.push_back(new IpSocket(connFd));
     if (m_newConnectionClient) {
         m_newConnectionClient->notifyCompletion(this);
     }
 }
 
+void IpServer::notifyWrite()
+{
+    // We never registered this to be called, so...
+    assert(false);
+}
+
 bool IpServer::isListening() const
 {
-    return m_listenFd >= 0;
+    return isValidFileDescriptor(m_listenFd);
 }
 
 void IpServer::close()
 {
-    if (m_listenFd >= 0) {
-#ifdef WINDOWS
+    if (isValidFileDescriptor(m_listenFd)) {
+#ifdef _WIN32
         closesocket(m_listenFd);
 #else
         ::close(m_listenFd);
 #endif
-        m_listenFd = -1;
+        m_listenFd = InvalidFileDescriptor;
     }
 }
 
diff --git a/connection/ipserver.h b/connection/ipserver.h
index d341b14..1e94d09 100644
--- a/connection/ipserver.h
+++ b/connection/ipserver.h
@@ -43,9 +43,10 @@ public:
     FileDescriptor fileDescriptor() const override;
 
     void notifyRead() override;
+    void notifyWrite() override;
 
 private:
-    int m_listenFd;
+    FileDescriptor m_listenFd;
 };
 
 #endif // IPSERVER_H
diff --git a/connection/ipsocket.cpp b/connection/ipsocket.cpp
index 16d4556..347c6ee 100644
--- a/connection/ipsocket.cpp
+++ b/connection/ipsocket.cpp
@@ -25,18 +25,27 @@
 
 #include "connectioninfo.h"
 
-#include <errno.h>
-#include <fcntl.h>
+#ifdef __unix__
+#include <netinet/in.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
-#include <netinet/in.h>
+#include <fcntl.h>
 #include <unistd.h>
+#endif
+#ifdef _WIN32
+#include <winsock2.h>
+typedef SSIZE_T ssize_t;
+#endif
+
+#include <errno.h>
 
 #include <cassert>
 #include <climits>
 #include <cstdlib>
 #include <cstring>
 
+#include <iostream>
+
 // HACK, put this somewhere else (get the value from original d-bus? or is it \
infinite?)  static const int maxFds = 12;
 
@@ -46,57 +55,67 @@ IpSocket::IpSocket(const ConnectionInfo &ci)
    : m_fd(-1)
 {
     assert(ci.socketType() == ConnectionInfo::SocketType::Ip);
-#ifdef WINDOWS
+#ifdef _WIN32
     WSAData wsadata;
     // IPv6 requires Winsock v2.0 or better (but we're not using IPv6 - yet!)
     if (WSAStartup(MAKEWORD(2, 0), &wsadata) != 0) {
+        std::cerr << "IpSocket contruction failed A.\n";
         return;
     }
 #endif
-    const int fd = socket(AF_INET, SOCK_STREAM, 0);
-    if (fd < 0) {
+    const FileDescriptor fd = socket(AF_INET, SOCK_STREAM, 0);
+    if (!isValidFileDescriptor(fd)) {
+        std::cerr << "IpSocket contruction failed B.\n";
         return;
     }
-    // don't let forks inherit the file descriptor - that can cause confusion...
-    fcntl(fd, F_SETFD, FD_CLOEXEC);
 
-#ifdef WINDOWS
+    struct sockaddr_in addr;
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(ci.port());
+    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+    bool ok = connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0;
+
+    // only make it non-blocking after connect() because Winsock returns
+    // WSAEWOULDBLOCK when connecting a non-blocking socket
+#ifdef _WIN32
     unsigned long value = 1; // 0 blocking, != 0 non-blocking
     if (ioctlsocket(fd, FIONBIO, &value) != NO_ERROR) {
         // something along the lines of... WS_ERROR_DEBUG(WSAGetLastError());
+        std::cerr << "IpSocket contruction failed C.\n";
         closesocket(fd);
         return;
     }
 #else
+    // don't let forks inherit the file descriptor - that can cause confusion...
+    fcntl(fd, F_SETFD, FD_CLOEXEC);
+
     // To be able to use the same send() and recv() calls as Windows, also set the \
                non-blocking
     // property on the socket descriptor here instead of passing MSG_DONTWAIT to \
send() and recv().  const int oldFlags = fcntl(fd, F_GETFL);
     if (oldFlags == -1) {
         ::close(fd);
+        std::cerr << "IpSocket contruction failed D.\n";
         return;
     }
     fcntl(fd, F_SETFL, oldFlags & O_NONBLOCK);
 #endif
 
-    struct sockaddr_in addr;
-    addr.sin_family = AF_INET;
-    addr.sin_port = htons(ci.port());
-    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-
-    bool ok = connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0;
 
     if (ok) {
         m_fd = fd;
     } else {
-#ifdef WINDOWS
+#ifdef _WIN32
+        std::cerr << "IpSocket contruction failed E. Error is " << WSAGetLastError() \
<< ".\n";  closesocket(fd);
 #else
+        std::cerr << "IpSocket contruction failed E. Error is " << errno << ".\n";
         ::close(fd);
 #endif
     }
 }
 
-IpSocket::IpSocket(int fd)
+IpSocket::IpSocket(FileDescriptor fd)
    : m_fd(fd)
 {
 }
@@ -104,7 +123,7 @@ IpSocket::IpSocket(int fd)
 IpSocket::~IpSocket()
 {
     close();
-#ifdef WINDOWS
+#ifdef _WIN32
     WSACleanup();
 #endif
 }
@@ -112,20 +131,21 @@ IpSocket::~IpSocket()
 void IpSocket::close()
 {
     setEventDispatcher(nullptr);
-    if (m_fd >= 0) {
-#ifdef WINDOWS
+    if (isValidFileDescriptor(m_fd)) {
+#ifdef _WIN32
         closesocket(m_fd);
 #else
         ::close(m_fd);
 #endif
+        m_fd = InvalidFileDescriptor;
     }
-    m_fd = -1;
 }
 
 uint32 IpSocket::write(chunk a)
 {
-    if (m_fd < 0) {
-        return 0; // TODO -1?
+    if (!isValidFileDescriptor(m_fd)) {
+        std::cerr << "\nIpSocket::write() failed A.\n\n";
+        return 0; // TODO -1 and return int32?
     }
 
     const uint32 initialLength = a.length;
@@ -153,17 +173,23 @@ uint32 IpSocket::write(chunk a)
 
 uint32 IpSocket::availableBytesForReading()
 {
+#ifdef _WIN32
+    u_long available = 0;
+    if (ioctlsocket(m_fd, FIONREAD, &available) != NO_ERROR) {
+#else
     uint32 available = 0;
     if (ioctl(m_fd, FIONREAD, &available) < 0) {
+#endif
         available = 0;
     }
-    return available;
+    return uint32(available);
 }
 
 chunk IpSocket::read(byte *buffer, uint32 maxSize)
 {
     chunk ret;
     if (maxSize <= 0) {
+        std::cerr << "\nIpSocket::read() failed A.\n\n";
         return ret;
     }
 
@@ -192,10 +218,10 @@ chunk IpSocket::read(byte *buffer, uint32 maxSize)
 
 bool IpSocket::isOpen()
 {
-    return m_fd != -1;
+    return isValidFileDescriptor(m_fd);
 }
 
-int IpSocket::fileDescriptor() const
+FileDescriptor IpSocket::fileDescriptor() const
 {
     return m_fd;
 }
diff --git a/connection/ipsocket.h b/connection/ipsocket.h
index 8062ec9..1c2ece8 100644
--- a/connection/ipsocket.h
+++ b/connection/ipsocket.h
@@ -37,7 +37,7 @@ public:
     // Connect to local socket at socketFilePath
     IpSocket(const ConnectionInfo &ci);
     // Use an already open file descriptor
-    IpSocket(int fd);
+    IpSocket(FileDescriptor fd);
 
     ~IpSocket();
 
@@ -47,7 +47,7 @@ public:
     chunk read(byte *buffer, uint32 maxSize) override;
     void close() override;
     bool isOpen() override;
-    int fileDescriptor() const override;
+    FileDescriptor fileDescriptor() const override;
     void notifyRead() override;
     // end IConnection
 
@@ -59,7 +59,7 @@ private:
     friend class IEventLoop;
     friend class IConnectionListener;
 
-    int m_fd;
+    FileDescriptor m_fd;
 };
 
 #endif // IPSOCKET_H
diff --git a/connection/iserver.cpp b/connection/iserver.cpp
index 9c6511a..0fe237e 100644
--- a/connection/iserver.cpp
+++ b/connection/iserver.cpp
@@ -27,7 +27,9 @@
 #include "eventdispatcher_p.h"
 #include "iconnection.h"
 #include "ipserver.h"
+#ifdef __unix__
 #include "localserver.h"
+#endif
 
 #include <string>
 
@@ -52,10 +54,12 @@ IServer *IServer::create(const ConnectionInfo &ci)
     }
 
     switch (ci.socketType()) {
+#ifdef __unix__
     case ConnectionInfo::SocketType::Unix:
         return new LocalServer(ci.path());
     case ConnectionInfo::SocketType::AbstractUnix:
         return new LocalServer(std::string(1, '\0') + ci.path());
+#endif
     case ConnectionInfo::SocketType::Ip:
         return new IpServer(ci);
     default:
@@ -99,11 +103,3 @@ EventDispatcher *IServer::eventDispatcher() const
 {
     return m_eventDispatcher;
 }
-
-void IServer::notifyRead()
-{
-}
-
-void IServer::notifyWrite()
-{
-}
diff --git a/connection/iserver.h b/connection/iserver.h
index 2999a34..0d3ffca 100644
--- a/connection/iserver.h
+++ b/connection/iserver.h
@@ -55,9 +55,7 @@ public:
 
 protected:
     friend class EventDispatcher;
-    // IioEventClient; default implementations supplied because subclasses may only \
                need one of them
-    virtual void notifyRead() override;
-    virtual void notifyWrite() override;
+    // notifyRead() and notifyWrite() from IioEventClient stay pure virtual
 
     std::deque<IConnection *> m_incomingConnections;
     ICompletionClient *m_newConnectionClient;
diff --git a/connection/localserver.cpp b/connection/localserver.cpp
index 09b2f67..cf6b9c7 100644
--- a/connection/localserver.cpp
+++ b/connection/localserver.cpp
@@ -31,6 +31,7 @@
 #include <sys/un.h>
 #include <unistd.h>
 
+#include <cassert>
 #include <cstring>
 
 LocalServer::LocalServer(const std::string &socketFilePath)
@@ -84,6 +85,12 @@ void LocalServer::notifyRead()
     }
 }
 
+void LocalServer::notifyWrite()
+{
+    // We never registered this to be called, so...
+    assert(false);
+}
+
 bool LocalServer::isListening() const
 {
     return m_listenFd >= 0;
diff --git a/connection/localserver.h b/connection/localserver.h
index 9156d4c..4689119 100644
--- a/connection/localserver.h
+++ b/connection/localserver.h
@@ -31,6 +31,8 @@
 class LocalServer : public IServer
 {
 public:
+    // This is for now intended only for client to client connections, so UID (via \
SCM_CREDENTIALS) +    // is not checked - instead socketFilePath should only be \
accessible by the appropriate user(s).  LocalServer(const std::string \
&socketFilePath);  ~LocalServer();
 
@@ -41,6 +43,7 @@ public:
     FileDescriptor fileDescriptor() const override;
 
     void notifyRead() override;
+    void notifyWrite() override;
 
 private:
     int m_listenFd;
diff --git a/connection/localsocket.h b/connection/localsocket.h
index b194785..ad1a9fb 100644
--- a/connection/localsocket.h
+++ b/connection/localsocket.h
@@ -46,7 +46,7 @@ public:
     chunk read(byte *buffer, uint32 maxSize) override;
     void close() override;
     bool isOpen() override;
-    int fileDescriptor() const override;
+    FileDescriptor fileDescriptor() const override;
     void notifyRead() override;
     // end IConnection
 
diff --git a/connection/platform.h b/connection/platform.h
index d4e9728..191f559 100644
--- a/connection/platform.h
+++ b/connection/platform.h
@@ -24,8 +24,37 @@
 #ifndef PLATFORM_H
 #define PLATFORM_H
 
-// TODO different definitions on non-POSIX OS
+// Note about invalid descriptors: On Unix they are -1 signed, on Windows they are
+// ~0 unsigned. Same bit pattern, not directly interchangeable in use.
+
+#ifdef __unix__
 typedef int FileDescriptor;
-static const int invalidFileDescriptor = -1;
+#endif
+
+#ifdef _WIN32
+// this is ugly, but including all of winsock2.h in lots of headers is also ugly...
+#ifdef _WIN64
+typedef unsigned long long int FileDescriptor;
+#else
+typedef unsigned int FileDescriptor;
+#endif
+#endif
+
+enum InvalidFileDescriptorEnum : FileDescriptor {
+#ifdef _WIN32
+    InvalidFileDescriptor = ~ FileDescriptor(0)
+#else
+    InvalidFileDescriptor = -1
+#endif
+};
+
+static inline bool isValidFileDescriptor(FileDescriptor fd)
+{
+#ifdef _WIN32
+    return fd != InvalidFileDescriptor;
+#else
+    return fd >= 0;
+#endif
+}
 
 #endif // PLATFORM_H
diff --git a/connection/stringtools.cpp b/connection/stringtools.cpp
index fbfed69..2ac9c08 100644
--- a/connection/stringtools.cpp
+++ b/connection/stringtools.cpp
@@ -25,6 +25,8 @@
 
 #include <sstream>
 
+#include "sha1.c"
+
 using namespace std;
 
 vector<string> split(const string &s, char delimiter, bool keepEmptyParts)
@@ -44,8 +46,18 @@ string hexEncode(const string &s)
 {
     stringstream ss;
     for (size_t i = 0; i < s.length(); i++) {
-        const char c = s[i];
-        ss << std::hex << int(c >> 4) << int(c & 0xf);
+        const byte b = static_cast<byte>(s[i]);
+        ss << std::hex << uint(b >> 4) << uint(b & 0xf);
     }
     return ss.str();
 }
+
+string sha1Hex(const string &s)
+{
+    sha1nfo sha;
+    sha1_init(&sha);
+    sha1_write(&sha, s.c_str(), s.length());
+    // SHA-1 produces a 160 bits result, which is 20 bytes
+    string shaResult(reinterpret_cast<char *>(sha1_result(&sha)), 20);
+    return hexEncode(shaResult);
+}
\ No newline at end of file
diff --git a/connection/stringtools.h b/connection/stringtools.h
index 1e38bca..597fce9 100644
--- a/connection/stringtools.h
+++ b/connection/stringtools.h
@@ -32,6 +32,7 @@
 // export is for dferryclient library...
 std::vector<std::string> DFERRY_EXPORT split(const std::string &s, char delimiter, \
bool keepEmptyParts = true);  std::string hexEncode(const std::string &s);
+std::string sha1Hex(const std::string &s);
 
 inline std::string toStdString(cstring cstr)
 {
diff --git a/events/event.h b/events/event.h
index 6d4a6f7..c8deb59 100644
--- a/events/event.h
+++ b/events/event.h
@@ -18,7 +18,7 @@ struct Event
         SpontaneousMessageReceived,
         PendingReplySuccess,
         PendingReplyFailure,
-        PendingReplyCancel,
+        PendingReplyCancel, // 5
         MainTransceiverDisconnect,
         SecondaryTransceiverConnect,
         SecondaryTransceiverDisconnect,
diff --git a/events/eventdispatcher.cpp b/events/eventdispatcher.cpp
index a4b323b..3b6ccad 100644
--- a/events/eventdispatcher.cpp
+++ b/events/eventdispatcher.cpp
@@ -24,7 +24,14 @@
 #include "eventdispatcher.h"
 #include "eventdispatcher_p.h"
 
+#ifdef __linux__
 #include "epolleventpoller.h"
+#elif _WIN32
+#include "selecteventpoller_win32.h"
+#else
+#include "selecteventpoller_unix.h"
+#endif
+
 #include "event.h"
 #include "ieventpoller.h"
 #include "iioeventclient.h"
@@ -44,8 +51,12 @@ using namespace std;
 EventDispatcher::EventDispatcher()
    : d(new EventDispatcherPrivate)
 {
-    // TODO other backend on other platforms
+#ifdef __linux__
     d->m_poller = new EpollEventPoller(this);
+#else
+    // TODO high performance IO multiplexers for non-Linux platforms
+    d->m_poller = new SelectEventPoller(this);
+#endif
 }
 
 EventDispatcherPrivate::~EventDispatcherPrivate()
@@ -55,7 +66,7 @@ EventDispatcherPrivate::~EventDispatcherPrivate()
     }
 
     for (const pair<uint64 /* due */, Timer*> &dt : m_timers) {
-        dt.second->m_eventDispatcher = 0;
+        dt.second->m_eventDispatcher = nullptr;
         dt.second->m_isRunning = false;
     }
 
@@ -65,7 +76,7 @@ EventDispatcherPrivate::~EventDispatcherPrivate()
 EventDispatcher::~EventDispatcher()
 {
     delete d;
-    d = 0;
+    d = nullptr;
 }
 
 bool EventDispatcher::poll(int timeout)
@@ -74,7 +85,7 @@ bool EventDispatcher::poll(int timeout)
     if (timeout < 0) {
         timeout = nextDue;
     } else if (nextDue >= 0) {
-        timeout = std::min(timeout, nextDue);
+        timeout = min(timeout, nextDue);
     }
 
 #ifdef EVENTDISPATCHER_DEBUG
@@ -128,28 +139,31 @@ void \
EventDispatcherPrivate::setReadWriteInterest(IioEventClient *ioc, bool read  
 void EventDispatcherPrivate::notifyClientForReading(FileDescriptor fd)
 {
-    unordered_map<int, IioEventClient *>::iterator it = m_ioClients.find(fd);
+    unordered_map<FileDescriptor, IioEventClient *>::iterator it = \
m_ioClients.find(fd);  if (it != m_ioClients.end()) {
         it->second->notifyRead();
     } else {
+
 #ifdef IEVENTDISPATCHER_DEBUG
         // while interesting for debugging, this is not an error if a connection was \
                in the epoll
         // set and disconnected in its notifyRead() or notifyWrite() implementation
-        printf("EventDispatcher::notifyRead(): unhandled file descriptor %d.\n", \
fd); +        std::cerr << "EventDispatcherPrivate::notifyClientForReading(): \
unhandled file descriptor " +                  <<  fd << ".\n";
 #endif
     }
 }
 
 void EventDispatcherPrivate::notifyClientForWriting(FileDescriptor fd)
 {
-    unordered_map<int, IioEventClient *>::iterator it = m_ioClients.find(fd);
+    unordered_map<FileDescriptor, IioEventClient *>::iterator it = \
m_ioClients.find(fd);  if (it != m_ioClients.end()) {
         it->second->notifyWrite();
     } else {
 #ifdef IEVENTDISPATCHER_DEBUG
         // while interesting for debugging, this is not an error if a connection was \
                in the epoll
         // set and disconnected in its notifyRead() or notifyWrite() implementation
-        printf("EventDispatcher::notifyWrite(): unhandled file descriptor %d.\n", \
fd); +        std::cerr << "EventDispatcherPrivate::notifyClientForWriting(): \
unhandled file descriptor " +                  <<  fd << ".\n";
 #endif
     }
 }
diff --git a/events/eventdispatcher_p.h b/events/eventdispatcher_p.h
index 44b159b..eaa8b0a 100644
--- a/events/eventdispatcher_p.h
+++ b/events/eventdispatcher_p.h
@@ -77,7 +77,7 @@ public:
     void queueEvent(std::unique_ptr<Event> evt); // safe to call from any thread
     void processAuxEvents();
 
-    IEventPoller *m_poller;
+    IEventPoller *m_poller = nullptr;
     std::unordered_map<FileDescriptor, IioEventClient*> m_ioClients;
 
     static const int s_maxTimerSerial = 0x3ff; // 10 bits set
diff --git a/events/platformtime.cpp b/events/platformtime.cpp
index fcf18bb..711a583 100644
--- a/events/platformtime.cpp
+++ b/events/platformtime.cpp
@@ -23,19 +23,29 @@
 
 #include "platformtime.h"
 
+#ifdef _WIN32
+// GetTickCount64() requires Vista or greater which is NT version 0x0600
+#define _WIN32_WINNT 0x0600
+#define WIN32_LEAN_AND_MEAN
+#include "windows.h"
+#else
 #include <time.h>
+#endif
 
 namespace PlatformTime
 {
 
 uint64 monotonicMsecs()
 {
-    // POSIX implementation
+#ifdef _WIN32
+    return GetTickCount64();
+#else
     timespec tspec;
     // performance note: at least on Linux AMD64, clock_gettime(CLOCK_MONOTONIC) \
                does not (usually?)
     // make a syscall, so it's surprisingly cheap; presumably it uses some built-in \
CPU timer feature  clock_gettime(CLOCK_MONOTONIC, &tspec);
     return uint64(tspec.tv_sec) * 1000 + uint64(tspec.tv_nsec) / 1000000;
+#endif
 }
 
 }
diff --git a/events/selecteventpoller_unix.cpp b/events/selecteventpoller_unix.cpp
new file mode 100644
index 0000000..8147664
--- /dev/null
+++ b/events/selecteventpoller_unix.cpp
@@ -0,0 +1,163 @@
+/*
+   Copyright (C) 2015 Andreas Hartmetz <ahartmetz@gmail.com>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public
+   License as published by the Free Software Foundation; either
+   version 2 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LGPL.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+
+   Alternatively, this file is available under the Mozilla Public License
+   Version 1.1.  You may obtain a copy of the License at
+   http://www.mozilla.org/MPL/
+*/
+
+#include "selecteventpoller_unix.h"
+
+#include "eventdispatcher_p.h"
+#include "iconnection.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <cassert>
+#include <cstdio>
+
+
+SelectEventPoller::SelectEventPoller(EventDispatcher *dispatcher)
+   : IEventPoller(dispatcher)
+{
+    pipe2(m_interruptPipe, O_NONBLOCK);
+    resetFdSets();
+}
+
+SelectEventPoller::~SelectEventPoller()
+{
+    close(m_interruptPipe[0]);
+    close(m_interruptPipe[1]);
+}
+
+IEventPoller::InterruptAction SelectEventPoller::poll(int timeout)
+{
+    IEventPoller::InterruptAction ret = IEventPoller::NoInterrupt;
+
+    resetFdSets();
+
+    int nfds = m_interruptPipe[0];
+
+    // set up the interruption listener
+    FD_SET(m_interruptPipe[0], &m_readSet);
+
+    for (const auto &fdRw : m_fds) {
+        if (fdRw.second.readEnabled) {
+            nfds = std::max(nfds, fdRw.first);
+            FD_SET(fdRw.first, &m_readSet);
+        }
+        if (fdRw.second.writeEnabled) {
+            nfds = std::max(nfds, fdRw.first);
+            FD_SET(fdRw.first, &m_writeSet);
+        }
+    }
+
+    struct timeval tv;
+    struct timeval *tvPointer = nullptr;
+    if (timeout >= 0) {
+        tv.tv_sec = timeout / 1000;
+        tv.tv_usec = (timeout % 1000) * 1000;
+        tvPointer = &tv;
+    }
+
+    // select!
+    int numEvents = select(nfds + 1, &m_readSet, &m_writeSet, nullptr, tvPointer);
+
+    // check for interruption
+    if (FD_ISSET(m_interruptPipe[0], &m_readSet)) {
+        // interrupt; read bytes from pipe to clear buffers and get the interrupt \
type +        ret = IEventPoller::ProcessAuxEvents;
+        char buf;
+        while (read(m_interruptPipe[0], &buf, 1) > 0) {
+            if (buf == 'S') {
+                ret = IEventPoller::Stop;
+            }
+        }
+    }
+
+    if (ret == IEventPoller::Stop) {
+        // ### discarding the rest of the events, to avoid touching "dead" data \
while shutting down +        numEvents = 0;
+    }
+
+    // dispatch reads and writes
+    if (numEvents < 0) {
+        // TODO error handling
+    } else if (numEvents > 0) {
+        for (auto fdClient : EventDispatcherPrivate::get(m_dispatcher)->m_ioClients) \
{ +            if (FD_ISSET(fdClient.first, &m_readSet)) {
+                EventDispatcherPrivate::get(m_dispatcher)->notifyClientForReading(fdClient.first);
 +                numEvents--;
+            }
+            if (FD_ISSET(fdClient.first, &m_writeSet)) {
+                EventDispatcherPrivate::get(m_dispatcher)->notifyClientForWriting(fdClient.first);
 +                numEvents--;
+            }
+            if (numEvents <= 0) {
+                break;
+            }
+        }
+    }
+
+    return ret;
+}
+
+void SelectEventPoller::resetFdSets()
+{
+    FD_ZERO(&m_readSet);
+    FD_ZERO(&m_writeSet);
+}
+
+void SelectEventPoller::interrupt(IEventPoller::InterruptAction action)
+{
+    assert(action == IEventPoller::ProcessAuxEvents || action == \
IEventPoller::Stop); +    // write a byte to the write end so the poll waiting on the \
read end returns +    char buf = (action == IEventPoller::Stop) ? 'S' : 'N';
+    write(m_interruptPipe[1], &buf, 1);
+}
+
+FileDescriptor SelectEventPoller::pollDescriptor() const
+{
+    return -1; // HACK! what are we going to do about this?
+}
+
+void SelectEventPoller::addIoEventClient(IioEventClient *ioc)
+{
+    // The main select specific part of registration is in setReadWriteInterest().
+    // Here we just check fd limits.
+    if (ioc->fileDescriptor() >= FD_SETSIZE) {
+        // TODO error...
+        return;
+    }
+
+    RwEnabled rw = { false, false };
+    m_fds.emplace(ioc->fileDescriptor(), rw);
+}
+
+void SelectEventPoller::removeIoEventClient(IioEventClient *ioc)
+{
+    m_fds.erase(ioc->fileDescriptor());
+}
+
+void SelectEventPoller::setReadWriteInterest(IioEventClient *ioc, bool read, bool \
write) +{
+    RwEnabled &rw = m_fds.at(ioc->fileDescriptor());
+    rw.readEnabled = read;
+    rw.writeEnabled = write;
+}
diff --git a/events/selecteventpoller_unix.h b/events/selecteventpoller_unix.h
new file mode 100644
index 0000000..932558a
--- /dev/null
+++ b/events/selecteventpoller_unix.h
@@ -0,0 +1,72 @@
+/*
+   Copyright (C) 2015 Andreas Hartmetz <ahartmetz@gmail.com>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public
+   License as published by the Free Software Foundation; either
+   version 2 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LGPL.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+
+   Alternatively, this file is available under the Mozilla Public License
+   Version 1.1.  You may obtain a copy of the License at
+   http://www.mozilla.org/MPL/
+*/
+
+#ifndef SELECTEVENTPOLLER_H
+#define SELECTEVENTPOLLER_H
+
+#include "ieventpoller.h"
+
+#include <unordered_map>
+#include <vector>
+
+#include <sys/select.h>
+
+class SelectEventPoller : public IEventPoller
+{
+public:
+    SelectEventPoller(EventDispatcher *dispatcher);
+    ~SelectEventPoller();
+    IEventPoller::InterruptAction poll(int timeout) override;
+    void interrupt(IEventPoller::InterruptAction) override;
+
+    // TODO figure out how to handle plugging into other event loops in the general \
case; +    //      there seems to be some single-fd mechanism available on most \
platforms and where +    //      there isn't, a list of descriptors (propagate only \
changes?) should work +    FileDescriptor pollDescriptor() const;
+
+    // reimplemented from IEventPoller
+    void addIoEventClient(IioEventClient *ioc) override;
+    void removeIoEventClient(IioEventClient *ioc) override;
+    void setReadWriteInterest(IioEventClient *ioc, bool read, bool write) override;
+
+private:
+    void notifyRead(int fd);
+    void resetFdSets();
+
+    struct RwEnabled {
+        bool readEnabled : 1;
+        bool writeEnabled : 1;
+    };
+
+    std::unordered_map<FileDescriptor, RwEnabled> m_fds;
+
+    std::vector<int> m_readFds;
+    std::vector<int> m_writeFds;
+
+    fd_set m_readSet;
+    fd_set m_writeSet;
+
+    int m_interruptPipe[2];
+};
+
+#endif // SELECTEVENTPOLLER_H
diff --git a/events/selecteventpoller_win32.cpp b/events/selecteventpoller_win32.cpp
new file mode 100644
index 0000000..f641e16
--- /dev/null
+++ b/events/selecteventpoller_win32.cpp
@@ -0,0 +1,254 @@
+/*
+   Copyright (C) 2015 Andreas Hartmetz <ahartmetz@gmail.com>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public
+   License as published by the Free Software Foundation; either
+   version 2 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LGPL.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+
+   Alternatively, this file is available under the Mozilla Public License
+   Version 1.1.  You may obtain a copy of the License at
+   http://www.mozilla.org/MPL/
+*/
+
+#include "selecteventpoller_win32.h"
+
+#include "eventdispatcher_p.h"
+#include "iconnection.h"
+
+#include <iostream>
+#include <thread>
+
+#include <cassert>
+#include <cstdio>
+
+thread_local static SelectEventPoller *tls_selectPoller = nullptr;
+
+static SOCKET createInterruptSocket()
+{
+    SOCKET ret = socket(AF_INET, SOCK_DGRAM, 0);
+    if (ret == INVALID_SOCKET) {
+        std::cerr << "createInterruptSocket() Error A.\n";
+        return ret;
+    }
+    unsigned long value = 1; // 0 blocking, != 0 non-blocking
+    if (ioctlsocket(ret, FIONBIO, &value) != NO_ERROR) {
+        // something along the lines of... WS_ERROR_DEBUG(WSAGetLastError());
+        std::cerr << "createInterruptSocket() Error B.\n";
+        closesocket(ret);
+        return INVALID_SOCKET;
+    }
+    return ret;
+}
+
+VOID CALLBACK triggerInterruptSocket(ULONG_PTR dwParam)
+{
+    SelectEventPoller *const sep = tls_selectPoller;
+    if (sep) {
+        sep->doInterruptSocket(dwParam != 0);
+    } else {
+        std::cerr << "triggerInterruptSocket() ignoring (apparently) spurios \
APC!\n"; +    }
+}
+
+void SelectEventPoller::doInterruptSocket(bool isStop)
+{
+    const IEventPoller::InterruptAction newAction = isStop ? IEventPoller::Stop
+                                                           : \
IEventPoller::ProcessAuxEvents; +    if (newAction > m_interruptAction) {
+        m_interruptAction = newAction;
+    }
+
+    // This runs from the blocked select(). Signal the socket by closing it, thus \
properly interrupting +    // the select().
+
+    if (m_interruptSocket != INVALID_SOCKET) {
+        // closesocket() may enter an alertable walt, which may run APCs, which may \
call us *again*. +        // Prevent that by clearing m_interruptSocket *before* \
possibly triggering the APC. +        SOCKET sock = m_interruptSocket;
+        m_interruptSocket = INVALID_SOCKET;
+        closesocket(sock); // <- recursion here
+    }
+}
+
+SelectEventPoller::SelectEventPoller(EventDispatcher *dispatcher)
+   : IEventPoller(dispatcher),
+     m_selectThreadHandle(INVALID_HANDLE_VALUE),
+     m_interruptSocket(INVALID_SOCKET),
+     m_interruptAction(IEventPoller::NoInterrupt)
+{
+    // IFF there is still an asynchronous procedure call queued for this thread \
(which usually happens +    // in the mean thread), we want it to trigger nothing, in \
case any Winsock functions (say socket())... +    // enters an alertable wait.
+    tls_selectPoller = nullptr;
+    if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), \
GetCurrentProcess(), +                         &m_selectThreadHandle,
+                         0, FALSE, DUPLICATE_SAME_ACCESS)) {
+          assert(false); // H4X, gross!
+    }
+
+    // XXX TOXIC BUG!! m_selectThreadHandle = GetCurrentThread();
+
+    WSAData wsadata;
+    // IPv6 requires Winsock v2.0 or better (but we're not using IPv6 - yet!)
+    if (WSAStartup(MAKEWORD(2, 0), &wsadata) != 0) {
+        return;
+    }
+
+    m_interruptSocket = createInterruptSocket();
+    // stupid: flush any pending APCs from previous instances; closesocket() will do \
a brief alertable +    // wait, apparently.
+    closesocket(m_interruptSocket);
+
+    m_interruptSocket = createInterruptSocket();
+
+    tls_selectPoller = this;
+}
+
+SelectEventPoller::~SelectEventPoller()
+{
+    tls_selectPoller = nullptr;
+    if (m_interruptSocket != INVALID_SOCKET) {
+        closesocket(m_interruptSocket);
+    }
+    CloseHandle(m_selectThreadHandle);
+}
+
+IEventPoller::InterruptAction SelectEventPoller::poll(int timeout)
+{
+    // Check if some other code called an alertable waiting function which ran our \
user APC, +    // which closed m_interruptSocket and set m_interruptAction. Process \
it here if so. +    IEventPoller::InterruptAction ret = m_interruptAction;
+    if (ret != IEventPoller::NoInterrupt) {
+        assert(m_interruptSocket == INVALID_SOCKET);
+        m_interruptAction = IEventPoller::NoInterrupt;
+        m_interruptSocket = createInterruptSocket(); // re-arm
+        return ret;
+    }
+
+    resetFdSets();
+
+    // ### doing FD_SET "manually", avoiding a scan of the whole list for each set \
action - there is +    //     no danger of duplicates because our input is a set \
which already guarantees uniqueness. +    for (const auto &fdRw : m_fds) {
+        if (fdRw.second.readEnabled) {
+            // FD_SET(fdRw.first, &m_readSet);
+            if (m_readSet.fd_count < FD_SETSIZE) {
+                m_readSet.fd_array[m_readSet.fd_count++] = fdRw.first;
+            }
+        }
+        if (fdRw.second.writeEnabled) {
+            // FD_SET(fdRw.first, &m_writeSet);
+            if (m_writeSet.fd_count < FD_SETSIZE) {
+                m_writeSet.fd_array[m_writeSet.fd_count++] = fdRw.first;
+            }
+        }
+    }
+
+    if (m_interruptSocket == INVALID_SOCKET) {
+        assert(m_interruptAction != IEventPoller::NoInterrupt);
+        m_interruptSocket = createInterruptSocket();
+    }
+    FD_SET(m_interruptSocket, &m_exceptSet);
+
+    struct timeval tv;
+    struct timeval *tvPointer = nullptr;
+    if (timeout >= 0) {
+        tv.tv_sec = timeout / 1000;
+        tv.tv_usec = (timeout % 1000) * 1000;
+        tvPointer = &tv;
+    }
+
+    // select!
+    int numEvents = select(0, &m_readSet, &m_writeSet, &m_exceptSet, tvPointer);
+    if (numEvents == -1) {
+        std::cerr << "Error code is " << WSAGetLastError() << " and except set has "
+                  << m_exceptSet.fd_count << " elements.\n";
+    }
+
+    // check for interruption
+    ret = m_interruptAction;
+    if (ret != IEventPoller::NoInterrupt) {
+        assert(m_interruptSocket == INVALID_SOCKET);
+        m_interruptAction = IEventPoller::NoInterrupt;
+        m_interruptSocket = createInterruptSocket(); // re-arm
+        //numEvents--; // 1) We got here because the interrupt socket's exceptfd \
became active. +        //                2) However, fds in the except set don't \
count as "sockets /ready/" for +        //                   the return value of \
select(). +        return ret;
+    }
+
+    // dispatch reads and writes
+    if (numEvents < 0) {
+        // TODO error handling ?
+    }
+
+    // This being Windows-specfic code, and with Windows's famous binary \
compatibility, we may +    // as well exploit that the Windows fd_set struct allows \
for relatively efficient querying +    // if you just iterate over its internal list, \
instead of searching the list for each file +    // descriptor like with FD_ISSET.
+    // numEvents -= m_readSet.fd_count + m_writeSet.fd_count;
+    for (uint i = 0; i < m_readSet.fd_count; i++) {
+        EventDispatcherPrivate::get(m_dispatcher)->notifyClientForReading(m_readSet.fd_array[i]);
 +    }
+    for (uint i = 0; i < m_writeSet.fd_count; i++) {
+        EventDispatcherPrivate::get(m_dispatcher)->notifyClientForWriting(m_writeSet.fd_array[i]);
 +    }
+
+    return ret;
+}
+
+void SelectEventPoller::resetFdSets()
+{
+    FD_ZERO(&m_readSet);
+    FD_ZERO(&m_writeSet);
+    FD_ZERO(&m_exceptSet);
+}
+
+void SelectEventPoller::interrupt(IEventPoller::InterruptAction action)
+{
+    assert(action == IEventPoller::ProcessAuxEvents || action == \
IEventPoller::Stop); +    const ULONG_PTR dwParam = action == IEventPoller::Stop ? \
0x1 : 0x0; +    QueueUserAPC(triggerInterruptSocket, m_selectThreadHandle, dwParam);
+}
+
+FileDescriptor SelectEventPoller::pollDescriptor() const
+{
+    return -1; // HACK! what are we going to do about this?
+}
+
+void SelectEventPoller::addIoEventClient(IioEventClient *ioc)
+{
+    // The main select specific part of registration is in setReadWriteInterest().
+    // Here we just check fd limits.
+    if (m_fds.size() + 1 >= FD_SETSIZE) {
+        std::cerr << "SelectEventPoller::addIoEventClient() failed A.\n";
+        // TODO error...
+        return;
+    }
+
+    RwEnabled rw = { false, false };
+    m_fds.emplace(ioc->fileDescriptor(), rw);
+}
+
+void SelectEventPoller::removeIoEventClient(IioEventClient *ioc)
+{
+    m_fds.erase(ioc->fileDescriptor());
+}
+
+void SelectEventPoller::setReadWriteInterest(IioEventClient *ioc, bool read, bool \
write) +{
+    RwEnabled &rw = m_fds.at(ioc->fileDescriptor());
+    rw.readEnabled = read;
+    rw.writeEnabled = write;
+}
diff --git a/events/selecteventpoller_win32.h b/events/selecteventpoller_win32.h
new file mode 100644
index 0000000..df3fd86
--- /dev/null
+++ b/events/selecteventpoller_win32.h
@@ -0,0 +1,86 @@
+/*
+   Copyright (C) 2015 Andreas Hartmetz <ahartmetz@gmail.com>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public
+   License as published by the Free Software Foundation; either
+   version 2 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LGPL.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+
+   Alternatively, this file is available under the Mozilla Public License
+   Version 1.1.  You may obtain a copy of the License at
+   http://www.mozilla.org/MPL/
+*/
+
+#ifndef SELECTEVENTPOLLER_H
+#define SELECTEVENTPOLLER_H
+
+#include "ieventpoller.h"
+
+#include <unordered_map>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#ifdef FD_SETSIZE
+#error We must be able to set FD_SETSIZE - make sure that nothing else sets it!
+#endif
+#define FD_SETSIZE 1024
+#include <winsock2.h>
+
+// Windows select() diverges from "proper Unix" select() just enough to seriously \
hurt readability +// when handling the differences with ifdefs, so use a separate \
implementation. +// Besides, the fd_set from winsock2.h is actually an array of \
sockets (not a bitmap like on Unix), which +// can be exploited to achive poll()-like \
performance characteristics without dealing with the problems +// that WSAPoll() has. \
That is currently not implemented. +
+class SelectEventPoller : public IEventPoller
+{
+public:
+    SelectEventPoller(EventDispatcher *dispatcher);
+    ~SelectEventPoller();
+    IEventPoller::InterruptAction poll(int timeout) override;
+    void interrupt(IEventPoller::InterruptAction) override;
+
+    // TODO figure out how to handle plugging into other event loops in the general \
case; +    //      there seems to be some single-fd mechanism available on most \
platforms and where +    //      there isn't, a list of descriptors (propagate only \
changes?) should work +    FileDescriptor pollDescriptor() const;
+
+    // reimplemented from IEventPoller
+    void addIoEventClient(IioEventClient *ioc) override;
+    void removeIoEventClient(IioEventClient *ioc) override;
+    void setReadWriteInterest(IioEventClient *ioc, bool read, bool write) override;
+
+private:
+    void notifyRead(int fd);
+    void resetFdSets();
+
+    friend VOID CALLBACK triggerInterruptSocket(ULONG_PTR dwParam);
+    void doInterruptSocket(bool isStop);
+
+    HANDLE m_selectThreadHandle;
+    FileDescriptor m_interruptSocket;
+    IEventPoller::InterruptAction m_interruptAction;
+
+    struct RwEnabled {
+        bool readEnabled : 1;
+        bool writeEnabled : 1;
+    };
+
+    std::unordered_map<FileDescriptor, RwEnabled> m_fds;
+
+    fd_set m_readSet;
+    fd_set m_writeSet;
+    fd_set m_exceptSet;
+};
+
+#endif // SELECTEVENTPOLLER_H
diff --git a/serialization/arguments.h b/serialization/arguments.h
index dd89e62..2cda29c 100644
--- a/serialization/arguments.h
+++ b/serialization/arguments.h
@@ -148,7 +148,7 @@ public:
 
     // error handling is done by asking state() or isError(), not by method return \
                values.
     // occasionally looking at isError() is less work than checking every call.
-    class Reader
+    class DFERRY_EXPORT Reader
     {
     public:
         explicit Reader(const Arguments &al);
@@ -258,7 +258,7 @@ public:
         DataUnion m_u;
     };
 
-    class Writer
+    class DFERRY_EXPORT Writer
     {
     public:
         explicit Writer();
diff --git a/serialization/basictypeio.h b/serialization/basictypeio.h
index 6f8f8ef..3367567 100644
--- a/serialization/basictypeio.h
+++ b/serialization/basictypeio.h
@@ -26,6 +26,8 @@
 
 #include "types.h"
 
+#include <algorithm> // for std::min on Windows...
+
 static inline uint32 align(uint32 index, uint32 alignment)
 {
     const uint32 maxStepUp = alignment - 1;
diff --git a/serialization/message.cpp b/serialization/message.cpp
index dfdcf0e..96ef270 100644
--- a/serialization/message.cpp
+++ b/serialization/message.cpp
@@ -35,6 +35,8 @@
 #include <sstream>
 #include <thread>
 
+#include <iostream>
+
 using namespace std;
 
 #ifdef BIGENDIAN
@@ -625,6 +627,7 @@ void Message::setExpectsReply(bool expectsReply)
 void MessagePrivate::receive(IConnection *conn)
 {
     if (m_state > LastSteadyState) {
+        std::cerr << "MessagePrivate::receive() Error A.\n";
         return;
     }
     conn->addClient(this);
@@ -642,13 +645,14 @@ bool Message::isReceiving() const
 void MessagePrivate::send(IConnection *conn)
 {
     if (!m_buffer.length && !serialize()) {
-
+        std::cerr << "MessagePrivate::send() Error A.\n";
         // m_error.setCode();
         // notifyCompletionClient(); would call into Transceiver, but it's easer for \
                Transceiver to handle
         //                           the error from non-callback code, directly in \
the caller of send().  return;
     }
     if (m_state > MessagePrivate::LastSteadyState) {
+        std::cerr << "MessagePrivate::send() Error B.\n";
         // TODO error feedback
         return;
     }
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index a0f2334..19d0e45 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -7,6 +7,7 @@ set(TESTUTIL_HEADERS
     testutil.h)
 
 add_library(testutil SHARED ${TESTUTIL_SOURCES} ${TESTUTIL_HEADERS})
+target_compile_definitions(testutil PRIVATE -DBUILDING_TESTUTIL)
 
 add_subdirectory(buslogic)
 add_subdirectory(client)
diff --git a/tests/buslogic/CMakeLists.txt b/tests/buslogic/CMakeLists.txt
index 316d909..1c8f088 100644
--- a/tests/buslogic/CMakeLists.txt
+++ b/tests/buslogic/CMakeLists.txt
@@ -10,4 +10,6 @@ ENDMACRO(BUSLOGICTESTS)
 
 BUSLOGICTESTS(pendingreply threads)
 
-target_link_libraries(tst_threads pthread)
+if (UNIX)
+    target_link_libraries(tst_threads pthread)
+endif()
diff --git a/tests/serialization/tst_message.cpp \
b/tests/serialization/tst_message.cpp index 2b677c8..bae2a48 100644
--- a/tests/serialization/tst_message.cpp
+++ b/tests/serialization/tst_message.cpp
@@ -91,6 +91,7 @@ void testBasic(const ConnectionInfo &clientConnection)
 
 int main(int, char *[])
 {
+#ifdef __linux__
     {
         ConnectionInfo clientConnection(ConnectionInfo::Bus::PeerToPeer);
         clientConnection.setSocketType(ConnectionInfo::SocketType::AbstractUnix);
@@ -98,6 +99,8 @@ int main(int, char *[])
         clientConnection.setPath("dferry.Test.Message");
         testBasic(clientConnection);
     }
+#endif
+    // TODO: SocketType::Unix works on any Unix-compatible OS, but we'll need to \
construct a path  {
         ConnectionInfo clientConnection(ConnectionInfo::Bus::PeerToPeer);
         clientConnection.setSocketType(ConnectionInfo::SocketType::Ip);
diff --git a/tests/testutil.h b/tests/testutil.h
index a3b97f3..0a8212a 100644
--- a/tests/testutil.h
+++ b/tests/testutil.h
@@ -21,12 +21,28 @@
    http://www.mozilla.org/MPL/
 */
 
-#include "export.h"
-
 #include <cstdio>
 #include <cstdlib>
 
+#ifdef _WIN32
+
+#ifdef BUILDING_TESTUTIL
+#define TESTUTIL_EXPORT __declspec(dllexport)
+#else
+#define TESTUTIL_EXPORT __declspec(dllimport)
+#endif
+
+#else
+
+#ifdef BUILDING_TESTUTIL
+#define TESTUTIL_EXPORT __attribute__ ((visibility ("protected")))
+#else
+#define TESTUTIL_EXPORT __attribute__ ((visibility ("default")))
+#endif
+
+#endif // _WIN32
+
 static inline void test_no_op() {}
-void DFERRY_EXPORT test_fail(const char *cond, const char *file, int line);
+void TESTUTIL_EXPORT test_fail(const char *cond, const char *file, int line);
 
 #define TEST(cond) (!(cond) ? test_fail(#cond, __FILE__, __LINE__) : test_no_op())
diff --git a/util/export.h b/util/export.h
index f1f16f8..2f418c9 100644
--- a/util/export.h
+++ b/util/export.h
@@ -21,6 +21,22 @@
    http://www.mozilla.org/MPL/
 */
 
+#ifdef _WIN32
+
+#ifdef BUILDING_LIBDFER
+#define DFERRY_EXPORT __declspec(dllexport)
+#else
+#define DFERRY_EXPORT __declspec(dllimport)
+#endif
+
+#ifdef BUILDING_LIBDFERCLIENT
+#define DFERRYCLIENT_EXPORT __declspec(dllexport)
+#else
+#define DFERRYCLIENT_EXPORT __declspec(dllimport)
+#endif
+
+#else
+
 #ifdef BUILDING_LIBDFER
 #define DFERRY_EXPORT __attribute__ ((visibility ("protected")))
 #else
@@ -32,3 +48,5 @@
 #else
 #define DFERRYCLIENT_EXPORT __attribute__ ((visibility ("default")))
 #endif
+
+#endif // _WIN32
\ No newline at end of file
diff --git a/util/sha1.c b/util/sha1.c
new file mode 100644
index 0000000..196bf47
--- /dev/null
+++ b/util/sha1.c
@@ -0,0 +1,313 @@
+/* This code is public-domain - it is based on libcrypt
+ * placed in the public domain by Wei Dai and other contributors.
+ */
+// gcc -Wall -DSHA1TEST -o sha1test sha1.c && ./sha1test
+
+#include <stdint.h>
+#include <string.h>
+
+/* This is the only section changed for dferry - apparently our endianness macro is \
exotic */ +#ifdef BIGENDIAN
+#define SHA_BIG_ENDIAN
+#endif
+
+/* header */
+
+#define HASH_LENGTH 20
+#define BLOCK_LENGTH 64
+
+typedef struct sha1nfo {
+	uint32_t buffer[BLOCK_LENGTH/4];
+	uint32_t state[HASH_LENGTH/4];
+	uint32_t byteCount;
+	uint8_t bufferOffset;
+	uint8_t keyBuffer[BLOCK_LENGTH];
+	uint8_t innerHash[HASH_LENGTH];
+} sha1nfo;
+
+/* public API - prototypes - TODO: doxygen*/
+
+/**
+ */
+void sha1_init(sha1nfo *s);
+/**
+ */
+void sha1_writebyte(sha1nfo *s, uint8_t data);
+/**
+ */
+void sha1_write(sha1nfo *s, const char *data, size_t len);
+/**
+ */
+uint8_t* sha1_result(sha1nfo *s);
+/**
+ */
+void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength);
+/**
+ */
+uint8_t* sha1_resultHmac(sha1nfo *s);
+
+
+/* code */
+#define SHA1_K0  0x5a827999
+#define SHA1_K20 0x6ed9eba1
+#define SHA1_K40 0x8f1bbcdc
+#define SHA1_K60 0xca62c1d6
+
+void sha1_init(sha1nfo *s) {
+	s->state[0] = 0x67452301;
+	s->state[1] = 0xefcdab89;
+	s->state[2] = 0x98badcfe;
+	s->state[3] = 0x10325476;
+	s->state[4] = 0xc3d2e1f0;
+	s->byteCount = 0;
+	s->bufferOffset = 0;
+}
+
+uint32_t sha1_rol32(uint32_t number, uint8_t bits) {
+	return ((number << bits) | (number >> (32-bits)));
+}
+
+void sha1_hashBlock(sha1nfo *s) {
+	uint8_t i;
+	uint32_t a,b,c,d,e,t;
+
+	a=s->state[0];
+	b=s->state[1];
+	c=s->state[2];
+	d=s->state[3];
+	e=s->state[4];
+	for (i=0; i<80; i++) {
+		if (i>=16) {
+			t = s->buffer[(i+13)&15] ^ s->buffer[(i+8)&15] ^ s->buffer[(i+2)&15] ^ \
s->buffer[i&15]; +			s->buffer[i&15] = sha1_rol32(t,1);
+		}
+		if (i<20) {
+			t = (d ^ (b & (c ^ d))) + SHA1_K0;
+		} else if (i<40) {
+			t = (b ^ c ^ d) + SHA1_K20;
+		} else if (i<60) {
+			t = ((b & c) | (d & (b | c))) + SHA1_K40;
+		} else {
+			t = (b ^ c ^ d) + SHA1_K60;
+		}
+		t+=sha1_rol32(a,5) + e + s->buffer[i&15];
+		e=d;
+		d=c;
+		c=sha1_rol32(b,30);
+		b=a;
+		a=t;
+	}
+	s->state[0] += a;
+	s->state[1] += b;
+	s->state[2] += c;
+	s->state[3] += d;
+	s->state[4] += e;
+}
+
+void sha1_addUncounted(sha1nfo *s, uint8_t data) {
+	uint8_t * const b = (uint8_t*) s->buffer;
+#ifdef SHA_BIG_ENDIAN
+	b[s->bufferOffset] = data;
+#else
+	b[s->bufferOffset ^ 3] = data;
+#endif
+	s->bufferOffset++;
+	if (s->bufferOffset == BLOCK_LENGTH) {
+		sha1_hashBlock(s);
+		s->bufferOffset = 0;
+	}
+}
+
+void sha1_writebyte(sha1nfo *s, uint8_t data) {
+	++s->byteCount;
+	sha1_addUncounted(s, data);
+}
+
+void sha1_write(sha1nfo *s, const char *data, size_t len) {
+	for (;len--;) sha1_writebyte(s, (uint8_t) *data++);
+}
+
+void sha1_pad(sha1nfo *s) {
+	// Implement SHA-1 padding (fips180-2  §5.1.1)
+
+	// Pad with 0x80 followed by 0x00 until the end of the block
+	sha1_addUncounted(s, 0x80);
+	while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00);
+
+	// Append length in the last 8 bytes
+	sha1_addUncounted(s, 0); // We're only using 32 bit lengths
+	sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths
+	sha1_addUncounted(s, 0); // So zero pad the top bits
+	sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8
+	sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well \
as +	sha1_addUncounted(s, s->byteCount >> 13); // byte.
+	sha1_addUncounted(s, s->byteCount >> 5);
+	sha1_addUncounted(s, s->byteCount << 3);
+}
+
+uint8_t* sha1_result(sha1nfo *s) {
+	// Pad to complete the last block
+	sha1_pad(s);
+
+#ifndef SHA_BIG_ENDIAN
+	// Swap byte order back
+	int i;
+	for (i=0; i<5; i++) {
+		s->state[i]=
+			  (((s->state[i])<<24)& 0xff000000)
+			| (((s->state[i])<<8) & 0x00ff0000)
+			| (((s->state[i])>>8) & 0x0000ff00)
+			| (((s->state[i])>>24)& 0x000000ff);
+	}
+#endif
+
+	// Return pointer to hash (20 characters)
+	return (uint8_t*) s->state;
+}
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5c
+
+void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) {
+	uint8_t i;
+	memset(s->keyBuffer, 0, BLOCK_LENGTH);
+	if (keyLength > BLOCK_LENGTH) {
+		// Hash long keys
+		sha1_init(s);
+		for (;keyLength--;) sha1_writebyte(s, *key++);
+		memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH);
+	} else {
+		// Block length keys are used as is
+		memcpy(s->keyBuffer, key, keyLength);
+	}
+	// Start inner hash
+	sha1_init(s);
+	for (i=0; i<BLOCK_LENGTH; i++) {
+		sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD);
+	}
+}
+
+uint8_t* sha1_resultHmac(sha1nfo *s) {
+	uint8_t i;
+	// Complete inner hash
+	memcpy(s->innerHash,sha1_result(s),HASH_LENGTH);
+	// Calculate outer hash
+	sha1_init(s);
+	for (i=0; i<BLOCK_LENGTH; i++) sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD);
+	for (i=0; i<HASH_LENGTH; i++) sha1_writebyte(s, s->innerHash[i]);
+	return sha1_result(s);
+}
+
+/* self-test */
+
+#if SHA1TEST
+#include <stdio.h>
+
+uint8_t hmacKey1[]={
+	0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
+	0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
+	0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
+	0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f
+};
+uint8_t hmacKey2[]={
+	0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
+	0x40,0x41,0x42,0x43
+};
+uint8_t hmacKey3[]={
+	0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
+	0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
+	0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,
+	0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+	0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
+	0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
+	0xb0,0xb1,0xb2,0xb3
+};
+uint8_t hmacKey4[]={
+	0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,
+	0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+	0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
+	0xa0
+};
+
+void printHash(uint8_t* hash) {
+	int i;
+	for (i=0; i<20; i++) {
+		printf("%02x", hash[i]);
+	}
+	printf("\n");
+}
+
+
+int main (int argc, char **argv) {
+	uint32_t a;
+	sha1nfo s;
+
+	// SHA tests
+	printf("Test: FIPS 180-2 C.1 and RFC3174 7.3 TEST1\n");
+	printf("Expect:a9993e364706816aba3e25717850c26c9cd0d89d\n");
+	printf("Result:");
+	sha1_init(&s);
+	sha1_write(&s, "abc", 3);
+	printHash(sha1_result(&s));
+	printf("\n\n");
+
+	printf("Test: FIPS 180-2 C.2 and RFC3174 7.3 TEST2\n");
+	printf("Expect:84983e441c3bd26ebaae4aa1f95129e5e54670f1\n");
+	printf("Result:");
+	sha1_init(&s);
+	sha1_write(&s, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56);
+	printHash(sha1_result(&s));
+	printf("\n\n");
+
+	printf("Test: RFC3174 7.3 TEST4\n");
+	printf("Expect:dea356a2cddd90c7a7ecedc5ebb563934f460452\n");
+	printf("Result:");
+	sha1_init(&s);
+	for (a=0; a<80; a++) sha1_write(&s, "01234567", 8);
+	printHash(sha1_result(&s));
+	printf("\n\n");
+
+	// HMAC tests
+	printf("Test: FIPS 198a A.1\n");
+	printf("Expect:4f4ca3d5d68ba7cc0a1208c9c61e9c5da0403c0a\n");
+	printf("Result:");
+	sha1_initHmac(&s, hmacKey1, 64);
+	sha1_write(&s, "Sample #1",9);
+	printHash(sha1_resultHmac(&s));
+	printf("\n\n");
+
+	printf("Test: FIPS 198a A.2\n");
+	printf("Expect:0922d3405faa3d194f82a45830737d5cc6c75d24\n");
+	printf("Result:");
+	sha1_initHmac(&s, hmacKey2, 20);
+	sha1_write(&s, "Sample #2", 9);
+	printHash(sha1_resultHmac(&s));
+	printf("\n\n");
+
+	printf("Test: FIPS 198a A.3\n");
+	printf("Expect:bcf41eab8bb2d802f3d05caf7cb092ecf8d1a3aa\n");
+	printf("Result:");
+	sha1_initHmac(&s, hmacKey3,100);
+	sha1_write(&s, "Sample #3", 9);
+	printHash(sha1_resultHmac(&s));
+	printf("\n\n");
+
+	printf("Test: FIPS 198a A.4\n");
+	printf("Expect:9ea886efe268dbecce420c7524df32e0751a2a26\n");
+	printf("Result:");
+	sha1_initHmac(&s, hmacKey4,49);
+	sha1_write(&s, "Sample #4", 9);
+	printHash(sha1_resultHmac(&s));
+	printf("\n\n");
+
+	// Long tests
+	printf("Test: FIPS 180-2 C.3 and RFC3174 7.3 TEST3\n");
+	printf("Expect:34aa973cd4c4daa4f61eeb2bdbad27316534016f\n");
+	printf("Result:");
+	sha1_init(&s);
+	for (a=0; a<1000000; a++) sha1_writebyte(&s, 'a');
+	printHash(sha1_result(&s));
+
+	return 0;
+}
+#endif /* self-test */
diff --git a/util/types.h b/util/types.h
index 7098ea5..10aec1b 100644
--- a/util/types.h
+++ b/util/types.h
@@ -27,13 +27,20 @@
 #include "export.h"
 
 // ### this belongs into a different header
+#ifdef __GNUC__
 #define likely(x)    __builtin_expect(!!(x), 1)
 #define unlikely(x)  __builtin_expect(!!(x), 0)
+#else
+// !!() for maximum compatibility with the non-no-op versions
+#define likely(x)    !!(x)
+#define unlikely(x)  !!(x)
+#endif
 
 typedef unsigned char byte;
 typedef short int int16;
 typedef unsigned short int uint16;
 typedef int int32;
+typedef unsigned int uint; // Windows doesn't define uint by default
 typedef unsigned int uint32;
 typedef long long int int64;
 typedef unsigned long long int uint64;
diff --git a/util/winutil.cpp b/util/winutil.cpp
new file mode 100644
index 0000000..ad206c0
--- /dev/null
+++ b/util/winutil.cpp
@@ -0,0 +1,96 @@
+/*
+   Copyright (C) 2015 Andreas Hartmetz <ahartmetz@gmail.com>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public
+   License as published by the Free Software Foundation; either
+   version 2 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LGPL.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+
+   Alternatively, this file is available under the Mozilla Public License
+   Version 1.1.  You may obtain a copy of the License at
+   http://www.mozilla.org/MPL/
+*/
+
+#include "winutil.h"
+
+#include <iostream>
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <sddl.h>
+#endif
+
+
+using namespace std;
+
+string fetchWindowsSid()
+{
+    // Since this code is adapted from libdbus, keep the processId parameter which \
is only used +    // by the server, in case we need it later. The fixed value should \
let currently dead code +    // get optimized out.
+    const DWORD processId = 0;
+    const HANDLE processHandle = processId == 0 ? GetCurrentProcess() :
+                                 OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, \
FALSE, processId); +
+    bool ok = true;
+
+    HANDLE processToken = INVALID_HANDLE_VALUE;
+    if (ok && !OpenProcessToken(processHandle, TOKEN_QUERY, &processToken)) {
+        ok = false;
+        cerr << "OpenProcessToken failed " << GetLastError() << '\n';
+    }
+
+    PSID psid;
+    if (ok) {
+        DWORD n;
+        SetLastError(0);
+        GetTokenInformation(processToken, TokenUser, nullptr, 0, &n);
+        TOKEN_USER *token_user = static_cast<TOKEN_USER *>(alloca(n));
+        // cerr << "GetTokenInformation to get length: length is " << n
+        //     << " and GetLastError() returns " << GetLastError() << ".\n";
+        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || !token_user) {
+            n = 0;
+        }
+        if (GetTokenInformation(processToken, TokenUser, token_user, n, &n)) {
+            psid = token_user->User.Sid;
+        } else {
+            ok = false;
+            cerr << "GetTokenInformation failed " << GetLastError() << '\n';
+        }
+    }
+
+    if (ok && !IsValidSid(psid)) {
+        ok = false;
+        cerr << "IsValidSid() says no\n";
+    }
+
+    string ret;
+    if (ok) {
+        char *sidChar = nullptr;
+        if (ConvertSidToStringSidA(psid, &sidChar)) {
+            ret = sidChar;
+            LocalFree(sidChar);
+        } else {
+            ok = false;
+            cerr << "invalid SID in ConvertSidToStringSidA()\n";
+        }
+    }
+
+    CloseHandle(processHandle);
+    if (processToken != INVALID_HANDLE_VALUE) {
+        CloseHandle(processToken);
+    }
+
+    return ret;
+}
diff --git a/connection/platform.h b/util/winutil.h
similarity index 79%
copy from connection/platform.h
copy to util/winutil.h
index d4e9728..2d77923 100644
--- a/connection/platform.h
+++ b/util/winutil.h
@@ -1,5 +1,5 @@
 /*
-   Copyright (C) 2013 Andreas Hartmetz <ahartmetz@gmail.com>
+   Copyright (C) 2015 Andreas Hartmetz <ahartmetz@gmail.com>
 
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
@@ -21,11 +21,11 @@
    http://www.mozilla.org/MPL/
 */
 
-#ifndef PLATFORM_H
-#define PLATFORM_H
+#ifndef WINDO_H
+#define WINDO_H
 
-// TODO different definitions on non-POSIX OS
-typedef int FileDescriptor;
-static const int invalidFileDescriptor = -1;
+#include <string>
 
-#endif // PLATFORM_H
+std::string fetchWindowsSid();
+
+#endif // WINDO_H


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic