diff --git a/.gitmodules b/.gitmodules
index 37a56b62424537cca94c0a906df27a1f879fa535..aac10606b55e87e1e852bbae747f641a089c10e7 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -14,18 +14,6 @@
 	path = libdap-stream
 	url = https://gitlab.demlabs.net/cellframe/libdap-stream.git
 	branch = master
-[submodule "libdap-stream-ch-vpn"]
-	path = libdap-stream-ch-vpn
-	url = https://gitlab.demlabs.net/cellframe/libdap-stream-ch-vpn.git
-	branch = master
-[submodule "libdap-stream-ch-av"]
-	path = libdap-stream-ch-av
-	url = https://gitlab.demlabs.net/cellframe/libdap-stream-ch-av.git
-	branch = master
-[submodule "libdap-stream-ch-chat"]
-	path = libdap-stream-ch-chat
-	url = https://gitlab.demlabs.net/cellframe/libdap-stream-ch-chat.git
-	branch = master
 [submodule "libdap-server-core"]
 	path = libdap-server-core
 	url = https://gitlab.demlabs.net/cellframe/libdap-server-core.git
@@ -34,6 +22,10 @@
 	path = libdap-stream-ch-chain
 	url = https://gitlab.demlabs.net/cellframe/libdap-stream-ch-chain.git
 	branch = master
+[submodule "libdap-stream-ch-chat"]
+	path = libdap-stream-ch-chat
+	url = https://gitlab.demlabs.net/cellframe/libdap-stream-ch-chat.git
+	branch = master
 [submodule "libdap-chain-net"]
 	path = libdap-chain-net
 	url = https://gitlab.demlabs.net/cellframe/libdap-chain-net.git
@@ -122,14 +114,6 @@
 	path = libdap-crypto
 	url = https://gitlab.demlabs.net/cellframe/libdap-crypto.git
 	branch = master
-[submodule "libdap-stream-ch-iot"]
-	path = libdap-stream-ch-iot
-	url = https://gitlab.demlabs.net/cellframe/libdap-stream-ch-iot.git
-	branch = master
-[submodule "libdap-iot"]
-	path = libdap-iot
-	url = https://gitlab.demlabs.net/cellframe/libdap-iot.git
-	branch = master
 [submodule "test/libdap-test"]
 	path = test/libdap-test
 	url = https://gitlab.demlabs.net/cellframe/libdap-test.git
@@ -138,3 +122,7 @@
 	path = libdap-server-http-db
 	url = https://gitlab.demlabs.net/cellframe/libdap-server-http-db
 	branch = master
+[submodule "libdap-chain-common"]
+	path = libdap-chain-common
+	url = https://gitlab.demlabs.net/cellframe/libdap-chain-common
+	branch = master
diff --git a/CMakeLists.txt b/CMakeLists.txt
old mode 100755
new mode 100644
index a402c26946b7a3b7530d144e247007543430a15e..64b4713342d3543b2582f42974724b7275a246f2
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -51,8 +51,8 @@ SET(DESTDIR "/opt/${PROJECT_NAME}")
 SET( CPACK_GENERATOR "DEB")
 SET( CPACK_PACKAGE_NAME  "${PROJECT_NAME}")
 SET( CPACK_PACKAGE_VERSION_MAJOR 2)
-SET( CPACK_PACKAGE_VERSION_MINOR 6)
-SET( CPACK_PACKAGE_VERSION_PATCH 12)
+SET( CPACK_PACKAGE_VERSION_MINOR 8)
+SET( CPACK_PACKAGE_VERSION_PATCH 7)
 
 SET( CPACK_SYSTEM_NAME "debian-9.9-amd64")
 SET( CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}-${CPACK_PACKAGE_VERSION_PATCH}")
@@ -86,6 +86,7 @@ if(WIN32)
   add_definitions("-DHAVE_PREAD")
   add_definitions("-DHAVE_MMAP")
   add_definitions("-DHAVE_STRNDUP")
+  add_compile_definitions(WINVER=0x0600 _WIN32_WINNT=0x0600)
 
   if(DAP_RELEASE)
     set(_CCOPT "-static -std=gnu11 -Wall -O3 -fno-ident -ffast-math -ftree-vectorize -mfpmath=sse -mmmx -msse2 -fno-asynchronous-unwind-tables -ffunction-sections -Wl,--gc-sections -Wl,--strip-all")
@@ -98,12 +99,11 @@ if(WIN32)
   set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} ${_LOPT}")
 
   include_directories(libdap/src/win32/)
-  include_directories(3rdparty/wepoll/include/)
   include_directories(3rdparty/uthash/src/)
   include_directories(3rdparty/libjson-c/)
   include_directories(3rdparty/curl/include/)
   include_directories(3rdparty/libsqlite3/)
-
+  include_directories(sources/wepoll/)
   include_directories(libdap-server-http-db-auth/)
   include_directories(libdap-chain-net-srv-vpn/)
 endif()
@@ -145,8 +145,9 @@ add_subdirectory(libdap-server-udp)
 add_subdirectory(libdap-server)
 
 
-add_subdirectory(libdap-chain)
 add_subdirectory(libdap-chain-crypto)
+add_subdirectory(libdap-chain-common)
+add_subdirectory(libdap-chain)
 add_subdirectory(libdap-chain-wallet)
 add_subdirectory(libdap-chain-cs-dag)
 add_subdirectory(libdap-chain-cs-dag-poa)
@@ -169,8 +170,8 @@ add_subdirectory(libdap-stream)
 add_subdirectory(libdap-stream-ch)
 add_subdirectory(libdap-client)
 
-add_subdirectory(libdap-stream-ch-vpn)
 add_subdirectory(libdap-stream-ch-chain)
+#add_subdirectory(libdap-stream-ch-chat)
 add_subdirectory(libdap-stream-ch-chain-net)
 add_subdirectory(libdap-stream-ch-chain-net-srv)
 
@@ -219,7 +220,6 @@ if(WIN32)
 
   target_link_libraries(${NODE_CLI_TARGET} dap_chain_net
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libjson-c[x86_64CLANG].a
-    ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/wepoll[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libsqlite3[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libmongoc[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libbson[x86_64CLANG].a
@@ -252,7 +252,7 @@ if(WIN32)
   )
   set_property(TARGET ${NODE_CLI_TARGET} APPEND_STRING PROPERTY LINK_FLAGS "-mconsole")
     target_link_libraries(${NODE_TOOL_TARGET} dap_core dap_crypto dap_server_core dap_enc_server dap_udp_server dap_session
-    dap_enc_server dap_stream dap_stream_ch_vpn dap_stream_ch_chain dap_stream_ch_chain_net
+    dap_enc_server dap_stream dap_stream_ch_chain dap_stream_ch_chain_net
     dap_stream_ch_chain_net_srv dap_chain dap_chain_crypto dap_client
     dap_chain_cs_dag dap_chain_cs_dag_poa dap_chain_cs_dag_pos
     dap_chain_net dap_chain_net_srv dap_chain_net_srv_app dap_chain_net_srv_app_db
@@ -260,7 +260,6 @@ if(WIN32)
     dap_chain_wallet dap_chain_global_db dap_chain_mempool dap_cuttdb
 
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libjson-c[x86_64CLANG].a
-    ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/wepoll[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libsqlite3[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libmongoc[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libbson[x86_64CLANG].a
@@ -293,16 +292,14 @@ if(WIN32)
   )
   set_property(TARGET ${NODE_TOOL_TARGET} APPEND_STRING PROPERTY LINK_FLAGS "-mconsole")
 
-
   target_link_libraries(${PROJECT_NAME} dap_core dap_crypto dap_server_core dap_enc_server dap_udp_server dap_session
-    dap_enc_server dap_stream dap_stream_ch_vpn dap_stream_ch_chain dap_stream_ch_chain_net
+    dap_enc_server dap_stream dap_stream_ch_chain dap_stream_ch_chain_net
     dap_stream_ch_chain_net_srv dap_chain dap_chain_crypto dap_client
     dap_chain_cs_dag dap_chain_cs_dag_poa dap_chain_cs_dag_pos
     dap_chain_net dap_chain_net_srv dap_chain_net_srv_app dap_chain_net_srv_app_db
     dap_chain_net_srv_datum dap_chain_net_srv_datum_pool
     dap_chain_wallet dap_chain_global_db dap_chain_mempool dap_chain_gdb
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libjson-c[x86_64CLANG].a
-    ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/wepoll[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libsqlite3[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libmongoc[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libbson[x86_64CLANG].a
@@ -347,7 +344,7 @@ if(UNIX)
 
     set(NODE_LIBRARIES
         dap_core dap_crypto dap_crypto dap_server_core dap_enc_server dap_udp_server dap_session
-        dap_enc_server dap_stream dap_stream_ch_vpn dap_stream_ch_chain dap_stream_ch_chain_net
+        dap_enc_server dap_stream dap_stream_ch_chain dap_stream_ch_chain_net
         dap_stream_ch_chain_net_srv dap_chain dap_chain_crypto dap_client
         dap_chain_cs_dag dap_chain_cs_dag_poa dap_chain_cs_dag_pos
         dap_chain_net dap_chain_net_srv dap_chain_net_srv_app dap_chain_net_srv_app_db
diff --git a/dist/etc/network/kelvin-testnet/chain-plasma.cfg b/dist/etc/network/kelvin-testnet/chain-plasma.cfg
index 5fed52b050bae6b7c23650103faaa3b5b615f6e0..f81cc26d454f076a640ba6ba159e3ed124ee7d57 100644
--- a/dist/etc/network/kelvin-testnet/chain-plasma.cfg
+++ b/dist/etc/network/kelvin-testnet/chain-plasma.cfg
@@ -18,7 +18,9 @@ datum_add_hashes_count=3
 # KELT - test token, KEL - main tokem
 tokens_hold=[KELT,KEL]
 # 1000 coins for both
-tokens_hold_value=[1000000000000000,1000000000000000]
+tokens_hold_value=[1000000000,1000000000]
+# Confirmations minimum
+confirmations_minimum=3
 
 [files]
 storage_dir=/opt/cellframe-node/var/lib/network/kelvin-testnet/plasma
diff --git a/dist/share/configs/cellframe-node.cfg.tpl b/dist/share/configs/cellframe-node.cfg.tpl
index e5c99b68750c272a08e9e02c3e8db71f7ffca4b1..5a0ad0f18a91a01c4169d509e636db8c4414054c 100644
--- a/dist/share/configs/cellframe-node.cfg.tpl
+++ b/dist/share/configs/cellframe-node.cfg.tpl
@@ -38,19 +38,13 @@ tx_cond_create=false
 # tx_cond_templates=[mywallet0:0.00001:3600:KELT:kelvin-testnet,mywallet1:0.000001:3600:cETH:kelvin-testnet,mywallet0:1:10:WOOD:private]
 
 # VPN stream channel processing module
-[vpn]
+[srv_vpn]
 #   Turn to true if you want to share VPN service from you node 
 enabled=false
 #   List of loca security access groups. Built in: expats,admins,services,nobody,everybody
-access_groups=expats,services,admins 
 network_address=10.11.12.0
 network_mask=255.255.255.0
-
-# old VPN server
-[vpn_old]
-enabled=true
-network_address=10.11.11.0
-network_mask=255.255.255.0
+pricelist=[kelvin-testnet:0.00001:KELT:3600:SEC:mywallet0,kelvin-testnet:0.00001:cETH:3600:SEC:mywallet1,private:1:WOOD:10:SEC:mywallet0]
 
 # Console interface server
 [conserver]
diff --git a/dist/share/configs/service/vpn.cfg b/dist/share/configs/service/vpn.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..0256d96397cfadfdf00b67ebf2abf1bc8bf4c585
--- /dev/null
+++ b/dist/share/configs/service/vpn.cfg
@@ -0,0 +1,4 @@
+[general]
+enabled=true
+uid=0x0000000000000001
+pricelist=[kelvin-testnet:0.00001:KELT:3600:SEC:mywallet0,kelvin-testnet:0.00001:cETH:3600:SEC:mywallet1,private:1:WOOD:10:SEC:mywallet0]
diff --git a/lib/[x86_64CLANG]/wepoll[x86_64CLANG].a b/lib/[x86_64CLANG]/wepoll[x86_64CLANG].a
deleted file mode 100644
index ccfe34c9e9e4664ffabcbd9832ff991afcf5e22f..0000000000000000000000000000000000000000
Binary files a/lib/[x86_64CLANG]/wepoll[x86_64CLANG].a and /dev/null differ
diff --git a/libdap b/libdap
index 1c0614797a3a2a2f4c179630025600e177637a65..ee6e8617e1f589b45f15e327ed18cddfa87915f8 160000
--- a/libdap
+++ b/libdap
@@ -1 +1 @@
-Subproject commit 1c0614797a3a2a2f4c179630025600e177637a65
+Subproject commit ee6e8617e1f589b45f15e327ed18cddfa87915f8
diff --git a/libdap-chain b/libdap-chain
index 04be73e68e1994e86a71154370616a3a3b2b85ae..777394aa9c175db4e6245c95c990a6f2c5717a58 160000
--- a/libdap-chain
+++ b/libdap-chain
@@ -1 +1 @@
-Subproject commit 04be73e68e1994e86a71154370616a3a3b2b85ae
+Subproject commit 777394aa9c175db4e6245c95c990a6f2c5717a58
diff --git a/libdap-chain-common b/libdap-chain-common
new file mode 160000
index 0000000000000000000000000000000000000000..5af3788fc532f217678165ff7f625a579083f0f1
--- /dev/null
+++ b/libdap-chain-common
@@ -0,0 +1 @@
+Subproject commit 5af3788fc532f217678165ff7f625a579083f0f1
diff --git a/libdap-chain-crypto b/libdap-chain-crypto
index f8dfd888432234c0314057f5551a578dd770003c..73d80a90c7adb46f7322595ce899c718b78eea71 160000
--- a/libdap-chain-crypto
+++ b/libdap-chain-crypto
@@ -1 +1 @@
-Subproject commit f8dfd888432234c0314057f5551a578dd770003c
+Subproject commit 73d80a90c7adb46f7322595ce899c718b78eea71
diff --git a/libdap-chain-cs-dag b/libdap-chain-cs-dag
index 447d6db4e8587d6118e968eb1109fbf2a3356247..1319d136f54fa65962a73881680df6216ce78086 160000
--- a/libdap-chain-cs-dag
+++ b/libdap-chain-cs-dag
@@ -1 +1 @@
-Subproject commit 447d6db4e8587d6118e968eb1109fbf2a3356247
+Subproject commit 1319d136f54fa65962a73881680df6216ce78086
diff --git a/libdap-chain-cs-dag-poa b/libdap-chain-cs-dag-poa
index db06bbd6a784ff2496f2d1bb9cbb54924efe83b6..810db1b64905477cde62e981d6f160da501660c7 160000
--- a/libdap-chain-cs-dag-poa
+++ b/libdap-chain-cs-dag-poa
@@ -1 +1 @@
-Subproject commit db06bbd6a784ff2496f2d1bb9cbb54924efe83b6
+Subproject commit 810db1b64905477cde62e981d6f160da501660c7
diff --git a/libdap-chain-cs-dag-pos b/libdap-chain-cs-dag-pos
index b1a0d674dbb56938e0ba58862ad1d638420e554f..25a1256e47a85854e143c540fbabc8dc3fe1f7ae 160000
--- a/libdap-chain-cs-dag-pos
+++ b/libdap-chain-cs-dag-pos
@@ -1 +1 @@
-Subproject commit b1a0d674dbb56938e0ba58862ad1d638420e554f
+Subproject commit 25a1256e47a85854e143c540fbabc8dc3fe1f7ae
diff --git a/libdap-chain-gdb b/libdap-chain-gdb
index 76181225215f91f3a2a42671f735f70ade46d045..25f9668706f8e728351db9295e6a1fe0111d32bc 160000
--- a/libdap-chain-gdb
+++ b/libdap-chain-gdb
@@ -1 +1 @@
-Subproject commit 76181225215f91f3a2a42671f735f70ade46d045
+Subproject commit 25f9668706f8e728351db9295e6a1fe0111d32bc
diff --git a/libdap-chain-global-db b/libdap-chain-global-db
index 8c6bbf4458bd10614bf48bb2da7e84b4f80f0d33..6b1f0c34cf0fd9c2e20441b4c0ce621c8febf1b8 160000
--- a/libdap-chain-global-db
+++ b/libdap-chain-global-db
@@ -1 +1 @@
-Subproject commit 8c6bbf4458bd10614bf48bb2da7e84b4f80f0d33
+Subproject commit 6b1f0c34cf0fd9c2e20441b4c0ce621c8febf1b8
diff --git a/libdap-chain-mempool b/libdap-chain-mempool
index 4bbdcf60e10de8462820e45d7cc3715dca4c2139..a063222c80bc25cf70fdb3b6391f202bb2c08c65 160000
--- a/libdap-chain-mempool
+++ b/libdap-chain-mempool
@@ -1 +1 @@
-Subproject commit 4bbdcf60e10de8462820e45d7cc3715dca4c2139
+Subproject commit a063222c80bc25cf70fdb3b6391f202bb2c08c65
diff --git a/libdap-chain-net b/libdap-chain-net
index 94ff5a32e30465d3e5986a11753caf57f67e5096..1c70a8e84f80c7441b13eb9f4d71574b1c4e184e 160000
--- a/libdap-chain-net
+++ b/libdap-chain-net
@@ -1 +1 @@
-Subproject commit 94ff5a32e30465d3e5986a11753caf57f67e5096
+Subproject commit 1c70a8e84f80c7441b13eb9f4d71574b1c4e184e
diff --git a/libdap-chain-net-srv b/libdap-chain-net-srv
index 5edea70af6a5ede0c759e9d83a5741376f4da9fb..72da709759280291f16399cfaf4198eb819ebb9f 160000
--- a/libdap-chain-net-srv
+++ b/libdap-chain-net-srv
@@ -1 +1 @@
-Subproject commit 5edea70af6a5ede0c759e9d83a5741376f4da9fb
+Subproject commit 72da709759280291f16399cfaf4198eb819ebb9f
diff --git a/libdap-chain-net-srv-app b/libdap-chain-net-srv-app
index e5f23784de6b8cc16b80b4bc755c67df5fc7565f..dc6e3b30992d3988c7f9f3a159c8286e28bea9ae 160000
--- a/libdap-chain-net-srv-app
+++ b/libdap-chain-net-srv-app
@@ -1 +1 @@
-Subproject commit e5f23784de6b8cc16b80b4bc755c67df5fc7565f
+Subproject commit dc6e3b30992d3988c7f9f3a159c8286e28bea9ae
diff --git a/libdap-chain-net-srv-app-db b/libdap-chain-net-srv-app-db
index ce7f0333b77f8290428949b593e0ca2bd3df01b3..995568917a9796dec412ad13124628fb8fff53ba 160000
--- a/libdap-chain-net-srv-app-db
+++ b/libdap-chain-net-srv-app-db
@@ -1 +1 @@
-Subproject commit ce7f0333b77f8290428949b593e0ca2bd3df01b3
+Subproject commit 995568917a9796dec412ad13124628fb8fff53ba
diff --git a/libdap-chain-net-srv-datum b/libdap-chain-net-srv-datum
index 5186f865f3a1bd10ef61f3cb039d85c9b32c7ad7..a317a59e7eec49738288a476548906aab6ea43fe 160000
--- a/libdap-chain-net-srv-datum
+++ b/libdap-chain-net-srv-datum
@@ -1 +1 @@
-Subproject commit 5186f865f3a1bd10ef61f3cb039d85c9b32c7ad7
+Subproject commit a317a59e7eec49738288a476548906aab6ea43fe
diff --git a/libdap-chain-net-srv-datum-pool b/libdap-chain-net-srv-datum-pool
index 0eb7c08027ff1a643c90b5ca59a8e4267e71ca6f..640e49fdc8c2a78aa9ae0930adcf03e149df6607 160000
--- a/libdap-chain-net-srv-datum-pool
+++ b/libdap-chain-net-srv-datum-pool
@@ -1 +1 @@
-Subproject commit 0eb7c08027ff1a643c90b5ca59a8e4267e71ca6f
+Subproject commit 640e49fdc8c2a78aa9ae0930adcf03e149df6607
diff --git a/libdap-chain-net-srv-vpn b/libdap-chain-net-srv-vpn
index 5475ce3822a5816572ff115f988bcc10827dc9f0..b69f85aaaa347eee9f106bb08a8b7494439899cb 160000
--- a/libdap-chain-net-srv-vpn
+++ b/libdap-chain-net-srv-vpn
@@ -1 +1 @@
-Subproject commit 5475ce3822a5816572ff115f988bcc10827dc9f0
+Subproject commit b69f85aaaa347eee9f106bb08a8b7494439899cb
diff --git a/libdap-chain-wallet b/libdap-chain-wallet
index 5273ba5ba0a15dd94e680f9d961ab150f3764848..1d23760cc25190ff114eac4cab212459360936f8 160000
--- a/libdap-chain-wallet
+++ b/libdap-chain-wallet
@@ -1 +1 @@
-Subproject commit 5273ba5ba0a15dd94e680f9d961ab150f3764848
+Subproject commit 1d23760cc25190ff114eac4cab212459360936f8
diff --git a/libdap-client b/libdap-client
index d3f5458f352294547b1ac68404d5ccdc8dea28ba..802517a9cce864cf5b55eaf1e2c70bb97f4dbec7 160000
--- a/libdap-client
+++ b/libdap-client
@@ -1 +1 @@
-Subproject commit d3f5458f352294547b1ac68404d5ccdc8dea28ba
+Subproject commit 802517a9cce864cf5b55eaf1e2c70bb97f4dbec7
diff --git a/libdap-crypto b/libdap-crypto
index c81202ba5d48e5d77afcc8aea9962a10560527f3..16a16e71b9cfae4fd8df530ed8cd6146010ae7e0 160000
--- a/libdap-crypto
+++ b/libdap-crypto
@@ -1 +1 @@
-Subproject commit c81202ba5d48e5d77afcc8aea9962a10560527f3
+Subproject commit 16a16e71b9cfae4fd8df530ed8cd6146010ae7e0
diff --git a/libdap-iot b/libdap-iot
deleted file mode 160000
index d26d949e670f888e185365ea8a40a1d6f4a36e02..0000000000000000000000000000000000000000
--- a/libdap-iot
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit d26d949e670f888e185365ea8a40a1d6f4a36e02
diff --git a/libdap-server b/libdap-server
index e3f07ae78aaecfada9f44abdd48a4ebbce9e2184..0f224b4de595d688cbbac6c2616286b8079eba88 160000
--- a/libdap-server
+++ b/libdap-server
@@ -1 +1 @@
-Subproject commit e3f07ae78aaecfada9f44abdd48a4ebbce9e2184
+Subproject commit 0f224b4de595d688cbbac6c2616286b8079eba88
diff --git a/libdap-server-core b/libdap-server-core
index 73a86ef8202ee67a05098d186aac2deacb207f63..8ed6c9dee385a9178c4b07b097e62e62ecfb6923 160000
--- a/libdap-server-core
+++ b/libdap-server-core
@@ -1 +1 @@
-Subproject commit 73a86ef8202ee67a05098d186aac2deacb207f63
+Subproject commit 8ed6c9dee385a9178c4b07b097e62e62ecfb6923
diff --git a/libdap-server-http-db b/libdap-server-http-db
index f3f0b0ed919d37368e86210940ebb9de686879a1..8d974654dc02860dae64b313aadc1c062c5c4dc3 160000
--- a/libdap-server-http-db
+++ b/libdap-server-http-db
@@ -1 +1 @@
-Subproject commit f3f0b0ed919d37368e86210940ebb9de686879a1
+Subproject commit 8d974654dc02860dae64b313aadc1c062c5c4dc3
diff --git a/libdap-server-http-db-auth b/libdap-server-http-db-auth
index 05f39736dba67c489454f3bb505d6baf900e27f2..c03c689e1cf6b7e68b0033c0bc1f745c7c64a46d 160000
--- a/libdap-server-http-db-auth
+++ b/libdap-server-http-db-auth
@@ -1 +1 @@
-Subproject commit 05f39736dba67c489454f3bb505d6baf900e27f2
+Subproject commit c03c689e1cf6b7e68b0033c0bc1f745c7c64a46d
diff --git a/libdap-server-udp b/libdap-server-udp
index a14daa6c39aed9bba32684872d03afc7778db61e..7500d54e139a92586b1488edffcb12f4f1c824ec 160000
--- a/libdap-server-udp
+++ b/libdap-server-udp
@@ -1 +1 @@
-Subproject commit a14daa6c39aed9bba32684872d03afc7778db61e
+Subproject commit 7500d54e139a92586b1488edffcb12f4f1c824ec
diff --git a/libdap-stream b/libdap-stream
index eb3f6b659c2ad25e1b9906eb9820e46de7443103..4781932c88e82e1fe38f8859ef56a8367f580d4b 160000
--- a/libdap-stream
+++ b/libdap-stream
@@ -1 +1 @@
-Subproject commit eb3f6b659c2ad25e1b9906eb9820e46de7443103
+Subproject commit 4781932c88e82e1fe38f8859ef56a8367f580d4b
diff --git a/libdap-stream-ch b/libdap-stream-ch
index daeb35e4680f659d41ade4da7514138e56278685..3bdf48f5401b54b582c2d28b71c9e596bcc6c090 160000
--- a/libdap-stream-ch
+++ b/libdap-stream-ch
@@ -1 +1 @@
-Subproject commit daeb35e4680f659d41ade4da7514138e56278685
+Subproject commit 3bdf48f5401b54b582c2d28b71c9e596bcc6c090
diff --git a/libdap-stream-ch-av b/libdap-stream-ch-av
deleted file mode 160000
index 89736f8e1bf22bc89d938ab5469e8235187f98db..0000000000000000000000000000000000000000
--- a/libdap-stream-ch-av
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 89736f8e1bf22bc89d938ab5469e8235187f98db
diff --git a/libdap-stream-ch-chain b/libdap-stream-ch-chain
index 5dcf99b114a8fc1b48d124feba528e0b9f413cd1..fa1c163731454548ab4078a38b1532e9a6623b24 160000
--- a/libdap-stream-ch-chain
+++ b/libdap-stream-ch-chain
@@ -1 +1 @@
-Subproject commit 5dcf99b114a8fc1b48d124feba528e0b9f413cd1
+Subproject commit fa1c163731454548ab4078a38b1532e9a6623b24
diff --git a/libdap-stream-ch-chain-net b/libdap-stream-ch-chain-net
index 6875d05d0d3c3da2c9562b4840f48a5267e865ca..3c768e4b51cd4dc87893769ee0693992138be675 160000
--- a/libdap-stream-ch-chain-net
+++ b/libdap-stream-ch-chain-net
@@ -1 +1 @@
-Subproject commit 6875d05d0d3c3da2c9562b4840f48a5267e865ca
+Subproject commit 3c768e4b51cd4dc87893769ee0693992138be675
diff --git a/libdap-stream-ch-chain-net-srv b/libdap-stream-ch-chain-net-srv
index e136afe77716b6e327310ccaa7f58ad75c63b2c5..1f34d25645ec14af02ea9e79df4acdf4256f0682 160000
--- a/libdap-stream-ch-chain-net-srv
+++ b/libdap-stream-ch-chain-net-srv
@@ -1 +1 @@
-Subproject commit e136afe77716b6e327310ccaa7f58ad75c63b2c5
+Subproject commit 1f34d25645ec14af02ea9e79df4acdf4256f0682
diff --git a/libdap-stream-ch-iot b/libdap-stream-ch-iot
deleted file mode 160000
index 0d61d274c60237daa5ddc0c72faa9689fc2c06a7..0000000000000000000000000000000000000000
--- a/libdap-stream-ch-iot
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 0d61d274c60237daa5ddc0c72faa9689fc2c06a7
diff --git a/libdap-stream-ch-vpn b/libdap-stream-ch-vpn
deleted file mode 160000
index 29383e0b47628acf0dc085f69a87a449dd03acb5..0000000000000000000000000000000000000000
--- a/libdap-stream-ch-vpn
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 29383e0b47628acf0dc085f69a87a449dd03acb5
diff --git a/sources/main.c b/sources/main.c
index 39ef71389ae9a49a20fa867680a6a0cac64153b1..2bcdecfc65b989c74095ee59feb850bced75e85e 100755
--- a/sources/main.c
+++ b/sources/main.c
@@ -34,15 +34,11 @@
 #include <signal.h>
 
 #ifdef _WIN32
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0600
 #include <winsock2.h>
 #include <windows.h>
 #include <mswsock.h>
 #include <ws2tcpip.h>
 #include <io.h>
-//#include "wrappers.h"
-#include <wepoll.h>
 #include <pthread.h>
 #include "userenv.h"
 
@@ -104,7 +100,6 @@
 #include "dap_stream_session.h"
 #include "dap_stream.h"
 #include "dap_stream_ctl.h"
-#include "dap_stream_ch_vpn.h"
 #include "dap_stream_ch_chain.h"
 #include "dap_stream_ch_chain_net.h"
 #include "dap_stream_ch_chain_net_srv.h"
@@ -146,7 +141,7 @@ int main( int argc, const char **argv )
 #endif
 {
 	dap_server_t *l_server = NULL; // DAP Server instance
-	bool bDebugMode = true;
+    bool l_debug_mode = true;
 	bool bServerEnabled = true;
 	int rc = 0;
 
@@ -155,23 +150,21 @@ int main( int argc, const char **argv )
 	#endif
 
     {
-        char l_log_file_path[MAX_PATH];
+        char l_log_file_path [ MAX_PATH ];
 #ifdef _WIN32
         dap_sprintf(s_sys_dir_path, "%s/%s", regGetUsrPath(), DAP_APP_NAME);
         l_sys_dir_path_len = strlen(s_sys_dir_path);
         memcpy(l_log_file_path, s_sys_dir_path, l_sys_dir_path_len);
         memcpy(s_pid_file_path, s_sys_dir_path, l_sys_dir_path_len);
 #endif
-
-        dap_snprintf(l_log_file_path + l_sys_dir_path_len, sizeof (l_log_file_path), "%s/%s.log", SYSTEM_LOGS_DIR, DAP_APP_NAME);
-        dap_mkdir_with_parents(SYSTEM_LOGS_DIR);
+        dap_snprintf( l_log_file_path + l_sys_dir_path_len , sizeof ( l_log_file_path ), "%s/%s.log", SYSTEM_LOGS_DIR , DAP_APP_NAME );
+        dap_mkdir_with_parents( SYSTEM_LOGS_DIR );
 
         if ( dap_common_init( DAP_APP_NAME, l_log_file_path ) != 0 ) {
             printf( "Fatal Error: Can't init common functions module" );
             return -2;
         }
-
-        dap_snprintf(s_sys_dir_path + l_sys_dir_path_len, sizeof(s_sys_dir_path), "%s", SYSTEM_CONFIGS_DIR);
+        dap_snprintf(s_sys_dir_path + l_sys_dir_path_len, sizeof( s_sys_dir_path ), "%s", SYSTEM_CONFIGS_DIR);
         dap_config_init( s_sys_dir_path );
         memset(s_sys_dir_path + l_sys_dir_path_len, '\0', MAX_PATH - l_sys_dir_path_len);
         if ( (g_config = dap_config_open(DAP_APP_NAME)) == NULL ) {
@@ -188,15 +181,15 @@ int main( int argc, const char **argv )
         CreateMutexW( NULL, FALSE, (WCHAR *) L"DAP_CELLFRAME_NODE_74E9201D33F7F7F684D2FEF1982799A79B6BF94B568446A8D1DE947B00E3C75060F3FD5BF277592D02F77D7E50935E56" );
 	#endif
 
-      bDebugMode = dap_config_get_item_bool_default( g_config,"general","debug_mode", false );
+      l_debug_mode = dap_config_get_item_bool_default( g_config,"general","debug_mode", false );
     //  bDebugMode = true;//dap_config_get_item_bool_default( g_config,"general","debug_mode", false );
 
-	if ( bDebugMode )
+    if ( l_debug_mode )
 	    log_it( L_ATT, "*** DEBUG MODE ***" );
 	else
  	   log_it( L_ATT, "*** NORMAL MODE ***" );
 
-    dap_log_level_set( bDebugMode ? L_DEBUG: L_INFO );
+    dap_log_level_set( l_debug_mode ? L_DEBUG : L_NOTICE );
 
     log_it( L_DAP, "*** CellFrame Node version: %s ***", DAP_VERSION );
 
@@ -286,7 +279,7 @@ int main( int argc, const char **argv )
         return -65;
     }
 
-    if( dap_chain_net_srv_init() !=0){
+    if( dap_chain_net_srv_init(g_config) !=0){
         log_it(L_CRITICAL,"Can't init dap chain network service module");
         return -66;
     }
@@ -307,7 +300,7 @@ int main( int argc, const char **argv )
     }
 #ifndef _WIN32
     // vpn server
-    if(dap_config_get_item_bool_default(g_config, "vpn", "enabled", false)) {
+    if(dap_config_get_item_bool_default(g_config, "srv_vpn", "enabled", false)) {
         if(dap_chain_net_srv_vpn_init(g_config) != 0) {
             log_it(L_ERROR, "Can't init dap chain network service vpn module");
             return -70;
@@ -381,19 +374,6 @@ int main( int argc, const char **argv )
     }
 
     if ( l_server ) { // If listener server is initialized
-        //bool is_traffick_track_enable = dap_config_get_item_bool_default(g_config, "traffic_track", "enable", false);
-
-#if 0
-        if ( is_traffick_track_enable ) {
-            time_t timeout = // TODO add default timeout (get_item_int32_default)
-                    dap_config_get_item_int32(g_config, "traffic_track", "callback_timeout");
-
-            dap_traffic_track_init( l_server, timeout );
-            dap_traffic_callback_set( dap_chain_net_srv_traffic_callback );
-            //dap_traffic_callback_set(db_auth_traffic_track_callback);
-        }
-#endif
-
         // TCP-specific things
 		if ( dap_config_get_item_int32_default(g_config, "server", "listen_port_tcp",-1) > 0) {
             // Init HTTP-specific values
@@ -426,19 +406,12 @@ int main( int argc, const char **argv )
         log_it( L_INFO, "No enabled server, working in client mode only" );
 
 
-    // VPN channel
-    if(dap_config_get_item_bool_default(g_config,"vpn_old","enabled",false)){
-        dap_stream_ch_vpn_init(dap_config_get_item_str_default(g_config, "vpn_old", "network_address", NULL),
-                   dap_config_get_item_str_default(g_config, "vpn_old", "network_mask", NULL));
-
-    }
-
     // Chain Network init
 
 	dap_stream_ch_chain_init( );
 	dap_stream_ch_chain_net_init( );
 
-///    dap_stream_ch_chain_net_srv_init();
+    dap_stream_ch_chain_net_srv_init();
 
     // New event loop init
 	dap_events_init( 0, 0 );
@@ -449,7 +422,7 @@ int main( int argc, const char **argv )
 ///        dap_stream_ch_vpn_deinit();
 
 
-    dap_chain_net_load_all();
+    //dap_chain_net_load_all();
 
 #ifdef DAP_OS_LINUX
 #ifndef __ANDROID__
@@ -508,7 +481,7 @@ void parse_args( int argc, const char **argv ) {
 	    	if ( pid == 0 ) {
 	        	log_it( L_ERROR, "Can't read pid from file" );
 	        	exit( -20 );
-	      	} 
+	      	}
 
 	    	if ( kill_process(pid) ) {
 	        	log_it( L_INFO, "Server successfully stopped" );
@@ -519,7 +492,7 @@ void parse_args( int argc, const char **argv ) {
 	    	exit( -21 );
 	    }
 
-    	case 'D': 
+    	case 'D':
     	{
         	log_it( L_INFO, "Daemonize server starting..." );
         	exit_if_server_already_running( );
diff --git a/sources/main_node_cli_net.c b/sources/main_node_cli_net.c
index 022d5baa657842bb2d319f85e238c540dc96fcb6..7a8006dc0e672b164f13710f4d860ba5c0d7aca4 100644
--- a/sources/main_node_cli_net.c
+++ b/sources/main_node_cli_net.c
@@ -39,15 +39,11 @@
 #include <assert.h>
 
 #ifdef _WIN32
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0600
 #include <winsock2.h>
 #include <windows.h>
 #include <mswsock.h>
 #include <ws2tcpip.h>
 #include <io.h>
-//#include "wrappers.h"
-#include <wepoll.h>
 #include <pthread.h>
 #else
 #include <sys/socket.h>
diff --git a/sources/main_node_cli_shell.c b/sources/main_node_cli_shell.c
index 47fc88134ad225827ed9ab4af0c57c17ad3359f5..c72067016439ef04871b1be835a0f4fe0c5f9a20 100644
--- a/sources/main_node_cli_shell.c
+++ b/sources/main_node_cli_shell.c
@@ -15,15 +15,11 @@
 #include <locale.h>
 
 #ifdef _WIN32
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0600
 #include <winsock2.h>
 #include <windows.h>
 #include <mswsock.h>
 #include <ws2tcpip.h>
 #include <io.h>
-//#include "wrappers.h"
-#include <wepoll.h>
 #include <pthread.h>
 #else
 #include <sys/ttydefaults.h>
diff --git a/sources/main_node_tool.c b/sources/main_node_tool.c
index 96cf9610202e4cd5d454a819270ee0a3ddda51c5..ba50ef10dab9141fd5154e7356d81ef0355f1e44 100644
--- a/sources/main_node_tool.c
+++ b/sources/main_node_tool.c
@@ -29,15 +29,11 @@
 #include <string.h>
 
 #ifdef _WIN32
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0600
 #include <winsock2.h>
 #include <windows.h>
 #include <mswsock.h>
 #include <ws2tcpip.h>
 #include <io.h>
-//#include "wrappers.h"
-#include <wepoll.h>
 #endif
 
 #include <pthread.h>
@@ -78,7 +74,6 @@
 
 #include "dap_stream_session.h"
 #include "dap_stream.h"
-#include "dap_stream_ch_vpn.h"
 #include "dap_stream_ch_chain.h"
 #include "dap_stream_ch_chain_net.h"
 #include "dap_stream_ch_chain_net_srv.h"
diff --git a/sources/wepoll/LICENSE b/sources/wepoll/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..6c8b1c842b1a4c319c9398e95228994c2fbcbb6c
--- /dev/null
+++ b/sources/wepoll/LICENSE
@@ -0,0 +1,28 @@
+wepoll - epoll for Windows
+https://github.com/piscisaureus/wepoll
+
+Copyright 2012-2019, Bert Belder <bertbelder@gmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+  * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+  * Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/sources/wepoll/README.md b/sources/wepoll/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..d334d0833c9fba4d564ea8bbbe7d279288cd5964
--- /dev/null
+++ b/sources/wepoll/README.md
@@ -0,0 +1,202 @@
+# wepoll - epoll for windows
+
+[![][ci status badge]][ci status link]
+
+This library implements the [epoll][man epoll] API for Windows
+applications. It is fast and scalable, and it closely resembles the API
+and behavior of Linux' epoll.
+
+## Rationale
+
+Unlike Linux, OS X, and many other operating systems, Windows doesn't
+have a good API for receiving socket state notifications. It only
+supports the `select` and `WSAPoll` APIs, but they
+[don't scale][select scale] and suffer from
+[other issues][wsapoll broken].
+
+Using I/O completion ports isn't always practical when software is
+designed to be cross-platform. Wepoll offers an alternative that is
+much closer to a drop-in replacement for software that was designed
+to run on Linux.
+
+## Features
+
+* Can poll 100000s of sockets efficiently.
+* Fully thread-safe.
+* Multiple threads can poll the same epoll port.
+* Sockets can be added to multiple epoll sets.
+* All epoll events (`EPOLLIN`, `EPOLLOUT`, `EPOLLPRI`, `EPOLLRDHUP`)
+  are supported.
+* Level-triggered and one-shot (`EPOLLONESTHOT`) modes are supported
+* Trivial to embed: you need [only two files][dist].
+
+## Limitations
+
+* Only works with sockets.
+* Edge-triggered (`EPOLLET`) mode isn't supported.
+
+## How to use
+
+The library is [distributed][dist] as a single source file
+([wepoll.c][wepoll.c]) and a single header file ([wepoll.h][wepoll.h]).<br>
+Compile the .c file as part of your project, and include the header wherever
+needed.
+
+## Compatibility
+
+* Requires Windows Vista or higher.
+* Can be compiled with recent versions of MSVC, Clang, and GCC.
+
+## API
+
+### General remarks
+
+* The epoll port is a `HANDLE`, not a file descriptor.
+* All functions set both `errno` and `GetLastError()` on failure.
+* For more extensive documentation, see the [epoll(7) man page][man epoll],
+  and the per-function man pages that are linked below.
+
+### epoll_create/epoll_create1
+
+```c
+HANDLE epoll_create(int size);
+HANDLE epoll_create1(int flags);
+```
+
+* Create a new epoll instance (port).
+* `size` is ignored but most be greater than zero.
+* `flags` must be zero as there are no supported flags.
+* Returns `NULL` on failure.
+* [Linux man page][man epoll_create]
+
+### epoll_close
+
+```c
+int epoll_close(HANDLE ephnd);
+```
+
+* Close an epoll port.
+* Do not attempt to close the epoll port with `close()`,
+  `CloseHandle()` or `closesocket()`.
+
+### epoll_ctl
+
+```c
+int epoll_ctl(HANDLE ephnd,
+              int op,
+              SOCKET sock,
+              struct epoll_event* event);
+```
+
+* Control which socket events are monitored by an epoll port.
+* `ephnd` must be a HANDLE created by
+  [`epoll_create()`](#epoll_createepoll_create1) or
+  [`epoll_create1()`](#epoll_createepoll_create1).
+* `op` must be one of `EPOLL_CTL_ADD`, `EPOLL_CTL_MOD`, `EPOLL_CTL_DEL`.
+* `sock` must be a valid socket created by [`socket()`][msdn socket],
+  [`WSASocket()`][msdn wsasocket], or [`accept()`][msdn accept].
+* `event` should be a pointer to a [`struct epoll_event`](#struct-epoll_event).<br>
+  If `op` is `EPOLL_CTL_DEL` then the `event` parameter is ignored, and it
+  may be `NULL`.
+* Returns 0 on success, -1 on failure.
+* It is recommended to always explicitly remove a socket from its epoll
+  set using `EPOLL_CTL_DEL` *before* closing it.<br>
+  As on Linux, closed sockets are automatically removed from the epoll set, but
+  wepoll may not be able to detect that a socket was closed until the next call
+  to [`epoll_wait()`](#epoll_wait).
+* [Linux man page][man epoll_ctl]
+
+### epoll_wait
+
+```c
+int epoll_wait(HANDLE ephnd,
+               struct epoll_event* events,
+               int maxevents,
+               int timeout);
+```
+
+* Receive socket events from an epoll port.
+* `events` should point to a caller-allocated array of
+  [`epoll_event`](#struct-epoll_event) structs, which will receive the
+  reported events.
+* `maxevents` is the maximum number of events that will be written to the
+  `events` array, and must be greater than zero.
+* `timeout` specifies whether to block when no events are immediately available.
+  - `<0` block indefinitely
+  - `0`  report any events that are already waiting, but don't block
+  - `≥1` block for at most N milliseconds
+* Return value:
+  - `-1` an error occurred
+  - `0`  timed out without any events to report
+  - `≥1` the number of events stored in the `events` buffer
+* [Linux man page][man epoll_wait]
+
+### struct epoll_event
+
+```c
+typedef union epoll_data {
+  void* ptr;
+  int fd;
+  uint32_t u32;
+  uint64_t u64;
+  SOCKET sock;        /* Windows specific */
+  HANDLE hnd;         /* Windows specific */
+} epoll_data_t;
+```
+
+```c
+struct epoll_event {
+  uint32_t events;    /* Epoll events and flags */
+  epoll_data_t data;  /* User data variable */
+};
+```
+
+* The `events` field is a bit mask containing the events being
+  monitored/reported, and optional flags.<br>
+  Flags are accepted by [`epoll_ctl()`](#epoll_ctl), but they are not reported
+  back by [`epoll_wait()`](#epoll_wait).
+* The `data` field can be used to associate application-specific information
+  with a socket; its value will be returned unmodified by
+  [`epoll_wait()`](#epoll_wait).
+* [Linux man page][man epoll_ctl]
+
+| Event         | Description                                                          |
+|---------------|----------------------------------------------------------------------|
+| `EPOLLIN`     | incoming data available, or incoming connection ready to be accepted |
+| `EPOLLOUT`    | ready to send data, or outgoing connection successfully established  |
+| `EPOLLRDHUP`  | remote peer initiated graceful socket shutdown                       |
+| `EPOLLPRI`    | out-of-band data available for reading                               |
+| `EPOLLERR`    | socket error<sup>1</sup>                                             |
+| `EPOLLHUP`    | socket hang-up<sup>1</sup>                                           |
+| `EPOLLRDNORM` | same as `EPOLLIN`                                                    |
+| `EPOLLRDBAND` | same as `EPOLLPRI`                                                   |
+| `EPOLLWRNORM` | same as `EPOLLOUT`                                                   |
+| `EPOLLWRBAND` | same as `EPOLLOUT`                                                   |
+| `EPOLLMSG`    | never reported                                                       |
+
+| Flag             | Description               |
+|------------------|---------------------------|
+| `EPOLLONESHOT`   | report event(s) only once |
+| `EPOLLET`        | not supported by wepoll   |
+| `EPOLLEXCLUSIVE` | not supported by wepoll   |
+| `EPOLLWAKEUP`    | not supported by wepoll   |
+
+<sup>1</sup>: the `EPOLLERR` and `EPOLLHUP` events may always be reported by
+[`epoll_wait()`](#epoll_wait), regardless of the event mask that was passed to
+[`epoll_ctl()`](#epoll_ctl).
+
+
+[ci status badge]:  https://ci.appveyor.com/api/projects/status/github/piscisaureus/wepoll?branch=master&svg=true
+[ci status link]:   https://ci.appveyor.com/project/piscisaureus/wepoll/branch/master
+[dist]:             https://github.com/piscisaureus/wepoll/tree/dist
+[man epoll]:        http://man7.org/linux/man-pages/man7/epoll.7.html
+[man epoll_create]: http://man7.org/linux/man-pages/man2/epoll_create.2.html
+[man epoll_ctl]:    http://man7.org/linux/man-pages/man2/epoll_ctl.2.html
+[man epoll_wait]:   http://man7.org/linux/man-pages/man2/epoll_wait.2.html
+[msdn accept]:      https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx
+[msdn socket]:      https://msdn.microsoft.com/en-us/library/windows/desktop/ms740506(v=vs.85).aspx
+[msdn wsasocket]:   https://msdn.microsoft.com/en-us/library/windows/desktop/ms742212(v=vs.85).aspx
+[select scale]:     https://daniel.haxx.se/docs/poll-vs-select.html
+[wsapoll broken]:   https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/
+[wepoll.c]:         https://github.com/piscisaureus/wepoll/blob/dist/wepoll.c
+[wepoll.h]:         https://github.com/piscisaureus/wepoll/blob/dist/wepoll.h
diff --git a/sources/wepoll/wepoll.c b/sources/wepoll/wepoll.c
new file mode 100644
index 0000000000000000000000000000000000000000..651673aad37227314985327a42f6d94790fdb653
--- /dev/null
+++ b/sources/wepoll/wepoll.c
@@ -0,0 +1,2189 @@
+/*
+ * wepoll - epoll for Windows
+ * https://github.com/piscisaureus/wepoll
+ *
+ * Copyright 2012-2019, Bert Belder <bertbelder@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WEPOLL_EXPORT
+#define WEPOLL_EXPORT
+#endif
+
+#include <stdint.h>
+
+enum EPOLL_EVENTS {
+  EPOLLIN      = (int) (1U <<  0),
+  EPOLLPRI     = (int) (1U <<  1),
+  EPOLLOUT     = (int) (1U <<  2),
+  EPOLLERR     = (int) (1U <<  3),
+  EPOLLHUP     = (int) (1U <<  4),
+  EPOLLRDNORM  = (int) (1U <<  6),
+  EPOLLRDBAND  = (int) (1U <<  7),
+  EPOLLWRNORM  = (int) (1U <<  8),
+  EPOLLWRBAND  = (int) (1U <<  9),
+  EPOLLMSG     = (int) (1U << 10), /* Never reported. */
+  EPOLLRDHUP   = (int) (1U << 13),
+  EPOLLONESHOT = (int) (1U << 31)
+};
+
+#define EPOLLIN      (1U <<  0)
+#define EPOLLPRI     (1U <<  1)
+#define EPOLLOUT     (1U <<  2)
+#define EPOLLERR     (1U <<  3)
+#define EPOLLHUP     (1U <<  4)
+#define EPOLLRDNORM  (1U <<  6)
+#define EPOLLRDBAND  (1U <<  7)
+#define EPOLLWRNORM  (1U <<  8)
+#define EPOLLWRBAND  (1U <<  9)
+#define EPOLLMSG     (1U << 10)
+#define EPOLLRDHUP   (1U << 13)
+#define EPOLLONESHOT (1U << 31)
+
+#define EPOLL_CTL_ADD 1
+#define EPOLL_CTL_MOD 2
+#define EPOLL_CTL_DEL 3
+
+typedef void* HANDLE;
+typedef uintptr_t SOCKET;
+
+typedef union epoll_data {
+  void* ptr;
+  int fd;
+  uint32_t u32;
+  uint64_t u64;
+  SOCKET sock; /* Windows specific */
+  HANDLE hnd;  /* Windows specific */
+} epoll_data_t;
+
+struct epoll_event {
+  uint32_t events;   /* Epoll events and flags */
+  epoll_data_t data; /* User data variable */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+WEPOLL_EXPORT HANDLE epoll_create(int size);
+WEPOLL_EXPORT HANDLE epoll_create1(int flags);
+
+WEPOLL_EXPORT int epoll_close(HANDLE ephnd);
+
+WEPOLL_EXPORT int epoll_ctl(HANDLE ephnd,
+                            int op,
+                            SOCKET sock,
+                            struct epoll_event* event);
+
+WEPOLL_EXPORT int epoll_wait(HANDLE ephnd,
+                             struct epoll_event* events,
+                             int maxevents,
+                             int timeout);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include <malloc.h>
+#include <stdlib.h>
+
+#define WEPOLL_INTERNAL static
+#define WEPOLL_INTERNAL_VAR static
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#endif
+
+#ifdef _WIN32_WINNT
+#undef _WIN32_WINNT
+#endif
+
+#define _WIN32_WINNT 0x0600
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#ifndef __GNUC__
+#pragma warning(push, 1)
+#endif
+
+#include <WS2tcpip.h>
+#include <WinSock2.h>
+#include <Windows.h>
+
+#ifndef __GNUC__
+#pragma warning(pop)
+#endif
+
+WEPOLL_INTERNAL int nt_global_init(void);
+
+typedef LONG NTSTATUS;
+typedef NTSTATUS* PNTSTATUS;
+
+#ifndef NT_SUCCESS
+#define NT_SUCCESS(status) (((NTSTATUS)(status)) >= 0)
+#endif
+
+#ifndef STATUS_SUCCESS
+#define STATUS_SUCCESS ((NTSTATUS) 0x00000000L)
+#endif
+
+#ifndef STATUS_PENDING
+#define STATUS_PENDING ((NTSTATUS) 0x00000103L)
+#endif
+
+#ifndef STATUS_CANCELLED
+#define STATUS_CANCELLED ((NTSTATUS) 0xC0000120L)
+#endif
+
+typedef struct _IO_STATUS_BLOCK {
+  NTSTATUS Status;
+  ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+typedef VOID(NTAPI* PIO_APC_ROUTINE)(PVOID ApcContext,
+                                     PIO_STATUS_BLOCK IoStatusBlock,
+                                     ULONG Reserved);
+
+typedef struct _UNICODE_STRING {
+  USHORT Length;
+  USHORT MaximumLength;
+  PWSTR Buffer;
+} UNICODE_STRING, *PUNICODE_STRING;
+
+#define RTL_CONSTANT_STRING(s) \
+  { sizeof(s) - sizeof((s)[0]), sizeof(s), s }
+
+typedef struct _OBJECT_ATTRIBUTES {
+  ULONG Length;
+  HANDLE RootDirectory;
+  PUNICODE_STRING ObjectName;
+  ULONG Attributes;
+  PVOID SecurityDescriptor;
+  PVOID SecurityQualityOfService;
+} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
+
+#define RTL_CONSTANT_OBJECT_ATTRIBUTES(ObjectName, Attributes) \
+  { sizeof(OBJECT_ATTRIBUTES), NULL, ObjectName, Attributes, NULL, NULL }
+
+#ifndef FILE_OPEN
+#define FILE_OPEN 0x00000001UL
+#endif
+
+#define KEYEDEVENT_WAIT 0x00000001UL
+#define KEYEDEVENT_WAKE 0x00000002UL
+#define KEYEDEVENT_ALL_ACCESS \
+  (STANDARD_RIGHTS_REQUIRED | KEYEDEVENT_WAIT | KEYEDEVENT_WAKE)
+
+#define NT_NTDLL_IMPORT_LIST(X)           \
+  X(NTSTATUS,                             \
+    NTAPI,                                \
+    NtCreateFile,                         \
+    (PHANDLE FileHandle,                  \
+     ACCESS_MASK DesiredAccess,           \
+     POBJECT_ATTRIBUTES ObjectAttributes, \
+     PIO_STATUS_BLOCK IoStatusBlock,      \
+     PLARGE_INTEGER AllocationSize,       \
+     ULONG FileAttributes,                \
+     ULONG ShareAccess,                   \
+     ULONG CreateDisposition,             \
+     ULONG CreateOptions,                 \
+     PVOID EaBuffer,                      \
+     ULONG EaLength))                     \
+                                          \
+  X(NTSTATUS,                             \
+    NTAPI,                                \
+    NtCreateKeyedEvent,                   \
+    (PHANDLE KeyedEventHandle,            \
+     ACCESS_MASK DesiredAccess,           \
+     POBJECT_ATTRIBUTES ObjectAttributes, \
+     ULONG Flags))                        \
+                                          \
+  X(NTSTATUS,                             \
+    NTAPI,                                \
+    NtDeviceIoControlFile,                \
+    (HANDLE FileHandle,                   \
+     HANDLE Event,                        \
+     PIO_APC_ROUTINE ApcRoutine,          \
+     PVOID ApcContext,                    \
+     PIO_STATUS_BLOCK IoStatusBlock,      \
+     ULONG IoControlCode,                 \
+     PVOID InputBuffer,                   \
+     ULONG InputBufferLength,             \
+     PVOID OutputBuffer,                  \
+     ULONG OutputBufferLength))           \
+                                          \
+  X(NTSTATUS,                             \
+    NTAPI,                                \
+    NtReleaseKeyedEvent,                  \
+    (HANDLE KeyedEventHandle,             \
+     PVOID KeyValue,                      \
+     BOOLEAN Alertable,                   \
+     PLARGE_INTEGER Timeout))             \
+                                          \
+  X(NTSTATUS,                             \
+    NTAPI,                                \
+    NtWaitForKeyedEvent,                  \
+    (HANDLE KeyedEventHandle,             \
+     PVOID KeyValue,                      \
+     BOOLEAN Alertable,                   \
+     PLARGE_INTEGER Timeout))             \
+                                          \
+  X(ULONG, WINAPI, RtlNtStatusToDosError, (NTSTATUS Status))
+
+#define X(return_type, attributes, name, parameters) \
+  WEPOLL_INTERNAL_VAR return_type(attributes* name) parameters;
+NT_NTDLL_IMPORT_LIST(X)
+#undef X
+
+#include <assert.h>
+#include <stddef.h>
+
+#ifndef _SSIZE_T_DEFINED
+typedef intptr_t ssize_t;
+#endif
+
+#define array_count(a) (sizeof(a) / (sizeof((a)[0])))
+
+#define container_of(ptr, type, member) \
+  ((type*) ((uintptr_t) (ptr) - offsetof(type, member)))
+
+#define unused_var(v) ((void) (v))
+
+/* Polyfill `inline` for older versions of msvc (up to Visual Studio 2013) */
+#if defined(_MSC_VER) && _MSC_VER < 1900
+#define inline __inline
+#endif
+
+#define AFD_POLL_RECEIVE           0x0001
+#define AFD_POLL_RECEIVE_EXPEDITED 0x0002
+#define AFD_POLL_SEND              0x0004
+#define AFD_POLL_DISCONNECT        0x0008
+#define AFD_POLL_ABORT             0x0010
+#define AFD_POLL_LOCAL_CLOSE       0x0020
+#define AFD_POLL_ACCEPT            0x0080
+#define AFD_POLL_CONNECT_FAIL      0x0100
+
+typedef struct _AFD_POLL_HANDLE_INFO {
+  HANDLE Handle;
+  ULONG Events;
+  NTSTATUS Status;
+} AFD_POLL_HANDLE_INFO, *PAFD_POLL_HANDLE_INFO;
+
+typedef struct _AFD_POLL_INFO {
+  LARGE_INTEGER Timeout;
+  ULONG NumberOfHandles;
+  ULONG Exclusive;
+  AFD_POLL_HANDLE_INFO Handles[1];
+} AFD_POLL_INFO, *PAFD_POLL_INFO;
+
+WEPOLL_INTERNAL int afd_create_helper_handle(HANDLE iocp,
+                                             HANDLE* afd_helper_handle_out);
+
+WEPOLL_INTERNAL int afd_poll(HANDLE afd_helper_handle,
+                             AFD_POLL_INFO* poll_info,
+                             OVERLAPPED* overlapped);
+
+#define return_map_error(value) \
+  do {                          \
+    err_map_win_error();        \
+    return (value);             \
+  } while (0)
+
+#define return_set_error(value, error) \
+  do {                                 \
+    err_set_win_error(error);          \
+    return (value);                    \
+  } while (0)
+
+WEPOLL_INTERNAL void err_map_win_error(void);
+WEPOLL_INTERNAL void err_set_win_error(DWORD error);
+WEPOLL_INTERNAL int err_check_handle(HANDLE handle);
+
+WEPOLL_INTERNAL int ws_global_init(void);
+WEPOLL_INTERNAL SOCKET ws_get_base_socket(SOCKET socket);
+
+#define IOCTL_AFD_POLL 0x00012024
+
+static UNICODE_STRING afd__helper_name =
+    RTL_CONSTANT_STRING(L"\\Device\\Afd\\Wepoll");
+
+static OBJECT_ATTRIBUTES afd__helper_attributes =
+    RTL_CONSTANT_OBJECT_ATTRIBUTES(&afd__helper_name, 0);
+
+int afd_create_helper_handle(HANDLE iocp, HANDLE* afd_helper_handle_out) {
+  HANDLE afd_helper_handle;
+  IO_STATUS_BLOCK iosb;
+  NTSTATUS status;
+
+  /* By opening \Device\Afd without specifying any extended attributes, we'll
+   * get a handle that lets us talk to the AFD driver, but that doesn't have an
+   * associated endpoint (so it's not a socket). */
+  status = NtCreateFile(&afd_helper_handle,
+                        SYNCHRONIZE,
+                        &afd__helper_attributes,
+                        &iosb,
+                        NULL,
+                        0,
+                        FILE_SHARE_READ | FILE_SHARE_WRITE,
+                        FILE_OPEN,
+                        0,
+                        NULL,
+                        0);
+  if (status != STATUS_SUCCESS)
+    return_set_error(-1, RtlNtStatusToDosError(status));
+
+  if (CreateIoCompletionPort(afd_helper_handle, iocp, 0, 0) == NULL)
+    goto error;
+
+  if (!SetFileCompletionNotificationModes(afd_helper_handle,
+                                          FILE_SKIP_SET_EVENT_ON_HANDLE))
+    goto error;
+
+  *afd_helper_handle_out = afd_helper_handle;
+  return 0;
+
+error:
+  CloseHandle(afd_helper_handle);
+  return_map_error(-1);
+}
+
+int afd_poll(HANDLE afd_helper_handle,
+             AFD_POLL_INFO* poll_info,
+             OVERLAPPED* overlapped) {
+  IO_STATUS_BLOCK* iosb;
+  HANDLE event;
+  void* apc_context;
+  NTSTATUS status;
+
+  /* Blocking operation is not supported. */
+  assert(overlapped != NULL);
+
+  iosb = (IO_STATUS_BLOCK*) &overlapped->Internal;
+  event = overlapped->hEvent;
+
+  /* Do what other windows APIs would do: if hEvent has it's lowest bit set,
+   * don't post a completion to the completion port. */
+  if ((uintptr_t) event & 1) {
+    event = (HANDLE)((uintptr_t) event & ~(uintptr_t) 1);
+    apc_context = NULL;
+  } else {
+    apc_context = overlapped;
+  }
+
+  iosb->Status = STATUS_PENDING;
+  status = NtDeviceIoControlFile(afd_helper_handle,
+                                 event,
+                                 NULL,
+                                 apc_context,
+                                 iosb,
+                                 IOCTL_AFD_POLL,
+                                 poll_info,
+                                 sizeof *poll_info,
+                                 poll_info,
+                                 sizeof *poll_info);
+
+  if (status == STATUS_SUCCESS)
+    return 0;
+  else if (status == STATUS_PENDING)
+    return_set_error(-1, ERROR_IO_PENDING);
+  else
+    return_set_error(-1, RtlNtStatusToDosError(status));
+}
+
+WEPOLL_INTERNAL int epoll_global_init(void);
+
+WEPOLL_INTERNAL int init(void);
+
+#include <stdbool.h>
+
+typedef struct queue_node queue_node_t;
+
+typedef struct queue_node {
+  queue_node_t* prev;
+  queue_node_t* next;
+} queue_node_t;
+
+typedef struct queue {
+  queue_node_t head;
+} queue_t;
+
+WEPOLL_INTERNAL void queue_init(queue_t* queue);
+WEPOLL_INTERNAL void queue_node_init(queue_node_t* node);
+
+WEPOLL_INTERNAL queue_node_t* queue_first(const queue_t* queue);
+WEPOLL_INTERNAL queue_node_t* queue_last(const queue_t* queue);
+
+WEPOLL_INTERNAL void queue_prepend(queue_t* queue, queue_node_t* node);
+WEPOLL_INTERNAL void queue_append(queue_t* queue, queue_node_t* node);
+WEPOLL_INTERNAL void queue_move_first(queue_t* queue, queue_node_t* node);
+WEPOLL_INTERNAL void queue_move_last(queue_t* queue, queue_node_t* node);
+WEPOLL_INTERNAL void queue_remove(queue_node_t* node);
+
+WEPOLL_INTERNAL bool queue_empty(const queue_t* queue);
+WEPOLL_INTERNAL bool queue_enqueued(const queue_node_t* node);
+
+typedef struct port_state port_state_t;
+typedef struct poll_group poll_group_t;
+
+WEPOLL_INTERNAL poll_group_t* poll_group_acquire(port_state_t* port);
+WEPOLL_INTERNAL void poll_group_release(poll_group_t* poll_group);
+
+WEPOLL_INTERNAL void poll_group_delete(poll_group_t* poll_group);
+
+WEPOLL_INTERNAL poll_group_t* poll_group_from_queue_node(
+    queue_node_t* queue_node);
+WEPOLL_INTERNAL HANDLE
+    poll_group_get_afd_helper_handle(poll_group_t* poll_group);
+
+/* N.b.: the tree functions do not set errno or LastError when they fail. Each
+ * of the API functions has at most one failure mode. It is up to the caller to
+ * set an appropriate error code when necessary. */
+
+typedef struct tree tree_t;
+typedef struct tree_node tree_node_t;
+
+typedef struct tree {
+  tree_node_t* root;
+} tree_t;
+
+typedef struct tree_node {
+  tree_node_t* left;
+  tree_node_t* right;
+  tree_node_t* parent;
+  uintptr_t key;
+  bool red;
+} tree_node_t;
+
+WEPOLL_INTERNAL void tree_init(tree_t* tree);
+WEPOLL_INTERNAL void tree_node_init(tree_node_t* node);
+
+WEPOLL_INTERNAL int tree_add(tree_t* tree, tree_node_t* node, uintptr_t key);
+WEPOLL_INTERNAL void tree_del(tree_t* tree, tree_node_t* node);
+
+WEPOLL_INTERNAL tree_node_t* tree_find(const tree_t* tree, uintptr_t key);
+WEPOLL_INTERNAL tree_node_t* tree_root(const tree_t* tree);
+
+typedef struct port_state port_state_t;
+typedef struct sock_state sock_state_t;
+
+WEPOLL_INTERNAL sock_state_t* sock_new(port_state_t* port_state,
+                                       SOCKET socket);
+WEPOLL_INTERNAL void sock_delete(port_state_t* port_state,
+                                 sock_state_t* sock_state);
+WEPOLL_INTERNAL void sock_force_delete(port_state_t* port_state,
+                                       sock_state_t* sock_state);
+
+WEPOLL_INTERNAL int sock_set_event(port_state_t* port_state,
+                                   sock_state_t* sock_state,
+                                   const struct epoll_event* ev);
+
+WEPOLL_INTERNAL int sock_update(port_state_t* port_state,
+                                sock_state_t* sock_state);
+WEPOLL_INTERNAL int sock_feed_event(port_state_t* port_state,
+                                    OVERLAPPED* overlapped,
+                                    struct epoll_event* ev);
+
+WEPOLL_INTERNAL sock_state_t* sock_state_from_queue_node(
+    queue_node_t* queue_node);
+WEPOLL_INTERNAL queue_node_t* sock_state_to_queue_node(
+    sock_state_t* sock_state);
+WEPOLL_INTERNAL sock_state_t* sock_state_from_tree_node(
+    tree_node_t* tree_node);
+WEPOLL_INTERNAL tree_node_t* sock_state_to_tree_node(sock_state_t* sock_state);
+
+/* The reflock is a special kind of lock that normally prevents a chunk of
+ * memory from being freed, but does allow the chunk of memory to eventually be
+ * released in a coordinated fashion.
+ *
+ * Under normal operation, threads increase and decrease the reference count,
+ * which are wait-free operations.
+ *
+ * Exactly once during the reflock's lifecycle, a thread holding a reference to
+ * the lock may "destroy" the lock; this operation blocks until all other
+ * threads holding a reference to the lock have dereferenced it. After
+ * "destroy" returns, the calling thread may assume that no other threads have
+ * a reference to the lock.
+ *
+ * Attemmpting to lock or destroy a lock after reflock_unref_and_destroy() has
+ * been called is invalid and results in undefined behavior. Therefore the user
+ * should use another lock to guarantee that this can't happen.
+ */
+
+typedef struct reflock {
+  volatile long state; /* 32-bit Interlocked APIs operate on `long` values. */
+} reflock_t;
+
+WEPOLL_INTERNAL int reflock_global_init(void);
+
+WEPOLL_INTERNAL void reflock_init(reflock_t* reflock);
+WEPOLL_INTERNAL void reflock_ref(reflock_t* reflock);
+WEPOLL_INTERNAL void reflock_unref(reflock_t* reflock);
+WEPOLL_INTERNAL void reflock_unref_and_destroy(reflock_t* reflock);
+
+typedef struct ts_tree {
+  tree_t tree;
+  SRWLOCK lock;
+} ts_tree_t;
+
+typedef struct ts_tree_node {
+  tree_node_t tree_node;
+  reflock_t reflock;
+} ts_tree_node_t;
+
+WEPOLL_INTERNAL void ts_tree_init(ts_tree_t* rtl);
+WEPOLL_INTERNAL void ts_tree_node_init(ts_tree_node_t* node);
+
+WEPOLL_INTERNAL int ts_tree_add(ts_tree_t* ts_tree,
+                                ts_tree_node_t* node,
+                                uintptr_t key);
+
+WEPOLL_INTERNAL ts_tree_node_t* ts_tree_del_and_ref(ts_tree_t* ts_tree,
+                                                    uintptr_t key);
+WEPOLL_INTERNAL ts_tree_node_t* ts_tree_find_and_ref(ts_tree_t* ts_tree,
+                                                     uintptr_t key);
+
+WEPOLL_INTERNAL void ts_tree_node_unref(ts_tree_node_t* node);
+WEPOLL_INTERNAL void ts_tree_node_unref_and_destroy(ts_tree_node_t* node);
+
+typedef struct port_state port_state_t;
+typedef struct sock_state sock_state_t;
+
+typedef struct port_state {
+  HANDLE iocp;
+  tree_t sock_tree;
+  queue_t sock_update_queue;
+  queue_t sock_deleted_queue;
+  queue_t poll_group_queue;
+  ts_tree_node_t handle_tree_node;
+  CRITICAL_SECTION lock;
+  size_t active_poll_count;
+} port_state_t;
+
+WEPOLL_INTERNAL port_state_t* port_new(HANDLE* iocp_out);
+WEPOLL_INTERNAL int port_close(port_state_t* port_state);
+WEPOLL_INTERNAL int port_delete(port_state_t* port_state);
+
+WEPOLL_INTERNAL int port_wait(port_state_t* port_state,
+                              struct epoll_event* events,
+                              int maxevents,
+                              int timeout);
+
+WEPOLL_INTERNAL int port_ctl(port_state_t* port_state,
+                             int op,
+                             SOCKET sock,
+                             struct epoll_event* ev);
+
+WEPOLL_INTERNAL int port_register_socket_handle(port_state_t* port_state,
+                                                sock_state_t* sock_state,
+                                                SOCKET socket);
+WEPOLL_INTERNAL void port_unregister_socket_handle(port_state_t* port_state,
+                                                   sock_state_t* sock_state);
+WEPOLL_INTERNAL sock_state_t* port_find_socket(port_state_t* port_state,
+                                               SOCKET socket);
+
+WEPOLL_INTERNAL void port_request_socket_update(port_state_t* port_state,
+                                                sock_state_t* sock_state);
+WEPOLL_INTERNAL void port_cancel_socket_update(port_state_t* port_state,
+                                               sock_state_t* sock_state);
+
+WEPOLL_INTERNAL void port_add_deleted_socket(port_state_t* port_state,
+                                             sock_state_t* sock_state);
+WEPOLL_INTERNAL void port_remove_deleted_socket(port_state_t* port_state,
+                                                sock_state_t* sock_state);
+
+static ts_tree_t epoll__handle_tree;
+
+static inline port_state_t* epoll__handle_tree_node_to_port(
+    ts_tree_node_t* tree_node) {
+  return container_of(tree_node, port_state_t, handle_tree_node);
+}
+
+int epoll_global_init(void) {
+  ts_tree_init(&epoll__handle_tree);
+  return 0;
+}
+
+static HANDLE epoll__create(void) {
+  port_state_t* port_state;
+  HANDLE ephnd;
+
+  if (init() < 0)
+    return NULL;
+
+  port_state = port_new(&ephnd);
+  if (port_state == NULL)
+    return NULL;
+
+  if (ts_tree_add(&epoll__handle_tree,
+                  &port_state->handle_tree_node,
+                  (uintptr_t) ephnd) < 0) {
+    /* This should never happen. */
+    port_delete(port_state);
+    return_set_error(NULL, ERROR_ALREADY_EXISTS);
+  }
+
+  return ephnd;
+}
+
+HANDLE epoll_create(int size) {
+  if (size <= 0)
+    return_set_error(NULL, ERROR_INVALID_PARAMETER);
+
+  return epoll__create();
+}
+
+HANDLE epoll_create1(int flags) {
+  if (flags != 0)
+    return_set_error(NULL, ERROR_INVALID_PARAMETER);
+
+  return epoll__create();
+}
+
+int epoll_close(HANDLE ephnd) {
+  ts_tree_node_t* tree_node;
+  port_state_t* port_state;
+
+  if (init() < 0)
+    return -1;
+
+  tree_node = ts_tree_del_and_ref(&epoll__handle_tree, (uintptr_t) ephnd);
+  if (tree_node == NULL) {
+    err_set_win_error(ERROR_INVALID_PARAMETER);
+    goto err;
+  }
+
+  port_state = epoll__handle_tree_node_to_port(tree_node);
+  port_close(port_state);
+
+  ts_tree_node_unref_and_destroy(tree_node);
+
+  return port_delete(port_state);
+
+err:
+  err_check_handle(ephnd);
+  return -1;
+}
+
+int epoll_ctl(HANDLE ephnd, int op, SOCKET sock, struct epoll_event* ev) {
+  ts_tree_node_t* tree_node;
+  port_state_t* port_state;
+  int r;
+
+  if (init() < 0)
+    return -1;
+
+  tree_node = ts_tree_find_and_ref(&epoll__handle_tree, (uintptr_t) ephnd);
+  if (tree_node == NULL) {
+    err_set_win_error(ERROR_INVALID_PARAMETER);
+    goto err;
+  }
+
+  port_state = epoll__handle_tree_node_to_port(tree_node);
+  r = port_ctl(port_state, op, sock, ev);
+
+  ts_tree_node_unref(tree_node);
+
+  if (r < 0)
+    goto err;
+
+  return 0;
+
+err:
+  /* On Linux, in the case of epoll_ctl_mod(), EBADF takes priority over other
+   * errors. Wepoll mimics this behavior. */
+  err_check_handle(ephnd);
+  err_check_handle((HANDLE) sock);
+  return -1;
+}
+
+int epoll_wait(HANDLE ephnd,
+               struct epoll_event* events,
+               int maxevents,
+               int timeout) {
+  ts_tree_node_t* tree_node;
+  port_state_t* port_state;
+  int num_events;
+
+  if (maxevents <= 0)
+    return_set_error(-1, ERROR_INVALID_PARAMETER);
+
+  if (init() < 0)
+    return -1;
+
+  tree_node = ts_tree_find_and_ref(&epoll__handle_tree, (uintptr_t) ephnd);
+  if (tree_node == NULL) {
+    err_set_win_error(ERROR_INVALID_PARAMETER);
+    goto err;
+  }
+
+  port_state = epoll__handle_tree_node_to_port(tree_node);
+  num_events = port_wait(port_state, events, maxevents, timeout);
+
+  ts_tree_node_unref(tree_node);
+
+  if (num_events < 0)
+    goto err;
+
+  return num_events;
+
+err:
+  err_check_handle(ephnd);
+  return -1;
+}
+
+#include <errno.h>
+
+#define ERR__ERRNO_MAPPINGS(X)               \
+  X(ERROR_ACCESS_DENIED, EACCES)             \
+  X(ERROR_ALREADY_EXISTS, EEXIST)            \
+  X(ERROR_BAD_COMMAND, EACCES)               \
+  X(ERROR_BAD_EXE_FORMAT, ENOEXEC)           \
+  X(ERROR_BAD_LENGTH, EACCES)                \
+  X(ERROR_BAD_NETPATH, ENOENT)               \
+  X(ERROR_BAD_NET_NAME, ENOENT)              \
+  X(ERROR_BAD_NET_RESP, ENETDOWN)            \
+  X(ERROR_BAD_PATHNAME, ENOENT)              \
+  X(ERROR_BROKEN_PIPE, EPIPE)                \
+  X(ERROR_CANNOT_MAKE, EACCES)               \
+  X(ERROR_COMMITMENT_LIMIT, ENOMEM)          \
+  X(ERROR_CONNECTION_ABORTED, ECONNABORTED)  \
+  X(ERROR_CONNECTION_ACTIVE, EISCONN)        \
+  X(ERROR_CONNECTION_REFUSED, ECONNREFUSED)  \
+  X(ERROR_CRC, EACCES)                       \
+  X(ERROR_DIR_NOT_EMPTY, ENOTEMPTY)          \
+  X(ERROR_DISK_FULL, ENOSPC)                 \
+  X(ERROR_DUP_NAME, EADDRINUSE)              \
+  X(ERROR_FILENAME_EXCED_RANGE, ENOENT)      \
+  X(ERROR_FILE_NOT_FOUND, ENOENT)            \
+  X(ERROR_GEN_FAILURE, EACCES)               \
+  X(ERROR_GRACEFUL_DISCONNECT, EPIPE)        \
+  X(ERROR_HOST_DOWN, EHOSTUNREACH)           \
+  X(ERROR_HOST_UNREACHABLE, EHOSTUNREACH)    \
+  X(ERROR_INSUFFICIENT_BUFFER, EFAULT)       \
+  X(ERROR_INVALID_ADDRESS, EADDRNOTAVAIL)    \
+  X(ERROR_INVALID_FUNCTION, EINVAL)          \
+  X(ERROR_INVALID_HANDLE, EBADF)             \
+  X(ERROR_INVALID_NETNAME, EADDRNOTAVAIL)    \
+  X(ERROR_INVALID_PARAMETER, EINVAL)         \
+  X(ERROR_INVALID_USER_BUFFER, EMSGSIZE)     \
+  X(ERROR_IO_PENDING, EINPROGRESS)           \
+  X(ERROR_LOCK_VIOLATION, EACCES)            \
+  X(ERROR_MORE_DATA, EMSGSIZE)               \
+  X(ERROR_NETNAME_DELETED, ECONNABORTED)     \
+  X(ERROR_NETWORK_ACCESS_DENIED, EACCES)     \
+  X(ERROR_NETWORK_BUSY, ENETDOWN)            \
+  X(ERROR_NETWORK_UNREACHABLE, ENETUNREACH)  \
+  X(ERROR_NOACCESS, EFAULT)                  \
+  X(ERROR_NONPAGED_SYSTEM_RESOURCES, ENOMEM) \
+  X(ERROR_NOT_ENOUGH_MEMORY, ENOMEM)         \
+  X(ERROR_NOT_ENOUGH_QUOTA, ENOMEM)          \
+  X(ERROR_NOT_FOUND, ENOENT)                 \
+  X(ERROR_NOT_LOCKED, EACCES)                \
+  X(ERROR_NOT_READY, EACCES)                 \
+  X(ERROR_NOT_SAME_DEVICE, EXDEV)            \
+  X(ERROR_NOT_SUPPORTED, ENOTSUP)            \
+  X(ERROR_NO_MORE_FILES, ENOENT)             \
+  X(ERROR_NO_SYSTEM_RESOURCES, ENOMEM)       \
+  X(ERROR_OPERATION_ABORTED, EINTR)          \
+  X(ERROR_OUT_OF_PAPER, EACCES)              \
+  X(ERROR_PAGED_SYSTEM_RESOURCES, ENOMEM)    \
+  X(ERROR_PAGEFILE_QUOTA, ENOMEM)            \
+  X(ERROR_PATH_NOT_FOUND, ENOENT)            \
+  X(ERROR_PIPE_NOT_CONNECTED, EPIPE)         \
+  X(ERROR_PORT_UNREACHABLE, ECONNRESET)      \
+  X(ERROR_PROTOCOL_UNREACHABLE, ENETUNREACH) \
+  X(ERROR_REM_NOT_LIST, ECONNREFUSED)        \
+  X(ERROR_REQUEST_ABORTED, EINTR)            \
+  X(ERROR_REQ_NOT_ACCEP, EWOULDBLOCK)        \
+  X(ERROR_SECTOR_NOT_FOUND, EACCES)          \
+  X(ERROR_SEM_TIMEOUT, ETIMEDOUT)            \
+  X(ERROR_SHARING_VIOLATION, EACCES)         \
+  X(ERROR_TOO_MANY_NAMES, ENOMEM)            \
+  X(ERROR_TOO_MANY_OPEN_FILES, EMFILE)       \
+  X(ERROR_UNEXP_NET_ERR, ECONNABORTED)       \
+  X(ERROR_WAIT_NO_CHILDREN, ECHILD)          \
+  X(ERROR_WORKING_SET_QUOTA, ENOMEM)         \
+  X(ERROR_WRITE_PROTECT, EACCES)             \
+  X(ERROR_WRONG_DISK, EACCES)                \
+  X(WSAEACCES, EACCES)                       \
+  X(WSAEADDRINUSE, EADDRINUSE)               \
+  X(WSAEADDRNOTAVAIL, EADDRNOTAVAIL)         \
+  X(WSAEAFNOSUPPORT, EAFNOSUPPORT)           \
+  X(WSAECONNABORTED, ECONNABORTED)           \
+  X(WSAECONNREFUSED, ECONNREFUSED)           \
+  X(WSAECONNRESET, ECONNRESET)               \
+  X(WSAEDISCON, EPIPE)                       \
+  X(WSAEFAULT, EFAULT)                       \
+  X(WSAEHOSTDOWN, EHOSTUNREACH)              \
+  X(WSAEHOSTUNREACH, EHOSTUNREACH)           \
+  X(WSAEINPROGRESS, EBUSY)                   \
+  X(WSAEINTR, EINTR)                         \
+  X(WSAEINVAL, EINVAL)                       \
+  X(WSAEISCONN, EISCONN)                     \
+  X(WSAEMSGSIZE, EMSGSIZE)                   \
+  X(WSAENETDOWN, ENETDOWN)                   \
+  X(WSAENETRESET, EHOSTUNREACH)              \
+  X(WSAENETUNREACH, ENETUNREACH)             \
+  X(WSAENOBUFS, ENOMEM)                      \
+  X(WSAENOTCONN, ENOTCONN)                   \
+  X(WSAENOTSOCK, ENOTSOCK)                   \
+  X(WSAEOPNOTSUPP, EOPNOTSUPP)               \
+  X(WSAEPROCLIM, ENOMEM)                     \
+  X(WSAESHUTDOWN, EPIPE)                     \
+  X(WSAETIMEDOUT, ETIMEDOUT)                 \
+  X(WSAEWOULDBLOCK, EWOULDBLOCK)             \
+  X(WSANOTINITIALISED, ENETDOWN)             \
+  X(WSASYSNOTREADY, ENETDOWN)                \
+  X(WSAVERNOTSUPPORTED, ENOSYS)
+
+static errno_t err__map_win_error_to_errno(DWORD error) {
+  switch (error) {
+#define X(error_sym, errno_sym) \
+  case error_sym:               \
+    return errno_sym;
+    ERR__ERRNO_MAPPINGS(X)
+#undef X
+  }
+  return EINVAL;
+}
+
+void err_map_win_error(void) {
+  errno = err__map_win_error_to_errno(GetLastError());
+}
+
+void err_set_win_error(DWORD error) {
+  SetLastError(error);
+  errno = err__map_win_error_to_errno(error);
+}
+
+int err_check_handle(HANDLE handle) {
+  DWORD flags;
+
+  /* GetHandleInformation() succeeds when passed INVALID_HANDLE_VALUE, so check
+   * for this condition explicitly. */
+  if (handle == INVALID_HANDLE_VALUE)
+    return_set_error(-1, ERROR_INVALID_HANDLE);
+
+  if (!GetHandleInformation(handle, &flags))
+    return_map_error(-1);
+
+  return 0;
+}
+
+static bool init__done = false;
+static INIT_ONCE init__once = INIT_ONCE_STATIC_INIT;
+
+static BOOL CALLBACK init__once_callback(INIT_ONCE* once,
+                                         void* parameter,
+                                         void** context) {
+  unused_var(once);
+  unused_var(parameter);
+  unused_var(context);
+
+  /* N.b. that initialization order matters here. */
+  if (ws_global_init() < 0 || nt_global_init() < 0 ||
+      reflock_global_init() < 0 || epoll_global_init() < 0)
+    return FALSE;
+
+  init__done = true;
+  return TRUE;
+}
+
+int init(void) {
+  if (!init__done &&
+      !InitOnceExecuteOnce(&init__once, init__once_callback, NULL, NULL))
+    /* `InitOnceExecuteOnce()` itself is infallible, and it doesn't set any
+     * error code when the once-callback returns FALSE. We return -1 here to
+     * indicate that global initialization failed; the failing init function is
+     * resposible for setting `errno` and calling `SetLastError()`. */
+    return -1;
+
+  return 0;
+}
+
+/* Set up a workaround for the following problem:
+ *   FARPROC addr = GetProcAddress(...);
+ *   MY_FUNC func = (MY_FUNC) addr;          <-- GCC 8 warning/error.
+ *   MY_FUNC func = (MY_FUNC) (void*) addr;  <-- MSVC  warning/error.
+ * To compile cleanly with either compiler, do casts with this "bridge" type:
+ *   MY_FUNC func = (MY_FUNC) (nt__fn_ptr_cast_t) addr; */
+#ifdef __GNUC__
+typedef void* nt__fn_ptr_cast_t;
+#else
+typedef FARPROC nt__fn_ptr_cast_t;
+#endif
+
+#define X(return_type, attributes, name, parameters) \
+  WEPOLL_INTERNAL return_type(attributes* name) parameters = NULL;
+NT_NTDLL_IMPORT_LIST(X)
+#undef X
+
+int nt_global_init(void) {
+  HMODULE ntdll;
+  FARPROC fn_ptr;
+
+  ntdll = GetModuleHandleW(L"ntdll.dll");
+  if (ntdll == NULL)
+    return -1;
+
+#define X(return_type, attributes, name, parameters) \
+  fn_ptr = GetProcAddress(ntdll, #name);             \
+  if (fn_ptr == NULL)                                \
+    return -1;                                       \
+  name = (return_type(attributes*) parameters)(nt__fn_ptr_cast_t) fn_ptr;
+  NT_NTDLL_IMPORT_LIST(X)
+#undef X
+
+  return 0;
+}
+
+#include <string.h>
+
+static const size_t POLL_GROUP__MAX_GROUP_SIZE = 32;
+
+typedef struct poll_group {
+  port_state_t* port_state;
+  queue_node_t queue_node;
+  HANDLE afd_helper_handle;
+  size_t group_size;
+} poll_group_t;
+
+static poll_group_t* poll_group__new(port_state_t* port_state) {
+  poll_group_t* poll_group = malloc(sizeof *poll_group);
+  if (poll_group == NULL)
+    return_set_error(NULL, ERROR_NOT_ENOUGH_MEMORY);
+
+  memset(poll_group, 0, sizeof *poll_group);
+
+  queue_node_init(&poll_group->queue_node);
+  poll_group->port_state = port_state;
+
+  if (afd_create_helper_handle(port_state->iocp,
+                               &poll_group->afd_helper_handle) < 0) {
+    free(poll_group);
+    return NULL;
+  }
+
+  queue_append(&port_state->poll_group_queue, &poll_group->queue_node);
+
+  return poll_group;
+}
+
+void poll_group_delete(poll_group_t* poll_group) {
+  assert(poll_group->group_size == 0);
+  CloseHandle(poll_group->afd_helper_handle);
+  queue_remove(&poll_group->queue_node);
+  free(poll_group);
+}
+
+poll_group_t* poll_group_from_queue_node(queue_node_t* queue_node) {
+  return container_of(queue_node, poll_group_t, queue_node);
+}
+
+HANDLE poll_group_get_afd_helper_handle(poll_group_t* poll_group) {
+  return poll_group->afd_helper_handle;
+}
+
+poll_group_t* poll_group_acquire(port_state_t* port_state) {
+  queue_t* queue = &port_state->poll_group_queue;
+  poll_group_t* poll_group =
+      !queue_empty(queue)
+          ? container_of(queue_last(queue), poll_group_t, queue_node)
+          : NULL;
+
+  if (poll_group == NULL ||
+      poll_group->group_size >= POLL_GROUP__MAX_GROUP_SIZE)
+    poll_group = poll_group__new(port_state);
+  if (poll_group == NULL)
+    return NULL;
+
+  if (++poll_group->group_size == POLL_GROUP__MAX_GROUP_SIZE)
+    queue_move_first(&port_state->poll_group_queue, &poll_group->queue_node);
+
+  return poll_group;
+}
+
+void poll_group_release(poll_group_t* poll_group) {
+  port_state_t* port_state = poll_group->port_state;
+
+  poll_group->group_size--;
+  assert(poll_group->group_size < POLL_GROUP__MAX_GROUP_SIZE);
+
+  queue_move_last(&port_state->poll_group_queue, &poll_group->queue_node);
+
+  /* Poll groups are currently only freed when the epoll port is closed. */
+}
+
+#define PORT__MAX_ON_STACK_COMPLETIONS 256
+
+static port_state_t* port__alloc(void) {
+  port_state_t* port_state = malloc(sizeof *port_state);
+  if (port_state == NULL)
+    return_set_error(NULL, ERROR_NOT_ENOUGH_MEMORY);
+
+  return port_state;
+}
+
+static void port__free(port_state_t* port) {
+  assert(port != NULL);
+  free(port);
+}
+
+static HANDLE port__create_iocp(void) {
+  HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
+  if (iocp == NULL)
+    return_map_error(NULL);
+
+  return iocp;
+}
+
+port_state_t* port_new(HANDLE* iocp_out) {
+  port_state_t* port_state;
+  HANDLE iocp;
+
+  port_state = port__alloc();
+  if (port_state == NULL)
+    goto err1;
+
+  iocp = port__create_iocp();
+  if (iocp == NULL)
+    goto err2;
+
+  memset(port_state, 0, sizeof *port_state);
+
+  port_state->iocp = iocp;
+  tree_init(&port_state->sock_tree);
+  queue_init(&port_state->sock_update_queue);
+  queue_init(&port_state->sock_deleted_queue);
+  queue_init(&port_state->poll_group_queue);
+  ts_tree_node_init(&port_state->handle_tree_node);
+  InitializeCriticalSection(&port_state->lock);
+
+  *iocp_out = iocp;
+  return port_state;
+
+err2:
+  port__free(port_state);
+err1:
+  return NULL;
+}
+
+static int port__close_iocp(port_state_t* port_state) {
+  HANDLE iocp = port_state->iocp;
+  port_state->iocp = NULL;
+
+  if (!CloseHandle(iocp))
+    return_map_error(-1);
+
+  return 0;
+}
+
+int port_close(port_state_t* port_state) {
+  int result;
+
+  EnterCriticalSection(&port_state->lock);
+  result = port__close_iocp(port_state);
+  LeaveCriticalSection(&port_state->lock);
+
+  return result;
+}
+
+int port_delete(port_state_t* port_state) {
+  tree_node_t* tree_node;
+  queue_node_t* queue_node;
+
+  /* At this point the IOCP port should have been closed. */
+  assert(port_state->iocp == NULL);
+
+  while ((tree_node = tree_root(&port_state->sock_tree)) != NULL) {
+    sock_state_t* sock_state = sock_state_from_tree_node(tree_node);
+    sock_force_delete(port_state, sock_state);
+  }
+
+  while ((queue_node = queue_first(&port_state->sock_deleted_queue)) != NULL) {
+    sock_state_t* sock_state = sock_state_from_queue_node(queue_node);
+    sock_force_delete(port_state, sock_state);
+  }
+
+  while ((queue_node = queue_first(&port_state->poll_group_queue)) != NULL) {
+    poll_group_t* poll_group = poll_group_from_queue_node(queue_node);
+    poll_group_delete(poll_group);
+  }
+
+  assert(queue_empty(&port_state->sock_update_queue));
+
+  DeleteCriticalSection(&port_state->lock);
+
+  port__free(port_state);
+
+  return 0;
+}
+
+static int port__update_events(port_state_t* port_state) {
+  queue_t* sock_update_queue = &port_state->sock_update_queue;
+
+  /* Walk the queue, submitting new poll requests for every socket that needs
+   * it. */
+  while (!queue_empty(sock_update_queue)) {
+    queue_node_t* queue_node = queue_first(sock_update_queue);
+    sock_state_t* sock_state = sock_state_from_queue_node(queue_node);
+
+    if (sock_update(port_state, sock_state) < 0)
+      return -1;
+
+    /* sock_update() removes the socket from the update queue. */
+  }
+
+  return 0;
+}
+
+static void port__update_events_if_polling(port_state_t* port_state) {
+  if (port_state->active_poll_count > 0)
+    port__update_events(port_state);
+}
+
+static int port__feed_events(port_state_t* port_state,
+                             struct epoll_event* epoll_events,
+                             OVERLAPPED_ENTRY* iocp_events,
+                             DWORD iocp_event_count) {
+  int epoll_event_count = 0;
+  DWORD i;
+
+  for (i = 0; i < iocp_event_count; i++) {
+    OVERLAPPED* overlapped = iocp_events[i].lpOverlapped;
+    struct epoll_event* ev = &epoll_events[epoll_event_count];
+
+    epoll_event_count += sock_feed_event(port_state, overlapped, ev);
+  }
+
+  return epoll_event_count;
+}
+
+static int port__poll(port_state_t* port_state,
+                      struct epoll_event* epoll_events,
+                      OVERLAPPED_ENTRY* iocp_events,
+                      DWORD maxevents,
+                      DWORD timeout) {
+  DWORD completion_count;
+
+  if (port__update_events(port_state) < 0)
+    return -1;
+
+  port_state->active_poll_count++;
+
+  LeaveCriticalSection(&port_state->lock);
+
+  BOOL r = GetQueuedCompletionStatusEx(port_state->iocp,
+                                       iocp_events,
+                                       maxevents,
+                                       &completion_count,
+                                       timeout,
+                                       FALSE);
+
+  EnterCriticalSection(&port_state->lock);
+
+  port_state->active_poll_count--;
+
+  if (!r)
+    return_map_error(-1);
+
+  return port__feed_events(
+      port_state, epoll_events, iocp_events, completion_count);
+}
+
+int port_wait(port_state_t* port_state,
+              struct epoll_event* events,
+              int maxevents,
+              int timeout) {
+  OVERLAPPED_ENTRY stack_iocp_events[PORT__MAX_ON_STACK_COMPLETIONS];
+  OVERLAPPED_ENTRY* iocp_events;
+  uint64_t due = 0;
+  DWORD gqcs_timeout;
+  int result;
+
+  /* Check whether `maxevents` is in range. */
+  if (maxevents <= 0)
+    return_set_error(-1, ERROR_INVALID_PARAMETER);
+
+  /* Decide whether the IOCP completion list can live on the stack, or allocate
+   * memory for it on the heap. */
+  if ((size_t) maxevents <= array_count(stack_iocp_events)) {
+    iocp_events = stack_iocp_events;
+  } else if ((iocp_events =
+                  malloc((size_t) maxevents * sizeof *iocp_events)) == NULL) {
+    iocp_events = stack_iocp_events;
+    maxevents = array_count(stack_iocp_events);
+  }
+
+  /* Compute the timeout for GetQueuedCompletionStatus, and the wait end
+   * time, if the user specified a timeout other than zero or infinite. */
+  if (timeout > 0) {
+    due = GetTickCount64() + (uint64_t) timeout;
+    gqcs_timeout = (DWORD) timeout;
+  } else if (timeout == 0) {
+    gqcs_timeout = 0;
+  } else {
+    gqcs_timeout = INFINITE;
+  }
+
+  EnterCriticalSection(&port_state->lock);
+
+  /* Dequeue completion packets until either at least one interesting event
+   * has been discovered, or the timeout is reached. */
+  for (;;) {
+    uint64_t now;
+
+    result = port__poll(
+        port_state, events, iocp_events, (DWORD) maxevents, gqcs_timeout);
+    if (result < 0 || result > 0)
+      break; /* Result, error, or time-out. */
+
+    if (timeout < 0)
+      continue; /* When timeout is negative, never time out. */
+
+    /* Update time. */
+    now = GetTickCount64();
+
+    /* Do not allow the due time to be in the past. */
+    if (now >= due) {
+      SetLastError(WAIT_TIMEOUT);
+      break;
+    }
+
+    /* Recompute time-out argument for GetQueuedCompletionStatus. */
+    gqcs_timeout = (DWORD)(due - now);
+  }
+
+  port__update_events_if_polling(port_state);
+
+  LeaveCriticalSection(&port_state->lock);
+
+  if (iocp_events != stack_iocp_events)
+    free(iocp_events);
+
+  if (result >= 0)
+    return result;
+  else if (GetLastError() == WAIT_TIMEOUT)
+    return 0;
+  else
+    return -1;
+}
+
+static int port__ctl_add(port_state_t* port_state,
+                         SOCKET sock,
+                         struct epoll_event* ev) {
+  sock_state_t* sock_state = sock_new(port_state, sock);
+  if (sock_state == NULL)
+    return -1;
+
+  if (sock_set_event(port_state, sock_state, ev) < 0) {
+    sock_delete(port_state, sock_state);
+    return -1;
+  }
+
+  port__update_events_if_polling(port_state);
+
+  return 0;
+}
+
+static int port__ctl_mod(port_state_t* port_state,
+                         SOCKET sock,
+                         struct epoll_event* ev) {
+  sock_state_t* sock_state = port_find_socket(port_state, sock);
+  if (sock_state == NULL)
+    return -1;
+
+  if (sock_set_event(port_state, sock_state, ev) < 0)
+    return -1;
+
+  port__update_events_if_polling(port_state);
+
+  return 0;
+}
+
+static int port__ctl_del(port_state_t* port_state, SOCKET sock) {
+  sock_state_t* sock_state = port_find_socket(port_state, sock);
+  if (sock_state == NULL)
+    return -1;
+
+  sock_delete(port_state, sock_state);
+
+  return 0;
+}
+
+static int port__ctl_op(port_state_t* port_state,
+                        int op,
+                        SOCKET sock,
+                        struct epoll_event* ev) {
+  switch (op) {
+    case EPOLL_CTL_ADD:
+      return port__ctl_add(port_state, sock, ev);
+    case EPOLL_CTL_MOD:
+      return port__ctl_mod(port_state, sock, ev);
+    case EPOLL_CTL_DEL:
+      return port__ctl_del(port_state, sock);
+    default:
+      return_set_error(-1, ERROR_INVALID_PARAMETER);
+  }
+}
+
+int port_ctl(port_state_t* port_state,
+             int op,
+             SOCKET sock,
+             struct epoll_event* ev) {
+  int result;
+
+  EnterCriticalSection(&port_state->lock);
+  result = port__ctl_op(port_state, op, sock, ev);
+  LeaveCriticalSection(&port_state->lock);
+
+  return result;
+}
+
+int port_register_socket_handle(port_state_t* port_state,
+                                sock_state_t* sock_state,
+                                SOCKET socket) {
+  if (tree_add(&port_state->sock_tree,
+               sock_state_to_tree_node(sock_state),
+               socket) < 0)
+    return_set_error(-1, ERROR_ALREADY_EXISTS);
+  return 0;
+}
+
+void port_unregister_socket_handle(port_state_t* port_state,
+                                   sock_state_t* sock_state) {
+  tree_del(&port_state->sock_tree, sock_state_to_tree_node(sock_state));
+}
+
+sock_state_t* port_find_socket(port_state_t* port_state, SOCKET socket) {
+  tree_node_t* tree_node = tree_find(&port_state->sock_tree, socket);
+  if (tree_node == NULL)
+    return_set_error(NULL, ERROR_NOT_FOUND);
+  return sock_state_from_tree_node(tree_node);
+}
+
+void port_request_socket_update(port_state_t* port_state,
+                                sock_state_t* sock_state) {
+  if (queue_enqueued(sock_state_to_queue_node(sock_state)))
+    return;
+  queue_append(&port_state->sock_update_queue,
+               sock_state_to_queue_node(sock_state));
+}
+
+void port_cancel_socket_update(port_state_t* port_state,
+                               sock_state_t* sock_state) {
+  unused_var(port_state);
+  if (!queue_enqueued(sock_state_to_queue_node(sock_state)))
+    return;
+  queue_remove(sock_state_to_queue_node(sock_state));
+}
+
+void port_add_deleted_socket(port_state_t* port_state,
+                             sock_state_t* sock_state) {
+  if (queue_enqueued(sock_state_to_queue_node(sock_state)))
+    return;
+  queue_append(&port_state->sock_deleted_queue,
+               sock_state_to_queue_node(sock_state));
+}
+
+void port_remove_deleted_socket(port_state_t* port_state,
+                                sock_state_t* sock_state) {
+  unused_var(port_state);
+  if (!queue_enqueued(sock_state_to_queue_node(sock_state)))
+    return;
+  queue_remove(sock_state_to_queue_node(sock_state));
+}
+
+void queue_init(queue_t* queue) {
+  queue_node_init(&queue->head);
+}
+
+void queue_node_init(queue_node_t* node) {
+  node->prev = node;
+  node->next = node;
+}
+
+static inline void queue__detach_node(queue_node_t* node) {
+  node->prev->next = node->next;
+  node->next->prev = node->prev;
+}
+
+queue_node_t* queue_first(const queue_t* queue) {
+  return !queue_empty(queue) ? queue->head.next : NULL;
+}
+
+queue_node_t* queue_last(const queue_t* queue) {
+  return !queue_empty(queue) ? queue->head.prev : NULL;
+}
+
+void queue_prepend(queue_t* queue, queue_node_t* node) {
+  node->next = queue->head.next;
+  node->prev = &queue->head;
+  node->next->prev = node;
+  queue->head.next = node;
+}
+
+void queue_append(queue_t* queue, queue_node_t* node) {
+  node->next = &queue->head;
+  node->prev = queue->head.prev;
+  node->prev->next = node;
+  queue->head.prev = node;
+}
+
+void queue_move_first(queue_t* queue, queue_node_t* node) {
+  queue__detach_node(node);
+  queue_prepend(queue, node);
+}
+
+void queue_move_last(queue_t* queue, queue_node_t* node) {
+  queue__detach_node(node);
+  queue_append(queue, node);
+}
+
+void queue_remove(queue_node_t* node) {
+  queue__detach_node(node);
+  queue_node_init(node);
+}
+
+bool queue_empty(const queue_t* queue) {
+  return !queue_enqueued(&queue->head);
+}
+
+bool queue_enqueued(const queue_node_t* node) {
+  return node->prev != node;
+}
+
+static const long REFLOCK__REF          = (long) 0x00000001;
+static const long REFLOCK__REF_MASK     = (long) 0x0fffffff;
+static const long REFLOCK__DESTROY      = (long) 0x10000000;
+static const long REFLOCK__DESTROY_MASK = (long) 0xf0000000;
+static const long REFLOCK__POISON       = (long) 0x300dead0;
+
+static HANDLE reflock__keyed_event = NULL;
+
+int reflock_global_init(void) {
+  NTSTATUS status =
+      NtCreateKeyedEvent(&reflock__keyed_event, KEYEDEVENT_ALL_ACCESS, NULL, 0);
+  if (status != STATUS_SUCCESS)
+    return_set_error(-1, RtlNtStatusToDosError(status));
+  return 0;
+}
+
+void reflock_init(reflock_t* reflock) {
+  reflock->state = 0;
+}
+
+static void reflock__signal_event(void* address) {
+  NTSTATUS status =
+      NtReleaseKeyedEvent(reflock__keyed_event, address, FALSE, NULL);
+  if (status != STATUS_SUCCESS)
+    abort();
+}
+
+static void reflock__await_event(void* address) {
+  NTSTATUS status =
+      NtWaitForKeyedEvent(reflock__keyed_event, address, FALSE, NULL);
+  if (status != STATUS_SUCCESS)
+    abort();
+}
+
+void reflock_ref(reflock_t* reflock) {
+  long state = InterlockedAdd(&reflock->state, REFLOCK__REF);
+
+  /* Verify that the counter didn't overflow and the lock isn't destroyed. */
+  assert((state & REFLOCK__DESTROY_MASK) == 0);
+  unused_var(state);
+}
+
+void reflock_unref(reflock_t* reflock) {
+  long state = InterlockedAdd(&reflock->state, -REFLOCK__REF);
+
+  /* Verify that the lock was referenced and not already destroyed. */
+  assert((state & REFLOCK__DESTROY_MASK & ~REFLOCK__DESTROY) == 0);
+
+  if (state == REFLOCK__DESTROY)
+    reflock__signal_event(reflock);
+}
+
+void reflock_unref_and_destroy(reflock_t* reflock) {
+  long state =
+      InterlockedAdd(&reflock->state, REFLOCK__DESTROY - REFLOCK__REF);
+  long ref_count = state & REFLOCK__REF_MASK;
+
+  /* Verify that the lock was referenced and not already destroyed. */
+  assert((state & REFLOCK__DESTROY_MASK) == REFLOCK__DESTROY);
+
+  if (ref_count != 0)
+    reflock__await_event(reflock);
+
+  state = InterlockedExchange(&reflock->state, REFLOCK__POISON);
+  assert(state == REFLOCK__DESTROY);
+}
+
+static const uint32_t SOCK__KNOWN_EPOLL_EVENTS =
+    EPOLLIN | EPOLLPRI | EPOLLOUT | EPOLLERR | EPOLLHUP | EPOLLRDNORM |
+    EPOLLRDBAND | EPOLLWRNORM | EPOLLWRBAND | EPOLLMSG | EPOLLRDHUP;
+
+typedef enum sock__poll_status {
+  SOCK__POLL_IDLE = 0,
+  SOCK__POLL_PENDING,
+  SOCK__POLL_CANCELLED
+} sock__poll_status_t;
+
+typedef struct sock_state {
+  OVERLAPPED overlapped;
+  AFD_POLL_INFO poll_info;
+  queue_node_t queue_node;
+  tree_node_t tree_node;
+  poll_group_t* poll_group;
+  SOCKET base_socket;
+  epoll_data_t user_data;
+  uint32_t user_events;
+  uint32_t pending_events;
+  sock__poll_status_t poll_status;
+  bool delete_pending;
+} sock_state_t;
+
+static inline sock_state_t* sock__alloc(void) {
+  sock_state_t* sock_state = malloc(sizeof *sock_state);
+  if (sock_state == NULL)
+    return_set_error(NULL, ERROR_NOT_ENOUGH_MEMORY);
+  return sock_state;
+}
+
+static inline void sock__free(sock_state_t* sock_state) {
+  free(sock_state);
+}
+
+static int sock__cancel_poll(sock_state_t* sock_state) {
+  HANDLE afd_helper_handle =
+      poll_group_get_afd_helper_handle(sock_state->poll_group);
+  assert(sock_state->poll_status == SOCK__POLL_PENDING);
+
+  /* CancelIoEx() may fail with ERROR_NOT_FOUND if the overlapped operation has
+   * already completed. This is not a problem and we proceed normally. */
+  if (!HasOverlappedIoCompleted(&sock_state->overlapped) &&
+      !CancelIoEx(afd_helper_handle, &sock_state->overlapped) &&
+      GetLastError() != ERROR_NOT_FOUND)
+    return_map_error(-1);
+
+  sock_state->poll_status = SOCK__POLL_CANCELLED;
+  sock_state->pending_events = 0;
+  return 0;
+}
+
+sock_state_t* sock_new(port_state_t* port_state, SOCKET socket) {
+  SOCKET base_socket;
+  poll_group_t* poll_group;
+  sock_state_t* sock_state;
+
+  if (socket == 0 || socket == INVALID_SOCKET)
+    return_set_error(NULL, ERROR_INVALID_HANDLE);
+
+  base_socket = ws_get_base_socket(socket);
+  if (base_socket == INVALID_SOCKET)
+    return NULL;
+
+  poll_group = poll_group_acquire(port_state);
+  if (poll_group == NULL)
+    return NULL;
+
+  sock_state = sock__alloc();
+  if (sock_state == NULL)
+    goto err1;
+
+  memset(sock_state, 0, sizeof *sock_state);
+
+  sock_state->base_socket = base_socket;
+  sock_state->poll_group = poll_group;
+
+  tree_node_init(&sock_state->tree_node);
+  queue_node_init(&sock_state->queue_node);
+
+  if (port_register_socket_handle(port_state, sock_state, socket) < 0)
+    goto err2;
+
+  return sock_state;
+
+err2:
+  sock__free(sock_state);
+err1:
+  poll_group_release(poll_group);
+
+  return NULL;
+}
+
+static int sock__delete(port_state_t* port_state,
+                        sock_state_t* sock_state,
+                        bool force) {
+  if (!sock_state->delete_pending) {
+    if (sock_state->poll_status == SOCK__POLL_PENDING)
+      sock__cancel_poll(sock_state);
+
+    port_cancel_socket_update(port_state, sock_state);
+    port_unregister_socket_handle(port_state, sock_state);
+
+    sock_state->delete_pending = true;
+  }
+
+  /* If the poll request still needs to complete, the sock_state object can't
+   * be free()d yet. `sock_feed_event()` or `port_close()` will take care
+   * of this later. */
+  if (force || sock_state->poll_status == SOCK__POLL_IDLE) {
+    /* Free the sock_state now. */
+    port_remove_deleted_socket(port_state, sock_state);
+    poll_group_release(sock_state->poll_group);
+    sock__free(sock_state);
+  } else {
+    /* Free the socket later. */
+    port_add_deleted_socket(port_state, sock_state);
+  }
+
+  return 0;
+}
+
+void sock_delete(port_state_t* port_state, sock_state_t* sock_state) {
+  sock__delete(port_state, sock_state, false);
+}
+
+void sock_force_delete(port_state_t* port_state, sock_state_t* sock_state) {
+  sock__delete(port_state, sock_state, true);
+}
+
+int sock_set_event(port_state_t* port_state,
+                   sock_state_t* sock_state,
+                   const struct epoll_event* ev) {
+  /* EPOLLERR and EPOLLHUP are always reported, even when not requested by the
+   * caller. However they are disabled after a event has been reported for a
+   * socket for which the EPOLLONESHOT flag as set. */
+  uint32_t events = ev->events | EPOLLERR | EPOLLHUP;
+
+  sock_state->user_events = events;
+  sock_state->user_data = ev->data;
+
+  if ((events & SOCK__KNOWN_EPOLL_EVENTS & ~sock_state->pending_events) != 0)
+    port_request_socket_update(port_state, sock_state);
+
+  return 0;
+}
+
+static inline DWORD sock__epoll_events_to_afd_events(uint32_t epoll_events) {
+  /* Always monitor for AFD_POLL_LOCAL_CLOSE, which is triggered when the
+   * socket is closed with closesocket() or CloseHandle(). */
+  DWORD afd_events = AFD_POLL_LOCAL_CLOSE;
+
+  if (epoll_events & (EPOLLIN | EPOLLRDNORM))
+    afd_events |= AFD_POLL_RECEIVE | AFD_POLL_ACCEPT;
+  if (epoll_events & (EPOLLPRI | EPOLLRDBAND))
+    afd_events |= AFD_POLL_RECEIVE_EXPEDITED;
+  if (epoll_events & (EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND))
+    afd_events |= AFD_POLL_SEND;
+  if (epoll_events & (EPOLLIN | EPOLLRDNORM | EPOLLRDHUP))
+    afd_events |= AFD_POLL_DISCONNECT;
+  if (epoll_events & EPOLLHUP)
+    afd_events |= AFD_POLL_ABORT;
+  if (epoll_events & EPOLLERR)
+    afd_events |= AFD_POLL_CONNECT_FAIL;
+
+  return afd_events;
+}
+
+static inline uint32_t sock__afd_events_to_epoll_events(DWORD afd_events) {
+  uint32_t epoll_events = 0;
+
+  if (afd_events & (AFD_POLL_RECEIVE | AFD_POLL_ACCEPT))
+    epoll_events |= EPOLLIN | EPOLLRDNORM;
+  if (afd_events & AFD_POLL_RECEIVE_EXPEDITED)
+    epoll_events |= EPOLLPRI | EPOLLRDBAND;
+  if (afd_events & AFD_POLL_SEND)
+    epoll_events |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;
+  if (afd_events & AFD_POLL_DISCONNECT)
+    epoll_events |= EPOLLIN | EPOLLRDNORM | EPOLLRDHUP;
+  if (afd_events & AFD_POLL_ABORT)
+    epoll_events |= EPOLLHUP;
+  if (afd_events & AFD_POLL_CONNECT_FAIL)
+    /* Linux reports all these events after connect() has failed. */
+    epoll_events |=
+        EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLRDNORM | EPOLLWRNORM | EPOLLRDHUP;
+
+  return epoll_events;
+}
+
+int sock_update(port_state_t* port_state, sock_state_t* sock_state) {
+  assert(!sock_state->delete_pending);
+
+  if ((sock_state->poll_status == SOCK__POLL_PENDING) &&
+      (sock_state->user_events & SOCK__KNOWN_EPOLL_EVENTS &
+       ~sock_state->pending_events) == 0) {
+    /* All the events the user is interested in are already being monitored by
+     * the pending poll operation. It might spuriously complete because of an
+     * event that we're no longer interested in; when that happens we'll submit
+     * a new poll operation with the updated event mask. */
+
+  } else if (sock_state->poll_status == SOCK__POLL_PENDING) {
+    /* A poll operation is already pending, but it's not monitoring for all the
+     * events that the user is interested in. Therefore, cancel the pending
+     * poll operation; when we receive it's completion package, a new poll
+     * operation will be submitted with the correct event mask. */
+    if (sock__cancel_poll(sock_state) < 0)
+      return -1;
+
+  } else if (sock_state->poll_status == SOCK__POLL_CANCELLED) {
+    /* The poll operation has already been cancelled, we're still waiting for
+     * it to return. For now, there's nothing that needs to be done. */
+
+  } else if (sock_state->poll_status == SOCK__POLL_IDLE) {
+    /* No poll operation is pending; start one. */
+    sock_state->poll_info.Exclusive = FALSE;
+    sock_state->poll_info.NumberOfHandles = 1;
+    sock_state->poll_info.Timeout.QuadPart = INT64_MAX;
+    sock_state->poll_info.Handles[0].Handle = (HANDLE) sock_state->base_socket;
+    sock_state->poll_info.Handles[0].Status = 0;
+    sock_state->poll_info.Handles[0].Events =
+        sock__epoll_events_to_afd_events(sock_state->user_events);
+
+    memset(&sock_state->overlapped, 0, sizeof sock_state->overlapped);
+
+    if (afd_poll(poll_group_get_afd_helper_handle(sock_state->poll_group),
+                 &sock_state->poll_info,
+                 &sock_state->overlapped) < 0) {
+      switch (GetLastError()) {
+        case ERROR_IO_PENDING:
+          /* Overlapped poll operation in progress; this is expected. */
+          break;
+        case ERROR_INVALID_HANDLE:
+          /* Socket closed; it'll be dropped from the epoll set. */
+          return sock__delete(port_state, sock_state, false);
+        default:
+          /* Other errors are propagated to the caller. */
+          return_map_error(-1);
+      }
+    }
+
+    /* The poll request was successfully submitted. */
+    sock_state->poll_status = SOCK__POLL_PENDING;
+    sock_state->pending_events = sock_state->user_events;
+
+  } else {
+    /* Unreachable. */
+    assert(false);
+  }
+
+  port_cancel_socket_update(port_state, sock_state);
+  return 0;
+}
+
+int sock_feed_event(port_state_t* port_state,
+                    OVERLAPPED* overlapped,
+                    struct epoll_event* ev) {
+  sock_state_t* sock_state =
+      container_of(overlapped, sock_state_t, overlapped);
+  AFD_POLL_INFO* poll_info = &sock_state->poll_info;
+  uint32_t epoll_events = 0;
+
+  sock_state->poll_status = SOCK__POLL_IDLE;
+  sock_state->pending_events = 0;
+
+  if (sock_state->delete_pending) {
+    /* Socket has been deleted earlier and can now be freed. */
+    return sock__delete(port_state, sock_state, false);
+
+  } else if ((NTSTATUS) overlapped->Internal == STATUS_CANCELLED) {
+    /* The poll request was cancelled by CancelIoEx. */
+
+  } else if (!NT_SUCCESS(overlapped->Internal)) {
+    /* The overlapped request itself failed in an unexpected way. */
+    epoll_events = EPOLLERR;
+
+  } else if (poll_info->NumberOfHandles < 1) {
+    /* This poll operation succeeded but didn't report any socket events. */
+
+  } else if (poll_info->Handles[0].Events & AFD_POLL_LOCAL_CLOSE) {
+    /* The poll operation reported that the socket was closed. */
+    return sock__delete(port_state, sock_state, false);
+
+  } else {
+    /* Events related to our socket were reported. */
+    epoll_events =
+        sock__afd_events_to_epoll_events(poll_info->Handles[0].Events);
+  }
+
+  /* Requeue the socket so a new poll request will be submitted. */
+  port_request_socket_update(port_state, sock_state);
+
+  /* Filter out events that the user didn't ask for. */
+  epoll_events &= sock_state->user_events;
+
+  /* Return if there are no epoll events to report. */
+  if (epoll_events == 0)
+    return 0;
+
+  /* If the the socket has the EPOLLONESHOT flag set, unmonitor all events,
+   * even EPOLLERR and EPOLLHUP. But always keep looking for closed sockets. */
+  if (sock_state->user_events & EPOLLONESHOT)
+    sock_state->user_events = 0;
+
+  ev->data = sock_state->user_data;
+  ev->events = epoll_events;
+  return 1;
+}
+
+queue_node_t* sock_state_to_queue_node(sock_state_t* sock_state) {
+  return &sock_state->queue_node;
+}
+
+sock_state_t* sock_state_from_tree_node(tree_node_t* tree_node) {
+  return container_of(tree_node, sock_state_t, tree_node);
+}
+
+tree_node_t* sock_state_to_tree_node(sock_state_t* sock_state) {
+  return &sock_state->tree_node;
+}
+
+sock_state_t* sock_state_from_queue_node(queue_node_t* queue_node) {
+  return container_of(queue_node, sock_state_t, queue_node);
+}
+
+void ts_tree_init(ts_tree_t* ts_tree) {
+  tree_init(&ts_tree->tree);
+  InitializeSRWLock(&ts_tree->lock);
+}
+
+void ts_tree_node_init(ts_tree_node_t* node) {
+  tree_node_init(&node->tree_node);
+  reflock_init(&node->reflock);
+}
+
+int ts_tree_add(ts_tree_t* ts_tree, ts_tree_node_t* node, uintptr_t key) {
+  int r;
+
+  AcquireSRWLockExclusive(&ts_tree->lock);
+  r = tree_add(&ts_tree->tree, &node->tree_node, key);
+  ReleaseSRWLockExclusive(&ts_tree->lock);
+
+  return r;
+}
+
+static inline ts_tree_node_t* ts_tree__find_node(ts_tree_t* ts_tree,
+                                                 uintptr_t key) {
+  tree_node_t* tree_node = tree_find(&ts_tree->tree, key);
+  if (tree_node == NULL)
+    return NULL;
+
+  return container_of(tree_node, ts_tree_node_t, tree_node);
+}
+
+ts_tree_node_t* ts_tree_del_and_ref(ts_tree_t* ts_tree, uintptr_t key) {
+  ts_tree_node_t* ts_tree_node;
+
+  AcquireSRWLockExclusive(&ts_tree->lock);
+
+  ts_tree_node = ts_tree__find_node(ts_tree, key);
+  if (ts_tree_node != NULL) {
+    tree_del(&ts_tree->tree, &ts_tree_node->tree_node);
+    reflock_ref(&ts_tree_node->reflock);
+  }
+
+  ReleaseSRWLockExclusive(&ts_tree->lock);
+
+  return ts_tree_node;
+}
+
+ts_tree_node_t* ts_tree_find_and_ref(ts_tree_t* ts_tree, uintptr_t key) {
+  ts_tree_node_t* ts_tree_node;
+
+  AcquireSRWLockShared(&ts_tree->lock);
+
+  ts_tree_node = ts_tree__find_node(ts_tree, key);
+  if (ts_tree_node != NULL)
+    reflock_ref(&ts_tree_node->reflock);
+
+  ReleaseSRWLockShared(&ts_tree->lock);
+
+  return ts_tree_node;
+}
+
+void ts_tree_node_unref(ts_tree_node_t* node) {
+  reflock_unref(&node->reflock);
+}
+
+void ts_tree_node_unref_and_destroy(ts_tree_node_t* node) {
+  reflock_unref_and_destroy(&node->reflock);
+}
+
+void tree_init(tree_t* tree) {
+  memset(tree, 0, sizeof *tree);
+}
+
+void tree_node_init(tree_node_t* node) {
+  memset(node, 0, sizeof *node);
+}
+
+#define TREE__ROTATE(cis, trans)   \
+  tree_node_t* p = node;           \
+  tree_node_t* q = node->trans;    \
+  tree_node_t* parent = p->parent; \
+                                   \
+  if (parent) {                    \
+    if (parent->left == p)         \
+      parent->left = q;            \
+    else                           \
+      parent->right = q;           \
+  } else {                         \
+    tree->root = q;                \
+  }                                \
+                                   \
+  q->parent = parent;              \
+  p->parent = q;                   \
+  p->trans = q->cis;               \
+  if (p->trans)                    \
+    p->trans->parent = p;          \
+  q->cis = p;
+
+static inline void tree__rotate_left(tree_t* tree, tree_node_t* node) {
+  TREE__ROTATE(left, right)
+}
+
+static inline void tree__rotate_right(tree_t* tree, tree_node_t* node) {
+  TREE__ROTATE(right, left)
+}
+
+#define TREE__INSERT_OR_DESCEND(side) \
+  if (parent->side) {                 \
+    parent = parent->side;            \
+  } else {                            \
+    parent->side = node;              \
+    break;                            \
+  }
+
+#define TREE__FIXUP_AFTER_INSERT(cis, trans) \
+  tree_node_t* grandparent = parent->parent; \
+  tree_node_t* uncle = grandparent->trans;   \
+                                             \
+  if (uncle && uncle->red) {                 \
+    parent->red = uncle->red = false;        \
+    grandparent->red = true;                 \
+    node = grandparent;                      \
+  } else {                                   \
+    if (node == parent->trans) {             \
+      tree__rotate_##cis(tree, parent);      \
+      node = parent;                         \
+      parent = node->parent;                 \
+    }                                        \
+    parent->red = false;                     \
+    grandparent->red = true;                 \
+    tree__rotate_##trans(tree, grandparent); \
+  }
+
+int tree_add(tree_t* tree, tree_node_t* node, uintptr_t key) {
+  tree_node_t* parent;
+
+  parent = tree->root;
+  if (parent) {
+    for (;;) {
+      if (key < parent->key) {
+        TREE__INSERT_OR_DESCEND(left)
+      } else if (key > parent->key) {
+        TREE__INSERT_OR_DESCEND(right)
+      } else {
+        return -1;
+      }
+    }
+  } else {
+    tree->root = node;
+  }
+
+  node->key = key;
+  node->left = node->right = NULL;
+  node->parent = parent;
+  node->red = true;
+
+  for (; parent && parent->red; parent = node->parent) {
+    if (parent == parent->parent->left) {
+      TREE__FIXUP_AFTER_INSERT(left, right)
+    } else {
+      TREE__FIXUP_AFTER_INSERT(right, left)
+    }
+  }
+  tree->root->red = false;
+
+  return 0;
+}
+
+#define TREE__FIXUP_AFTER_REMOVE(cis, trans)       \
+  tree_node_t* sibling = parent->trans;            \
+                                                   \
+  if (sibling->red) {                              \
+    sibling->red = false;                          \
+    parent->red = true;                            \
+    tree__rotate_##cis(tree, parent);              \
+    sibling = parent->trans;                       \
+  }                                                \
+  if ((sibling->left && sibling->left->red) ||     \
+      (sibling->right && sibling->right->red)) {   \
+    if (!sibling->trans || !sibling->trans->red) { \
+      sibling->cis->red = false;                   \
+      sibling->red = true;                         \
+      tree__rotate_##trans(tree, sibling);         \
+      sibling = parent->trans;                     \
+    }                                              \
+    sibling->red = parent->red;                    \
+    parent->red = sibling->trans->red = false;     \
+    tree__rotate_##cis(tree, parent);              \
+    node = tree->root;                             \
+    break;                                         \
+  }                                                \
+  sibling->red = true;
+
+void tree_del(tree_t* tree, tree_node_t* node) {
+  tree_node_t* parent = node->parent;
+  tree_node_t* left = node->left;
+  tree_node_t* right = node->right;
+  tree_node_t* next;
+  bool red;
+
+  if (!left) {
+    next = right;
+  } else if (!right) {
+    next = left;
+  } else {
+    next = right;
+    while (next->left)
+      next = next->left;
+  }
+
+  if (parent) {
+    if (parent->left == node)
+      parent->left = next;
+    else
+      parent->right = next;
+  } else {
+    tree->root = next;
+  }
+
+  if (left && right) {
+    red = next->red;
+    next->red = node->red;
+    next->left = left;
+    left->parent = next;
+    if (next != right) {
+      parent = next->parent;
+      next->parent = node->parent;
+      node = next->right;
+      parent->left = node;
+      next->right = right;
+      right->parent = next;
+    } else {
+      next->parent = parent;
+      parent = next;
+      node = next->right;
+    }
+  } else {
+    red = node->red;
+    node = next;
+  }
+
+  if (node)
+    node->parent = parent;
+  if (red)
+    return;
+  if (node && node->red) {
+    node->red = false;
+    return;
+  }
+
+  do {
+    if (node == tree->root)
+      break;
+    if (node == parent->left) {
+      TREE__FIXUP_AFTER_REMOVE(left, right)
+    } else {
+      TREE__FIXUP_AFTER_REMOVE(right, left)
+    }
+    node = parent;
+    parent = parent->parent;
+  } while (!node->red);
+
+  if (node)
+    node->red = false;
+}
+
+tree_node_t* tree_find(const tree_t* tree, uintptr_t key) {
+  tree_node_t* node = tree->root;
+  while (node) {
+    if (key < node->key)
+      node = node->left;
+    else if (key > node->key)
+      node = node->right;
+    else
+      return node;
+  }
+  return NULL;
+}
+
+tree_node_t* tree_root(const tree_t* tree) {
+  return tree->root;
+}
+
+#ifndef SIO_BASE_HANDLE
+#define SIO_BASE_HANDLE 0x48000022
+#endif
+
+int ws_global_init(void) {
+  int r;
+  WSADATA wsa_data;
+
+  r = WSAStartup(MAKEWORD(2, 2), &wsa_data);
+  if (r != 0)
+    return_set_error(-1, (DWORD) r);
+
+  return 0;
+}
+
+SOCKET ws_get_base_socket(SOCKET socket) {
+  SOCKET base_socket;
+  DWORD bytes;
+
+  if (WSAIoctl(socket,
+               SIO_BASE_HANDLE,
+               NULL,
+               0,
+               &base_socket,
+               sizeof base_socket,
+               &bytes,
+               NULL,
+               NULL) == SOCKET_ERROR)
+    return_map_error(INVALID_SOCKET);
+
+  return base_socket;
+}
diff --git a/3rdparty/wepoll/include/wepoll.h b/sources/wepoll/wepoll.h
similarity index 52%
rename from 3rdparty/wepoll/include/wepoll.h
rename to sources/wepoll/wepoll.h
index 4cca4c2d01e5811c1ca7ae3dad7b0f3c0a2d8fe1..eebde2111fe1afaa3c75b8d19ada8b9ba5345c06 100644
--- a/3rdparty/wepoll/include/wepoll.h
+++ b/sources/wepoll/wepoll.h
@@ -1,3 +1,34 @@
+/*
+ * wepoll - epoll for Windows
+ * https://github.com/piscisaureus/wepoll
+ *
+ * Copyright 2012-2019, Bert Belder <bertbelder@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
 #ifndef WEPOLL_H_
 #define WEPOLL_H_
 
@@ -7,8 +38,6 @@
 
 #include <stdint.h>
 
-/* clang-format off */
-
 enum EPOLL_EVENTS {
   EPOLLIN      = (int) (1U <<  0),
   EPOLLPRI     = (int) (1U <<  1),
@@ -41,8 +70,6 @@ enum EPOLL_EVENTS {
 #define EPOLL_CTL_MOD 2
 #define EPOLL_CTL_DEL 3
 
-/* clang-format on */
-
 typedef void* HANDLE;
 typedef uintptr_t SOCKET;
 
@@ -69,15 +96,15 @@ WEPOLL_EXPORT HANDLE epoll_create1(int flags);
 
 WEPOLL_EXPORT int epoll_close(HANDLE ephnd);
 
-WEPOLL_EXPORT int epoll_ctl( HANDLE ephnd,
+WEPOLL_EXPORT int epoll_ctl(HANDLE ephnd,
                             int op,
                             SOCKET sock,
-                            struct epoll_event* event );
+                            struct epoll_event* event);
 
-WEPOLL_EXPORT int epoll_wait( HANDLE ephnd,
+WEPOLL_EXPORT int epoll_wait(HANDLE ephnd,
                              struct epoll_event* events,
                              int maxevents,
-                             int timeout );
+                             int timeout);
 
 #ifdef __cplusplus
 } /* extern "C" */