From b44689b38ff020723e25d76fc3046c999a50d4d6 Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Sun, 14 Oct 2018 17:41:57 +0100 Subject: [PATCH] Implement IDirectPlay8Peer::DestroyPeer() --- src/DirectPlay8Peer.cpp | 197 +++++++++++++++++++++++++++++++++++++- src/DirectPlay8Peer.hpp | 1 + src/Messages.hpp | 14 +++ tests/DirectPlay8Peer.cpp | 155 ++++++++++++++++++++++++++++++ 4 files changed, 366 insertions(+), 1 deletion(-) diff --git a/src/DirectPlay8Peer.cpp b/src/DirectPlay8Peer.cpp index d696bdd..a5ccc22 100644 --- a/src/DirectPlay8Peer.cpp +++ b/src/DirectPlay8Peer.cpp @@ -1692,7 +1692,77 @@ HRESULT DirectPlay8Peer::EnumHosts(PDPN_APPLICATION_DESC CONST pApplicationDesc, HRESULT DirectPlay8Peer::DestroyPeer(CONST DPNID dpnidClient, CONST void* CONST pvDestroyData, CONST DWORD dwDestroyDataSize, CONST DWORD dwFlags) { - UNIMPLEMENTED("DirectPlay8Peer::DestroyPeer"); + std::unique_lock l(lock); + + switch(state) + { + case STATE_NEW: return DPNERR_UNINITIALIZED; + case STATE_INITIALISED: return DPNERR_NOCONNECTION; + case STATE_HOSTING: break; + case STATE_CONNECTING_TO_HOST: return DPNERR_CONNECTING; + case STATE_CONNECTING_TO_PEERS: return DPNERR_CONNECTING; + case STATE_CONNECT_FAILED: return DPNERR_CONNECTING; + case STATE_CONNECTED: return DPNERR_NOTHOST; + case STATE_CLOSING: return DPNERR_CONNECTIONLOST; + case STATE_TERMINATED: return DPNERR_HOSTTERMINATEDSESSION; + } + + if(dpnidClient == local_player_id) + { + /* Can't destroy the local peer? */ + return DPNERR_INVALIDPARAM; + } + + Peer *peer = get_peer_by_player_id(dpnidClient); + if(peer == NULL) + { + return DPNERR_INVALIDPLAYER; + } + + PacketSerialiser destroy_peer_base(DPLITE_MSGID_DESTROY_PEER); + destroy_peer_base.append_dword(peer->player_id); + + PacketSerialiser destroy_peer_full(DPLITE_MSGID_DESTROY_PEER); + destroy_peer_full.append_dword(peer->player_id); + destroy_peer_full.append_data(pvDestroyData, dwDestroyDataSize); + + /* Notify the peer we are destroying it and initiate the connection shutdown. */ + + peer->sq.send(SendQueue::SEND_PRI_HIGH, destroy_peer_full, NULL, [](std::unique_lock &l, HRESULT result) {}); + + peer->state = Peer::PS_CLOSING; + + 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_HOSTDESTROYEDPLAYER; + + l.unlock(); + message_handler(message_handler_ctx, DPN_MSGID_DESTROY_PLAYER, &dp); + l.lock(); + + player_to_peer_id.erase(dpnidClient); + + /* Notify the other peers, in case the other peer is malfunctioning and doesn't remove + * itself from the session gracefully. + */ + + for(auto p = peers.begin(); p != peers.end(); ++p) + { + Peer *o_peer = p->second; + + if(o_peer->state != Peer::PS_CONNECTED) + { + continue; + } + + o_peer->sq.send(SendQueue::SEND_PRI_HIGH, destroy_peer_base, NULL, [](std::unique_lock &l, HRESULT result) {}); + } + + return S_OK; } HRESULT DirectPlay8Peer::ReturnBuffer(CONST DPNHANDLE hBufferHandle, CONST DWORD dwFlags) @@ -2433,6 +2503,12 @@ void DirectPlay8Peer::io_peer_recv(std::unique_lock &l, unsigned int break; } + case DPLITE_MSGID_DESTROY_PEER: + { + handle_destroy_peer(l, peer_id, *pd); + break; + } + default: log_printf( "Unexpected message type %u received from peer %u", @@ -3604,6 +3680,125 @@ void DirectPlay8Peer::handle_appdesc(std::unique_lock &l, unsigned i } } +void DirectPlay8Peer::handle_destroy_peer(std::unique_lock &l, unsigned int peer_id, const PacketDeserialiser &pd) +{ + Peer *peer = get_peer_by_peer_id(peer_id); + assert(peer != NULL); + + try { + DWORD destroy_player_id = pd.get_dword(0); + + if(peer->state != Peer::PS_CONNECTED) + { + log_printf("Received unexpected DPLITE_MSGID_DESTROY_PEER from peer %u, in state %u", + peer_id, (unsigned)(peer->state)); + return; + } + + /* host_player_id must be initialised by this point, as the host is always the + * first peer to enter state PS_CONNECTED, initialising it in the process. + */ + + if(peer->player_id != host_player_id && peer->player_id != destroy_player_id) + { + log_printf("Received unexpected DPLITE_MSGID_DESTROY_PEER from non-host peer %u", + peer_id); + return; + } + + if(destroy_player_id == local_player_id) + { + /* The host called DestroyPeer() on US! */ + + std::pair terminate_data = pd.get_data(1); + + DPNMSG_TERMINATE_SESSION ts; + memset(&ts, 0, sizeof(ts)); + + ts.dwSize = sizeof(DPNMSG_TERMINATE_SESSION); + ts.hResultCode = DPNERR_HOSTTERMINATEDSESSION; + ts.pvTerminateData = (void*)(terminate_data.first); /* TODO: Make non-const copy? */ + ts.dwTerminateDataSize = terminate_data.second; + + state = STATE_TERMINATED; + + l.unlock(); + message_handler(message_handler_ctx, DPN_MSGID_TERMINATE_SESSION, &ts); + l.lock(); + + 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_SESSIONTERMINATED; + + l.unlock(); + message_handler(message_handler_ctx, DPN_MSGID_DESTROY_PLAYER, &dp); + l.lock(); + + 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_SESSIONTERMINATED; + + PacketSerialiser destroy_peer(DPLITE_MSGID_DESTROY_PEER); + destroy_peer.append_dword(local_player_id); + + peer->sq.send(SendQueue::SEND_PRI_HIGH, destroy_peer, NULL, [](std::unique_lock &l, HRESULT result) {}); + peer->state = Peer::PS_CLOSING; + + 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_HOSTTERMINATEDSESSION, DPNDESTROYPLAYERREASON_NORMAL); + + pi = peers.begin(); + } + } + } + else{ + /* The host called DestroyPeer() on another peer in the session. + * + * Reciving an unknown player ID here is normal; the host notifies all + * peers of the DestroyPeer() and whichever end processes it first will + * close the connection. + */ + + auto destroy_peer_id = player_to_peer_id.find(destroy_player_id); + if(destroy_peer_id != player_to_peer_id.end()) + { + peer_destroy(l, destroy_peer_id->second, DPNERR_CONNECTIONLOST, DPNDESTROYPLAYERREASON_HOSTDESTROYEDPLAYER); + } + } + } + catch(const PacketDeserialiser::Error &e) + { + log_printf("Received invalid DPLITE_MSGID_DESTROY_PEER from peer %u: %s", + peer_id, e.what()); + } +} + /* Check if we have finished connecting and should enter STATE_CONNECTED. * * This is called after processing either of: diff --git a/src/DirectPlay8Peer.hpp b/src/DirectPlay8Peer.hpp index b811048..eda8375 100644 --- a/src/DirectPlay8Peer.hpp +++ b/src/DirectPlay8Peer.hpp @@ -211,6 +211,7 @@ class DirectPlay8Peer: public IDirectPlay8Peer void handle_playerinfo(std::unique_lock &l, unsigned int peer_id, const PacketDeserialiser &pd); void handle_ack(std::unique_lock &l, unsigned int peer_id, const PacketDeserialiser &pd); void handle_appdesc(std::unique_lock &l, unsigned int peer_id, const PacketDeserialiser &pd); + void handle_destroy_peer(std::unique_lock &l, unsigned int peer_id, const PacketDeserialiser &pd); void connect_check(std::unique_lock &l); void connect_fail(std::unique_lock &l, HRESULT hResultCode, const void *pvApplicationReplyData, DWORD dwApplicationReplyDataSize); diff --git a/src/Messages.hpp b/src/Messages.hpp index 608e8cf..bf9f156 100644 --- a/src/Messages.hpp +++ b/src/Messages.hpp @@ -135,4 +135,18 @@ * DWORD - Error code (DPNERR_HOSTREJECTEDCONNECTION, DPNERR_INVALIDAPPLICATION, etc) */ +#define DPLITE_MSGID_DESTROY_PEER 13 + +/* Peer has been destroyed by the host calling IDirectPlay8Peer::DestroyPeer() + * + * Sent from host to peer being terminated, to all other peers and from the terminated peer to each + * peer when it closes the connection. + * + * The victim peer must remove itself from the session after receiving this message. All other + * peers will eject it when they receive notifcation from the server. + * + * DWORD - Player ID of peer being destroyed + * DATA - DPNMSG_TERMINATE_SESSION.pvTerminateData (only from host to victim) +*/ + #endif /* !DPLITE_MESSAGES_HPP */ diff --git a/tests/DirectPlay8Peer.cpp b/tests/DirectPlay8Peer.cpp index c61ddb9..da48a5d 100644 --- a/tests/DirectPlay8Peer.cpp +++ b/tests/DirectPlay8Peer.cpp @@ -6640,3 +6640,158 @@ TEST(DirectPlay8Peer, EnumPlayersTooBig) std::set p2_player_ids(p2_buffer, p2_buffer + p2_max_results); EXPECT_EQ(p2_player_ids, expect_player_ids); } + +TEST(DirectPlay8Peer, DestroyPeer) +{ + const unsigned char DESTROY_DATA[] = { 0xAA, 0xBB, 0x00, 0xDD, 0xEE, 0xFF }; + + 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([&host, &peer1](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, peer1.first_cc_dpnidLocal); + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_HOSTDESTROYEDPLAYER); + } + + return DPN_OK; + }); + + std::set p1_dp_dpnidPlayer; + int p1_ts = 0; + + peer1.expect_begin(); + peer1.expect_push( + [&host, &peer1, &peer2, &DESTROY_DATA, &p1_dp_dpnidPlayer, &p1_ts] + (DWORD dwMessageType, PVOID pMessage) + { + if(dwMessageType == DPN_MSGID_TERMINATE_SESSION) + { + DPNMSG_TERMINATE_SESSION *ts = (DPNMSG_TERMINATE_SESSION*)(pMessage); + + EXPECT_EQ(ts->dwSize, sizeof(DPNMSG_TERMINATE_SESSION)); + EXPECT_EQ(ts->hResultCode, DPNERR_HOSTTERMINATEDSESSION); + + EXPECT_EQ( + std::string((const char*)(ts->pvTerminateData), ts->dwTerminateDataSize), + std::string((const char*)(DESTROY_DATA), sizeof(DESTROY_DATA))); + + ++p1_ts; + } + else 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 || 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)); + + if(dp->dpnidPlayer == peer1.first_cc_dpnidLocal) + { + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_SESSIONTERMINATED); + } + else{ + EXPECT_TRUE((dp->dwReason == DPNDESTROYPLAYERREASON_SESSIONTERMINATED || dp->dwReason == DPNDESTROYPLAYERREASON_CONNECTIONLOST)); + } + + p1_dp_dpnidPlayer.insert(dp->dpnidPlayer); + } + else{ + ADD_FAILURE() << "Unexpected message type: " << dwMessageType; + } + + return DPN_OK; + }, 4); + + peer2.expect_begin(); + peer2.expect_push([&peer1](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, peer1.first_cc_dpnidLocal); + EXPECT_EQ(dp->pvPlayerContext, (void*)~(uintptr_t)(dp->dpnidPlayer)); + EXPECT_EQ(dp->dwReason, DPNDESTROYPLAYERREASON_HOSTDESTROYEDPLAYER); + } + + return DPN_OK; + }); + + ASSERT_EQ(host->DestroyPeer(peer1.first_cc_dpnidLocal, DESTROY_DATA, sizeof(DESTROY_DATA), 0), S_OK); + + Sleep(250); + + peer2.expect_end(); + peer1.expect_end(); + host.expect_end(); + + std::set all_players; + all_players.insert(host.first_cp_dpnidPlayer); + all_players.insert(peer1.first_cc_dpnidLocal); + all_players.insert(peer2.first_cc_dpnidLocal); + + EXPECT_EQ(p1_dp_dpnidPlayer, all_players); + EXPECT_EQ(p1_ts, 1); +}