mirror of
https://github.com/solemnwarning/directplay-lite
synced 2024-12-30 16:45:37 +01:00
Initial prototype of network I/O and session enumeration.
The threading model used for processing messages here will need redesigning; we can't allow the application to block the event loop when processing a message in case it calls something which won't return until a message is processed. Final model will probably use a pool of workers which will handle I/O one-at-a-time, blocking and allowing other threads to deal with the I/O when in the application message callback.
This commit is contained in:
parent
286c86ea46
commit
c63945facd
12
Makefile
12
Makefile
@ -1,14 +1,18 @@
|
||||
CXX := i686-w64-mingw32-g++
|
||||
CXXFLAGS := -std=c++11 -Wall
|
||||
CXXFLAGS := -std=c++11 -Wall -D_WIN32_WINNT=0x0600 -ggdb
|
||||
|
||||
TEST_CXXFLAGS := $(CXXFLAGS) -I./googletest/include/
|
||||
|
||||
all: dpnet.dll
|
||||
|
||||
check: tests/all-tests.exe
|
||||
ifeq ($(OS),Windows_NT)
|
||||
tests/all-tests.exe
|
||||
else
|
||||
wine tests/all-tests.exe
|
||||
endif
|
||||
|
||||
dpnet.dll: src/dpnet.o src/dpnet.def src/DirectPlay8Address.o src/DirectPlay8Peer.o src/network.o src/packet.o
|
||||
dpnet.dll: src/dpnet.o src/dpnet.def src/DirectPlay8Address.o src/DirectPlay8Peer.o src/network.o src/packet.o src/SendQueue.o src/AsyncHandleAllocator.o src/HostEnumerator.o src/COMAPIException.o
|
||||
$(CXX) $(CXXFLAGS) -Wl,--enable-stdcall-fixup -shared -o $@ $^ -ldxguid -lws2_32 -static-libstdc++ -static-libgcc
|
||||
|
||||
tests/DirectPlay8Address.exe: tests/DirectPlay8Address.o src/DirectPlay8Address.o googletest/src/gtest-all.o googletest/src/gtest_main.o
|
||||
@ -19,8 +23,10 @@ tests/PacketSerialiser.exe: tests/PacketSerialiser.o src/packet.o googletest/src
|
||||
|
||||
tests/all-tests.exe: tests/DirectPlay8Address.o src/DirectPlay8Address.o \
|
||||
tests/PacketSerialiser.o tests/PacketDeserialiser.o src/packet.o \
|
||||
tests/DirectPlay8Peer.o src/DirectPlay8Peer.o \
|
||||
src/SendQueue.o src/AsyncHandleAllocator.o src/HostEnumerator.o src/network.o src/COMAPIException.o \
|
||||
googletest/src/gtest-all.o googletest/src/gtest_main.o
|
||||
$(CXX) $(TEST_CXXFLAGS) -o $@ $^ -ldxguid -lole32 -static-libstdc++ -static-libgcc
|
||||
$(CXX) $(TEST_CXXFLAGS) -o $@ $^ -ldxguid -lole32 -static-libstdc++ -static-libgcc -lws2_32
|
||||
|
||||
src/%.o: src/%.cpp
|
||||
$(CXX) $(CXXFLAGS) -c -o $@ $<
|
||||
|
47
src/AsyncHandleAllocator.cpp
Normal file
47
src/AsyncHandleAllocator.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include <dplay8.h>
|
||||
|
||||
#include "AsyncHandleAllocator.hpp"
|
||||
|
||||
AsyncHandleAllocator::AsyncHandleAllocator():
|
||||
next_enum_id(1),
|
||||
next_connect_id(1),
|
||||
next_send_id(1) {}
|
||||
|
||||
DPNHANDLE AsyncHandleAllocator::new_enum()
|
||||
{
|
||||
DPNHANDLE handle = next_enum_id++ | TYPE_ENUM;
|
||||
|
||||
next_enum_id &= ~TYPE_MASK;
|
||||
if(next_enum_id == 0)
|
||||
{
|
||||
next_enum_id = 1;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
DPNHANDLE AsyncHandleAllocator::new_connect()
|
||||
{
|
||||
DPNHANDLE handle = next_connect_id++ | TYPE_ENUM;
|
||||
|
||||
next_connect_id &= ~TYPE_MASK;
|
||||
if(next_connect_id == 0)
|
||||
{
|
||||
next_connect_id = 1;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
DPNHANDLE AsyncHandleAllocator::new_send()
|
||||
{
|
||||
DPNHANDLE handle = next_send_id++ | TYPE_ENUM;
|
||||
|
||||
next_send_id &= ~TYPE_MASK;
|
||||
if(next_send_id == 0)
|
||||
{
|
||||
next_send_id = 1;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
40
src/AsyncHandleAllocator.hpp
Normal file
40
src/AsyncHandleAllocator.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef DPLITE_ASYNCHANDLEALLOCATOR_HPP
|
||||
#define DPLITE_ASYNCHANDLEALLOCATOR_HPP
|
||||
|
||||
#include <dplay8.h>
|
||||
|
||||
/* There is an instance of this class in each DirectPlay8Peer/etc instance to allocate DPNHANDLEs
|
||||
* for async operations.
|
||||
*
|
||||
* Handles are allocated sequentially, they aren't currently tracked, but I doubt anyone will ever
|
||||
* have enough running at once to wrap around and conflict.
|
||||
*
|
||||
* The handle's type is encoded in the high bits so CancelAsyncOperation() can know where it needs
|
||||
* to look rather than having to search through each type of async task.
|
||||
*
|
||||
* 0x00000000 and 0xFFFFFFFF are both impossible values as they have significance to some parts of
|
||||
* DirectPlay.
|
||||
*/
|
||||
|
||||
class AsyncHandleAllocator
|
||||
{
|
||||
private:
|
||||
DPNHANDLE next_enum_id;
|
||||
DPNHANDLE next_connect_id;
|
||||
DPNHANDLE next_send_id;
|
||||
|
||||
public:
|
||||
static const DPNHANDLE TYPE_MASK = 0xC0000000;
|
||||
|
||||
static const DPNHANDLE TYPE_ENUM = 0x00000000;
|
||||
static const DPNHANDLE TYPE_CONNECT = 0x40000000;
|
||||
static const DPNHANDLE TYPE_SEND = 0x80000000;
|
||||
|
||||
AsyncHandleAllocator();
|
||||
|
||||
DPNHANDLE new_enum();
|
||||
DPNHANDLE new_connect();
|
||||
DPNHANDLE new_send();
|
||||
};
|
||||
|
||||
#endif /* !DPLITE_ASYNCHANDLEALLOCATOR_HPP */
|
21
src/COMAPIException.cpp
Normal file
21
src/COMAPIException.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "COMAPIException.hpp"
|
||||
|
||||
COMAPIException::COMAPIException(HRESULT result):
|
||||
hr(result)
|
||||
{
|
||||
snprintf(what_s, sizeof(what_s), "COMAPIException, HRESULT %08X", (unsigned)(result));
|
||||
}
|
||||
|
||||
COMAPIException::~COMAPIException() {}
|
||||
|
||||
HRESULT COMAPIException::result() const noexcept
|
||||
{
|
||||
return hr;
|
||||
}
|
||||
|
||||
const char *COMAPIException::what() const noexcept
|
||||
{
|
||||
return what_s;
|
||||
}
|
22
src/COMAPIException.hpp
Normal file
22
src/COMAPIException.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef DPLITE_COMAPIEXCEPTION_HPP
|
||||
#define DPLITE_COMAPIEXCEPTION_HPP
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <exception>
|
||||
#include <windows.h>
|
||||
|
||||
class COMAPIException: public std::exception
|
||||
{
|
||||
private:
|
||||
const HRESULT hr;
|
||||
char what_s[64];
|
||||
|
||||
public:
|
||||
COMAPIException(HRESULT result);
|
||||
virtual ~COMAPIException();
|
||||
|
||||
HRESULT result() const noexcept;
|
||||
virtual const char *what() const noexcept;
|
||||
};
|
||||
|
||||
#endif /* !DPLITE_COMAPIEXCEPTION_HPP */
|
@ -1,13 +1,19 @@
|
||||
#include <winsock2.h>
|
||||
#include <atomic>
|
||||
#include <dplay8.h>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <objbase.h>
|
||||
#include <stdexcept>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <tuple>
|
||||
#include <windows.h>
|
||||
|
||||
#include "COMAPIException.hpp"
|
||||
#include "DirectPlay8Address.hpp"
|
||||
#include "DirectPlay8Peer.hpp"
|
||||
#include "Messages.hpp"
|
||||
#include "network.hpp"
|
||||
|
||||
#define UNIMPLEMENTED(fmt, ...) \
|
||||
@ -17,15 +23,29 @@
|
||||
DirectPlay8Peer::DirectPlay8Peer(std::atomic<unsigned int> *global_refcount):
|
||||
global_refcount(global_refcount),
|
||||
local_refcount(0),
|
||||
state(STATE_DISCONNECTED),
|
||||
state(STATE_NEW),
|
||||
udp_socket(-1),
|
||||
listener_socket(-1),
|
||||
discovery_socket(-1)
|
||||
{
|
||||
io_event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if(io_event == NULL)
|
||||
{
|
||||
throw std::runtime_error("Cannot create event object");
|
||||
}
|
||||
|
||||
AddRef();
|
||||
}
|
||||
|
||||
DirectPlay8Peer::~DirectPlay8Peer() {}
|
||||
DirectPlay8Peer::~DirectPlay8Peer()
|
||||
{
|
||||
if(state != STATE_NEW)
|
||||
{
|
||||
Close(0);
|
||||
}
|
||||
|
||||
CloseHandle(io_event);
|
||||
}
|
||||
|
||||
HRESULT DirectPlay8Peer::QueryInterface(REFIID riid, void **ppvObject)
|
||||
{
|
||||
@ -71,9 +91,23 @@ ULONG DirectPlay8Peer::Release(void)
|
||||
|
||||
HRESULT DirectPlay8Peer::Initialize(PVOID CONST pvUserContext, CONST PFNDPNMESSAGEHANDLER pfn, CONST DWORD dwFlags)
|
||||
{
|
||||
if(state != STATE_NEW)
|
||||
{
|
||||
return DPNERR_ALREADYINITIALIZED;
|
||||
}
|
||||
|
||||
message_handler = pfn;
|
||||
message_handler_ctx = pvUserContext;
|
||||
|
||||
WSADATA wd;
|
||||
if(WSAStartup(MAKEWORD(2,2), &wd) != 0)
|
||||
{
|
||||
/* TODO */
|
||||
return DPNERR_GENERIC;
|
||||
}
|
||||
|
||||
state = STATE_INITIALISED;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@ -84,7 +118,64 @@ HRESULT DirectPlay8Peer::EnumServiceProviders(CONST GUID* CONST pguidServiceProv
|
||||
|
||||
HRESULT DirectPlay8Peer::CancelAsyncOperation(CONST DPNHANDLE hAsyncHandle, CONST DWORD dwFlags)
|
||||
{
|
||||
UNIMPLEMENTED("DirectPlay8Peer::CancelAsyncOperation");
|
||||
if(dwFlags & DPNCANCEL_PLAYER_SENDS)
|
||||
{
|
||||
/* Cancel sends to player ID in hAsyncHandle */
|
||||
UNIMPLEMENTED("DirectPlay8Peer::CancelAsyncOperation");
|
||||
}
|
||||
else if(dwFlags & (DPNCANCEL_ENUM | DPNCANCEL_CONNECT | DPNCANCEL_ALL_OPERATIONS))
|
||||
{
|
||||
/* Cancel all outstanding operations of one or more types. */
|
||||
|
||||
if(dwFlags & (DPNCANCEL_ENUM | DPNCANCEL_ALL_OPERATIONS))
|
||||
{
|
||||
std::unique_lock<std::mutex> l(lock);
|
||||
|
||||
for(auto ei = host_enums.begin(); ei != host_enums.end(); ++ei)
|
||||
{
|
||||
ei->second.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if(dwFlags & (DPNCANCEL_CONNECT | DPNCANCEL_ALL_OPERATIONS))
|
||||
{
|
||||
/* TODO: Cancel in-progress connect. */
|
||||
}
|
||||
|
||||
if(dwFlags & DPNCANCEL_ALL_OPERATIONS)
|
||||
{
|
||||
/* TODO: Cancel all sends */
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
else if((hAsyncHandle & AsyncHandleAllocator::TYPE_MASK) == AsyncHandleAllocator::TYPE_ENUM)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(lock);
|
||||
|
||||
auto ei = host_enums.find(hAsyncHandle);
|
||||
if(ei == host_enums.end())
|
||||
{
|
||||
return DPNERR_INVALIDHANDLE;
|
||||
}
|
||||
|
||||
/* TODO: Make successive cancels for the same handle before it is destroyed fail? */
|
||||
|
||||
ei->second.cancel();
|
||||
return S_OK;
|
||||
}
|
||||
else if((hAsyncHandle & AsyncHandleAllocator::TYPE_MASK) == AsyncHandleAllocator::TYPE_CONNECT)
|
||||
{
|
||||
UNIMPLEMENTED("DirectPlay8Peer::CancelAsyncOperation");
|
||||
}
|
||||
else if((hAsyncHandle & AsyncHandleAllocator::TYPE_MASK) == AsyncHandleAllocator::TYPE_SEND)
|
||||
{
|
||||
UNIMPLEMENTED("DirectPlay8Peer::CancelAsyncOperation");
|
||||
}
|
||||
else{
|
||||
/* Unrecognised handle type. */
|
||||
return DPNERR_INVALIDHANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT DirectPlay8Peer::Connect(CONST DPN_APPLICATION_DESC* CONST pdnAppDesc, IDirectPlay8Address* CONST pHostAddr, IDirectPlay8Address* CONST pDeviceInfo, CONST DPN_SECURITY_DESC* CONST pdnSecurity, CONST DPN_SECURITY_CREDENTIALS* CONST pdnCredentials, CONST void* CONST pvUserConnectData, CONST DWORD dwUserConnectDataSize, void* CONST pvPlayerContext, void* CONST pvAsyncContext, DPNHANDLE* CONST phAsyncHandle, CONST DWORD dwFlags)
|
||||
@ -104,9 +195,12 @@ HRESULT DirectPlay8Peer::GetSendQueueInfo(CONST DPNID dpnid, DWORD* CONST pdwNum
|
||||
|
||||
HRESULT DirectPlay8Peer::Host(CONST DPN_APPLICATION_DESC* CONST pdnAppDesc, IDirectPlay8Address **CONST prgpDeviceInfo, CONST DWORD cDeviceInfo, CONST DPN_SECURITY_DESC* CONST pdnSecurity, CONST DPN_SECURITY_CREDENTIALS* CONST pdnCredentials, void* CONST pvPlayerContext, CONST DWORD dwFlags)
|
||||
{
|
||||
if(state != STATE_DISCONNECTED)
|
||||
switch(state)
|
||||
{
|
||||
return DPNERR_ALREADYCONNECTED;
|
||||
case STATE_NEW: return DPNERR_UNINITIALIZED;
|
||||
case STATE_INITIALISED: break;
|
||||
case STATE_HOSTING: return DPNERR_ALREADYCONNECTED;
|
||||
case STATE_CONNECTED: return DPNERR_ALREADYCONNECTED;
|
||||
}
|
||||
|
||||
if(pdnAppDesc->dwSize != sizeof(DPN_APPLICATION_DESC))
|
||||
@ -124,6 +218,13 @@ HRESULT DirectPlay8Peer::Host(CONST DPN_APPLICATION_DESC* CONST pdnAppDesc, IDir
|
||||
/* Not supported yet. */
|
||||
}
|
||||
|
||||
/* Generate a random GUID for this session. */
|
||||
HRESULT guid_err = CoCreateGuid(&instance_guid);
|
||||
if(guid_err != S_OK)
|
||||
{
|
||||
return guid_err;
|
||||
}
|
||||
|
||||
application_guid = pdnAppDesc->guidApplication;
|
||||
max_players = pdnAppDesc->dwMaxPlayers;
|
||||
session_name = pdnAppDesc->pwszSessionName;
|
||||
@ -172,13 +273,56 @@ HRESULT DirectPlay8Peer::Host(CONST DPN_APPLICATION_DESC* CONST pdnAppDesc, IDir
|
||||
|
||||
if(port == 0)
|
||||
{
|
||||
port = DEFAULT_HOST_PORT;
|
||||
/* Ephemeral port range as defined by IANA. */
|
||||
const int AUTO_PORT_MIN = 49152;
|
||||
const int AUTO_PORT_MAX = 65535;
|
||||
|
||||
for(int p = AUTO_PORT_MIN; p <= AUTO_PORT_MAX; ++p)
|
||||
{
|
||||
/* TODO: Only continue if creation failed due to address conflict. */
|
||||
|
||||
udp_socket = create_udp_socket(ipaddr, port);
|
||||
if(udp_socket == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
listener_socket = create_listener_socket(ipaddr, port);
|
||||
if(listener_socket == -1)
|
||||
{
|
||||
closesocket(udp_socket);
|
||||
udp_socket = -1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if(udp_socket == -1)
|
||||
{
|
||||
return DPNERR_GENERIC;
|
||||
}
|
||||
}
|
||||
else{
|
||||
udp_socket = create_udp_socket(ipaddr, port);
|
||||
if(udp_socket == -1)
|
||||
{
|
||||
return DPNERR_GENERIC;
|
||||
}
|
||||
|
||||
listener_socket = create_listener_socket(ipaddr, port);
|
||||
if(listener_socket == -1)
|
||||
{
|
||||
closesocket(udp_socket);
|
||||
udp_socket = -1;
|
||||
|
||||
return DPNERR_GENERIC;
|
||||
}
|
||||
}
|
||||
|
||||
udp_socket = create_udp_socket (ipaddr, port);
|
||||
listener_socket = create_listener_socket(ipaddr, port);
|
||||
|
||||
if(udp_socket == -1 || listener_socket == -1)
|
||||
if(WSAEventSelect(udp_socket, io_event, FD_READ | FD_WRITE) != 0
|
||||
|| WSAEventSelect(listener_socket, io_event, FD_ACCEPT) != 0)
|
||||
{
|
||||
return DPNERR_GENERIC;
|
||||
}
|
||||
@ -186,8 +330,17 @@ HRESULT DirectPlay8Peer::Host(CONST DPN_APPLICATION_DESC* CONST pdnAppDesc, IDir
|
||||
if(!(pdnAppDesc->dwFlags & DPNSESSION_NODPNSVR))
|
||||
{
|
||||
discovery_socket = create_discovery_socket();
|
||||
|
||||
if(discovery_socket == -1
|
||||
|| WSAEventSelect(discovery_socket, io_event, FD_READ) != 0)
|
||||
{
|
||||
return DPNERR_GENERIC;
|
||||
}
|
||||
}
|
||||
|
||||
io_run = true;
|
||||
io_thread = std::thread(&DirectPlay8Peer::io_main, this);
|
||||
|
||||
state = STATE_HOSTING;
|
||||
|
||||
return S_OK;
|
||||
@ -195,7 +348,7 @@ HRESULT DirectPlay8Peer::Host(CONST DPN_APPLICATION_DESC* CONST pdnAppDesc, IDir
|
||||
|
||||
HRESULT DirectPlay8Peer::GetApplicationDesc(DPN_APPLICATION_DESC* CONST pAppDescBuffer, DWORD* CONST pcbDataSize, CONST DWORD dwFlags)
|
||||
{
|
||||
if(state == STATE_DISCONNECTED)
|
||||
if(state != STATE_HOSTING && state != STATE_CONNECTED)
|
||||
{
|
||||
return DPNERR_NOCONNECTION;
|
||||
}
|
||||
@ -213,7 +366,7 @@ HRESULT DirectPlay8Peer::GetApplicationDesc(DPN_APPLICATION_DESC* CONST pAppDesc
|
||||
pAppDescBuffer->guidInstance = instance_guid;
|
||||
pAppDescBuffer->guidApplication = application_guid;
|
||||
pAppDescBuffer->dwMaxPlayers = max_players;
|
||||
pAppDescBuffer->dwCurrentPlayers = peers.size() + 1;
|
||||
pAppDescBuffer->dwCurrentPlayers = other_player_ids.size() + 1;
|
||||
|
||||
if(!password.empty())
|
||||
{
|
||||
@ -257,7 +410,7 @@ HRESULT DirectPlay8Peer::SetApplicationDesc(CONST DPN_APPLICATION_DESC* CONST pa
|
||||
{
|
||||
if(state == STATE_HOSTING)
|
||||
{
|
||||
if(pad->dwMaxPlayers > 0 && pad->dwMaxPlayers <= peers.size())
|
||||
if(pad->dwMaxPlayers > 0 && pad->dwMaxPlayers <= other_player_ids.size())
|
||||
{
|
||||
/* Can't set dwMaxPlayers below current player count. */
|
||||
return DPNERR_INVALIDPARAM;
|
||||
@ -354,12 +507,114 @@ HRESULT DirectPlay8Peer::GetLocalHostAddresses(IDirectPlay8Address** CONST prgpA
|
||||
|
||||
HRESULT DirectPlay8Peer::Close(CONST DWORD dwFlags)
|
||||
{
|
||||
UNIMPLEMENTED("DirectPlay8Peer::Close");
|
||||
if(state == STATE_NEW)
|
||||
{
|
||||
return DPNERR_UNINITIALIZED;
|
||||
}
|
||||
|
||||
if(state == STATE_HOSTING || state == STATE_CONNECTED)
|
||||
{
|
||||
io_run = false;
|
||||
SetEvent(io_event);
|
||||
|
||||
io_thread.join();
|
||||
}
|
||||
|
||||
CancelAsyncOperation(0, DPNCANCEL_ALL_OPERATIONS);
|
||||
|
||||
/* TODO: Wait properly. */
|
||||
|
||||
while(1)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(lock);
|
||||
if(host_enums.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Sleep(50);
|
||||
}
|
||||
|
||||
/* TODO: Clean sockets etc up. */
|
||||
|
||||
WSACleanup();
|
||||
|
||||
state = STATE_NEW;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT DirectPlay8Peer::EnumHosts(PDPN_APPLICATION_DESC CONST pApplicationDesc, IDirectPlay8Address* CONST pAddrHost, IDirectPlay8Address* CONST pDeviceInfo,PVOID CONST pUserEnumData, CONST DWORD dwUserEnumDataSize, CONST DWORD dwEnumCount, CONST DWORD dwRetryInterval, CONST DWORD dwTimeOut,PVOID CONST pvUserContext, DPNHANDLE* CONST pAsyncHandle, CONST DWORD dwFlags)
|
||||
{
|
||||
UNIMPLEMENTED("DirectPlay8Peer::EnumHosts");
|
||||
if(state == STATE_NEW)
|
||||
{
|
||||
return DPNERR_UNINITIALIZED;
|
||||
}
|
||||
|
||||
try {
|
||||
if(dwFlags & DPNENUMHOSTS_SYNC)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
HostEnumerator he(
|
||||
global_refcount,
|
||||
message_handler, message_handler_ctx,
|
||||
pApplicationDesc, pAddrHost, pDeviceInfo, pUserEnumData, dwUserEnumDataSize,
|
||||
dwEnumCount, dwRetryInterval, dwTimeOut, pvUserContext,
|
||||
|
||||
[&result](HRESULT r)
|
||||
{
|
||||
result = r;
|
||||
});
|
||||
|
||||
he.wait();
|
||||
|
||||
return result;
|
||||
}
|
||||
else{
|
||||
DPNHANDLE handle = handle_alloc.new_enum();
|
||||
|
||||
*pAsyncHandle = handle;
|
||||
|
||||
std::unique_lock<std::mutex> l(lock);
|
||||
|
||||
host_enums.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(handle),
|
||||
std::forward_as_tuple(
|
||||
global_refcount,
|
||||
message_handler, message_handler_ctx,
|
||||
pApplicationDesc, pAddrHost, pDeviceInfo, pUserEnumData, dwUserEnumDataSize,
|
||||
dwEnumCount, dwRetryInterval, dwTimeOut, pvUserContext,
|
||||
|
||||
[this, handle, pvUserContext](HRESULT r)
|
||||
{
|
||||
DPNMSG_ASYNC_OP_COMPLETE oc;
|
||||
memset(&oc, 0, sizeof(oc));
|
||||
|
||||
oc.dwSize = sizeof(oc);
|
||||
oc.hAsyncOp = handle;
|
||||
oc.pvUserContext = pvUserContext;
|
||||
oc.hResultCode = r;
|
||||
|
||||
message_handler(message_handler_ctx, DPN_MSGID_ASYNC_OP_COMPLETE, &oc);
|
||||
|
||||
std::unique_lock<std::mutex> l(lock);
|
||||
host_enums.erase(handle);
|
||||
l.unlock();
|
||||
}));
|
||||
|
||||
return DPNSUCCESS_PENDING;
|
||||
}
|
||||
}
|
||||
catch(const COMAPIException &e)
|
||||
{
|
||||
return e.result();
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return DPNERR_GENERIC;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT DirectPlay8Peer::DestroyPeer(CONST DPNID dpnidClient, CONST void* CONST pvDestroyData, CONST DWORD dwDestroyDataSize, CONST DWORD dwFlags)
|
||||
@ -416,3 +671,352 @@ HRESULT DirectPlay8Peer::TerminateSession(void* CONST pvTerminateData, CONST DWO
|
||||
{
|
||||
UNIMPLEMENTED("DirectPlay8Peer::TerminateSession");
|
||||
}
|
||||
|
||||
void DirectPlay8Peer::io_main()
|
||||
{
|
||||
while(io_run)
|
||||
{
|
||||
WaitForSingleObject(io_event, INFINITE);
|
||||
|
||||
io_udp_recv(udp_socket);
|
||||
io_udp_send(udp_socket, udp_sq);
|
||||
|
||||
if(discovery_socket != -1)
|
||||
{
|
||||
io_udp_recv(discovery_socket);
|
||||
io_udp_send(discovery_socket, discovery_sq);
|
||||
}
|
||||
|
||||
io_listener_accept(listener_socket);
|
||||
|
||||
std::unique_lock<std::mutex> l(lock);
|
||||
|
||||
for(auto p = peers.begin(); p != peers.end();)
|
||||
{
|
||||
auto next_p = std::next(p);
|
||||
|
||||
if(!io_tcp_recv(&*p) || !io_tcp_send(&*p))
|
||||
{
|
||||
/* TODO: Complete outstanding sends (failed), drop player */
|
||||
closesocket(p->sock);
|
||||
peers.erase(p);
|
||||
}
|
||||
|
||||
p = next_p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DirectPlay8Peer::io_udp_recv(int sock)
|
||||
{
|
||||
struct sockaddr_in from_addr;
|
||||
int fa_len = sizeof(from_addr);
|
||||
|
||||
int r = recvfrom(sock, (char*)(recv_buf), sizeof(recv_buf), 0, (struct sockaddr*)(&from_addr), &fa_len);
|
||||
if(r <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Process message */
|
||||
std::unique_ptr<PacketDeserialiser> pd;
|
||||
|
||||
try {
|
||||
pd.reset(new PacketDeserialiser(recv_buf, r));
|
||||
}
|
||||
catch(const PacketDeserialiser::Error &e)
|
||||
{
|
||||
/* Malformed packet received */
|
||||
return;
|
||||
}
|
||||
|
||||
switch(pd->packet_type())
|
||||
{
|
||||
case DPLITE_MSGID_HOST_ENUM_REQUEST:
|
||||
{
|
||||
if(state == STATE_HOSTING)
|
||||
{
|
||||
handle_host_enum_request(*pd, &from_addr);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
/* TODO: Log "unrecognised packet type" */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DirectPlay8Peer::io_udp_send(int sock, SendQueue &sq)
|
||||
{
|
||||
SendQueue::Buffer *sqb;
|
||||
|
||||
while((sqb = sq.get_next()) != NULL)
|
||||
{
|
||||
std::pair<const void*, size_t> data = sqb->get_data();
|
||||
std::pair<const struct sockaddr*, int> addr = sqb->get_dest_addr();
|
||||
|
||||
int s = sendto(sock, (const char*)(data.first), data.second, 0, addr.first, addr.second);
|
||||
if(s == -1)
|
||||
{
|
||||
DWORD err = WSAGetLastError();
|
||||
|
||||
if(err != WSAEWOULDBLOCK)
|
||||
{
|
||||
/* TODO: More specific error codes */
|
||||
sq.complete(sqb, DPNERR_GENERIC);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
sq.complete(sqb, S_OK);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectPlay8Peer::io_listener_accept(int sock)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
int addrlen = sizeof(addr);
|
||||
|
||||
int newfd = accept(sock, (struct sockaddr*)(&addr), &addrlen);
|
||||
if(newfd == -1)
|
||||
{
|
||||
DWORD err = WSAGetLastError();
|
||||
|
||||
if(err == WSAEWOULDBLOCK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else{
|
||||
/* TODO */
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
u_long non_blocking = 1;
|
||||
if(ioctlsocket(newfd, FIONBIO, &non_blocking) != 0)
|
||||
{
|
||||
closesocket(newfd);
|
||||
return;
|
||||
}
|
||||
|
||||
peers.emplace_back(newfd, addr.sin_addr.s_addr, ntohs(addr.sin_port));
|
||||
}
|
||||
|
||||
bool DirectPlay8Peer::io_tcp_recv(Player *player)
|
||||
{
|
||||
int r = recv(player->sock, (char*)(player->recv_buf) + player->recv_buf_cur, sizeof(player->recv_buf) - player->recv_buf_cur, 0);
|
||||
if(r == 0)
|
||||
{
|
||||
/* Connection closed */
|
||||
return false;
|
||||
}
|
||||
else if(r == -1)
|
||||
{
|
||||
DWORD err = WSAGetLastError();
|
||||
|
||||
if(err == WSAEWOULDBLOCK)
|
||||
{
|
||||
/* Nothing to read */
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
/* Read error. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
player->recv_buf_cur += r;
|
||||
|
||||
if(player->recv_buf_cur >= sizeof(TLVChunk))
|
||||
{
|
||||
TLVChunk *header = (TLVChunk*)(player->recv_buf);
|
||||
size_t full_packet_size = sizeof(TLVChunk) + header->value_length;
|
||||
|
||||
if(full_packet_size > MAX_PACKET_SIZE)
|
||||
{
|
||||
/* Malformed packet received */
|
||||
return false;
|
||||
}
|
||||
|
||||
if(player->recv_buf_cur >= full_packet_size)
|
||||
{
|
||||
/* Process message */
|
||||
std::unique_ptr<PacketDeserialiser> pd;
|
||||
|
||||
try {
|
||||
pd.reset(new PacketDeserialiser(player->recv_buf, full_packet_size));
|
||||
}
|
||||
catch(const PacketDeserialiser::Error &e)
|
||||
{
|
||||
/* Malformed packet received */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Message at the front of the buffer has been dealt with, shift any
|
||||
* remaining data beyond it to the front and truncate it.
|
||||
*/
|
||||
|
||||
memmove(player->recv_buf, player->recv_buf + full_packet_size,
|
||||
player->recv_buf_cur - full_packet_size);
|
||||
player->recv_buf_cur -= full_packet_size;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectPlay8Peer::io_tcp_send(Player *player)
|
||||
{
|
||||
SendQueue::Buffer *sqb = player->sq.get_next();
|
||||
|
||||
while(player->send_buf != NULL || sqb != NULL)
|
||||
{
|
||||
if(player->sqb == NULL)
|
||||
{
|
||||
std::pair<const void*, size_t> sqb_data = sqb->get_data();
|
||||
|
||||
player->send_buf = (const unsigned char*)(sqb_data.first);
|
||||
player->send_remain = sqb_data.second;
|
||||
}
|
||||
|
||||
int s = send(player->sock, (const char*)(player->send_buf), player->send_remain, 0);
|
||||
if(s < 0)
|
||||
{
|
||||
DWORD err = WSAGetLastError();
|
||||
|
||||
if(err == WSAEWOULDBLOCK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else{
|
||||
/* TODO: Better error codes */
|
||||
player->sq.complete(sqb, DPNERR_GENERIC);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if((size_t)(s) == player->send_remain)
|
||||
{
|
||||
player->send_buf = NULL;
|
||||
|
||||
player->sq.complete(sqb, S_OK);
|
||||
sqb = player->sq.get_next();
|
||||
}
|
||||
else{
|
||||
player->send_buf += s;
|
||||
player->send_remain -= s;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class SQB_TODO: public SendQueue::Buffer
|
||||
{
|
||||
private:
|
||||
PFNDPNMESSAGEHANDLER message_handler;
|
||||
PVOID message_handler_ctx;
|
||||
|
||||
DPNMSG_ENUM_HOSTS_QUERY query;
|
||||
|
||||
public:
|
||||
SQB_TODO(const void *data, size_t data_size, const struct sockaddr_in *dest_addr,
|
||||
PFNDPNMESSAGEHANDLER message_handler, PVOID message_handler_ctx,
|
||||
DPNMSG_ENUM_HOSTS_QUERY query):
|
||||
Buffer(data, data_size, (const struct sockaddr*)(dest_addr), sizeof(*dest_addr)),
|
||||
message_handler(message_handler),
|
||||
message_handler_ctx(message_handler_ctx),
|
||||
query(query) {}
|
||||
|
||||
virtual void complete(HRESULT result) override
|
||||
{
|
||||
if(query.pvResponseData != NULL)
|
||||
{
|
||||
DPNMSG_RETURN_BUFFER rb;
|
||||
memset(&rb, 0, sizeof(rb));
|
||||
|
||||
rb.dwSize = sizeof(rb);
|
||||
rb.hResultCode = result;
|
||||
rb.pvBuffer = query.pvResponseData;
|
||||
rb.pvUserContext = query.pvResponseContext;
|
||||
|
||||
message_handler(message_handler_ctx, DPN_MSGID_RETURN_BUFFER, &rb);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void DirectPlay8Peer::handle_host_enum_request(const PacketDeserialiser &pd, const struct sockaddr_in *from_addr)
|
||||
{
|
||||
if(!pd.is_null(0))
|
||||
{
|
||||
GUID r_application_guid = pd.get_guid(0);
|
||||
|
||||
if(application_guid != r_application_guid)
|
||||
{
|
||||
/* This isn't the application you're looking for.
|
||||
* It can go about its business.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DPNMSG_ENUM_HOSTS_QUERY query;
|
||||
memset(&query, 0, sizeof(query));
|
||||
|
||||
query.dwSize = sizeof(query);
|
||||
query.pAddressSender = NULL; // TODO
|
||||
query.pAddressDevice = NULL; // TODO
|
||||
|
||||
if(!pd.is_null(1))
|
||||
{
|
||||
std::pair<const void*, size_t> data = pd.get_data(1);
|
||||
|
||||
query.pvReceivedData = (void*)(data.first); /* TODO: Make a non-const copy? */
|
||||
query.dwReceivedDataSize = data.second;
|
||||
}
|
||||
|
||||
query.dwMaxResponseDataSize = 9999; // TODO
|
||||
|
||||
DWORD req_tick = pd.get_dword(2);
|
||||
|
||||
if(message_handler(message_handler_ctx, DPN_MSGID_ENUM_HOSTS_QUERY, &query) == DPN_OK)
|
||||
{
|
||||
PacketSerialiser ps(DPLITE_MSGID_HOST_ENUM_RESPONSE);
|
||||
|
||||
ps.append_dword(password.empty() ? 0 : DPNSESSION_REQUIREPASSWORD);
|
||||
ps.append_guid(instance_guid);
|
||||
ps.append_guid(application_guid);
|
||||
ps.append_dword(max_players);
|
||||
ps.append_dword(other_player_ids.size() + 1);
|
||||
ps.append_wstring(session_name);
|
||||
|
||||
if(!application_data.empty())
|
||||
{
|
||||
ps.append_data(application_data.data(), application_data.size());
|
||||
}
|
||||
else{
|
||||
ps.append_null();
|
||||
}
|
||||
|
||||
if(query.pvResponseData != NULL && query.dwResponseDataSize != 0)
|
||||
{
|
||||
ps.append_data(query.pvResponseData, query.dwResponseDataSize);
|
||||
}
|
||||
else{
|
||||
ps.append_null();
|
||||
}
|
||||
|
||||
ps.append_dword(req_tick);
|
||||
|
||||
std::pair<const void*, size_t> raw_pkt = ps.raw_packet();
|
||||
|
||||
udp_sq.send(SendQueue::SEND_PRI_MEDIUM, new SQB_TODO(raw_pkt.first, raw_pkt.second, from_addr,
|
||||
message_handler, message_handler_ctx, query));
|
||||
}
|
||||
else{
|
||||
/* Application rejected the DPNMSG_ENUM_HOSTS_QUERY message. */
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,20 @@
|
||||
#ifndef DPLITE_DIRECTPLAY8PEER_HPP
|
||||
#define DPLITE_DIRECTPLAY8PEER_HPP
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <atomic>
|
||||
#include <dplay8.h>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <objbase.h>
|
||||
#include <stdint.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "AsyncHandleAllocator.hpp"
|
||||
#include "HostEnumerator.hpp"
|
||||
#include "network.hpp"
|
||||
#include "packet.hpp"
|
||||
#include "SendQueue.hpp"
|
||||
|
||||
class DirectPlay8Peer: public IDirectPlay8Peer
|
||||
{
|
||||
@ -17,11 +26,16 @@ class DirectPlay8Peer: public IDirectPlay8Peer
|
||||
PVOID message_handler_ctx;
|
||||
|
||||
enum {
|
||||
STATE_DISCONNECTED,
|
||||
STATE_NEW,
|
||||
STATE_INITIALISED,
|
||||
STATE_HOSTING,
|
||||
STATE_CONNECTED,
|
||||
} state;
|
||||
|
||||
AsyncHandleAllocator handle_alloc;
|
||||
|
||||
std::map<DPNHANDLE, HostEnumerator> host_enums;
|
||||
|
||||
GUID instance_guid;
|
||||
GUID application_guid;
|
||||
DWORD max_players;
|
||||
@ -33,8 +47,40 @@ class DirectPlay8Peer: public IDirectPlay8Peer
|
||||
int listener_socket; /* TCP listener socket. */
|
||||
int discovery_socket; /* Discovery UDP sockets, RECIEVES broadcasts only. */
|
||||
|
||||
unsigned char recv_buf[MAX_PACKET_SIZE];
|
||||
|
||||
SendQueue udp_sq;
|
||||
SendQueue discovery_sq;
|
||||
|
||||
HANDLE io_event;
|
||||
std::thread io_thread;
|
||||
std::atomic<bool> io_run;
|
||||
|
||||
struct Player
|
||||
{
|
||||
enum PlayerState {
|
||||
/* Peer has connected to us, we're waiting for the initial message from it. */
|
||||
PS_INIT,
|
||||
|
||||
/* We are the host and the peer has sent the initial connect request, we are waiting
|
||||
* for the application to process DPN_MSGID_INDICATE_CONNECT before we either add the
|
||||
* player to the session or reject it.
|
||||
*/
|
||||
PS_CONNECTING,
|
||||
|
||||
/* This is a fully-fledged peer. */
|
||||
PS_CONNECTED,
|
||||
|
||||
/* This peer is closing down. Discard any future messages received from it, but flush
|
||||
* anything waiting to be sent and keep the player DPNID valid until after the
|
||||
* application has processed the DPN_MSGID_DESTROY_PLAYER message and any outstanding
|
||||
* operations have completed or been cancelled.
|
||||
*/
|
||||
PS_CLOSING,
|
||||
};
|
||||
|
||||
enum PlayerState state;
|
||||
|
||||
/* This is the TCP socket to the peer, we may have connected to it, or it
|
||||
* may have connected to us depending who joined the session first, that
|
||||
* doesn't really matter.
|
||||
@ -42,10 +88,43 @@ class DirectPlay8Peer: public IDirectPlay8Peer
|
||||
int sock;
|
||||
|
||||
uint32_t ip; /* IPv4 address, network byte order. */
|
||||
uint16_t port; /* Port, host byte order. */
|
||||
uint16_t port; /* TCP and UDP port, host byte order. */
|
||||
|
||||
DPNID id; /* Player ID, not initialised before state PS_CONNECTED. */
|
||||
|
||||
unsigned char recv_buf[MAX_PACKET_SIZE];
|
||||
size_t recv_buf_cur;
|
||||
|
||||
SendQueue sq;
|
||||
SendQueue::Buffer *sqb;
|
||||
|
||||
const unsigned char *send_buf;
|
||||
size_t send_remain;
|
||||
|
||||
Player(int sock, uint32_t ip, uint16_t port):
|
||||
state(PS_INIT), sock(sock), ip(ip), port(port), recv_buf_cur(0), send_buf(NULL) {}
|
||||
};
|
||||
|
||||
std::map<DPNID, Player> peers;
|
||||
std::list<Player> peers;
|
||||
std::map<DPNID, std::list<Player>::iterator> other_player_ids;
|
||||
|
||||
/* Serialises access to:
|
||||
*
|
||||
* host_enums
|
||||
* pending_peers
|
||||
* peers
|
||||
*/
|
||||
std::mutex lock;
|
||||
|
||||
void io_main();
|
||||
|
||||
void io_udp_recv(int sock);
|
||||
void io_udp_send(int sock, SendQueue &q);
|
||||
void io_listener_accept(int sock);
|
||||
bool io_tcp_recv(Player *player);
|
||||
bool io_tcp_send(Player *player);
|
||||
|
||||
void handle_host_enum_request(const PacketDeserialiser &pd, const struct sockaddr_in *from_addr);
|
||||
|
||||
public:
|
||||
DirectPlay8Peer(std::atomic<unsigned int> *global_refcount);
|
||||
|
302
src/HostEnumerator.cpp
Normal file
302
src/HostEnumerator.cpp
Normal file
@ -0,0 +1,302 @@
|
||||
#include <memory>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#include "DirectPlay8Address.hpp"
|
||||
#include "HostEnumerator.hpp"
|
||||
#include "Messages.hpp"
|
||||
#include "packet.hpp"
|
||||
|
||||
const GUID GUID_NULL = { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } };
|
||||
|
||||
HostEnumerator::HostEnumerator(
|
||||
std::atomic<unsigned int> * const global_refcount,
|
||||
|
||||
PFNDPNMESSAGEHANDLER message_handler,
|
||||
PVOID message_handler_ctx,
|
||||
|
||||
PDPN_APPLICATION_DESC const pApplicationDesc,
|
||||
IDirectPlay8Address *const pdpaddrHost,
|
||||
IDirectPlay8Address *const pdpaddrDeviceInfo,
|
||||
PVOID const pvUserEnumData,
|
||||
const DWORD dwUserEnumDataSize,
|
||||
const DWORD dwEnumCount,
|
||||
const DWORD dwRetryInterval,
|
||||
const DWORD dwTimeOut,
|
||||
PVOID const pvUserContext,
|
||||
|
||||
std::function<void(HRESULT)> complete_cb):
|
||||
|
||||
global_refcount(global_refcount),
|
||||
message_handler(message_handler),
|
||||
message_handler_ctx(message_handler_ctx),
|
||||
complete_cb(complete_cb),
|
||||
user_context(pvUserContext),
|
||||
req_cancel(false)
|
||||
{
|
||||
/* TODO: Use address in pdpaddrHost, if provided. */
|
||||
|
||||
send_addr.sin_family = AF_INET;
|
||||
send_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
|
||||
send_addr.sin_port = htons(DISCOVERY_PORT);
|
||||
|
||||
if(pApplicationDesc != NULL)
|
||||
{
|
||||
application_guid = pApplicationDesc->guidApplication;
|
||||
}
|
||||
else{
|
||||
application_guid = GUID_NULL;
|
||||
}
|
||||
|
||||
if(pvUserEnumData != NULL && dwUserEnumDataSize > 0)
|
||||
{
|
||||
user_data.insert(
|
||||
user_data.end(),
|
||||
(const unsigned char*)(pvUserEnumData),
|
||||
(const unsigned char*)(pvUserEnumData) + dwUserEnumDataSize);
|
||||
}
|
||||
|
||||
tx_remain = (dwEnumCount == 0) ? DEFAULT_ENUM_COUNT : dwEnumCount;
|
||||
tx_interval = (dwRetryInterval == 0) ? DEFAULT_ENUM_INTERVAL : dwRetryInterval;
|
||||
rx_timeout = (dwTimeOut == 0) ? DEFAULT_ENUM_TIMEOUT : dwTimeOut;
|
||||
|
||||
/* TODO: Bind to interface in pdpaddrDeviceInfo, if provided. */
|
||||
|
||||
sock = create_udp_socket(0, 0);
|
||||
if(sock == -1)
|
||||
{
|
||||
throw std::runtime_error("Cannot create UDP socket");
|
||||
}
|
||||
|
||||
wake_thread = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if(wake_thread == NULL)
|
||||
{
|
||||
closesocket(sock);
|
||||
throw std::runtime_error("Cannot create wake_thread object");
|
||||
}
|
||||
|
||||
if(WSAEventSelect(sock, wake_thread, FD_READ))
|
||||
{
|
||||
CloseHandle(wake_thread);
|
||||
closesocket(sock);
|
||||
throw std::runtime_error("Cannot WSAEventSelect");
|
||||
}
|
||||
|
||||
thread = new std::thread(&HostEnumerator::main, this);
|
||||
}
|
||||
|
||||
HostEnumerator::~HostEnumerator()
|
||||
{
|
||||
cancel();
|
||||
|
||||
if(thread->joinable())
|
||||
{
|
||||
if(thread->get_id() == std::this_thread::get_id())
|
||||
{
|
||||
thread->detach();
|
||||
}
|
||||
else{
|
||||
thread->join();
|
||||
}
|
||||
}
|
||||
|
||||
delete thread;
|
||||
|
||||
CloseHandle(wake_thread);
|
||||
}
|
||||
|
||||
void HostEnumerator::main()
|
||||
{
|
||||
while(!req_cancel)
|
||||
{
|
||||
DWORD now = GetTickCount();
|
||||
|
||||
if(tx_remain > 0 && now >= next_tx_at)
|
||||
{
|
||||
PacketSerialiser ps(DPLITE_MSGID_HOST_ENUM_REQUEST);
|
||||
|
||||
if(application_guid != GUID_NULL)
|
||||
{
|
||||
ps.append_guid(application_guid);
|
||||
}
|
||||
else{
|
||||
ps.append_null();
|
||||
}
|
||||
|
||||
if(!user_data.empty())
|
||||
{
|
||||
ps.append_data(user_data.data(), user_data.size());
|
||||
}
|
||||
else{
|
||||
ps.append_null();
|
||||
}
|
||||
|
||||
ps.append_dword(now);
|
||||
|
||||
std::pair<const void*, size_t> raw = ps.raw_packet();
|
||||
|
||||
sendto(sock, (const char*)(raw.first), raw.second, 0,
|
||||
(struct sockaddr*)(&send_addr), sizeof(send_addr));
|
||||
|
||||
next_tx_at = now + tx_interval;
|
||||
--tx_remain;
|
||||
|
||||
if(rx_timeout != INFINITE)
|
||||
{
|
||||
stop_at = now + rx_timeout;
|
||||
}
|
||||
}
|
||||
|
||||
struct sockaddr_in from_addr;
|
||||
int addrlen = sizeof(from_addr);
|
||||
|
||||
int r = recvfrom(sock, (char*)(recv_buf), sizeof(recv_buf), 0, (struct sockaddr*)(&from_addr), &addrlen);
|
||||
if(r > 0)
|
||||
{
|
||||
handle_packet(recv_buf, r, &from_addr);
|
||||
}
|
||||
|
||||
if(tx_remain == 0 && stop_at > 0 && now >= stop_at)
|
||||
{
|
||||
/* No more requests to transmit and the wait for replies from the last one
|
||||
* has timed out.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD timeout = INFINITE;
|
||||
if(tx_remain > 0) { timeout = std::min((next_tx_at - now), timeout); }
|
||||
if(stop_at > 0) { timeout = std::min((stop_at - now), timeout); }
|
||||
|
||||
WaitForSingleObject(wake_thread, timeout);
|
||||
}
|
||||
|
||||
if(req_cancel)
|
||||
{
|
||||
complete_cb(DPNERR_USERCANCEL);
|
||||
}
|
||||
else{
|
||||
complete_cb(S_OK);
|
||||
}
|
||||
}
|
||||
|
||||
void HostEnumerator::handle_packet(const void *data, size_t size, struct sockaddr_in *from_addr)
|
||||
{
|
||||
std::unique_ptr<PacketDeserialiser> pd;
|
||||
|
||||
try {
|
||||
pd.reset(new PacketDeserialiser(data, size));
|
||||
}
|
||||
catch(const PacketDeserialiser::Error &e)
|
||||
{
|
||||
/* Malformed packet received */
|
||||
return;
|
||||
}
|
||||
|
||||
if(pd->packet_type() != DPLITE_MSGID_HOST_ENUM_RESPONSE)
|
||||
{
|
||||
/* Unexpected packet type. */
|
||||
return;
|
||||
}
|
||||
|
||||
DPN_APPLICATION_DESC app_desc;
|
||||
memset(&app_desc, 0, sizeof(app_desc));
|
||||
std::wstring app_desc_pwszSessionName;
|
||||
|
||||
const void *response_data = NULL;
|
||||
size_t response_data_size = 0;
|
||||
|
||||
DWORD request_tick_count;
|
||||
|
||||
try {
|
||||
app_desc.dwSize = sizeof(app_desc);
|
||||
|
||||
app_desc.dwFlags = pd->get_dword(0);
|
||||
app_desc.guidInstance = pd->get_guid(1);
|
||||
app_desc.guidApplication = pd->get_guid(2);
|
||||
app_desc.dwMaxPlayers = pd->get_dword(3);
|
||||
app_desc.dwCurrentPlayers = pd->get_dword(4);
|
||||
|
||||
app_desc_pwszSessionName = pd->get_wstring(5);
|
||||
app_desc.pwszSessionName = (wchar_t*)(app_desc_pwszSessionName.c_str());
|
||||
|
||||
if(!pd->is_null(6))
|
||||
{
|
||||
std::pair<const void*, size_t> app_data = pd->get_data(6);
|
||||
app_desc.pvApplicationReservedData = (void*)(app_data.first);
|
||||
app_desc.dwApplicationReservedDataSize = app_data.second;
|
||||
}
|
||||
|
||||
if(!pd->is_null(7))
|
||||
{
|
||||
std::pair<const void*, size_t> r_data = pd->get_data(7);
|
||||
response_data = r_data.first;
|
||||
response_data_size = r_data.second;
|
||||
}
|
||||
|
||||
request_tick_count = pd->get_dword(8);
|
||||
}
|
||||
catch(const PacketDeserialiser::Error &e)
|
||||
{
|
||||
/* Malformed packet received */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Build a DirectPlay8Address with the host/port where the response came from - thats the main
|
||||
* port for the host.
|
||||
*/
|
||||
|
||||
IDirectPlay8Address *sender_address = new DirectPlay8Address(global_refcount);
|
||||
sender_address->AddRef();
|
||||
sender_address->SetSP(&CLSID_DP8SP_TCPIP); /* TODO: Be IPX if application previously gave us an IPX address? */
|
||||
|
||||
char from_addr_ip_s[16];
|
||||
inet_ntop(AF_INET, &(from_addr->sin_addr), from_addr_ip_s, sizeof(from_addr_ip_s));
|
||||
|
||||
sender_address->AddComponent(DPNA_KEY_HOSTNAME,
|
||||
from_addr_ip_s, strlen(from_addr_ip_s) + 1, DPNA_DATATYPE_STRING_ANSI);
|
||||
|
||||
char from_addr_port_s[8];
|
||||
snprintf(from_addr_port_s, sizeof(from_addr_port_s), "%u", (unsigned)(ntohs(from_addr->sin_port)));
|
||||
|
||||
sender_address->AddComponent(DPNA_KEY_PORT,
|
||||
from_addr_port_s, strlen(from_addr_port_s) + 1, DPNA_DATATYPE_STRING_ANSI);
|
||||
|
||||
/* Build a DirectPlay8Address with the interface we received the response on.
|
||||
* TODO: Actually do this.
|
||||
*/
|
||||
|
||||
IDirectPlay8Address *device_address = new DirectPlay8Address(global_refcount);
|
||||
device_address->AddRef();
|
||||
device_address->SetSP(&CLSID_DP8SP_TCPIP); /* TODO: Be IPX if application previously gave us an IPX address? */
|
||||
|
||||
DPNMSG_ENUM_HOSTS_RESPONSE message;
|
||||
memset(&message, 0, sizeof(message));
|
||||
|
||||
message.dwSize = sizeof(message);
|
||||
message.pAddressSender = sender_address;
|
||||
message.pAddressDevice = device_address;
|
||||
message.pApplicationDescription = &app_desc;
|
||||
message.pvResponseData = (void*)(response_data);
|
||||
message.dwResponseDataSize = response_data_size;
|
||||
message.pvUserContext = user_context;
|
||||
message.dwRoundTripLatencyMS = GetTickCount() - request_tick_count;
|
||||
|
||||
message_handler(message_handler_ctx, DPN_MSGID_ENUM_HOSTS_RESPONSE, &message);
|
||||
|
||||
device_address->Release();
|
||||
sender_address->Release();
|
||||
}
|
||||
|
||||
void HostEnumerator::cancel()
|
||||
{
|
||||
req_cancel = true;
|
||||
SetEvent(wake_thread);
|
||||
}
|
||||
|
||||
void HostEnumerator::wait()
|
||||
{
|
||||
if(thread->joinable())
|
||||
{
|
||||
thread->join();
|
||||
}
|
||||
}
|
82
src/HostEnumerator.hpp
Normal file
82
src/HostEnumerator.hpp
Normal file
@ -0,0 +1,82 @@
|
||||
#ifndef DPLITE_HOSTENUMERATOR_HPP
|
||||
#define DPLITE_HOSTENUMERATOR_HPP
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <dplay8.h>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <windows.h>
|
||||
|
||||
#include "network.hpp"
|
||||
|
||||
#define DEFAULT_ENUM_COUNT 3
|
||||
#define DEFAULT_ENUM_INTERVAL 1000
|
||||
#define DEFAULT_ENUM_TIMEOUT 1000
|
||||
|
||||
class HostEnumerator
|
||||
{
|
||||
private:
|
||||
/* No copy c'tor. */
|
||||
HostEnumerator(const HostEnumerator&) = delete;
|
||||
|
||||
/* Pointer to the global refcount (if in use), for instantiating DirectPlay8Address objects. */
|
||||
std::atomic<unsigned int> * const global_refcount;
|
||||
|
||||
/* DirectPlay message handler and context value */
|
||||
PFNDPNMESSAGEHANDLER message_handler;
|
||||
PVOID message_handler_ctx;
|
||||
|
||||
std::function<void(HRESULT)> complete_cb;
|
||||
|
||||
struct sockaddr_in send_addr;
|
||||
|
||||
GUID application_guid; /* GUID of application to search for, or GUID_NULL */
|
||||
std::vector<unsigned char> user_data; /* Data to include in request. */
|
||||
|
||||
DWORD tx_remain; /* Number of remaining requests to transmit, may be INFINITE. */
|
||||
DWORD tx_interval; /* Number of milliseconds to wait between transmits. */
|
||||
DWORD rx_timeout; /* Number of milliseconds to wait for replies to a request. */
|
||||
|
||||
void *user_context; /* DPNMSG_ENUM_HOSTS_REPONSE.pvUserContext */
|
||||
|
||||
DWORD next_tx_at;
|
||||
DWORD stop_at;
|
||||
|
||||
int sock;
|
||||
HANDLE wake_thread;
|
||||
std::thread *thread;
|
||||
bool req_cancel;
|
||||
|
||||
unsigned char recv_buf[MAX_PACKET_SIZE];
|
||||
|
||||
void main();
|
||||
void handle_packet(const void *data, size_t size, struct sockaddr_in *from_addr);
|
||||
|
||||
public:
|
||||
HostEnumerator(
|
||||
std::atomic<unsigned int> * const global_refcount,
|
||||
|
||||
PFNDPNMESSAGEHANDLER message_handler,
|
||||
PVOID message_handler_ctx,
|
||||
|
||||
PDPN_APPLICATION_DESC const pApplicationDesc,
|
||||
IDirectPlay8Address *const pdpaddrHost,
|
||||
IDirectPlay8Address *const pdpaddrDeviceInfo,
|
||||
PVOID const pvUserEnumData,
|
||||
const DWORD dwUserEnumDataSize,
|
||||
const DWORD dwEnumCount,
|
||||
const DWORD dwRetryInterval,
|
||||
const DWORD dwTimeOut,
|
||||
PVOID const pvUserContext,
|
||||
|
||||
std::function<void(HRESULT)> complete_cb
|
||||
);
|
||||
|
||||
~HostEnumerator();
|
||||
|
||||
void cancel();
|
||||
void wait();
|
||||
};
|
||||
|
||||
#endif /* !DPLITE_HOSTENUMERATOR_HPP */
|
29
src/Messages.hpp
Normal file
29
src/Messages.hpp
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef DPLITE_MESSAGES_HPP
|
||||
#define DPLITE_MESSAGES_HPP
|
||||
|
||||
#define DPLITE_MSGID_HOST_ENUM_REQUEST 1
|
||||
|
||||
/* EnumHosts() request message.
|
||||
*
|
||||
* GUID - Application GUID, NULL to search for any
|
||||
* DATA | NULL - User data
|
||||
* DWORD - Current tick count, to be returned, for latency measurement
|
||||
*/
|
||||
|
||||
#define DPLITE_MSGID_HOST_ENUM_RESPONSE 2
|
||||
|
||||
/* EnumHosts() response message.
|
||||
*
|
||||
* DWORD - DPN_APPLICATION_DESC.dwFlags
|
||||
* GUID - DPN_APPLICATION_DESC.guidInstance
|
||||
* GUID - DPN_APPLICATION_DESC.guidApplication
|
||||
* DWORD - DPN_APPLICATION_DESC.dwMaxPlayers
|
||||
* DWORD - DPN_APPLICATION_DESC.dwCurrentPlayers
|
||||
* WSTRING - DPN_APPLICATION_DESC.pwszSessionName
|
||||
* DATA | NULL - DPN_APPLICATION_DESC.pvApplicationReservedData
|
||||
*
|
||||
* DATA | NULL - DPN_MSGID_ENUM_HOSTS_RESPONSE.pvResponseData
|
||||
* DWORD - Tick count from DPLITE_MSGID_HOST_ENUM_REQUEST
|
||||
*/
|
||||
|
||||
#endif /* !DPLITE_MESSAGES_HPP */
|
78
src/SendQueue.cpp
Normal file
78
src/SendQueue.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include "SendQueue.hpp"
|
||||
|
||||
void SendQueue::send(SendPriority priority, Buffer *buffer)
|
||||
{
|
||||
switch(priority)
|
||||
{
|
||||
case SEND_PRI_LOW:
|
||||
low_queue.push_back(buffer);
|
||||
break;
|
||||
|
||||
case SEND_PRI_MEDIUM:
|
||||
medium_queue.push_back(buffer);
|
||||
break;
|
||||
|
||||
case SEND_PRI_HIGH:
|
||||
high_queue.push_back(buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SendQueue::Buffer *SendQueue::get_next()
|
||||
{
|
||||
if(current != NULL)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
if(!high_queue.empty())
|
||||
{
|
||||
current = high_queue.front();
|
||||
high_queue.pop_front();
|
||||
}
|
||||
else if(!medium_queue.empty())
|
||||
{
|
||||
current = medium_queue.front();
|
||||
medium_queue.pop_front();
|
||||
}
|
||||
else if(!low_queue.empty())
|
||||
{
|
||||
current = low_queue.front();
|
||||
low_queue.pop_front();
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
void SendQueue::complete(SendQueue::Buffer *buffer, HRESULT result)
|
||||
{
|
||||
assert(buffer == current);
|
||||
|
||||
current = NULL;
|
||||
|
||||
buffer->complete(result);
|
||||
delete buffer;
|
||||
}
|
||||
|
||||
SendQueue::Buffer::Buffer(const void *data, size_t data_size, const struct sockaddr *dest_addr, int dest_addr_len):
|
||||
data((const unsigned char*)(data), (const unsigned char*)(data) + data_size)
|
||||
{
|
||||
assert((size_t)(dest_addr_len) <= sizeof(this->dest_addr));
|
||||
|
||||
memcpy(&(this->dest_addr), dest_addr, dest_addr_len);
|
||||
this->dest_addr_len = dest_addr_len;
|
||||
}
|
||||
|
||||
SendQueue::Buffer::~Buffer() {}
|
||||
|
||||
std::pair<const void*, size_t> SendQueue::Buffer::get_data()
|
||||
{
|
||||
return std::make_pair<const void*, size_t>(data.data(), data.size());
|
||||
}
|
||||
|
||||
std::pair<const struct sockaddr*, int> SendQueue::Buffer::get_dest_addr()
|
||||
{
|
||||
return std::make_pair<const struct sockaddr*, int>((struct sockaddr*)(&dest_addr), (int)(dest_addr_len));
|
||||
}
|
60
src/SendQueue.hpp
Normal file
60
src/SendQueue.hpp
Normal file
@ -0,0 +1,60 @@
|
||||
#ifndef DPLITE_SENDQUEUE_HPP
|
||||
#define DPLITE_SENDQUEUE_HPP
|
||||
|
||||
#include <winsock2.h>
|
||||
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <stdlib.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class SendQueue
|
||||
{
|
||||
public:
|
||||
enum SendPriority {
|
||||
SEND_PRI_LOW = 1,
|
||||
SEND_PRI_MEDIUM = 2,
|
||||
SEND_PRI_HIGH = 4,
|
||||
};
|
||||
|
||||
class Buffer {
|
||||
private:
|
||||
std::vector<unsigned char> data;
|
||||
|
||||
struct sockaddr_storage dest_addr;
|
||||
int dest_addr_len;
|
||||
|
||||
protected:
|
||||
Buffer(const void *data, size_t data_size, const struct sockaddr *dest_addr = NULL, int dest_addr_len = 0);
|
||||
|
||||
public:
|
||||
virtual ~Buffer();
|
||||
|
||||
std::pair<const void*, size_t> get_data();
|
||||
|
||||
std::pair<const struct sockaddr*, int> get_dest_addr();
|
||||
|
||||
virtual void complete(HRESULT result) = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
std::list<Buffer*> low_queue;
|
||||
std::list<Buffer*> medium_queue;
|
||||
std::list<Buffer*> high_queue;
|
||||
|
||||
Buffer *current;
|
||||
|
||||
public:
|
||||
SendQueue(): current(NULL) {}
|
||||
|
||||
/* No copy c'tor. */
|
||||
SendQueue(const SendQueue &src) = delete;
|
||||
|
||||
void send(SendPriority priority, Buffer *buffer);
|
||||
|
||||
Buffer *get_next();
|
||||
void complete(Buffer *buffer, HRESULT result);
|
||||
};
|
||||
|
||||
#endif /* !DPLITE_SENDQUEUE_HPP */
|
@ -1,3 +1,4 @@
|
||||
#include <winsock2.h>
|
||||
#include <atomic>
|
||||
#include <dplay8.h>
|
||||
#include <objbase.h>
|
||||
|
@ -12,6 +12,13 @@ int create_udp_socket(uint32_t ipaddr, uint16_t port)
|
||||
return -1;
|
||||
}
|
||||
|
||||
u_long non_blocking = 1;
|
||||
if(ioctlsocket(sock, FIONBIO, &non_blocking) != 0)
|
||||
{
|
||||
closesocket(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
BOOL broadcast = TRUE;
|
||||
if(setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)(&broadcast), sizeof(BOOL)) == -1)
|
||||
{
|
||||
@ -35,12 +42,19 @@ int create_udp_socket(uint32_t ipaddr, uint16_t port)
|
||||
|
||||
int create_listener_socket(uint32_t ipaddr, uint16_t port)
|
||||
{
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if(sock == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
u_long non_blocking = 1;
|
||||
if(ioctlsocket(sock, FIONBIO, &non_blocking) != 0)
|
||||
{
|
||||
closesocket(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_in addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = ipaddr;
|
||||
@ -69,6 +83,13 @@ int create_discovery_socket()
|
||||
return -1;
|
||||
}
|
||||
|
||||
u_long non_blocking = 1;
|
||||
if(ioctlsocket(sock, FIONBIO, &non_blocking) != 0)
|
||||
{
|
||||
closesocket(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
BOOL reuse = TRUE;
|
||||
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)(&reuse), sizeof(BOOL)) == -1)
|
||||
{
|
||||
|
@ -6,6 +6,7 @@
|
||||
#define DISCOVERY_PORT 6073
|
||||
#define DEFAULT_HOST_PORT 6072
|
||||
#define LISTEN_QUEUE_SIZE 16
|
||||
#define MAX_PACKET_SIZE (60 * 1024)
|
||||
|
||||
int create_udp_socket(uint32_t ipaddr, uint16_t port);
|
||||
int create_listener_socket(uint32_t ipaddr, uint16_t port);
|
||||
|
888
tests/DirectPlay8Peer.cpp
Normal file
888
tests/DirectPlay8Peer.cpp
Normal file
@ -0,0 +1,888 @@
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <gtest/gtest.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../src/DirectPlay8Peer.hpp"
|
||||
|
||||
static const GUID APP_GUID_1 = { 0xa6133957, 0x6f42, 0x46ce, { 0xa9, 0x88, 0x22, 0xf7, 0x79, 0x47, 0x08, 0x16 } };
|
||||
static const GUID APP_GUID_2 = { 0x5917faae, 0x7ab0, 0x42d2, { 0xae, 0x13, 0x9c, 0x54, 0x1b, 0x7f, 0xb5, 0xab } };
|
||||
|
||||
static HRESULT CALLBACK callback_shim(PVOID pvUserContext, DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
std::function<HRESULT(DWORD,PVOID)> *callback = (std::function<HRESULT(DWORD,PVOID)>*)(pvUserContext);
|
||||
return (*callback)(dwMessageType, pMessage);
|
||||
}
|
||||
|
||||
/* Wrapper around a DirectPlay8Peer which hosts a session. */
|
||||
struct SessionHost
|
||||
{
|
||||
DirectPlay8Peer dp8p;
|
||||
std::function<HRESULT(DWORD,PVOID)> cb;
|
||||
|
||||
SessionHost(
|
||||
GUID application_guid,
|
||||
const wchar_t *session_description,
|
||||
std::function<HRESULT(DWORD,PVOID)> cb =
|
||||
[](DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
return DPN_OK;
|
||||
}):
|
||||
dp8p(NULL),
|
||||
cb(cb)
|
||||
{
|
||||
if(dp8p.Initialize(&(this->cb), &callback_shim, 0) != S_OK)
|
||||
{
|
||||
throw std::runtime_error("DirectPlay8Peer::Initialize failed");
|
||||
}
|
||||
|
||||
{
|
||||
DPN_APPLICATION_DESC app_desc;
|
||||
memset(&app_desc, 0, sizeof(app_desc));
|
||||
|
||||
app_desc.dwSize = sizeof(app_desc);
|
||||
app_desc.guidApplication = application_guid;
|
||||
app_desc.pwszSessionName = (wchar_t*)(session_description);
|
||||
|
||||
if(dp8p.Host(&app_desc, NULL, 0, NULL, NULL, NULL, 0) != S_OK)
|
||||
{
|
||||
throw std::runtime_error("DirectPlay8Peer::Host failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct FoundSession
|
||||
{
|
||||
GUID application_guid;
|
||||
std::wstring session_description;
|
||||
|
||||
FoundSession(GUID application_guid, const std::wstring &session_description):
|
||||
application_guid(application_guid),
|
||||
session_description(session_description) {}
|
||||
|
||||
bool operator==(const FoundSession &rhs) const
|
||||
{
|
||||
return application_guid == rhs.application_guid
|
||||
&& session_description == rhs.session_description;
|
||||
}
|
||||
};
|
||||
|
||||
struct CompareGUID {
|
||||
bool operator()(const GUID &a, const GUID &b) const
|
||||
{
|
||||
return memcmp(&a, &b, sizeof(GUID)) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
static void EXPECT_SESSIONS(std::map<GUID, FoundSession, CompareGUID> got, const FoundSession *expect_begin, const FoundSession *expect_end)
|
||||
{
|
||||
std::list<FoundSession> expect(expect_begin, expect_end);
|
||||
|
||||
for(auto gi = got.begin(); gi != got.end();)
|
||||
{
|
||||
for(auto ei = expect.begin(); ei != expect.end();)
|
||||
{
|
||||
if(gi->second == *ei)
|
||||
{
|
||||
ei = expect.erase(ei);
|
||||
gi = got.erase(gi);
|
||||
goto NEXT_GI;
|
||||
}
|
||||
else{
|
||||
++ei;
|
||||
}
|
||||
}
|
||||
|
||||
++gi;
|
||||
NEXT_GI:
|
||||
{}
|
||||
}
|
||||
|
||||
for(auto gi = got.begin(); gi != got.end(); ++gi)
|
||||
{
|
||||
wchar_t application_guid_s[128];
|
||||
StringFromGUID2(gi->second.application_guid, application_guid_s, 128);
|
||||
|
||||
ADD_FAILURE() << "Extra session:" << std::endl
|
||||
<< " application_guid = " << application_guid_s << std::endl
|
||||
<< " session_description = " << gi->second.session_description;
|
||||
}
|
||||
|
||||
for(auto ei = expect.begin(); ei != expect.end(); ++ei)
|
||||
{
|
||||
wchar_t application_guid_s[128];
|
||||
StringFromGUID2(ei->application_guid, application_guid_s, 128);
|
||||
|
||||
ADD_FAILURE() << "Missing session:" << std::endl
|
||||
<< " application_guid = " << application_guid_s << std::endl
|
||||
<< " session_description = " << ei->session_description;
|
||||
}
|
||||
|
||||
if(got.empty() && expect.empty())
|
||||
{
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DirectPlay8Peer, EnumHostsSync)
|
||||
{
|
||||
SessionHost a1s1(APP_GUID_1, L"Application 1 Session 1");
|
||||
SessionHost a1s2(APP_GUID_1, L"Application 1 Session 2");
|
||||
SessionHost a2s1(APP_GUID_2, L"Application 2 Session 1");
|
||||
|
||||
std::map<GUID, FoundSession, CompareGUID> sessions;
|
||||
|
||||
bool got_async_op_complete = false;
|
||||
|
||||
std::function<HRESULT(DWORD,PVOID)> client_cb =
|
||||
[&sessions, &got_async_op_complete]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ENUM_HOSTS_RESPONSE)
|
||||
{
|
||||
DPNMSG_ENUM_HOSTS_RESPONSE *ehr = (DPNMSG_ENUM_HOSTS_RESPONSE*)(pMessage);
|
||||
|
||||
EXPECT_EQ(ehr->dwSize, sizeof(DPNMSG_ENUM_HOSTS_RESPONSE));
|
||||
EXPECT_EQ(ehr->pvUserContext, (void*)(0xBEEF));
|
||||
|
||||
sessions.emplace(
|
||||
ehr->pApplicationDescription->guidInstance,
|
||||
FoundSession(
|
||||
ehr->pApplicationDescription->guidApplication,
|
||||
ehr->pApplicationDescription->pwszSessionName));
|
||||
}
|
||||
else if(dwMessageType == DPN_MSGID_ASYNC_OP_COMPLETE)
|
||||
{
|
||||
got_async_op_complete = true;
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
};
|
||||
|
||||
DirectPlay8Peer client(NULL);
|
||||
|
||||
ASSERT_EQ(client.Initialize(&client_cb, &callback_shim, 0), S_OK);
|
||||
|
||||
DWORD start = GetTickCount();
|
||||
|
||||
ASSERT_EQ(client.EnumHosts(
|
||||
NULL, /* pApplicationDesc */
|
||||
NULL, /* pdpaddrHost */
|
||||
NULL, /* pdpaddrDeviceInfo */
|
||||
NULL, /* pvUserEnumData */
|
||||
0, /* dwUserEnumDataSize */
|
||||
3, /* dwEnumCount */
|
||||
500, /* dwRetryInterval */
|
||||
500, /* dwTimeOut*/
|
||||
(void*)(0xBEEF), /* pvUserContext */
|
||||
NULL, /* pAsyncHandle */
|
||||
DPNENUMHOSTS_SYNC /* dwFlags */
|
||||
), S_OK);
|
||||
|
||||
DWORD end = GetTickCount();
|
||||
|
||||
FoundSession expect_sessions[] = {
|
||||
FoundSession(APP_GUID_1, L"Application 1 Session 1"),
|
||||
FoundSession(APP_GUID_1, L"Application 1 Session 2"),
|
||||
FoundSession(APP_GUID_2, L"Application 2 Session 1"),
|
||||
};
|
||||
|
||||
EXPECT_SESSIONS(sessions, expect_sessions, expect_sessions + 3);
|
||||
|
||||
DWORD enum_time_ms = end - start;
|
||||
EXPECT_TRUE((enum_time_ms >= 1250) && (enum_time_ms <= 1750));
|
||||
|
||||
EXPECT_FALSE(got_async_op_complete);
|
||||
}
|
||||
|
||||
TEST(DirectPlay8Peer, EnumHostsAsync)
|
||||
{
|
||||
SessionHost a1s1(APP_GUID_1, L"Application 1 Session 1");
|
||||
SessionHost a1s2(APP_GUID_1, L"Application 1 Session 2");
|
||||
SessionHost a2s1(APP_GUID_2, L"Application 2 Session 1");
|
||||
|
||||
std::map<GUID, FoundSession, CompareGUID> sessions;
|
||||
|
||||
bool got_async_op_complete = false;
|
||||
DWORD got_async_op_complete_at;
|
||||
DPNHANDLE async_handle;
|
||||
|
||||
std::function<HRESULT(DWORD,PVOID)> callback =
|
||||
[&sessions, &got_async_op_complete, &got_async_op_complete_at, &async_handle]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ENUM_HOSTS_RESPONSE)
|
||||
{
|
||||
DPNMSG_ENUM_HOSTS_RESPONSE *ehr = (DPNMSG_ENUM_HOSTS_RESPONSE*)(pMessage);
|
||||
|
||||
EXPECT_EQ(ehr->dwSize, sizeof(DPNMSG_ENUM_HOSTS_RESPONSE));
|
||||
EXPECT_EQ(ehr->pvUserContext, (void*)(0xABCD));
|
||||
|
||||
sessions.emplace(
|
||||
ehr->pApplicationDescription->guidInstance,
|
||||
FoundSession(
|
||||
ehr->pApplicationDescription->guidApplication,
|
||||
ehr->pApplicationDescription->pwszSessionName));
|
||||
}
|
||||
else if(dwMessageType == DPN_MSGID_ASYNC_OP_COMPLETE)
|
||||
{
|
||||
got_async_op_complete_at = GetTickCount();
|
||||
|
||||
/* We shouldn't get DPNMSG_ASYNC_OP_COMPLETE multiple times. */
|
||||
EXPECT_FALSE(got_async_op_complete);
|
||||
|
||||
DPNMSG_ASYNC_OP_COMPLETE *oc = (DPNMSG_ASYNC_OP_COMPLETE*)(pMessage);
|
||||
|
||||
EXPECT_EQ(oc->dwSize, sizeof(DPNMSG_ASYNC_OP_COMPLETE));
|
||||
EXPECT_EQ(oc->hAsyncOp, async_handle);
|
||||
EXPECT_EQ(oc->pvUserContext, (void*)(0xABCD));
|
||||
EXPECT_EQ(oc->hResultCode, S_OK);
|
||||
|
||||
got_async_op_complete = true;
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
};
|
||||
|
||||
DirectPlay8Peer client(NULL);
|
||||
|
||||
ASSERT_EQ(client.Initialize(&callback, &callback_shim, 0), S_OK);
|
||||
|
||||
DWORD start = GetTickCount();
|
||||
|
||||
ASSERT_EQ(client.EnumHosts(
|
||||
NULL, /* pApplicationDesc */
|
||||
NULL, /* pdpaddrHost */
|
||||
NULL, /* pdpaddrDeviceInfo */
|
||||
NULL, /* pvUserEnumData */
|
||||
0, /* dwUserEnumDataSize */
|
||||
3, /* dwEnumCount */
|
||||
500, /* dwRetryInterval */
|
||||
500, /* dwTimeOut*/
|
||||
(void*)(0xABCD), /* pvUserContext */
|
||||
&async_handle, /* pAsyncHandle */
|
||||
0 /* dwFlags */
|
||||
), DPNSUCCESS_PENDING);
|
||||
|
||||
Sleep(3000);
|
||||
|
||||
FoundSession expect_sessions[] = {
|
||||
FoundSession(APP_GUID_1, L"Application 1 Session 1"),
|
||||
FoundSession(APP_GUID_1, L"Application 1 Session 2"),
|
||||
FoundSession(APP_GUID_2, L"Application 2 Session 1"),
|
||||
};
|
||||
|
||||
EXPECT_SESSIONS(sessions, expect_sessions, expect_sessions + 3);
|
||||
|
||||
EXPECT_TRUE(got_async_op_complete);
|
||||
|
||||
if(got_async_op_complete)
|
||||
{
|
||||
DWORD enum_time_ms = got_async_op_complete_at - start;
|
||||
EXPECT_TRUE((enum_time_ms >= 1250) && (enum_time_ms <= 1750));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DirectPlay8Peer, EnumHostsAsyncCancelByHandle)
|
||||
{
|
||||
bool got_async_op_complete = false;
|
||||
DWORD got_async_op_complete_at;
|
||||
DPNHANDLE async_handle;
|
||||
|
||||
std::function<HRESULT(DWORD,PVOID)> callback =
|
||||
[&got_async_op_complete, &got_async_op_complete_at, &async_handle]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ASYNC_OP_COMPLETE)
|
||||
{
|
||||
got_async_op_complete_at = GetTickCount();
|
||||
|
||||
/* We shouldn't get DPNMSG_ASYNC_OP_COMPLETE multiple times. */
|
||||
EXPECT_FALSE(got_async_op_complete);
|
||||
|
||||
DPNMSG_ASYNC_OP_COMPLETE *oc = (DPNMSG_ASYNC_OP_COMPLETE*)(pMessage);
|
||||
|
||||
EXPECT_EQ(oc->dwSize, sizeof(DPNMSG_ASYNC_OP_COMPLETE));
|
||||
EXPECT_EQ(oc->hAsyncOp, async_handle);
|
||||
EXPECT_EQ(oc->pvUserContext, (void*)(0xABCD));
|
||||
EXPECT_EQ(oc->hResultCode, DPNERR_USERCANCEL);
|
||||
|
||||
got_async_op_complete = true;
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
};
|
||||
|
||||
DirectPlay8Peer client(NULL);
|
||||
|
||||
ASSERT_EQ(client.Initialize(&callback, &callback_shim, 0), S_OK);
|
||||
|
||||
DWORD start = GetTickCount();
|
||||
|
||||
ASSERT_EQ(client.EnumHosts(
|
||||
NULL, /* pApplicationDesc */
|
||||
NULL, /* pdpaddrHost */
|
||||
NULL, /* pdpaddrDeviceInfo */
|
||||
NULL, /* pvUserEnumData */
|
||||
0, /* dwUserEnumDataSize */
|
||||
3, /* dwEnumCount */
|
||||
500, /* dwRetryInterval */
|
||||
500, /* dwTimeOut*/
|
||||
(void*)(0xABCD), /* pvUserContext */
|
||||
&async_handle, /* pAsyncHandle */
|
||||
0 /* dwFlags */
|
||||
), DPNSUCCESS_PENDING);
|
||||
|
||||
ASSERT_EQ(client.CancelAsyncOperation(async_handle, 0), S_OK);
|
||||
|
||||
Sleep(500);
|
||||
|
||||
EXPECT_TRUE(got_async_op_complete);
|
||||
|
||||
if(got_async_op_complete)
|
||||
{
|
||||
DWORD enum_time_ms = got_async_op_complete_at - start;
|
||||
EXPECT_TRUE(enum_time_ms <= 250);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DirectPlay8Peer, EnumHostsAsyncCancelAllEnums)
|
||||
{
|
||||
bool got_async_op_complete = false;
|
||||
DWORD got_async_op_complete_at;
|
||||
DPNHANDLE async_handle;
|
||||
|
||||
std::function<HRESULT(DWORD,PVOID)> callback =
|
||||
[&got_async_op_complete, &got_async_op_complete_at, &async_handle]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ASYNC_OP_COMPLETE)
|
||||
{
|
||||
got_async_op_complete_at = GetTickCount();
|
||||
|
||||
/* We shouldn't get DPNMSG_ASYNC_OP_COMPLETE multiple times. */
|
||||
EXPECT_FALSE(got_async_op_complete);
|
||||
|
||||
DPNMSG_ASYNC_OP_COMPLETE *oc = (DPNMSG_ASYNC_OP_COMPLETE*)(pMessage);
|
||||
|
||||
EXPECT_EQ(oc->dwSize, sizeof(DPNMSG_ASYNC_OP_COMPLETE));
|
||||
EXPECT_EQ(oc->hAsyncOp, async_handle);
|
||||
EXPECT_EQ(oc->pvUserContext, (void*)(0xABCD));
|
||||
EXPECT_EQ(oc->hResultCode, DPNERR_USERCANCEL);
|
||||
|
||||
got_async_op_complete = true;
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
};
|
||||
|
||||
DirectPlay8Peer client(NULL);
|
||||
|
||||
ASSERT_EQ(client.Initialize(&callback, &callback_shim, 0), S_OK);
|
||||
|
||||
DWORD start = GetTickCount();
|
||||
|
||||
ASSERT_EQ(client.EnumHosts(
|
||||
NULL, /* pApplicationDesc */
|
||||
NULL, /* pdpaddrHost */
|
||||
NULL, /* pdpaddrDeviceInfo */
|
||||
NULL, /* pvUserEnumData */
|
||||
0, /* dwUserEnumDataSize */
|
||||
3, /* dwEnumCount */
|
||||
500, /* dwRetryInterval */
|
||||
500, /* dwTimeOut*/
|
||||
(void*)(0xABCD), /* pvUserContext */
|
||||
&async_handle, /* pAsyncHandle */
|
||||
0 /* dwFlags */
|
||||
), DPNSUCCESS_PENDING);
|
||||
|
||||
ASSERT_EQ(client.CancelAsyncOperation(0, DPNCANCEL_ENUM), S_OK);
|
||||
|
||||
Sleep(500);
|
||||
|
||||
EXPECT_TRUE(got_async_op_complete);
|
||||
|
||||
if(got_async_op_complete)
|
||||
{
|
||||
DWORD enum_time_ms = got_async_op_complete_at - start;
|
||||
EXPECT_TRUE(enum_time_ms <= 250);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DirectPlay8Peer, EnumHostsAsyncCancelAllOperations)
|
||||
{
|
||||
bool got_async_op_complete = false;
|
||||
DWORD got_async_op_complete_at;
|
||||
DPNHANDLE async_handle;
|
||||
|
||||
std::function<HRESULT(DWORD,PVOID)> callback =
|
||||
[&got_async_op_complete, &got_async_op_complete_at, &async_handle]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ASYNC_OP_COMPLETE)
|
||||
{
|
||||
got_async_op_complete_at = GetTickCount();
|
||||
|
||||
/* We shouldn't get DPNMSG_ASYNC_OP_COMPLETE multiple times. */
|
||||
EXPECT_FALSE(got_async_op_complete);
|
||||
|
||||
DPNMSG_ASYNC_OP_COMPLETE *oc = (DPNMSG_ASYNC_OP_COMPLETE*)(pMessage);
|
||||
|
||||
EXPECT_EQ(oc->dwSize, sizeof(DPNMSG_ASYNC_OP_COMPLETE));
|
||||
EXPECT_EQ(oc->hAsyncOp, async_handle);
|
||||
EXPECT_EQ(oc->pvUserContext, (void*)(0xABCD));
|
||||
EXPECT_EQ(oc->hResultCode, DPNERR_USERCANCEL);
|
||||
|
||||
got_async_op_complete = true;
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
};
|
||||
|
||||
DirectPlay8Peer client(NULL);
|
||||
|
||||
ASSERT_EQ(client.Initialize(&callback, &callback_shim, 0), S_OK);
|
||||
|
||||
DWORD start = GetTickCount();
|
||||
|
||||
ASSERT_EQ(client.EnumHosts(
|
||||
NULL, /* pApplicationDesc */
|
||||
NULL, /* pdpaddrHost */
|
||||
NULL, /* pdpaddrDeviceInfo */
|
||||
NULL, /* pvUserEnumData */
|
||||
0, /* dwUserEnumDataSize */
|
||||
3, /* dwEnumCount */
|
||||
500, /* dwRetryInterval */
|
||||
500, /* dwTimeOut*/
|
||||
(void*)(0xABCD), /* pvUserContext */
|
||||
&async_handle, /* pAsyncHandle */
|
||||
0 /* dwFlags */
|
||||
), DPNSUCCESS_PENDING);
|
||||
|
||||
ASSERT_EQ(client.CancelAsyncOperation(0, DPNCANCEL_ALL_OPERATIONS), S_OK);
|
||||
|
||||
Sleep(500);
|
||||
|
||||
EXPECT_TRUE(got_async_op_complete);
|
||||
|
||||
if(got_async_op_complete)
|
||||
{
|
||||
DWORD enum_time_ms = got_async_op_complete_at - start;
|
||||
EXPECT_TRUE(enum_time_ms <= 250);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DirectPlay8Peer, EnumHostsAsyncCancelByClose)
|
||||
{
|
||||
bool got_async_op_complete = false;
|
||||
DWORD got_async_op_complete_at;
|
||||
DPNHANDLE async_handle;
|
||||
|
||||
std::function<HRESULT(DWORD,PVOID)> callback =
|
||||
[&got_async_op_complete, &got_async_op_complete_at, &async_handle]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ASYNC_OP_COMPLETE)
|
||||
{
|
||||
got_async_op_complete_at = GetTickCount();
|
||||
|
||||
/* We shouldn't get DPNMSG_ASYNC_OP_COMPLETE multiple times. */
|
||||
EXPECT_FALSE(got_async_op_complete);
|
||||
|
||||
DPNMSG_ASYNC_OP_COMPLETE *oc = (DPNMSG_ASYNC_OP_COMPLETE*)(pMessage);
|
||||
|
||||
EXPECT_EQ(oc->dwSize, sizeof(DPNMSG_ASYNC_OP_COMPLETE));
|
||||
EXPECT_EQ(oc->hAsyncOp, async_handle);
|
||||
EXPECT_EQ(oc->pvUserContext, (void*)(0xABCD));
|
||||
EXPECT_EQ(oc->hResultCode, DPNERR_USERCANCEL);
|
||||
|
||||
got_async_op_complete = true;
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
};
|
||||
|
||||
DirectPlay8Peer client(NULL);
|
||||
|
||||
ASSERT_EQ(client.Initialize(&callback, &callback_shim, 0), S_OK);
|
||||
|
||||
DWORD start = GetTickCount();
|
||||
|
||||
ASSERT_EQ(client.EnumHosts(
|
||||
NULL, /* pApplicationDesc */
|
||||
NULL, /* pdpaddrHost */
|
||||
NULL, /* pdpaddrDeviceInfo */
|
||||
NULL, /* pvUserEnumData */
|
||||
0, /* dwUserEnumDataSize */
|
||||
3, /* dwEnumCount */
|
||||
500, /* dwRetryInterval */
|
||||
500, /* dwTimeOut*/
|
||||
(void*)(0xABCD), /* pvUserContext */
|
||||
&async_handle, /* pAsyncHandle */
|
||||
0 /* dwFlags */
|
||||
), DPNSUCCESS_PENDING);
|
||||
|
||||
ASSERT_EQ(client.Close(0), S_OK);
|
||||
|
||||
EXPECT_TRUE(got_async_op_complete);
|
||||
|
||||
if(got_async_op_complete)
|
||||
{
|
||||
DWORD enum_time_ms = got_async_op_complete_at - start;
|
||||
EXPECT_TRUE(enum_time_ms <= 250);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DirectPlay8Peer, EnumHostsFilterByApplicationGUID)
|
||||
{
|
||||
bool right_app_got_host_enum_query = false;
|
||||
bool wrong_app_got_host_enum_query = false;
|
||||
|
||||
SessionHost a1s1(APP_GUID_1, L"Application 1 Session 1",
|
||||
[&wrong_app_got_host_enum_query]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ENUM_HOSTS_QUERY)
|
||||
{
|
||||
wrong_app_got_host_enum_query = true;
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
});
|
||||
|
||||
SessionHost a1s2(APP_GUID_1, L"Application 1 Session 2",
|
||||
[&wrong_app_got_host_enum_query]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ENUM_HOSTS_QUERY)
|
||||
{
|
||||
wrong_app_got_host_enum_query = true;
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
});
|
||||
|
||||
SessionHost a2s1(APP_GUID_2, L"Application 2 Session 1",
|
||||
[&right_app_got_host_enum_query]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ENUM_HOSTS_QUERY)
|
||||
{
|
||||
DPNMSG_ENUM_HOSTS_QUERY *ehq = (DPNMSG_ENUM_HOSTS_QUERY*)(pMessage);
|
||||
|
||||
EXPECT_EQ(ehq->dwSize, sizeof(DPNMSG_ENUM_HOSTS_QUERY));
|
||||
|
||||
/* TODO: Check pAddressSender, pAddressDevice */
|
||||
|
||||
EXPECT_EQ(ehq->pvReceivedData, nullptr);
|
||||
EXPECT_EQ(ehq->dwReceivedDataSize, 0);
|
||||
|
||||
EXPECT_EQ(ehq->pvResponseData, nullptr);
|
||||
EXPECT_EQ(ehq->dwResponseDataSize, 0);
|
||||
|
||||
right_app_got_host_enum_query = true;
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
});
|
||||
|
||||
std::map<GUID, FoundSession, CompareGUID> sessions;
|
||||
|
||||
std::function<HRESULT(DWORD,PVOID)> client_cb =
|
||||
[&sessions]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ENUM_HOSTS_RESPONSE)
|
||||
{
|
||||
DPNMSG_ENUM_HOSTS_RESPONSE *ehr = (DPNMSG_ENUM_HOSTS_RESPONSE*)(pMessage);
|
||||
|
||||
EXPECT_EQ(ehr->dwSize, sizeof(DPNMSG_ENUM_HOSTS_RESPONSE));
|
||||
EXPECT_EQ(ehr->pvUserContext, (void*)(0xBEEF));
|
||||
|
||||
sessions.emplace(
|
||||
ehr->pApplicationDescription->guidInstance,
|
||||
FoundSession(
|
||||
ehr->pApplicationDescription->guidApplication,
|
||||
ehr->pApplicationDescription->pwszSessionName));
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
};
|
||||
|
||||
DirectPlay8Peer client(NULL);
|
||||
|
||||
ASSERT_EQ(client.Initialize(&client_cb, &callback_shim, 0), S_OK);
|
||||
|
||||
DPN_APPLICATION_DESC app_desc;
|
||||
memset(&app_desc, 0, sizeof(app_desc));
|
||||
|
||||
app_desc.dwSize = sizeof(app_desc);
|
||||
app_desc.guidApplication = APP_GUID_2;
|
||||
|
||||
ASSERT_EQ(client.EnumHosts(
|
||||
&app_desc, /* pApplicationDesc */
|
||||
NULL, /* pdpaddrHost */
|
||||
NULL, /* pdpaddrDeviceInfo */
|
||||
NULL, /* pvUserEnumData */
|
||||
0, /* dwUserEnumDataSize */
|
||||
3, /* dwEnumCount */
|
||||
500, /* dwRetryInterval */
|
||||
500, /* dwTimeOut*/
|
||||
(void*)(0xBEEF), /* pvUserContext */
|
||||
NULL, /* pAsyncHandle */
|
||||
DPNENUMHOSTS_SYNC /* dwFlags */
|
||||
), S_OK);
|
||||
|
||||
FoundSession expect_sessions[] = {
|
||||
FoundSession(APP_GUID_2, L"Application 2 Session 1"),
|
||||
};
|
||||
|
||||
EXPECT_SESSIONS(sessions, expect_sessions, expect_sessions + 1);
|
||||
|
||||
EXPECT_TRUE(right_app_got_host_enum_query);
|
||||
EXPECT_FALSE(wrong_app_got_host_enum_query);
|
||||
}
|
||||
|
||||
TEST(DirectPlay8Peer, EnumHostsFilterByNULLApplicationGUID)
|
||||
{
|
||||
SessionHost a1s1(APP_GUID_1, L"Application 1 Session 1");
|
||||
SessionHost a1s2(APP_GUID_1, L"Application 1 Session 2");
|
||||
SessionHost a2s1(APP_GUID_2, L"Application 2 Session 1");
|
||||
|
||||
std::map<GUID, FoundSession, CompareGUID> sessions;
|
||||
|
||||
std::function<HRESULT(DWORD,PVOID)> client_cb =
|
||||
[&sessions]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ENUM_HOSTS_RESPONSE)
|
||||
{
|
||||
DPNMSG_ENUM_HOSTS_RESPONSE *ehr = (DPNMSG_ENUM_HOSTS_RESPONSE*)(pMessage);
|
||||
|
||||
EXPECT_EQ(ehr->dwSize, sizeof(DPNMSG_ENUM_HOSTS_RESPONSE));
|
||||
EXPECT_EQ(ehr->pvUserContext, (void*)(0xBEEF));
|
||||
|
||||
sessions.emplace(
|
||||
ehr->pApplicationDescription->guidInstance,
|
||||
FoundSession(
|
||||
ehr->pApplicationDescription->guidApplication,
|
||||
ehr->pApplicationDescription->pwszSessionName));
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
};
|
||||
|
||||
DirectPlay8Peer client(NULL);
|
||||
|
||||
ASSERT_EQ(client.Initialize(&client_cb, &callback_shim, 0), S_OK);
|
||||
|
||||
DPN_APPLICATION_DESC app_desc;
|
||||
memset(&app_desc, 0, sizeof(app_desc));
|
||||
|
||||
app_desc.dwSize = sizeof(app_desc);
|
||||
app_desc.guidApplication = GUID_NULL;
|
||||
|
||||
ASSERT_EQ(client.EnumHosts(
|
||||
&app_desc, /* pApplicationDesc */
|
||||
NULL, /* pdpaddrHost */
|
||||
NULL, /* pdpaddrDeviceInfo */
|
||||
NULL, /* pvUserEnumData */
|
||||
0, /* dwUserEnumDataSize */
|
||||
3, /* dwEnumCount */
|
||||
500, /* dwRetryInterval */
|
||||
500, /* dwTimeOut*/
|
||||
(void*)(0xBEEF), /* pvUserContext */
|
||||
NULL, /* pAsyncHandle */
|
||||
DPNENUMHOSTS_SYNC /* dwFlags */
|
||||
), S_OK);
|
||||
|
||||
FoundSession expect_sessions[] = {
|
||||
FoundSession(APP_GUID_1, L"Application 1 Session 1"),
|
||||
FoundSession(APP_GUID_1, L"Application 1 Session 2"),
|
||||
FoundSession(APP_GUID_2, L"Application 2 Session 1"),
|
||||
};
|
||||
|
||||
EXPECT_SESSIONS(sessions, expect_sessions, expect_sessions + 3);
|
||||
}
|
||||
|
||||
TEST(DirectPlay8Peer, EnumHostsDataInQuery)
|
||||
{
|
||||
static const unsigned char DATA[] = { 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
|
||||
|
||||
SessionHost a1s1(APP_GUID_1, L"Application 1 Session 1",
|
||||
[]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ENUM_HOSTS_QUERY)
|
||||
{
|
||||
DPNMSG_ENUM_HOSTS_QUERY *ehq = (DPNMSG_ENUM_HOSTS_QUERY*)(pMessage);
|
||||
|
||||
EXPECT_EQ(ehq->dwSize, sizeof(DPNMSG_ENUM_HOSTS_QUERY));
|
||||
|
||||
std::vector<unsigned char> got_data(
|
||||
(const unsigned char*)(ehq->pvReceivedData),
|
||||
(const unsigned char*)(ehq->pvReceivedData) + ehq->dwReceivedDataSize);
|
||||
|
||||
std::vector<unsigned char> expect_data(DATA, DATA + sizeof(DATA));
|
||||
|
||||
EXPECT_EQ(got_data, expect_data);
|
||||
|
||||
EXPECT_EQ(ehq->pvResponseData, nullptr);
|
||||
EXPECT_EQ(ehq->dwResponseDataSize, 0);
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
});
|
||||
|
||||
std::map<GUID, FoundSession, CompareGUID> sessions;
|
||||
|
||||
std::function<HRESULT(DWORD,PVOID)> client_cb =
|
||||
[&sessions]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ENUM_HOSTS_RESPONSE)
|
||||
{
|
||||
DPNMSG_ENUM_HOSTS_RESPONSE *ehr = (DPNMSG_ENUM_HOSTS_RESPONSE*)(pMessage);
|
||||
|
||||
EXPECT_EQ(ehr->dwSize, sizeof(DPNMSG_ENUM_HOSTS_RESPONSE));
|
||||
EXPECT_EQ(ehr->pvUserContext, (void*)(0xBEEF));
|
||||
|
||||
EXPECT_EQ(ehr->pvResponseData, nullptr);
|
||||
EXPECT_EQ(ehr->dwResponseDataSize, 0);
|
||||
|
||||
sessions.emplace(
|
||||
ehr->pApplicationDescription->guidInstance,
|
||||
FoundSession(
|
||||
ehr->pApplicationDescription->guidApplication,
|
||||
ehr->pApplicationDescription->pwszSessionName));
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
};
|
||||
|
||||
DirectPlay8Peer client(NULL);
|
||||
|
||||
ASSERT_EQ(client.Initialize(&client_cb, &callback_shim, 0), S_OK);
|
||||
|
||||
ASSERT_EQ(client.EnumHosts(
|
||||
NULL, /* pApplicationDesc */
|
||||
NULL, /* pdpaddrHost */
|
||||
NULL, /* pdpaddrDeviceInfo */
|
||||
(void*)(DATA), /* pvUserEnumData */
|
||||
sizeof(DATA), /* dwUserEnumDataSize */
|
||||
3, /* dwEnumCount */
|
||||
500, /* dwRetryInterval */
|
||||
500, /* dwTimeOut*/
|
||||
(void*)(0xBEEF), /* pvUserContext */
|
||||
NULL, /* pAsyncHandle */
|
||||
DPNENUMHOSTS_SYNC /* dwFlags */
|
||||
), S_OK);
|
||||
|
||||
FoundSession expect_sessions[] = {
|
||||
FoundSession(APP_GUID_1, L"Application 1 Session 1"),
|
||||
};
|
||||
|
||||
EXPECT_SESSIONS(sessions, expect_sessions, expect_sessions + 1);
|
||||
}
|
||||
|
||||
TEST(DirectPlay8Peer, EnumHostsDataInResponse)
|
||||
{
|
||||
static const unsigned char DATA[] = { 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
|
||||
|
||||
bool got_return_buffer = false;
|
||||
|
||||
SessionHost a1s1(APP_GUID_1, L"Application 1 Session 1",
|
||||
[&got_return_buffer]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ENUM_HOSTS_QUERY)
|
||||
{
|
||||
DPNMSG_ENUM_HOSTS_QUERY *ehq = (DPNMSG_ENUM_HOSTS_QUERY*)(pMessage);
|
||||
|
||||
EXPECT_EQ(ehq->dwSize, sizeof(DPNMSG_ENUM_HOSTS_QUERY));
|
||||
|
||||
EXPECT_EQ(ehq->pvReceivedData, nullptr);
|
||||
EXPECT_EQ(ehq->dwReceivedDataSize, 0);
|
||||
|
||||
EXPECT_EQ(ehq->pvResponseData, nullptr);
|
||||
EXPECT_EQ(ehq->dwResponseDataSize, 0);
|
||||
|
||||
ehq->pvResponseData = (void*)(DATA);
|
||||
ehq->dwResponseDataSize = sizeof(DATA);
|
||||
ehq->pvResponseContext = (void*)(0x1234);
|
||||
}
|
||||
else if(dwMessageType == DPN_MSGID_RETURN_BUFFER)
|
||||
{
|
||||
DPNMSG_RETURN_BUFFER *rb = (DPNMSG_RETURN_BUFFER*)(pMessage);
|
||||
|
||||
EXPECT_EQ(rb->dwSize, sizeof(*rb));
|
||||
EXPECT_EQ(rb->hResultCode, S_OK);
|
||||
EXPECT_EQ(rb->pvBuffer, DATA);
|
||||
EXPECT_EQ(rb->pvUserContext, (void*)(0x1234));
|
||||
|
||||
got_return_buffer = true;
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
});
|
||||
|
||||
std::map<GUID, FoundSession, CompareGUID> sessions;
|
||||
|
||||
std::function<HRESULT(DWORD,PVOID)> client_cb =
|
||||
[&sessions]
|
||||
(DWORD dwMessageType, PVOID pMessage)
|
||||
{
|
||||
if(dwMessageType == DPN_MSGID_ENUM_HOSTS_RESPONSE)
|
||||
{
|
||||
DPNMSG_ENUM_HOSTS_RESPONSE *ehr = (DPNMSG_ENUM_HOSTS_RESPONSE*)(pMessage);
|
||||
|
||||
EXPECT_EQ(ehr->dwSize, sizeof(DPNMSG_ENUM_HOSTS_RESPONSE));
|
||||
EXPECT_EQ(ehr->pvUserContext, (void*)(0xBEEF));
|
||||
|
||||
std::vector<unsigned char> got_data(
|
||||
(const unsigned char*)(ehr->pvResponseData),
|
||||
(const unsigned char*)(ehr->pvResponseData) + ehr->dwResponseDataSize);
|
||||
|
||||
std::vector<unsigned char> expect_data(DATA, DATA + sizeof(DATA));
|
||||
|
||||
EXPECT_EQ(got_data, expect_data);
|
||||
|
||||
sessions.emplace(
|
||||
ehr->pApplicationDescription->guidInstance,
|
||||
FoundSession(
|
||||
ehr->pApplicationDescription->guidApplication,
|
||||
ehr->pApplicationDescription->pwszSessionName));
|
||||
}
|
||||
|
||||
return DPN_OK;
|
||||
};
|
||||
|
||||
DirectPlay8Peer client(NULL);
|
||||
|
||||
ASSERT_EQ(client.Initialize(&client_cb, &callback_shim, 0), S_OK);
|
||||
|
||||
ASSERT_EQ(client.EnumHosts(
|
||||
NULL, /* pApplicationDesc */
|
||||
NULL, /* pdpaddrHost */
|
||||
NULL, /* pdpaddrDeviceInfo */
|
||||
NULL, /* pvUserEnumData */
|
||||
0, /* dwUserEnumDataSize */
|
||||
3, /* dwEnumCount */
|
||||
500, /* dwRetryInterval */
|
||||
500, /* dwTimeOut*/
|
||||
(void*)(0xBEEF), /* pvUserContext */
|
||||
NULL, /* pAsyncHandle */
|
||||
DPNENUMHOSTS_SYNC /* dwFlags */
|
||||
), S_OK);
|
||||
|
||||
FoundSession expect_sessions[] = {
|
||||
FoundSession(APP_GUID_1, L"Application 1 Session 1"),
|
||||
};
|
||||
|
||||
EXPECT_SESSIONS(sessions, expect_sessions, expect_sessions + 1);
|
||||
|
||||
EXPECT_TRUE(got_return_buffer);
|
||||
}
|
||||
|
||||
/* TODO: Test enumerating a session directly. */
|
Loading…
x
Reference in New Issue
Block a user