From 89a3174ecd7ad8745dcc8b43c358c3d2b4f26706 Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Thu, 8 Nov 2018 22:42:48 +0000 Subject: [PATCH] IDirectPlay8Peer: Implement explicit group destruction. Still TODO: Automatic destruction on disconnect. --- src/AsyncHandleAllocator.cpp | 13 ++ src/AsyncHandleAllocator.hpp | 3 + src/DirectPlay8Peer.cpp | 238 ++++++++++++++++++++++++++++++++++- src/DirectPlay8Peer.hpp | 2 + src/Messages.hpp | 14 +++ 5 files changed, 269 insertions(+), 1 deletion(-) diff --git a/src/AsyncHandleAllocator.cpp b/src/AsyncHandleAllocator.cpp index 0b22982..f6cd6de 100644 --- a/src/AsyncHandleAllocator.cpp +++ b/src/AsyncHandleAllocator.cpp @@ -72,3 +72,16 @@ DPNHANDLE AsyncHandleAllocator::new_cgroup() return handle; } + +DPNHANDLE AsyncHandleAllocator::new_dgroup() +{ + DPNHANDLE handle = next_dgroup_id++ | TYPE_DGROUP; + + next_dgroup_id &= ~TYPE_MASK; + if(next_dgroup_id == 0) + { + next_dgroup_id = 1; + } + + return handle; +} diff --git a/src/AsyncHandleAllocator.hpp b/src/AsyncHandleAllocator.hpp index d76559c..ffcbf8f 100644 --- a/src/AsyncHandleAllocator.hpp +++ b/src/AsyncHandleAllocator.hpp @@ -24,6 +24,7 @@ class AsyncHandleAllocator DPNHANDLE next_send_id; DPNHANDLE next_pinfo_id; DPNHANDLE next_cgroup_id; + DPNHANDLE next_dgroup_id; public: static const DPNHANDLE TYPE_MASK = 0x1C000000; @@ -33,6 +34,7 @@ class AsyncHandleAllocator static const DPNHANDLE TYPE_SEND = 0x08000000; /* SendTo() */ static const DPNHANDLE TYPE_PINFO = 0x0C000000; /* SetPeerInfo() */ static const DPNHANDLE TYPE_CGROUP = 0x10000000; /* CreateGroup() */ + static const DPNHANDLE TYPE_DGROUP = 0x14000000; /* DestroyGroup() */ AsyncHandleAllocator(); @@ -41,6 +43,7 @@ class AsyncHandleAllocator DPNHANDLE new_send(); DPNHANDLE new_pinfo(); DPNHANDLE new_cgroup(); + DPNHANDLE new_dgroup(); }; #endif /* !DPLITE_ASYNCHANDLEALLOCATOR_HPP */ diff --git a/src/DirectPlay8Peer.cpp b/src/DirectPlay8Peer.cpp index c37edab..003cf78 100644 --- a/src/DirectPlay8Peer.cpp +++ b/src/DirectPlay8Peer.cpp @@ -1354,7 +1354,145 @@ HRESULT DirectPlay8Peer::CreateGroup(CONST DPN_GROUP_INFO* CONST pdpnGroupInfo, HRESULT DirectPlay8Peer::DestroyGroup(CONST DPNID idGroup, PVOID CONST pvAsyncContext, DPNHANDLE* CONST phAsyncHandle, CONST DWORD dwFlags) { - UNIMPLEMENTED("DirectPlay8Peer::DestroyGroup"); + 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: break; + case STATE_CLOSING: return DPNERR_CONNECTIONLOST; + case STATE_TERMINATED: return DPNERR_CONNECTIONLOST; + } + + Group *group = get_group_by_id(idGroup); + if(group == NULL) + { + return DPNERR_INVALIDGROUP; + } + + if(destroyed_groups.find(idGroup) != destroyed_groups.end()) + { + /* The group is there, but we're already destroying it. */ + return DPNERR_INVALIDGROUP; + } + + DPNHANDLE async_handle; + if(!(dwFlags & DPNDESTROYGROUP_SYNC)) + { + async_handle = handle_alloc.new_dgroup(); + + if(phAsyncHandle != NULL) + { + *phAsyncHandle = async_handle; + } + } + + destroyed_groups.insert(idGroup); + + /* Send DPLITE_MSGID_GROUP_DESTROY to each PS_CONNECTED peer. */ + + PacketSerialiser group_destroy(DPLITE_MSGID_GROUP_DESTROY); + group_destroy.append_dword(idGroup); + + int *pending = new int(1); + std::condition_variable cv; + + auto complete = + [this, pending, dwFlags, &cv, async_handle, pvAsyncContext] + (std::unique_lock &l) + { + if(--(*pending) == 0) + { + if(dwFlags & DPNDESTROYGROUP_SYNC) + { + cv.notify_one(); + } + else{ + DPNMSG_ASYNC_OP_COMPLETE oc; + memset(&oc, 0, sizeof(oc)); + + oc.dwSize = sizeof(oc); + oc.hAsyncOp = async_handle; + oc.pvUserContext = pvAsyncContext; + oc.hResultCode = S_OK; + + l.unlock(); + message_handler(message_handler_ctx, DPN_MSGID_ASYNC_OP_COMPLETE, &oc); + l.lock(); + + delete pending; + } + } + }; + + for(auto p = peers.begin(); p != peers.end(); ++p) + { + Peer *peer = p->second; + + if(peer->state == Peer::PS_CONNECTED) + { + ++(*pending); + + peer->sq.send(SendQueue::SEND_PRI_HIGH, group_destroy, NULL, + [complete] + (std::unique_lock &l, HRESULT result) + { + if(result != S_OK) + { + log_printf("Failed to send DPLITE_MSGID_GROUP_DESTROY, session may be out of sync!"); + } + + complete(l); + }); + } + } + + /* Raise local DPNMSG_DESTROY_GROUP and then destroy the group. + * TODO: Do this in a properly managed worker thread. + */ + + std::thread t([this, idGroup, complete]() + { + std::unique_lock l(lock); + + Group *group = get_group_by_id(idGroup); + if(group != NULL) + { + DPNMSG_DESTROY_GROUP dg; + memset(&dg, 0, sizeof(dg)); + + dg.dwSize = sizeof(dg); + dg.dpnidGroup = idGroup; + dg.pvGroupContext = group->ctx; + dg.dwReason = DPNDESTROYGROUPREASON_NORMAL; + + l.unlock(); + message_handler(message_handler_ctx, DPN_MSGID_DESTROY_GROUP, &dg); + l.lock(); + + groups.erase(idGroup); + } + + complete(l); + }); + + t.detach(); + + if(dwFlags & DPNDESTROYGROUP_SYNC) + { + cv.wait(l, [&pending]() { return (*pending == 0); }); + delete pending; + + return S_OK; + } + else{ + return DPNSUCCESS_PENDING; + } } HRESULT DirectPlay8Peer::AddPlayerToGroup(CONST DPNID idGroup, CONST DPNID idClient, PVOID CONST pvAsyncContext, DPNHANDLE* CONST phAsyncHandle, CONST DWORD dwFlags) @@ -1952,6 +2090,8 @@ HRESULT DirectPlay8Peer::Close(CONST DWORD dwFlags) l.lock(); worker_pool = NULL; + destroyed_groups.clear(); + WSACleanup(); state = STATE_NEW; @@ -2984,6 +3124,12 @@ void DirectPlay8Peer::io_peer_recv(std::unique_lock &l, unsigned int break; } + case DPLITE_MSGID_GROUP_DESTROY: + { + handle_group_destroy(l, peer_id, *pd); + break; + } + default: log_printf( "Unexpected message type %u received from peer %u", @@ -3586,6 +3732,17 @@ void DirectPlay8Peer::handle_host_connect_request(std::unique_lock & peer->state = Peer::PS_CONNECTED; + /* Send DPLITE_MSGID_GROUP_DESTROY for each destroyed group. */ + + for(auto di = destroyed_groups.begin(); di != destroyed_groups.end(); ++di) + { + PacketSerialiser group_destroy(DPLITE_MSGID_GROUP_DESTROY); + group_destroy.append_dword(*di); + + peer->sq.send(SendQueue::SEND_PRI_HIGH, group_destroy, NULL, + [](std::unique_lock &l, HRESULT result) {}); + } + /* Send DPLITE_MSGID_GROUP_CREATE for each group. */ for(auto gi = groups.begin(); gi != groups.end(); ++gi) @@ -3593,6 +3750,12 @@ void DirectPlay8Peer::handle_host_connect_request(std::unique_lock & DPNID group_id = gi->first; Group *group = &(gi->second); + if(destroyed_groups.find(group_id) != destroyed_groups.end()) + { + /* Group is being destroyed. */ + continue; + } + PacketSerialiser group_create(DPLITE_MSGID_GROUP_CREATE); group_create.append_dword(group_id); group_create.append_wstring(group->name); @@ -3918,6 +4081,17 @@ void DirectPlay8Peer::handle_connect_peer(std::unique_lock &l, unsig peer->state = Peer::PS_CONNECTED; + /* Send DPLITE_MSGID_GROUP_DESTROY for each destroyed group. */ + + for(auto di = destroyed_groups.begin(); di != destroyed_groups.end(); ++di) + { + PacketSerialiser group_destroy(DPLITE_MSGID_GROUP_DESTROY); + group_destroy.append_dword(*di); + + peer->sq.send(SendQueue::SEND_PRI_HIGH, group_destroy, NULL, + [](std::unique_lock &l, HRESULT result) {}); + } + /* Send DPLITE_MSGID_GROUP_CREATE for each group. */ for(auto gi = groups.begin(); gi != groups.end(); ++gi) @@ -3925,6 +4099,12 @@ void DirectPlay8Peer::handle_connect_peer(std::unique_lock &l, unsig DPNID group_id = gi->first; Group *group = &(gi->second); + if(destroyed_groups.find(group_id) != destroyed_groups.end()) + { + /* Group is being destroyed. */ + continue; + } + PacketSerialiser group_create(DPLITE_MSGID_GROUP_CREATE); group_create.append_dword(group_id); group_create.append_wstring(group->name); @@ -4394,6 +4574,12 @@ void DirectPlay8Peer::handle_group_create(std::unique_lock &l, unsig return; } + if(destroyed_groups.find(group_id) != destroyed_groups.end()) + { + /* Group has already been destroyed. */ + return; + } + groups.emplace( std::piecewise_construct, std::forward_as_tuple(group_id), @@ -4427,6 +4613,56 @@ void DirectPlay8Peer::handle_group_create(std::unique_lock &l, unsig } } +void DirectPlay8Peer::handle_group_destroy(std::unique_lock &l, unsigned int peer_id, const PacketDeserialiser &pd) +{ + Peer *peer = get_peer_by_peer_id(peer_id); + assert(peer != NULL); + + try { + DPNID group_id = pd.get_dword(0); + + if(peer->state != Peer::PS_CONNECTED + && peer->state != Peer::PS_REQUESTING_HOST + && peer->state != Peer::PS_REQUESTING_PEER) + { + log_printf("Received unexpected DPLITE_MSGID_GROUP_DESTROY from peer %u, in state %u", + peer_id, (unsigned)(peer->state)); + return; + } + + if(destroyed_groups.find(group_id) != destroyed_groups.end()) + { + /* Group is already in the process of being destroyed. */ + return; + } + + destroyed_groups.insert(group_id); + + Group *group = get_group_by_id(group_id); + if(group != NULL) + { + DPNMSG_DESTROY_GROUP dg; + memset(&dg, 0, sizeof(dg)); + + dg.dwSize = sizeof(dg); + dg.dpnidGroup = group_id; + dg.pvGroupContext = group->ctx; + dg.dwReason = DPNDESTROYGROUPREASON_NORMAL; + + l.unlock(); + message_handler(message_handler_ctx, DPN_MSGID_DESTROY_GROUP, &dg); + l.lock(); + + groups.erase(group_id); + } + } + catch(const PacketDeserialiser::Error &e) + { + log_printf("Received invalid DPLITE_MSGID_GROUP_DESTROY 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 2b3e2d5..d0f1913 100644 --- a/src/DirectPlay8Peer.hpp +++ b/src/DirectPlay8Peer.hpp @@ -175,6 +175,7 @@ class DirectPlay8Peer: public IDirectPlay8Peer }; std::map groups; + std::set destroyed_groups; /* Serialises access to everything. * @@ -230,6 +231,7 @@ class DirectPlay8Peer: public IDirectPlay8Peer void handle_destroy_peer(std::unique_lock &l, unsigned int peer_id, const PacketDeserialiser &pd); void handle_terminate_session(std::unique_lock &l, unsigned int peer_id, const PacketDeserialiser &pd); void handle_group_create(std::unique_lock &l, unsigned int peer_id, const PacketDeserialiser &pd); + void handle_group_destroy(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 83885d2..85b936a 100644 --- a/src/Messages.hpp +++ b/src/Messages.hpp @@ -169,4 +169,18 @@ * DATA - Group data (empty = none) */ +#define DPLITE_MSGID_GROUP_DESTROY 17 + +/* DPLITE_MSGID_GROUP_DESTROY + * A group has been destroyed. + * + * This is sent from the peer which called DestroyGroup() to all other peers. + * Once this message has been received, the group ID is permenantly unavailable + * to be used in the session, this is to ensure a group isn't re-created by + * receiving a message about a group from a different peer that was sent while + * the group still existed after this was processed. + * + * DWORD - Group ID +*/ + #endif /* !DPLITE_MESSAGES_HPP */