From e7f5d0f68d30f40d7ae914833a46b3451319012f Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Thu, 11 Oct 2018 12:26:20 +0100 Subject: [PATCH] Proper implementation of IDirectPlay8Peer::Close() --- src/DirectPlay8Peer.cpp | 148 +++++++++--- src/DirectPlay8Peer.hpp | 14 +- tests/DirectPlay8Peer.cpp | 477 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 608 insertions(+), 31 deletions(-) diff --git a/src/DirectPlay8Peer.cpp b/src/DirectPlay8Peer.cpp index 513e762..c14702e 100644 --- a/src/DirectPlay8Peer.cpp +++ b/src/DirectPlay8Peer.cpp @@ -55,7 +55,7 @@ DirectPlay8Peer::~DirectPlay8Peer() { if(state != STATE_NEW) { - Close(0); + Close(DPNCLOSE_IMMEDIATE); } } @@ -108,6 +108,13 @@ HRESULT DirectPlay8Peer::Initialize(PVOID CONST pvUserContext, CONST PFNDPNMESSA return DPNERR_ALREADYINITIALIZED; } + WSADATA wd; + if(WSAStartup(MAKEWORD(2,2), &wd) != 0) + { + log_printf("WSAStartup() failed"); + return DPNERR_GENERIC; + } + message_handler = pfn; message_handler_ctx = pvUserContext; @@ -116,13 +123,6 @@ HRESULT DirectPlay8Peer::Initialize(PVOID CONST pvUserContext, CONST PFNDPNMESSA worker_pool->add_handle(udp_socket_event, [this]() { handle_udp_socket_event(); }); worker_pool->add_handle(other_socket_event, [this]() { handle_other_socket_event(); }); - WSADATA wd; - if(WSAStartup(MAKEWORD(2,2), &wd) != 0) - { - log_printf("WSAStartup() failed"); - return DPNERR_GENERIC; - } - state = STATE_INITIALISED; return S_OK; @@ -1369,29 +1369,27 @@ HRESULT DirectPlay8Peer::GetLocalHostAddresses(IDirectPlay8Address** CONST prgpA HRESULT DirectPlay8Peer::Close(CONST DWORD dwFlags) { - if(state == STATE_NEW) + std::unique_lock l(lock); + + bool was_connected = false; + + switch(state) { - return DPNERR_UNINITIALIZED; + case STATE_NEW: return DPNERR_UNINITIALIZED; + case STATE_INITIALISED: break; + case STATE_HOSTING: was_connected = true; break; + case STATE_CONNECTING: break; + case STATE_CONNECT_FAILED: break; + case STATE_CONNECTED: was_connected = true; break; + case STATE_CLOSING: return DPNERR_ALREADYCLOSING; } - CancelAsyncOperation(0, DPNCANCEL_ALL_OPERATIONS); - - /* TODO: Wait properly. */ - - while(1) + /* Signal all EnumHosts() calls to complete. */ + for(auto ei = host_enums.begin(); ei != host_enums.end(); ++ei) { - std::unique_lock l(lock); - if(host_enums.empty()) - { - break; - } - - Sleep(50); + ei->second.cancel(); } - delete worker_pool; - worker_pool = NULL; - if(discovery_socket != -1) { closesocket(discovery_socket); @@ -1410,6 +1408,88 @@ HRESULT DirectPlay8Peer::Close(CONST DWORD dwFlags) udp_socket = -1; } + state = STATE_CLOSING; + + if(dwFlags & DPNCLOSE_IMMEDIATE) + { + while(!peers.empty()) + { + unsigned int peer_id = peers.begin()->first; + peer_destroy(l, peer_id, DPNERR_USERCANCEL, DPNDESTROYPLAYERREASON_NORMAL); + } + } + else{ + for(auto pi = peers.begin(); pi != peers.end();) + { + unsigned int peer_id = pi->first; + Peer *peer = pi->second; + + if(peer->state == Peer::PS_CONNECTED) + { + DPNMSG_DESTROY_PLAYER dp; + memset(&dp, 0, sizeof(dp)); + + dp.dwSize = sizeof(dp); + dp.dpnidPlayer = peer->player_id; + dp.pvPlayerContext = peer->player_ctx; + dp.dwReason = DPNDESTROYPLAYERREASON_NORMAL; + + peer->state = Peer::PS_CLOSING; + + /* Wake up a worker to deal with closing the connection. */ + SetEvent(peer->event); + + l.unlock(); + message_handler(message_handler_ctx, DPN_MSGID_DESTROY_PLAYER, &dp); + l.lock(); + + pi = peers.begin(); + } + else if(peer->state == Peer::PS_CLOSING) + { + /* Do nothing. We're waiting for this peer to go away. */ + ++pi; + } + else{ + peer_destroy(l, peer_id, DPNERR_USERCANCEL, DPNDESTROYPLAYERREASON_NORMAL); + + pi = peers.begin(); + } + } + + /* Wait for remaining peers to finish disconnecting. */ + peer_destroyed.wait(l, [this]() { return peers.empty(); }); + } + + if(was_connected) + { + /* Raise a DPNMSG_DESTROY_PLAYER for ourself. */ + + DPNMSG_DESTROY_PLAYER dp; + memset(&dp, 0, sizeof(dp)); + + dp.dwSize = sizeof(DPNMSG_DESTROY_PLAYER); + dp.dpnidPlayer = local_player_id; + dp.pvPlayerContext = local_player_ctx; + dp.dwReason = DPNDESTROYPLAYERREASON_NORMAL; + + l.unlock(); + message_handler(message_handler_ctx, DPN_MSGID_DESTROY_PLAYER, &dp); + l.lock(); + } + + /* Wait for outstanding EnumHosts() calls. */ + host_enum_completed.wait(l, [this]() { return host_enums.empty(); }); + + /* We need to release the lock while the worker_pool destructor runs so that any worker + * threads waiting for it can finish. No other thread should mess with it while we are in + * STATE_CLOSING and we have no open sockets. + */ + l.unlock(); + delete worker_pool; + l.lock(); + worker_pool = NULL; + WSACleanup(); state = STATE_NEW; @@ -1427,6 +1507,8 @@ HRESULT DirectPlay8Peer::EnumHosts(PDPN_APPLICATION_DESC CONST pApplicationDesc, try { if(dwFlags & DPNENUMHOSTS_SYNC) { + /* TODO: Instantiate synchronous instances in host_enums. */ + HRESULT result; HostEnumerator he( @@ -1474,6 +1556,8 @@ HRESULT DirectPlay8Peer::EnumHosts(PDPN_APPLICATION_DESC CONST pApplicationDesc, std::unique_lock l(lock); host_enums.erase(handle); + + host_enum_completed.notify_all(); })); return DPNSUCCESS_PENDING; @@ -1719,6 +1803,11 @@ void DirectPlay8Peer::handle_udp_socket_event() { std::unique_lock l(lock); + if(udp_socket == -1) + { + return; + } + struct sockaddr_in from_addr; int fa_len = sizeof(from_addr); @@ -2419,6 +2508,11 @@ void DirectPlay8Peer::peer_destroy(std::unique_lock &l, unsigned int dp.pvPlayerContext = peer->player_ctx; dp.dwReason = destroy_player_reason; + /* Bodge to prevent io_peer_send() initiating a graceful shutdown + * while the application is handling the DPNMSG_DESTROY_PLAYER. + */ + peer->send_open = false; + peer->state = Peer::PS_CLOSING; l.unlock(); @@ -2446,6 +2540,8 @@ void DirectPlay8Peer::peer_destroy(std::unique_lock &l, unsigned int peers.erase(peer_id); delete peer; + + peer_destroyed.notify_all(); } /* Immediately close all sockets and erase all peers. */ @@ -3434,7 +3530,7 @@ void DirectPlay8Peer::connect_fail(std::unique_lock &l, HRESULT hRes } DirectPlay8Peer::Peer::Peer(enum PeerState state, int sock, uint32_t ip, uint16_t port): - state(state), sock(sock), ip(ip), port(port), recv_busy(false), recv_buf_cur(0), events(0), sq(event), next_ack_id(1) + state(state), sock(sock), ip(ip), port(port), recv_busy(false), recv_buf_cur(0), events(0), sq(event), send_open(true), next_ack_id(1) {} bool DirectPlay8Peer::Peer::enable_events(long events) diff --git a/src/DirectPlay8Peer.hpp b/src/DirectPlay8Peer.hpp index a43e58f..9394da4 100644 --- a/src/DirectPlay8Peer.hpp +++ b/src/DirectPlay8Peer.hpp @@ -34,11 +34,13 @@ class DirectPlay8Peer: public IDirectPlay8Peer STATE_CONNECTING, STATE_CONNECT_FAILED, STATE_CONNECTED, + STATE_CLOSING, } state; AsyncHandleAllocator handle_alloc; std::map host_enums; + std::condition_variable host_enum_completed; GUID instance_guid; GUID application_guid; @@ -53,7 +55,7 @@ class DirectPlay8Peer: public IDirectPlay8Peer uint32_t local_ip; uint16_t local_port; - int udp_socket; /* UDP socket, used for general send/recv operations. */ + int udp_socket; /* UDP socket, used for non-guaranteed send/recv operations. */ int listener_socket; /* TCP listener socket. */ int discovery_socket; /* Discovery UDP sockets, RECIEVES broadcasts only. */ @@ -154,14 +156,16 @@ class DirectPlay8Peer: public IDirectPlay8Peer unsigned int next_peer_id; std::map peers; + std::condition_variable peer_destroyed; std::map player_to_peer_id; - /* Serialises access to: + /* Serialises access to everything. * - * host_enums - * pending_peers - * peers + * All methods and event handlers hold this lock while executing. They will + * temporarily release it when executing the application message handler, after + * which they must reclaim it and check for any changes to the state which may + * affect them, such as a peer being destroyed. */ std::mutex lock; diff --git a/tests/DirectPlay8Peer.cpp b/tests/DirectPlay8Peer.cpp index 6b815e2..5ae7e74 100644 --- a/tests/DirectPlay8Peer.cpp +++ b/tests/DirectPlay8Peer.cpp @@ -2,7 +2,10 @@ #include #include #include +#include +#include #include +#include #include "../src/DirectPlay8Address.hpp" #include "../src/DirectPlay8Peer.hpp" @@ -91,6 +94,15 @@ class IDP8AddressInstance } } + IDP8AddressInstance(GUID service_provider, DWORD port): IDP8AddressInstance() + { + if(instance->SetSP(&service_provider) != S_OK + || instance->AddComponent(DPNA_KEY_PORT, &port, sizeof(DWORD), DPNA_DATATYPE_DWORD) != S_OK) + { + throw std::runtime_error("Address setup failed"); + } + } + ~IDP8AddressInstance() { #ifdef INSTANTIATE_FROM_COM @@ -217,6 +229,131 @@ struct SessionHost } }; +class TestPeer +{ + public: + DPNID first_cp_dpnidPlayer; + DPNID first_cc_dpnidLocal; + + private: + const char *ident; + + bool expecting; + std::list< std::function > callbacks; + std::mutex lock; + + IDirectPlay8Peer *instance; + + static HRESULT CALLBACK callback(PVOID pvUserContext, DWORD dwMessageType, PVOID pMessage) + { + TestPeer *t = (TestPeer*)(pvUserContext); + std::unique_lock l(t->lock); + + if(t->expecting) + { + if(!t->callbacks.empty()) + { + auto callback = t->callbacks.front(); + t->callbacks.pop_front(); + + l.unlock(); + + return callback(dwMessageType, pMessage); + } + else{ + ADD_FAILURE() << "[" << t->ident << "] Unexpected message with type " << dwMessageType; + return DPN_OK; + } + } + + if(dwMessageType == DPN_MSGID_CREATE_PLAYER) + { + DPNMSG_CREATE_PLAYER *cp = (DPNMSG_CREATE_PLAYER*)(pMessage); + + if(t->first_cp_dpnidPlayer == -1) + { + t->first_cp_dpnidPlayer = cp->dpnidPlayer; + } + + /* Invert the player ID to serve as a default context pointer. */ + cp->pvPlayerContext = (void*)~(uintptr_t)(cp->dpnidPlayer); + } + else if(dwMessageType == DPN_MSGID_CONNECT_COMPLETE) + { + DPNMSG_CONNECT_COMPLETE *cc = (DPNMSG_CONNECT_COMPLETE*)(pMessage); + + if(t->first_cc_dpnidLocal == -1) + { + t->first_cc_dpnidLocal = cc->dpnidLocal; + } + } + + return DPN_OK; + } + + public: + TestPeer(const char *ident): + first_cp_dpnidPlayer(-1), + first_cc_dpnidLocal(-1), + ident(ident), + expecting(false) + { + #ifdef INSTANTIATE_FROM_COM + CoInitialize(NULL); + CoCreateInstance(CLSID_DirectPlay8Peer, NULL, CLSCTX_INPROC_SERVER, IID_IDirectPlay8Peer, (void**)(&instance)); + #else + instance = new DirectPlay8Peer(NULL); + #endif + + if(instance->Initialize(this, &callback, 0) != S_OK) + { + throw std::runtime_error("IDirectPlay8Peer->Initialize() failed"); + } + } + + ~TestPeer() + { + #ifdef INSTANTIATE_FROM_COM + instance->Release(); + CoUninitialize(); + #else + instance->Release(); + #endif + } + + IDirectPlay8Peer &operator*() + { + return *instance; + } + + IDirectPlay8Peer *operator->() + { + return instance; + } + + void expect_begin() + { + std::unique_lock l(lock); + expecting = true; + } + + void expect_push(const std::function &callback) + { + std::unique_lock l(lock); + callbacks.push_back(callback); + } + + void expect_end() + { + std::unique_lock l(lock); + expecting = false; + + EXPECT_TRUE(callbacks.empty()); + + callbacks.clear(); + } +}; + struct FoundSession { GUID application_guid; @@ -2300,6 +2437,346 @@ TEST(DirectPlay8Peer, ConnectTwoPeersToHost) EXPECT_EQ(p2_cp2_dpnidPlayer, p1_player_id); } +TEST(DirectPlay8Peer, NonHostPeerSoftClose) +{ + DPN_APPLICATION_DESC app_desc; + memset(&app_desc, 0, sizeof(app_desc)); + + app_desc.dwSize = sizeof(app_desc); + app_desc.guidApplication = APP_GUID_1; + app_desc.pwszSessionName = L"Session 1"; + + IDP8AddressInstance host_addr(CLSID_DP8SP_TCPIP, PORT); + + TestPeer host("host"); + ASSERT_EQ(host->Host(&app_desc, &(host_addr.instance), 1, NULL, NULL, 0, 0), S_OK); + + IDP8AddressInstance connect_addr(CLSID_DP8SP_TCPIP, L"127.0.0.1", PORT); + + TestPeer peer1("peer1"); + ASSERT_EQ(peer1->Connect( + &app_desc, /* pdnAppDesc */ + connect_addr, /* pHostAddr */ + NULL, /* pDeviceInfo */ + NULL, /* pdnSecurity */ + NULL, /* pdnCredentials */ + NULL, /* pvUserConnectData */ + 0, /* dwUserConnectDataSize */ + 0, /* pvPlayerContext */ + NULL, /* pvAsyncContext */ + NULL, /* phAsyncHandle */ + DPNCONNECT_SYNC /* dwFlags */ + ), S_OK); + + TestPeer peer2("peer2"); + ASSERT_EQ(peer2->Connect( + &app_desc, /* pdnAppDesc */ + connect_addr, /* pHostAddr */ + NULL, /* pDeviceInfo */ + NULL, /* pdnSecurity */ + NULL, /* pdnCredentials */ + NULL, /* pvUserConnectData */ + 0, /* dwUserConnectDataSize */ + 0, /* pvPlayerContext */ + NULL, /* pvAsyncContext */ + NULL, /* phAsyncHandle */ + DPNCONNECT_SYNC /* dwFlags */ + ), S_OK); + + Sleep(100); + + host.expect_begin(); + host.expect_push([&peer2](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_DESTROY_PLAYER); + + if(dwMessageType == DPN_MSGID_DESTROY_PLAYER) + { + DPNMSG_DESTROY_PLAYER *dp = (DPNMSG_DESTROY_PLAYER*)(pMessage); + + EXPECT_EQ(dp->dwSize, sizeof(DPNMSG_DESTROY_PLAYER)); + EXPECT_EQ(dp->dpnidPlayer, peer2.first_cc_dpnidLocal); + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_NORMAL); + } + + return DPN_OK; + }); + + peer1.expect_begin(); + peer1.expect_push([&peer2](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_DESTROY_PLAYER); + + if(dwMessageType == DPN_MSGID_DESTROY_PLAYER) + { + DPNMSG_DESTROY_PLAYER *dp = (DPNMSG_DESTROY_PLAYER*)(pMessage); + + EXPECT_EQ(dp->dwSize, sizeof(DPNMSG_DESTROY_PLAYER)); + EXPECT_EQ(dp->dpnidPlayer, peer2.first_cc_dpnidLocal); + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_NORMAL); + } + + return DPN_OK; + }); + + DPNID p2_dp_dpnidPlayer1; + DPNID p2_dp_dpnidPlayer2; + + peer2.expect_begin(); + peer2.expect_push([&host, &peer1, &peer2, &p2_dp_dpnidPlayer1](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_DESTROY_PLAYER); + + if(dwMessageType == DPN_MSGID_DESTROY_PLAYER) + { + DPNMSG_DESTROY_PLAYER *dp = (DPNMSG_DESTROY_PLAYER*)(pMessage); + + EXPECT_EQ(dp->dwSize, sizeof(DPNMSG_DESTROY_PLAYER)); + + EXPECT_TRUE((dp->dpnidPlayer == host.first_cp_dpnidPlayer || dp->dpnidPlayer == peer1.first_cc_dpnidLocal)) + << "(dpnidPlayer = " << dp->dpnidPlayer + << ", host = " << host.first_cp_dpnidPlayer + << ", peer1 = " << peer1.first_cc_dpnidLocal + << ", peer2 = " << peer2.first_cc_dpnidLocal << ")"; + + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_NORMAL); + + p2_dp_dpnidPlayer1 = dp->dpnidPlayer; + } + + return DPN_OK; + }); + peer2.expect_push([&host, &peer1, &peer2, &p2_dp_dpnidPlayer2](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_DESTROY_PLAYER); + + if(dwMessageType == DPN_MSGID_DESTROY_PLAYER) + { + DPNMSG_DESTROY_PLAYER *dp = (DPNMSG_DESTROY_PLAYER*)(pMessage); + + EXPECT_EQ(dp->dwSize, sizeof(DPNMSG_DESTROY_PLAYER)); + + EXPECT_TRUE((dp->dpnidPlayer == host.first_cp_dpnidPlayer || dp->dpnidPlayer == peer1.first_cc_dpnidLocal)) + << "(dpnidPlayer = " << dp->dpnidPlayer + << ", host = " << host.first_cp_dpnidPlayer + << ", peer1 = " << peer1.first_cc_dpnidLocal + << ", peer2 = " << peer2.first_cc_dpnidLocal << ")"; + + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_NORMAL); + + p2_dp_dpnidPlayer2 = dp->dpnidPlayer; + } + + return DPN_OK; + }); + peer2.expect_push([&host, &peer1, &peer2](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_DESTROY_PLAYER); + + if(dwMessageType == DPN_MSGID_DESTROY_PLAYER) + { + DPNMSG_DESTROY_PLAYER *dp = (DPNMSG_DESTROY_PLAYER*)(pMessage); + + EXPECT_EQ(dp->dwSize, sizeof(DPNMSG_DESTROY_PLAYER)); + + EXPECT_TRUE((dp->dpnidPlayer == peer2.first_cc_dpnidLocal)) + << "(dpnidPlayer = " << dp->dpnidPlayer + << ", host = " << host.first_cp_dpnidPlayer + << ", peer1 = " << peer1.first_cc_dpnidLocal + << ", peer2 = " << peer2.first_cc_dpnidLocal << ")"; + + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_NORMAL); + } + + return DPN_OK; + }); + + peer2->Close(0); + + Sleep(100); + + peer2.expect_end(); + peer1.expect_end(); + host.expect_end(); + + EXPECT_NE(p2_dp_dpnidPlayer1, p2_dp_dpnidPlayer2); +} + +TEST(DirectPlay8Peer, NonHostPeerHardClose) +{ + DPN_APPLICATION_DESC app_desc; + memset(&app_desc, 0, sizeof(app_desc)); + + app_desc.dwSize = sizeof(app_desc); + app_desc.guidApplication = APP_GUID_1; + app_desc.pwszSessionName = L"Session 1"; + + IDP8AddressInstance host_addr(CLSID_DP8SP_TCPIP, PORT); + + TestPeer host("host"); + ASSERT_EQ(host->Host(&app_desc, &(host_addr.instance), 1, NULL, NULL, 0, 0), S_OK); + + IDP8AddressInstance connect_addr(CLSID_DP8SP_TCPIP, L"127.0.0.1", PORT); + + TestPeer peer1("peer1"); + ASSERT_EQ(peer1->Connect( + &app_desc, /* pdnAppDesc */ + connect_addr, /* pHostAddr */ + NULL, /* pDeviceInfo */ + NULL, /* pdnSecurity */ + NULL, /* pdnCredentials */ + NULL, /* pvUserConnectData */ + 0, /* dwUserConnectDataSize */ + 0, /* pvPlayerContext */ + NULL, /* pvAsyncContext */ + NULL, /* phAsyncHandle */ + DPNCONNECT_SYNC /* dwFlags */ + ), S_OK); + + TestPeer peer2("peer2"); + ASSERT_EQ(peer2->Connect( + &app_desc, /* pdnAppDesc */ + connect_addr, /* pHostAddr */ + NULL, /* pDeviceInfo */ + NULL, /* pdnSecurity */ + NULL, /* pdnCredentials */ + NULL, /* pvUserConnectData */ + 0, /* dwUserConnectDataSize */ + 0, /* pvPlayerContext */ + NULL, /* pvAsyncContext */ + NULL, /* phAsyncHandle */ + DPNCONNECT_SYNC /* dwFlags */ + ), S_OK); + + Sleep(100); + + host.expect_begin(); + host.expect_push([&peer2](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_DESTROY_PLAYER); + + if(dwMessageType == DPN_MSGID_DESTROY_PLAYER) + { + DPNMSG_DESTROY_PLAYER *dp = (DPNMSG_DESTROY_PLAYER*)(pMessage); + + EXPECT_EQ(dp->dwSize, sizeof(DPNMSG_DESTROY_PLAYER)); + EXPECT_EQ(dp->dpnidPlayer, peer2.first_cc_dpnidLocal); + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_CONNECTIONLOST); + } + + return DPN_OK; + }); + + peer1.expect_begin(); + peer1.expect_push([&peer2](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_DESTROY_PLAYER); + + if(dwMessageType == DPN_MSGID_DESTROY_PLAYER) + { + DPNMSG_DESTROY_PLAYER *dp = (DPNMSG_DESTROY_PLAYER*)(pMessage); + + EXPECT_EQ(dp->dwSize, sizeof(DPNMSG_DESTROY_PLAYER)); + EXPECT_EQ(dp->dpnidPlayer, peer2.first_cc_dpnidLocal); + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_CONNECTIONLOST); + } + + return DPN_OK; + }); + + DPNID p2_dp_dpnidPlayer1; + DPNID p2_dp_dpnidPlayer2; + + peer2.expect_begin(); + peer2.expect_push([&host, &peer1, &peer2, &p2_dp_dpnidPlayer1](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_DESTROY_PLAYER); + + if(dwMessageType == DPN_MSGID_DESTROY_PLAYER) + { + DPNMSG_DESTROY_PLAYER *dp = (DPNMSG_DESTROY_PLAYER*)(pMessage); + + EXPECT_EQ(dp->dwSize, sizeof(DPNMSG_DESTROY_PLAYER)); + + EXPECT_TRUE((dp->dpnidPlayer == host.first_cp_dpnidPlayer || dp->dpnidPlayer == peer1.first_cc_dpnidLocal)) + << "(dpnidPlayer = " << dp->dpnidPlayer + << ", host = " << host.first_cp_dpnidPlayer + << ", peer1 = " << peer1.first_cc_dpnidLocal + << ", peer2 = " << peer2.first_cc_dpnidLocal << ")"; + + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_NORMAL); + + p2_dp_dpnidPlayer1 = dp->dpnidPlayer; + } + + return DPN_OK; + }); + peer2.expect_push([&host, &peer1, &peer2, &p2_dp_dpnidPlayer2](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_DESTROY_PLAYER); + + if(dwMessageType == DPN_MSGID_DESTROY_PLAYER) + { + DPNMSG_DESTROY_PLAYER *dp = (DPNMSG_DESTROY_PLAYER*)(pMessage); + + EXPECT_EQ(dp->dwSize, sizeof(DPNMSG_DESTROY_PLAYER)); + + EXPECT_TRUE((dp->dpnidPlayer == host.first_cp_dpnidPlayer || dp->dpnidPlayer == peer1.first_cc_dpnidLocal)) + << "(dpnidPlayer = " << dp->dpnidPlayer + << ", host = " << host.first_cp_dpnidPlayer + << ", peer1 = " << peer1.first_cc_dpnidLocal + << ", peer2 = " << peer2.first_cc_dpnidLocal << ")"; + + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_NORMAL); + + p2_dp_dpnidPlayer2 = dp->dpnidPlayer; + } + + return DPN_OK; + }); + peer2.expect_push([&host, &peer1, &peer2](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_DESTROY_PLAYER); + + if(dwMessageType == DPN_MSGID_DESTROY_PLAYER) + { + DPNMSG_DESTROY_PLAYER *dp = (DPNMSG_DESTROY_PLAYER*)(pMessage); + + EXPECT_EQ(dp->dwSize, sizeof(DPNMSG_DESTROY_PLAYER)); + + EXPECT_TRUE((dp->dpnidPlayer == peer2.first_cc_dpnidLocal)) + << "(dpnidPlayer = " << dp->dpnidPlayer + << ", host = " << host.first_cp_dpnidPlayer + << ", peer1 = " << peer1.first_cc_dpnidLocal + << ", peer2 = " << peer2.first_cc_dpnidLocal << ")"; + + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_NORMAL); + } + + return DPN_OK; + }); + + peer2->Close(DPNCLOSE_IMMEDIATE); + + Sleep(100); + + peer2.expect_end(); + peer1.expect_end(); + host.expect_end(); + + EXPECT_NE(p2_dp_dpnidPlayer1, p2_dp_dpnidPlayer2); +} + TEST(DirectPlay8Peer, GetApplicationDesc) { const unsigned char APP_DATA[] = { 0x00, 0x01, 0x02, 0x03, 0x04 };