From c88355efb9dfa71ea6c63d891c2d5ef4417d24af Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Sun, 14 Oct 2018 01:13:57 +0100 Subject: [PATCH] DirectPlay8Peer: Support cancelling in-progress connections. --- src/DirectPlay8Peer.cpp | 36 ++++- tests/DirectPlay8Peer.cpp | 280 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 310 insertions(+), 6 deletions(-) diff --git a/src/DirectPlay8Peer.cpp b/src/DirectPlay8Peer.cpp index b3c76ba..347a41f 100644 --- a/src/DirectPlay8Peer.cpp +++ b/src/DirectPlay8Peer.cpp @@ -185,6 +185,8 @@ HRESULT DirectPlay8Peer::EnumServiceProviders(CONST GUID* CONST pguidServiceProv HRESULT DirectPlay8Peer::CancelAsyncOperation(CONST DPNHANDLE hAsyncHandle, CONST DWORD dwFlags) { + std::unique_lock l(lock); + if(dwFlags & DPNCANCEL_PLAYER_SENDS) { /* Cancel sends to player ID in hAsyncHandle */ @@ -196,8 +198,6 @@ HRESULT DirectPlay8Peer::CancelAsyncOperation(CONST DPNHANDLE hAsyncHandle, CONS if(dwFlags & (DPNCANCEL_ENUM | DPNCANCEL_ALL_OPERATIONS)) { - std::unique_lock l(lock); - for(auto ei = host_enums.begin(); ei != host_enums.end(); ++ei) { ei->second.cancel(); @@ -206,7 +206,11 @@ HRESULT DirectPlay8Peer::CancelAsyncOperation(CONST DPNHANDLE hAsyncHandle, CONS if(dwFlags & (DPNCANCEL_CONNECT | DPNCANCEL_ALL_OPERATIONS)) { - /* TODO: Cancel in-progress connect. */ + if((state == STATE_CONNECTING_TO_HOST || state == STATE_CONNECTING_TO_PEERS) && connect_handle != 0) + { + /* We have an ongoing asynchronous connection. */ + connect_fail(l, DPNERR_USERCANCEL, NULL, 0); + } } if(dwFlags & DPNCANCEL_ALL_OPERATIONS) @@ -218,8 +222,6 @@ HRESULT DirectPlay8Peer::CancelAsyncOperation(CONST DPNHANDLE hAsyncHandle, CONS } else if((hAsyncHandle & AsyncHandleAllocator::TYPE_MASK) == AsyncHandleAllocator::TYPE_ENUM) { - std::unique_lock l(lock); - auto ei = host_enums.find(hAsyncHandle); if(ei == host_enums.end()) { @@ -233,7 +235,20 @@ HRESULT DirectPlay8Peer::CancelAsyncOperation(CONST DPNHANDLE hAsyncHandle, CONS } else if((hAsyncHandle & AsyncHandleAllocator::TYPE_MASK) == AsyncHandleAllocator::TYPE_CONNECT) { - UNIMPLEMENTED("DirectPlay8Peer::CancelAsyncOperation"); + if(hAsyncHandle == connect_handle) + { + if(state == STATE_CONNECTING_TO_HOST || state == STATE_CONNECTING_TO_PEERS) + { + connect_fail(l, DPNERR_USERCANCEL, NULL, 0); + return S_OK; + } + else{ + return DPNERR_CANNOTCANCEL; + } + } + else{ + return DPNERR_INVALIDHANDLE; + } } else if((hAsyncHandle & AsyncHandleAllocator::TYPE_MASK) == AsyncHandleAllocator::TYPE_SEND) { @@ -1415,6 +1430,15 @@ HRESULT DirectPlay8Peer::Close(CONST DWORD dwFlags) udp_socket = -1; } + if(state == STATE_CONNECTING_TO_HOST || state == STATE_CONNECTING_TO_PEERS) + { + /* connect_fail() will change the state to STATE_CONNECT_FAILED, then finally to + * STATE_INITIALISED before it returns, this doesn't matter as we return it to + * STATE_CLOSING before the lock is released again. + */ + connect_fail(l, DPNERR_NOCONNECTION, NULL, 0); + } + state = STATE_CLOSING; /* When Close() is called as a host, the DPNMSG_DESTROY_PLAYER for the local player comes diff --git a/tests/DirectPlay8Peer.cpp b/tests/DirectPlay8Peer.cpp index 71d09c9..e727c18 100644 --- a/tests/DirectPlay8Peer.cpp +++ b/tests/DirectPlay8Peer.cpp @@ -1873,6 +1873,286 @@ TEST(DirectPlay8Peer, ConnectAsyncFail) EXPECT_EQ(p1_seq, 1); } +TEST(DirectPlay8Peer, ConnectAsyncCancelByHandle) +{ + /* We set up a host which blocks when processing DPN_MSGID_INDICATE_CONNECT, causing any + * attempted Connect() by a peer to take a while, giving us time to cancel it. + */ + SessionHost host(APP_GUID_1, L"Session 1", PORT, + [] + (DWORD dwMessageType, PVOID pMessage) + { + if(dwMessageType == DPN_MSGID_INDICATE_CONNECT) + { + Sleep(2000); + } + + return DPN_OK; + }); + + TestPeer peer1("peer1"); + + DPN_APPLICATION_DESC connect_to_app; + memset(&connect_to_app, 0, sizeof(connect_to_app)); + + connect_to_app.dwSize = sizeof(connect_to_app); + connect_to_app.guidApplication = APP_GUID_1; + + IDP8AddressInstance connect_to_addr(L"127.0.0.1", PORT); + + DPNHANDLE p1_connect_handle; + ASSERT_EQ(peer1->Connect( + &connect_to_app, /* pdnAppDesc */ + connect_to_addr, /* pHostAddr */ + NULL, /* pDeviceInfo */ + NULL, /* pdnSecurity */ + NULL, /* pdnCredentials */ + NULL, /* pvUserConnectData */ + 0, /* dwUserConnectDataSize */ + NULL, /* pvPlayerContext */ + (void*)(0xABCD), /* pvAsyncContext */ + &p1_connect_handle, /* phAsyncHandle */ + 0 /* dwFlags */ + ), DPNSUCCESS_PENDING); + + peer1.expect_begin(); + peer1.expect_push([&p1_connect_handle](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_CONNECT_COMPLETE); + + if(dwMessageType == DPN_MSGID_CONNECT_COMPLETE) + { + DPNMSG_CONNECT_COMPLETE *cc = (DPNMSG_CONNECT_COMPLETE*)(pMessage); + + EXPECT_EQ(cc->dwSize, sizeof(DPNMSG_CONNECT_COMPLETE)); + EXPECT_EQ(cc->hAsyncOp, p1_connect_handle); + EXPECT_EQ(cc->pvUserContext, (void*)(0xABCD)); + EXPECT_EQ(cc->hResultCode, DPNERR_USERCANCEL); + + EXPECT_EQ(cc->pvApplicationReplyData, (PVOID)(NULL)); + EXPECT_EQ(cc->dwApplicationReplyDataSize, 0); + } + + return DPN_OK; + }); + + ASSERT_EQ(peer1->CancelAsyncOperation(p1_connect_handle, 0), S_OK); + + Sleep(250); + + peer1.expect_end(); +} + +TEST(DirectPlay8Peer, ConnectAsyncCancelAllConnects) +{ + /* We set up a host which blocks when processing DPN_MSGID_INDICATE_CONNECT, causing any + * attempted Connect() by a peer to take a while, giving us time to cancel it. + */ + SessionHost host(APP_GUID_1, L"Session 1", PORT, + [] + (DWORD dwMessageType, PVOID pMessage) + { + if(dwMessageType == DPN_MSGID_INDICATE_CONNECT) + { + Sleep(2000); + } + + return DPN_OK; + }); + + TestPeer peer1("peer1"); + + DPN_APPLICATION_DESC connect_to_app; + memset(&connect_to_app, 0, sizeof(connect_to_app)); + + connect_to_app.dwSize = sizeof(connect_to_app); + connect_to_app.guidApplication = APP_GUID_1; + + IDP8AddressInstance connect_to_addr(L"127.0.0.1", PORT); + + DPNHANDLE p1_connect_handle; + ASSERT_EQ(peer1->Connect( + &connect_to_app, /* pdnAppDesc */ + connect_to_addr, /* pHostAddr */ + NULL, /* pDeviceInfo */ + NULL, /* pdnSecurity */ + NULL, /* pdnCredentials */ + NULL, /* pvUserConnectData */ + 0, /* dwUserConnectDataSize */ + NULL, /* pvPlayerContext */ + (void*)(0xABCD), /* pvAsyncContext */ + &p1_connect_handle, /* phAsyncHandle */ + 0 /* dwFlags */ + ), DPNSUCCESS_PENDING); + + peer1.expect_begin(); + peer1.expect_push([&p1_connect_handle](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_CONNECT_COMPLETE); + + if(dwMessageType == DPN_MSGID_CONNECT_COMPLETE) + { + DPNMSG_CONNECT_COMPLETE *cc = (DPNMSG_CONNECT_COMPLETE*)(pMessage); + + EXPECT_EQ(cc->dwSize, sizeof(DPNMSG_CONNECT_COMPLETE)); + EXPECT_EQ(cc->hAsyncOp, p1_connect_handle); + EXPECT_EQ(cc->pvUserContext, (void*)(0xABCD)); + EXPECT_EQ(cc->hResultCode, DPNERR_USERCANCEL); + + EXPECT_EQ(cc->pvApplicationReplyData, (PVOID)(NULL)); + EXPECT_EQ(cc->dwApplicationReplyDataSize, 0); + } + + return DPN_OK; + }); + + ASSERT_EQ(peer1->CancelAsyncOperation(NULL, DPNCANCEL_CONNECT), S_OK); + + Sleep(250); + + peer1.expect_end(); +} + +TEST(DirectPlay8Peer, ConnectAsyncCancelAllOperations) +{ + /* We set up a host which blocks when processing DPN_MSGID_INDICATE_CONNECT, causing any + * attempted Connect() by a peer to take a while, giving us time to cancel it. + */ + SessionHost host(APP_GUID_1, L"Session 1", PORT, + [] + (DWORD dwMessageType, PVOID pMessage) + { + if(dwMessageType == DPN_MSGID_INDICATE_CONNECT) + { + Sleep(2000); + } + + return DPN_OK; + }); + + TestPeer peer1("peer1"); + + DPN_APPLICATION_DESC connect_to_app; + memset(&connect_to_app, 0, sizeof(connect_to_app)); + + connect_to_app.dwSize = sizeof(connect_to_app); + connect_to_app.guidApplication = APP_GUID_1; + + IDP8AddressInstance connect_to_addr(L"127.0.0.1", PORT); + + DPNHANDLE p1_connect_handle; + ASSERT_EQ(peer1->Connect( + &connect_to_app, /* pdnAppDesc */ + connect_to_addr, /* pHostAddr */ + NULL, /* pDeviceInfo */ + NULL, /* pdnSecurity */ + NULL, /* pdnCredentials */ + NULL, /* pvUserConnectData */ + 0, /* dwUserConnectDataSize */ + NULL, /* pvPlayerContext */ + (void*)(0xABCD), /* pvAsyncContext */ + &p1_connect_handle, /* phAsyncHandle */ + 0 /* dwFlags */ + ), DPNSUCCESS_PENDING); + + peer1.expect_begin(); + peer1.expect_push([&p1_connect_handle](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_CONNECT_COMPLETE); + + if(dwMessageType == DPN_MSGID_CONNECT_COMPLETE) + { + DPNMSG_CONNECT_COMPLETE *cc = (DPNMSG_CONNECT_COMPLETE*)(pMessage); + + EXPECT_EQ(cc->dwSize, sizeof(DPNMSG_CONNECT_COMPLETE)); + EXPECT_EQ(cc->hAsyncOp, p1_connect_handle); + EXPECT_EQ(cc->pvUserContext, (void*)(0xABCD)); + EXPECT_EQ(cc->hResultCode, DPNERR_USERCANCEL); + + EXPECT_EQ(cc->pvApplicationReplyData, (PVOID)(NULL)); + EXPECT_EQ(cc->dwApplicationReplyDataSize, 0); + } + + return DPN_OK; + }); + + ASSERT_EQ(peer1->CancelAsyncOperation(NULL, DPNCANCEL_ALL_OPERATIONS), S_OK); + + Sleep(250); + + peer1.expect_end(); +} + +TEST(DirectPlay8Peer, ConnectAsyncCancelByClose) +{ + /* We set up a host which blocks when processing DPN_MSGID_INDICATE_CONNECT, causing any + * attempted Connect() by a peer to take a while, giving us time to cancel it. + */ + SessionHost host(APP_GUID_1, L"Session 1", PORT, + [] + (DWORD dwMessageType, PVOID pMessage) + { + if(dwMessageType == DPN_MSGID_INDICATE_CONNECT) + { + Sleep(2000); + } + + return DPN_OK; + }); + + TestPeer peer1("peer1"); + + DPN_APPLICATION_DESC connect_to_app; + memset(&connect_to_app, 0, sizeof(connect_to_app)); + + connect_to_app.dwSize = sizeof(connect_to_app); + connect_to_app.guidApplication = APP_GUID_1; + + IDP8AddressInstance connect_to_addr(L"127.0.0.1", PORT); + + DPNHANDLE p1_connect_handle; + ASSERT_EQ(peer1->Connect( + &connect_to_app, /* pdnAppDesc */ + connect_to_addr, /* pHostAddr */ + NULL, /* pDeviceInfo */ + NULL, /* pdnSecurity */ + NULL, /* pdnCredentials */ + NULL, /* pvUserConnectData */ + 0, /* dwUserConnectDataSize */ + NULL, /* pvPlayerContext */ + (void*)(0xABCD), /* pvAsyncContext */ + &p1_connect_handle, /* phAsyncHandle */ + 0 /* dwFlags */ + ), DPNSUCCESS_PENDING); + + peer1.expect_begin(); + peer1.expect_push([&p1_connect_handle](DWORD dwMessageType, PVOID pMessage) + { + EXPECT_EQ(dwMessageType, DPN_MSGID_CONNECT_COMPLETE); + + if(dwMessageType == DPN_MSGID_CONNECT_COMPLETE) + { + DPNMSG_CONNECT_COMPLETE *cc = (DPNMSG_CONNECT_COMPLETE*)(pMessage); + + EXPECT_EQ(cc->dwSize, sizeof(DPNMSG_CONNECT_COMPLETE)); + EXPECT_EQ(cc->hAsyncOp, p1_connect_handle); + EXPECT_EQ(cc->pvUserContext, (void*)(0xABCD)); + EXPECT_EQ(cc->hResultCode, DPNERR_NOCONNECTION); + + EXPECT_EQ(cc->pvApplicationReplyData, (PVOID)(NULL)); + EXPECT_EQ(cc->dwApplicationReplyDataSize, 0); + } + + return DPN_OK; + }); + + peer1->Close(DPNCLOSE_IMMEDIATE); + + Sleep(250); + + peer1.expect_end(); +} + TEST(DirectPlay8Peer, ConnectToIPX) { std::atomic testing(true);