1
0
mirror of https://github.com/solemnwarning/directplay-lite synced 2024-12-30 16:45:37 +01:00

Implement IDirectPlay8Peer::DestroyPeer()

This commit is contained in:
Daniel Collins 2018-10-14 17:41:57 +01:00
parent 7723fc04e3
commit b44689b38f
4 changed files with 366 additions and 1 deletions

View File

@ -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<std::mutex> 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<std::mutex> &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<std::mutex> &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<std::mutex> &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<std::mutex> &l, unsigned i
}
}
void DirectPlay8Peer::handle_destroy_peer(std::unique_lock<std::mutex> &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<const void*, size_t> 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<std::mutex> &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:

View File

@ -211,6 +211,7 @@ class DirectPlay8Peer: public IDirectPlay8Peer
void handle_playerinfo(std::unique_lock<std::mutex> &l, unsigned int peer_id, const PacketDeserialiser &pd);
void handle_ack(std::unique_lock<std::mutex> &l, unsigned int peer_id, const PacketDeserialiser &pd);
void handle_appdesc(std::unique_lock<std::mutex> &l, unsigned int peer_id, const PacketDeserialiser &pd);
void handle_destroy_peer(std::unique_lock<std::mutex> &l, unsigned int peer_id, const PacketDeserialiser &pd);
void connect_check(std::unique_lock<std::mutex> &l);
void connect_fail(std::unique_lock<std::mutex> &l, HRESULT hResultCode, const void *pvApplicationReplyData, DWORD dwApplicationReplyDataSize);

View File

@ -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 */

View File

@ -6640,3 +6640,158 @@ TEST(DirectPlay8Peer, EnumPlayersTooBig)
std::set<DPNID> 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<DPNID> 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<DPNID> 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);
}