diff --git a/src/DirectPlay8Address.cpp b/src/DirectPlay8Address.cpp index ad86b83..ad591ea 100644 --- a/src/DirectPlay8Address.cpp +++ b/src/DirectPlay8Address.cpp @@ -1,3 +1,5 @@ +#include +#include #include #include #include @@ -5,6 +7,7 @@ #include #include #include +#include #include "DirectPlay8Address.hpp" #include "Log.hpp" @@ -34,6 +37,55 @@ DirectPlay8Address::~DirectPlay8Address() clear_components(); } +/* Construct a DirectPlay8Address which represents a host address. + * + * service_provider must be CLSID_DP8SP_TCPIP or CLSID_DP8SP_IPX. + * sa must be an IPv4 address with a valid IP and port. +*/ +DirectPlay8Address *DirectPlay8Address::create_host_address(std::atomic *global_refcount, GUID service_provider, const struct sockaddr *sa) +{ + assert(service_provider == CLSID_DP8SP_TCPIP || service_provider == CLSID_DP8SP_IPX); + assert(sa->sa_family == AF_INET); + + const struct sockaddr_in *sa_v4 = (const struct sockaddr_in*)(sa); + + DirectPlay8Address *address = new DirectPlay8Address(global_refcount); + address->SetSP(&service_provider); + + if(service_provider == CLSID_DP8SP_TCPIP) + { + /* TCP/IP service provider, just stick the IP in the hostname field. */ + + char hostname[16]; + inet_ntop(AF_INET, &(sa_v4->sin_addr), hostname, sizeof(hostname)); + + address->AddComponent(DPNA_KEY_HOSTNAME, + hostname, strlen(hostname) + 1, DPNA_DATATYPE_STRING_ANSI); + } + else if(service_provider == CLSID_DP8SP_IPX) + { + /* IPX service provider. + * + * Network address is all zeros. + * Host address is IP address preceeded by two zero bytes. + */ + + uint32_t ipaddr_he = ntohl(sa_v4->sin_addr.s_addr); + + char hostname[32]; + snprintf(hostname, sizeof(hostname), "00000000,0000%08X", (unsigned)(ipaddr_he)); + + address->AddComponent(DPNA_KEY_HOSTNAME, + hostname, strlen(hostname) + 1, DPNA_DATATYPE_STRING_ANSI); + } + + DWORD port_dw = ntohs(sa_v4->sin_port); + + address->AddComponent(DPNA_KEY_PORT, &port_dw, sizeof(port_dw), DPNA_DATATYPE_DWORD); + + return address; +} + void DirectPlay8Address::clear_components() { for(auto c = components.begin(); c != components.end(); ++c) diff --git a/src/DirectPlay8Address.hpp b/src/DirectPlay8Address.hpp index f95e289..48a264b 100644 --- a/src/DirectPlay8Address.hpp +++ b/src/DirectPlay8Address.hpp @@ -1,6 +1,7 @@ #ifndef DPLITE_DIRECTPLAY8ADDRESS_HPP #define DPLITE_DIRECTPLAY8ADDRESS_HPP +#include #include #include #include @@ -69,6 +70,8 @@ class DirectPlay8Address: public IDirectPlay8Address DirectPlay8Address(const DirectPlay8Address &src); virtual ~DirectPlay8Address(); + static DirectPlay8Address *create_host_address(std::atomic *global_refcount, GUID service_provider, const struct sockaddr *sa); + /* IUnknown */ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override; virtual ULONG STDMETHODCALLTYPE AddRef() override; diff --git a/src/DirectPlay8Peer.cpp b/src/DirectPlay8Peer.cpp index 720b5ab..2eb0005 100644 --- a/src/DirectPlay8Peer.cpp +++ b/src/DirectPlay8Peer.cpp @@ -672,6 +672,11 @@ HRESULT DirectPlay8Peer::Host(CONST DPN_APPLICATION_DESC* CONST pdnAppDesc, IDir /* Not supported yet. */ } + if(cDeviceInfo == 0) + { + return DPNERR_INVALIDPARAM; + } + /* Generate a random GUID for this session. */ HRESULT guid_err = CoCreateGuid(&instance_guid); if(guid_err != S_OK) @@ -700,6 +705,7 @@ HRESULT DirectPlay8Peer::Host(CONST DPN_APPLICATION_DESC* CONST pdnAppDesc, IDir (unsigned char*)(pdnAppDesc->pvApplicationReservedData) + pdnAppDesc->dwApplicationReservedDataSize); } + GUID sp = GUID_NULL; uint32_t ipaddr = htonl(INADDR_ANY); uint16_t port = 0; @@ -707,6 +713,26 @@ HRESULT DirectPlay8Peer::Host(CONST DPN_APPLICATION_DESC* CONST pdnAppDesc, IDir { DirectPlay8Address *addr = (DirectPlay8Address*)(prgpDeviceInfo[i]); + GUID this_sp; + if(addr->GetSP(&this_sp) != S_OK) + { + return DPNERR_INVALIDDEVICEADDRESS; + } + + if(sp != GUID_NULL && this_sp != sp) + { + /* Multiple service providers specified, don't support this yet. */ + return E_NOTIMPL; + } + + if(this_sp != CLSID_DP8SP_TCPIP && this_sp != CLSID_DP8SP_IPX) + { + /* Only support TCP/IP and IPX addresses at this time. */ + return DPNERR_INVALIDDEVICEADDRESS; + } + + sp = this_sp; + DWORD addr_port_value; DWORD addr_port_size = sizeof(addr_port_value); DWORD addr_port_type; @@ -725,6 +751,8 @@ HRESULT DirectPlay8Peer::Host(CONST DPN_APPLICATION_DESC* CONST pdnAppDesc, IDir } } + service_provider = sp; + if(port == 0) { for(int p = AUTO_PORT_MIN; p <= AUTO_PORT_MAX; ++p) @@ -1274,7 +1302,22 @@ HRESULT DirectPlay8Peer::GetPeerInfo(CONST DPNID dpnid, DPN_PLAYER_INFO* CONST p HRESULT DirectPlay8Peer::GetPeerAddress(CONST DPNID dpnid, IDirectPlay8Address** CONST pAddress, CONST DWORD dwFlags) { - UNIMPLEMENTED("DirectPlay8Peer::GetPeerAddress"); + std::unique_lock l(lock); + + Peer *peer = get_peer_by_player_id(dpnid); + if(peer == NULL) + { + return DPNERR_INVALIDPLAYER; + } + + struct sockaddr_in sa; + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = peer->ip; + sa.sin_port = htons(peer->port); + + *pAddress = DirectPlay8Address::create_host_address(global_refcount, service_provider, (struct sockaddr*)(&sa)); + + return S_OK; } HRESULT DirectPlay8Peer::GetLocalHostAddresses(IDirectPlay8Address** CONST prgpAddress, DWORD* CONST pcAddress, CONST DWORD dwFlags) @@ -2245,8 +2288,11 @@ void DirectPlay8Peer::handle_host_enum_request(std::unique_lock &l, DPNMSG_ENUM_HOSTS_QUERY ehq; memset(&ehq, 0, sizeof(ehq)); + DirectPlay8Address *sender_address = DirectPlay8Address::create_host_address( + global_refcount, service_provider, (struct sockaddr*)(from_addr)); + ehq.dwSize = sizeof(ehq); - ehq.pAddressSender = NULL; // TODO + ehq.pAddressSender = sender_address; ehq.pAddressDevice = NULL; // TODO if(!pd.is_null(1)) @@ -2265,6 +2311,8 @@ void DirectPlay8Peer::handle_host_enum_request(std::unique_lock &l, HRESULT ehq_result = message_handler(message_handler_ctx, DPN_MSGID_ENUM_HOSTS_QUERY, &ehq); l.lock(); + sender_address->Release(); + std::vector response_data_buffer; if(ehq.dwResponseDataSize > 0) { @@ -2413,7 +2461,15 @@ void DirectPlay8Peer::handle_host_connect_request(std::unique_lock & ic.dwUserConnectDataSize = d.second; } - ic.pAddressPlayer = NULL; /* TODO */ + struct sockaddr_in peer_sa; + peer_sa.sin_family = AF_INET; + peer_sa.sin_addr.s_addr = peer->ip; + peer_sa.sin_port = htons(peer->port); + + DirectPlay8Address *peer_address = DirectPlay8Address::create_host_address( + global_refcount, service_provider, (struct sockaddr*)(&peer_sa)); + + ic.pAddressPlayer = peer_address; ic.pAddressDevice = NULL; /* TODO */ peer->state = Peer::PS_INDICATING; @@ -2422,6 +2478,8 @@ void DirectPlay8Peer::handle_host_connect_request(std::unique_lock & HRESULT ic_result = message_handler(message_handler_ctx, DPN_MSGID_INDICATE_CONNECT, &ic); l.lock(); + peer_address->Release(); + std::vector reply_data_buffer; if(ic.dwReplyDataSize > 0) { diff --git a/src/DirectPlay8Peer.hpp b/src/DirectPlay8Peer.hpp index d388134..01db70a 100644 --- a/src/DirectPlay8Peer.hpp +++ b/src/DirectPlay8Peer.hpp @@ -47,6 +47,8 @@ class DirectPlay8Peer: public IDirectPlay8Peer std::wstring password; std::vector application_data; + GUID service_provider; + /* Local IP and port for all our sockets, except discovery_socket. */ uint32_t local_ip; uint16_t local_port; diff --git a/src/HostEnumerator.cpp b/src/HostEnumerator.cpp index a9a8db2..401b8aa 100644 --- a/src/HostEnumerator.cpp +++ b/src/HostEnumerator.cpp @@ -1,7 +1,9 @@ #include #include +#include #include +#include "COMAPIException.hpp" #include "DirectPlay8Address.hpp" #include "HostEnumerator.hpp" #include "Messages.hpp" @@ -35,13 +37,88 @@ HostEnumerator::HostEnumerator( next_tx_at(0), req_cancel(false) { - /* TODO: Use address in pdpaddrHost, if provided. */ + if(pdpaddrDeviceInfo == NULL) + { + throw COMAPIException(DPNERR_INVALIDPARAM); + } + + if(pdpaddrDeviceInfo->GetSP(&service_provider) != S_OK) + { + throw COMAPIException(DPNERR_INVALIDDEVICEADDRESS); + } memset(&send_addr, 0, sizeof(send_addr)); send_addr.sin_family = AF_INET; send_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); send_addr.sin_port = htons(DISCOVERY_PORT); + if(pdpaddrHost != NULL) + { + GUID host_sp; + if(pdpaddrHost->GetSP(&host_sp) != S_OK) + { + throw COMAPIException(DPNERR_INVALIDHOSTADDRESS); + } + + if(host_sp != service_provider) + { + /* Service Provider in host address must match device address. */ + throw COMAPIException(DPNERR_INVALIDPARAM); + } + + /* Hostname component overrides discovery address, if provided. */ + + wchar_t hostname_value[128]; + DWORD hostname_size = sizeof(hostname_value); + DWORD hostname_type; + + if(pdpaddrHost->GetComponentByName(DPNA_KEY_HOSTNAME, hostname_value, &hostname_size, &hostname_type) == S_OK) + { + if(hostname_type != DPNA_DATATYPE_STRING) + { + throw COMAPIException(DPNERR_INVALIDHOSTADDRESS); + } + + if(host_sp == CLSID_DP8SP_TCPIP) + { + struct in_addr hostname_addr; + if(InetPtonW(AF_INET, hostname_value, &hostname_addr) == 1) + { + send_addr.sin_addr = hostname_addr; + } + else{ + throw COMAPIException(DPNERR_INVALIDHOSTADDRESS); + } + } + else if(host_sp == CLSID_DP8SP_IPX) + { + unsigned ip; + if(swscanf(hostname_value, L"00000000,0000%08X", &ip) != 1) + { + throw COMAPIException(DPNERR_INVALIDHOSTADDRESS); + } + + send_addr.sin_addr.s_addr = htonl(ip); + } + } + + /* Port component overrides discovery port, if provided. */ + + DWORD port_value; + DWORD port_size = sizeof(port_value); + DWORD port_type; + + if(pdpaddrHost->GetComponentByName(DPNA_KEY_PORT, &port_value, &port_size, &port_type) == S_OK) + { + if(port_type != DPNA_DATATYPE_DWORD || port_value > 65535) + { + throw COMAPIException(DPNERR_INVALIDHOSTADDRESS); + } + + send_addr.sin_port = htons(port_value); + } + } + if(pApplicationDesc != NULL) { application_guid = pApplicationDesc->guidApplication; @@ -248,26 +325,15 @@ void HostEnumerator::handle_packet(const void *data, size_t size, struct sockadd * port for the host. */ - IDirectPlay8Address *sender_address = new DirectPlay8Address(global_refcount); - sender_address->SetSP(&CLSID_DP8SP_TCPIP); /* TODO: Be IPX if application previously gave us an IPX address? */ - - char from_addr_ip_s[16]; - inet_ntop(AF_INET, &(from_addr->sin_addr), from_addr_ip_s, sizeof(from_addr_ip_s)); - - sender_address->AddComponent(DPNA_KEY_HOSTNAME, - from_addr_ip_s, strlen(from_addr_ip_s) + 1, DPNA_DATATYPE_STRING_ANSI); - - DWORD from_port_dw = ntohs(from_addr->sin_port); - - sender_address->AddComponent(DPNA_KEY_PORT, - &from_port_dw, sizeof(from_port_dw), DPNA_DATATYPE_DWORD); + IDirectPlay8Address *sender_address = DirectPlay8Address::create_host_address( + global_refcount, service_provider, (struct sockaddr*)(from_addr)); /* Build a DirectPlay8Address with the interface we received the response on. * TODO: Actually do this. */ IDirectPlay8Address *device_address = new DirectPlay8Address(global_refcount); - device_address->SetSP(&CLSID_DP8SP_TCPIP); /* TODO: Be IPX if application previously gave us an IPX address? */ + device_address->SetSP(&service_provider); DPNMSG_ENUM_HOSTS_RESPONSE message; memset(&message, 0, sizeof(message)); diff --git a/src/HostEnumerator.hpp b/src/HostEnumerator.hpp index 41df8f6..a600aad 100644 --- a/src/HostEnumerator.hpp +++ b/src/HostEnumerator.hpp @@ -29,6 +29,7 @@ class HostEnumerator std::function complete_cb; + GUID service_provider; struct sockaddr_in send_addr; GUID application_guid; /* GUID of application to search for, or GUID_NULL */ diff --git a/tests/DirectPlay8Peer.cpp b/tests/DirectPlay8Peer.cpp index c956179..83eca3a 100644 --- a/tests/DirectPlay8Peer.cpp +++ b/tests/DirectPlay8Peer.cpp @@ -136,7 +136,12 @@ struct SessionHost app_desc.guidApplication = application_guid; app_desc.pwszSessionName = (wchar_t*)(session_description); - if(dp8p->Host(&app_desc, NULL, 0, NULL, NULL, (void*)(0xB00), 0) != S_OK) + IDP8AddressInstance address; + address->SetSP(&CLSID_DP8SP_TCPIP); + + IDirectPlay8Address *addresses[] = { address }; + + if(dp8p->Host(&app_desc, addresses, 1, NULL, NULL, (void*)(0xB00), 0) != S_OK) { throw std::runtime_error("DirectPlay8Peer::Host failed"); } @@ -338,12 +343,15 @@ TEST(DirectPlay8Peer, EnumHostsSync) ASSERT_EQ(client->Initialize(&client_cb, &callback_shim, 0), S_OK); + IDP8AddressInstance device_address; + device_address->SetSP(&CLSID_DP8SP_TCPIP); + DWORD start = GetTickCount(); ASSERT_EQ(client->EnumHosts( NULL, /* pApplicationDesc */ NULL, /* pdpaddrHost */ - NULL, /* pdpaddrDeviceInfo */ + device_address, /* pdpaddrDeviceInfo */ NULL, /* pvUserEnumData */ 0, /* dwUserEnumDataSize */ 3, /* dwEnumCount */ @@ -423,12 +431,15 @@ TEST(DirectPlay8Peer, EnumHostsAsync) ASSERT_EQ(client->Initialize(&callback, &callback_shim, 0), S_OK); + IDP8AddressInstance device_address; + device_address->SetSP(&CLSID_DP8SP_TCPIP); + DWORD start = GetTickCount(); ASSERT_EQ(client->EnumHosts( NULL, /* pApplicationDesc */ NULL, /* pdpaddrHost */ - NULL, /* pdpaddrDeviceInfo */ + device_address, /* pdpaddrDeviceInfo */ NULL, /* pvUserEnumData */ 0, /* dwUserEnumDataSize */ 3, /* dwEnumCount */ @@ -492,12 +503,15 @@ TEST(DirectPlay8Peer, EnumHostsAsyncCancelByHandle) ASSERT_EQ(client->Initialize(&callback, &callback_shim, 0), S_OK); + IDP8AddressInstance device_address; + device_address->SetSP(&CLSID_DP8SP_TCPIP); + DWORD start = GetTickCount(); ASSERT_EQ(client->EnumHosts( NULL, /* pApplicationDesc */ NULL, /* pdpaddrHost */ - NULL, /* pdpaddrDeviceInfo */ + device_address, /* pdpaddrDeviceInfo */ NULL, /* pvUserEnumData */ 0, /* dwUserEnumDataSize */ 3, /* dwEnumCount */ @@ -555,12 +569,15 @@ TEST(DirectPlay8Peer, EnumHostsAsyncCancelAllEnums) ASSERT_EQ(client->Initialize(&callback, &callback_shim, 0), S_OK); + IDP8AddressInstance device_address; + device_address->SetSP(&CLSID_DP8SP_TCPIP); + DWORD start = GetTickCount(); ASSERT_EQ(client->EnumHosts( NULL, /* pApplicationDesc */ NULL, /* pdpaddrHost */ - NULL, /* pdpaddrDeviceInfo */ + device_address, /* pdpaddrDeviceInfo */ NULL, /* pvUserEnumData */ 0, /* dwUserEnumDataSize */ 3, /* dwEnumCount */ @@ -618,12 +635,15 @@ TEST(DirectPlay8Peer, EnumHostsAsyncCancelAllOperations) ASSERT_EQ(client->Initialize(&callback, &callback_shim, 0), S_OK); + IDP8AddressInstance device_address; + device_address->SetSP(&CLSID_DP8SP_TCPIP); + DWORD start = GetTickCount(); ASSERT_EQ(client->EnumHosts( NULL, /* pApplicationDesc */ NULL, /* pdpaddrHost */ - NULL, /* pdpaddrDeviceInfo */ + device_address, /* pdpaddrDeviceInfo */ NULL, /* pvUserEnumData */ 0, /* dwUserEnumDataSize */ 3, /* dwEnumCount */ @@ -681,12 +701,15 @@ TEST(DirectPlay8Peer, EnumHostsAsyncCancelByClose) ASSERT_EQ(client->Initialize(&callback, &callback_shim, 0), S_OK); + IDP8AddressInstance device_address; + device_address->SetSP(&CLSID_DP8SP_TCPIP); + DWORD start = GetTickCount(); ASSERT_EQ(client->EnumHosts( NULL, /* pApplicationDesc */ NULL, /* pdpaddrHost */ - NULL, /* pdpaddrDeviceInfo */ + device_address, /* pdpaddrDeviceInfo */ NULL, /* pvUserEnumData */ 0, /* dwUserEnumDataSize */ 3, /* dwEnumCount */ @@ -794,10 +817,13 @@ TEST(DirectPlay8Peer, EnumHostsFilterByApplicationGUID) app_desc.dwSize = sizeof(app_desc); app_desc.guidApplication = APP_GUID_2; + IDP8AddressInstance device_address; + device_address->SetSP(&CLSID_DP8SP_TCPIP); + ASSERT_EQ(client->EnumHosts( &app_desc, /* pApplicationDesc */ NULL, /* pdpaddrHost */ - NULL, /* pdpaddrDeviceInfo */ + device_address, /* pdpaddrDeviceInfo */ NULL, /* pvUserEnumData */ 0, /* dwUserEnumDataSize */ 3, /* dwEnumCount */ @@ -857,10 +883,13 @@ TEST(DirectPlay8Peer, EnumHostsFilterByNULLApplicationGUID) app_desc.dwSize = sizeof(app_desc); app_desc.guidApplication = GUID_NULL; + IDP8AddressInstance device_address; + device_address->SetSP(&CLSID_DP8SP_TCPIP); + ASSERT_EQ(client->EnumHosts( &app_desc, /* pApplicationDesc */ NULL, /* pdpaddrHost */ - NULL, /* pdpaddrDeviceInfo */ + device_address, /* pdpaddrDeviceInfo */ NULL, /* pvUserEnumData */ 0, /* dwUserEnumDataSize */ 3, /* dwEnumCount */ @@ -939,10 +968,13 @@ TEST(DirectPlay8Peer, EnumHostsDataInQuery) ASSERT_EQ(client->Initialize(&client_cb, &callback_shim, 0), S_OK); + IDP8AddressInstance device_address; + device_address->SetSP(&CLSID_DP8SP_TCPIP); + ASSERT_EQ(client->EnumHosts( NULL, /* pApplicationDesc */ NULL, /* pdpaddrHost */ - NULL, /* pdpaddrDeviceInfo */ + device_address, /* pdpaddrDeviceInfo */ (void*)(DATA), /* pvUserEnumData */ sizeof(DATA), /* dwUserEnumDataSize */ 3, /* dwEnumCount */ @@ -1036,10 +1068,13 @@ TEST(DirectPlay8Peer, EnumHostsDataInResponse) ASSERT_EQ(client->Initialize(&client_cb, &callback_shim, 0), S_OK); + IDP8AddressInstance device_address; + device_address->SetSP(&CLSID_DP8SP_TCPIP); + ASSERT_EQ(client->EnumHosts( NULL, /* pApplicationDesc */ NULL, /* pdpaddrHost */ - NULL, /* pdpaddrDeviceInfo */ + device_address, /* pdpaddrDeviceInfo */ NULL, /* pvUserEnumData */ 0, /* dwUserEnumDataSize */ 3, /* dwEnumCount */ @@ -1059,7 +1094,60 @@ TEST(DirectPlay8Peer, EnumHostsDataInResponse) EXPECT_TRUE(got_return_buffer); } -/* TODO: Test enumerating a session directly. */ +TEST(DirectPlay8Peer, EnumHostsSpecifyPort) +{ + SessionHost a1s1(APP_GUID_1, L"Application 1 Session 1", PORT); + SessionHost a1s2(APP_GUID_1, L"Application 1 Session 2", PORT + 1); + + std::map sessions; + + std::function client_cb = + [&sessions] + (DWORD dwMessageType, PVOID pMessage) + { + if(dwMessageType == DPN_MSGID_ENUM_HOSTS_RESPONSE) + { + DPNMSG_ENUM_HOSTS_RESPONSE *ehr = (DPNMSG_ENUM_HOSTS_RESPONSE*)(pMessage); + + sessions.emplace( + ehr->pApplicationDescription->guidInstance, + FoundSession( + ehr->pApplicationDescription->guidApplication, + ehr->pApplicationDescription->pwszSessionName)); + } + + return DPN_OK; + }; + + IDP8PeerInstance client; + + ASSERT_EQ(client->Initialize(&client_cb, &callback_shim, 0), S_OK); + + IDP8AddressInstance host_address(L"127.0.0.1", PORT); + + IDP8AddressInstance device_address; + device_address->SetSP(&CLSID_DP8SP_TCPIP); + + ASSERT_EQ(client->EnumHosts( + NULL, /* pApplicationDesc */ + host_address, /* pdpaddrHost */ + device_address, /* pdpaddrDeviceInfo */ + NULL, /* pvUserEnumData */ + 0, /* dwUserEnumDataSize */ + 3, /* dwEnumCount */ + 500, /* dwRetryInterval */ + 500, /* dwTimeOut*/ + NULL, /* pvUserContext */ + NULL, /* pAsyncHandle */ + DPNENUMHOSTS_SYNC /* dwFlags */ + ), S_OK); + + FoundSession expect_sessions[] = { + FoundSession(APP_GUID_1, L"Application 1 Session 1"), + }; + + EXPECT_SESSIONS(sessions, expect_sessions, expect_sessions + 1); +} TEST(DirectPlay8Peer, ConnectSync) {