diff --git a/Makefile b/Makefile index ac9a501..494efb0 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ BIN_FILES := $(shell cat manifest.bin.txt) SRC_FILES := $(shell cat manifest.src.txt) # Tests to compile before running the test suite. -TESTS := tests/addr.exe tests/addrcache.exe tests/ethernet.exe +TESTS := tests/addr.exe tests/addrcache.exe tests/ethernet.exe tools/fionread.exe # Tools to compile before running the test suite. TOOLS := tools/socket.exe tools/list-interfaces.exe tools/bind.exe tools/ipx-send.exe \ @@ -178,6 +178,9 @@ tests/%.o: tests/%.c $(CC) $(CFLAGS) $(DEPFLAGS) -I./ -c -o $@ $< $(DEPPOST) +tools/fionread.exe: tests/fionread.o tests/tap/basic.o src/addr.o + $(CC) $(CFLAGS) -o $@ $^ -lwsock32 + tools/%.exe: tools/%.o src/addr.o $(CC) $(CFLAGS) -o $@ $^ -lwsock32 -lole32 -lrpcrt4 diff --git a/changes.txt b/changes.txt index c9b2c65..65c4b44 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,10 @@ +Version TBA: + Added support for DOSBox IPX servers. + + Fixed implementation of FIONREAD ioctl (needed for Laser Arena). + + Fixed a memory leak. + Version 0.6.1: Added support for LLC and Novell "raw" 802.3 Ethernet frame formats. diff --git a/manifest.src.txt b/manifest.src.txt index 038d034..3f1832f 100644 --- a/manifest.src.txt +++ b/manifest.src.txt @@ -24,6 +24,8 @@ src/dpwsockx_stubs.txt src/firewall.c src/interface.c src/interface.h +src/interface2.c +src/interface2.h src/ipxwrapper.c src/ipxwrapper.def src/ipxwrapper.h @@ -63,6 +65,8 @@ tests/07-ethernet.t tests/10-socket.t tests/15-interfaces.t tests/20-bind.t +tests/25-fionread.t +tests/30-dosbox-ipx.t tests/30-eth-ipx.t tests/30-ip-ipx.t tests/40-ip-spx.t @@ -71,12 +75,15 @@ tests/addr.c tests/addrcache.c tests/config.pm tests/ethernet.c +tests/fionread.c tests/ptype.pm tests/lib/IPXWrapper/Capture/IPX.pm tests/lib/IPXWrapper/Capture/IPXLLC.pm tests/lib/IPXWrapper/Capture/IPXNovell.pm tests/lib/IPXWrapper/Capture/IPXOverUDP.pm +tests/lib/IPXWrapper/DOSBoxClient.pm +tests/lib/IPXWrapper/DOSBoxServer.pm tests/lib/IPXWrapper/SPX.pm tests/lib/IPXWrapper/Tool/Bind.pm tests/lib/IPXWrapper/Tool/DPTool.pm @@ -101,3 +108,20 @@ tools/socket.c tools/spx-client.c tools/spx-server.c tools/tools.h + +winpcap/include/pcap-bpf.h +winpcap/include/Packet32.h +winpcap/include/pcap/vlan.h +winpcap/include/pcap/sll.h +winpcap/include/pcap/bluetooth.h +winpcap/include/pcap/bpf.h +winpcap/include/pcap/usb.h +winpcap/include/pcap/namedb.h +winpcap/include/pcap/pcap.h +winpcap/include/pcap-namedb.h +winpcap/include/pcap-stdinc.h +winpcap/include/Win32-Extensions.h +winpcap/include/bittypes.h +winpcap/include/ip6_misc.h +winpcap/include/remote-ext.h +winpcap/include/pcap.h diff --git a/readme.txt b/readme.txt index c03ed9a..0d32259 100644 --- a/readme.txt +++ b/readme.txt @@ -62,6 +62,7 @@ The following have been reported to work: * Delta Force 2 * Diablo * Heroes of Might and Magic III + * Laser Arena * Need For Speed III - Hot Pursuit * Outlive * Rising Lands diff --git a/src/ipxwrapper.def b/src/ipxwrapper.def index c3f10ab..934c3d8 100644 --- a/src/ipxwrapper.def +++ b/src/ipxwrapper.def @@ -21,3 +21,4 @@ EXPORTS listen accept WSAAsyncSelect + select diff --git a/src/ipxwrapper.h b/src/ipxwrapper.h index ef02c4e..91fe6ab 100644 --- a/src/ipxwrapper.h +++ b/src/ipxwrapper.h @@ -60,6 +60,55 @@ typedef struct ipx_socket ipx_socket; typedef struct ipx_packet ipx_packet; +#define RECV_QUEUE_MAX_PACKETS 32 + +#define IPX_RECV_QUEUE_FREE -1 +#define IPX_RECV_QUEUE_LOCKED -2 + +/* Any AF_IPX IPX socket has an associated recv_queue. + * + * When a recv_pump() operation is running, the sockets lock has to be released + * in case the recv() blocks, which means the socket could be closed before it + * regains the lock. + * + * An ipx_recv_queue isn't destroyed until the refcount reaches zero. The + * ipx_socket holds one reference and each in-progress recv_pump() also holds a + * reference while the sockets lock isn't held. + * + * Access to the refcount is protected by refcount_lock. + * + * data[] holds an array of buffers for each queued packet, the status and size + * of which is indicated by the sizes[] array. + * + * If sizes[x] is IPX_RECV_QUEUE_FREE, the buffer is available to be claimed by + * a recv_pump() operation, which then sets it to IPX_RECV_QUEUE_LOCKED until + * it completes, which prevents a recv_pump() in another thread from trying to + * use the same receive buffer. Once a packet is read in, sizes[x] is set to + * the size of the packet and x is added to the end of the ready[] array. + * + * When a read is requested, the packet will be read from the data[] index + * stored in ready[0], and unless MSG_PEEK was used, that slot will then be + * released (sizes[x] set to IPX_RECV_QUEUE_FREE) and any subsequent slots in + * read[] will be advanced for the next read to pick up from ready[0]. + * + * Access to the ready, n_ready and sizes members is only permitted when a + * thread holds the main sockets lock. +*/ + +struct ipx_recv_queue +{ + CRITICAL_SECTION refcount_lock; + int refcount; + + int ready[RECV_QUEUE_MAX_PACKETS]; + int n_ready; + + unsigned char data[RECV_QUEUE_MAX_PACKETS][MAX_PKT_SIZE]; + int sizes[RECV_QUEUE_MAX_PACKETS]; +}; + +typedef struct ipx_recv_queue ipx_recv_queue; + struct ipx_socket { SOCKET fd; @@ -79,6 +128,8 @@ struct ipx_socket { /* Address used with connect call, only set when IPX_CONNECTED is */ struct sockaddr_ipx remote_addr; + struct ipx_recv_queue *recv_queue; + UT_hash_handle hh; }; @@ -165,5 +216,6 @@ int PASCAL r_getpeername(SOCKET fd, struct sockaddr *addr, int *addrlen); int PASCAL r_listen(SOCKET s, int backlog); SOCKET PASCAL r_accept(SOCKET s, struct sockaddr *addr, int *addrlen); int PASCAL r_WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent); +int WSAAPI r_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const PTIMEVAL timeout); #endif /* !IPXWRAPPER_H */ diff --git a/src/ipxwrapper_stubs.txt b/src/ipxwrapper_stubs.txt index a1eea56..e4acacb 100644 --- a/src/ipxwrapper_stubs.txt +++ b/src/ipxwrapper_stubs.txt @@ -7,7 +7,7 @@ htonl:4 ntohl:4 htons:4 ntohs:4 -select:4 +r_select:4 r_listen:4 r_accept:4 WSACreateEvent:4 diff --git a/src/winsock.c b/src/winsock.c index 2c920f1..ed5adf8 100644 --- a/src/winsock.c +++ b/src/winsock.c @@ -303,6 +303,26 @@ INT WINAPI WSHEnumProtocols(LPINT protocols, LPWSTR ign, LPVOID buf, LPDWORD bsp return do_EnumProtocols(protocols, buf, bsptr, false); } +static int recv_queue_adjust_refcount(ipx_recv_queue *recv_queue, int adj) +{ + EnterCriticalSection(&(recv_queue->refcount_lock)); + int new_refcount = (recv_queue->refcount += adj); + LeaveCriticalSection(&(recv_queue->refcount_lock)); + + return new_refcount; +} + +static void release_recv_queue(ipx_recv_queue *recv_queue) +{ + int new_refcount = recv_queue_adjust_refcount(recv_queue, -1); + + if(new_refcount == 0) + { + DeleteCriticalSection(&(recv_queue->refcount_lock)); + free(recv_queue); + } +} + SOCKET WSAAPI socket(int af, int type, int protocol) { log_printf(LOG_DEBUG, "socket(%d, %d, %d)", af, type, protocol); @@ -318,10 +338,38 @@ SOCKET WSAAPI socket(int af, int type, int protocol) return -1; } + ipx_recv_queue *recv_queue = malloc(sizeof(ipx_recv_queue)); + if(recv_queue == NULL) + { + free(nsock); + + WSASetLastError(ERROR_OUTOFMEMORY); + return -1; + } + + if(!InitializeCriticalSectionAndSpinCount(&(recv_queue->refcount_lock), 0x80000000)) + { + log_printf(LOG_ERROR, "Failed to initialise critical section: %s", w32_error(GetLastError())); + WSASetLastError(GetLastError()); + + free(recv_queue); + free(nsock); + return -1; + } + + recv_queue->refcount = 1; + recv_queue->n_ready = 0; + + for(int i = 0; i < RECV_QUEUE_MAX_PACKETS; ++i) + { + recv_queue->sizes[i] = IPX_RECV_QUEUE_FREE; + } + if((nsock->fd = r_socket(AF_INET, SOCK_DGRAM, 0)) == -1) { log_printf(LOG_ERROR, "Cannot create UDP socket: %s", w32_error(WSAGetLastError())); + release_recv_queue(recv_queue); free(nsock); return -1; } @@ -329,6 +377,8 @@ SOCKET WSAAPI socket(int af, int type, int protocol) nsock->flags = IPX_SEND | IPX_RECV | IPX_RECV_BCAST; nsock->s_ptype = (protocol ? protocol - NSPROTO_IPX : 0); + nsock->recv_queue = recv_queue; + log_printf(LOG_INFO, "IPX socket created (fd = %d)", nsock->fd); lock_sockets(); @@ -384,6 +434,8 @@ SOCKET WSAAPI socket(int af, int type, int protocol) nsock->flags |= IPX_IS_SPXII; } + nsock->recv_queue = NULL; + log_printf(LOG_INFO, "SPX socket created (fd = %d)", nsock->fd); lock_sockets(); @@ -425,6 +477,11 @@ int WSAAPI closesocket(SOCKET sockfd) log_printf(LOG_INFO, "Socket %d (%s) closed", sockfd, (sock->flags & IPX_IS_SPX ? "SPX" : "IPX")); + if(sock->recv_queue != NULL) + { + release_recv_queue(sock->recv_queue); + } + if(sock->flags & IPX_BOUND) { CloseHandle(sock->sock_mut); @@ -687,6 +744,121 @@ int WSAAPI getsockname(SOCKET fd, struct sockaddr *addr, int *addrlen) } } +static BOOL reclaim_socket(ipx_socket *sockptr, int lookup_fd) +{ + /* Reclaim the lock, ensure the socket hasn't been + * closed by the application (naughty!) while we were + * waiting. + */ + + ipx_socket *reclaim_sock = get_socket(lookup_fd); + if(sockptr != reclaim_sock) + { + log_printf(LOG_DEBUG, "Application closed socket while inside a WinSock call!"); + + if(reclaim_sock) + { + unlock_sockets(); + } + + return FALSE; + } + + return TRUE; +} + +static int recv_pump(ipx_socket *sockptr, BOOL block) +{ + int fd = sockptr->fd; + + if(!block) + { + fd_set read_fds; + FD_ZERO(&read_fds); + + FD_SET(fd, &read_fds); + + struct timeval timeout = { 0, 0 }; + + int r = r_select(-1, &read_fds, NULL, NULL, &timeout); + if(r == -1) + { + unlock_sockets(); + return -1; + } + else if(r == 0) + { + /* No packet waiting in underlying recv buffer. */ + return 0; + } + } + + ipx_recv_queue *queue = sockptr->recv_queue; + + int recv_slot = -1; + + for(int i = 0; i < RECV_QUEUE_MAX_PACKETS; ++i) + { + if(queue->sizes[i] == IPX_RECV_QUEUE_FREE) + { + queue->sizes[i] = IPX_RECV_QUEUE_LOCKED; + recv_queue_adjust_refcount(queue, 1); + + recv_slot = i; + break; + } + } + + if(recv_slot < 0) + { + /* No free recv_queue slots. */ + return 0; + } + + unlock_sockets(); + + int r = r_recv(fd, (char*)(queue->data[recv_slot]), MAX_PKT_SIZE, 0); + + if(!reclaim_socket(sockptr, fd)) + { + /* The application closed the socket while we were in the recv() call. + * Just discard our handle, let the queue be destroyed. + */ + + release_recv_queue(queue); + WSASetLastError(WSAENOTSOCK); + return -1; + } + + if(r == -1) + { + queue->sizes[recv_slot] = IPX_RECV_QUEUE_FREE; + release_recv_queue(queue); + unlock_sockets(); + return -1; + } + + struct ipx_packet *packet = (struct ipx_packet*)(queue->data[recv_slot]); + + if(r < sizeof(ipx_packet) - 1 || r != packet->size + sizeof(ipx_packet) - 1) + { + log_printf(LOG_ERROR, "Invalid packet received on loopback port!"); + + queue->sizes[recv_slot] = IPX_RECV_QUEUE_FREE; + release_recv_queue(queue); + + WSASetLastError(WSAEWOULDBLOCK); + unlock_sockets(); + return -1; + } + + queue->sizes[recv_slot] = r; + queue->ready[queue->n_ready] = recv_slot; + ++(queue->n_ready); + + return 1; +} + /* Recieve a packet from an IPX socket * addr must be NULL or a region of memory big enough for a sockaddr_ipx * @@ -694,40 +866,31 @@ int WSAAPI getsockname(SOCKET fd, struct sockaddr *addr, int *addrlen) * The size of the packet will be returned on success, even if it was truncated */ static int recv_packet(ipx_socket *sockptr, char *buf, int bufsize, int flags, struct sockaddr_ipx_ext *addr, int addrlen) { - SOCKET fd = sockptr->fd; - int is_bound = sockptr->flags & IPX_BOUND; - int extended_addr = sockptr->flags & IPX_EXT_ADDR; - - unlock_sockets(); - - if(!is_bound) { + if(!(sockptr->flags & IPX_BOUND)) + { + unlock_sockets(); + WSASetLastError(WSAEINVAL); return -1; } - char *recvbuf = malloc(MAX_PKT_SIZE); - if(!recvbuf) { - WSASetLastError(ERROR_OUTOFMEMORY); - return -1; - } - - struct ipx_packet *packet = (struct ipx_packet*)(recvbuf); - - int rval = r_recv(fd, recvbuf, MAX_PKT_SIZE, flags); - if(rval == -1) { - free(recvbuf); - return -1; - } - - if(rval < sizeof(ipx_packet) - 1 || rval != packet->size + sizeof(ipx_packet) - 1) + /* Loop here in case some crazy application does concurrent recv() calls + * and they race between putting packets on the queue and handling them. + */ + while(sockptr->recv_queue->n_ready < 1) { - log_printf(LOG_ERROR, "Invalid packet received on loopback port!"); - - free(recvbuf); - WSASetLastError(WSAEWOULDBLOCK); - return -1; + if(recv_pump(sockptr, TRUE) < 0) + { + /* Socket closed or recv() error. */ + return -1; + } } + int slot = sockptr->recv_queue->ready[0]; + + struct ipx_packet *packet = (struct ipx_packet*)(sockptr->recv_queue->data[slot]); + assert(sockptr->recv_queue->sizes[slot] >= 0); + if(min_log_level <= LOG_DEBUG) { IPX_STRING_ADDR(addr_s, addr32_in(packet->src_net), addr48_in(packet->src_node), packet->src_socket); @@ -741,7 +904,7 @@ static int recv_packet(ipx_socket *sockptr, char *buf, int bufsize, int flags, s memcpy(addr->sa_nodenum, packet->src_node, 6); addr->sa_socket = packet->src_socket; - if(extended_addr) { + if(sockptr->flags & IPX_EXT_ADDR) { if(addrlen >= sizeof(struct sockaddr_ipx_ext)) { addr->sa_ptype = packet->ptype; addr->sa_flags = 0; @@ -774,8 +937,17 @@ static int recv_packet(ipx_socket *sockptr, char *buf, int bufsize, int flags, s } memcpy(buf, packet->data, packet->size <= bufsize ? packet->size : bufsize); - rval = packet->size; - free(recvbuf); + int rval = packet->size; + + if((flags & MSG_PEEK) == 0) + { + sockptr->recv_queue->sizes[slot] = IPX_RECV_QUEUE_FREE; + + --(sockptr->recv_queue->n_ready); + memmove(&(sockptr->recv_queue->ready[0]), &(sockptr->recv_queue->ready[1]), (sockptr->recv_queue->n_ready * sizeof(int))); + } + + unlock_sockets(); return rval; } @@ -1625,39 +1797,33 @@ int PASCAL ioctlsocket(SOCKET fd, long cmd, u_long *argp) if(cmd == FIONREAD && !(sock->flags & IPX_IS_SPX)) { - /* Test to see if data is waiting. */ - - fd_set fdset; - struct timeval tv = {0,0}; - - FD_ZERO(&fdset); - FD_SET(sock->fd, &fdset); - - int r = select(1, &fdset, NULL, NULL, &tv); - - if(r == -1) + while(1) { - unlock_sockets(); - return -1; - } - else if(r == 0) - { - *(unsigned long*)(argp) = 0; + int r = recv_pump(sock, FALSE); + if(r < 0) + { + /* Error in recv_pump() */ + return -1; + } - unlock_sockets(); - return -1; + if(r == 0) + { + /* No more packets ready to read from underlying socket. */ + break; + } } - /* Get the size of the packet. */ + unsigned long accumulated_packet_data = 0; - char tmp_buf; - - if((r = recv_packet(sock, &tmp_buf, 1, MSG_PEEK, NULL, 0)) == -1) + for(int i = 0; i < sock->recv_queue->n_ready; ++i) { - return -1; + const ipx_packet *packet = (const ipx_packet*)(sock->recv_queue->data[ sock->recv_queue->ready[i] ]); + accumulated_packet_data += packet->size; } - *(unsigned long*)(argp) = r; + unlock_sockets(); + + *(unsigned long*)(argp) = accumulated_packet_data; return 0; } @@ -1898,7 +2064,7 @@ static int _connect_spx(ipx_socket *sock, struct sockaddr_ipx *ipxaddr) .tv_usec = ((wait_until - now) % 1000) * 1000 }; - if(select(1, &fdset, NULL, NULL, &tv) == -1) + if(r_select(1, &fdset, NULL, NULL, &tv) == -1) { closesocket(lookup_fd); free(packet); @@ -2011,7 +2177,7 @@ static int _connect_spx(ipx_socket *sock, struct sockaddr_ipx *ipxaddr) FD_ZERO(&e_fdset); FD_SET(sock->fd, &e_fdset); - if(select(1, NULL, &w_fdset, &e_fdset, NULL) == 1 && FD_ISSET(sock->fd, &w_fdset)) + if(r_select(1, NULL, &w_fdset, &e_fdset, NULL) == 1 && FD_ISSET(sock->fd, &w_fdset)) { goto CONNECTED; } @@ -2493,3 +2659,63 @@ int PASCAL WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent) return r_WSAAsyncSelect(s, hWnd, wMsg, lEvent); } + +int WSAAPI select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const PTIMEVAL timeout) +{ + const struct timeval TIMEOUT_IMMEDIATE = { 0, 0 }; + const struct timeval *use_timeout = timeout; + + fd_set force_read_fds; + FD_ZERO(&force_read_fds); + + if(readfds != NULL) + { + for(unsigned int i = 0; i < readfds->fd_count; ++i) + { + int fd = readfds->fd_array[i]; + + ipx_socket *sockptr = get_socket(fd); + if(sockptr != NULL) + { + if(sockptr->flags & IPX_IS_SPX) + { + unlock_sockets(); + continue; + } + + if(sockptr->recv_queue->n_ready > 0) + { + /* There is data in the receive queue for this socket, but + * the underlying socket isn't necessarily readable, so we + * reduce the select() timeout to zero to ensure it returns + * immediately and inject this fd back into readfds at the + * end if necessary. + */ + + FD_SET(fd, &force_read_fds); + use_timeout = &TIMEOUT_IMMEDIATE; + } + + unlock_sockets(); + } + } + } + + int r = r_select(nfds, readfds, writefds, exceptfds, (const PTIMEVAL)(use_timeout)); + + if(r >= 0) + { + for(unsigned int i = 0; i < force_read_fds.fd_count; ++i) + { + int fd = force_read_fds.fd_array[i]; + + if(!FD_ISSET(fd, readfds)) + { + FD_SET(fd, readfds); + ++r; + } + } + } + + return r; +} diff --git a/src/wsock32_stubs.txt b/src/wsock32_stubs.txt index 75450c4..6131143 100644 --- a/src/wsock32_stubs.txt +++ b/src/wsock32_stubs.txt @@ -15,7 +15,7 @@ ntohl ntohs recv:0 recvfrom:0 -select +select:0 send:0 sendto:0 setsockopt:0 diff --git a/tests/25-fionread.t b/tests/25-fionread.t new file mode 100644 index 0000000..0563dfc --- /dev/null +++ b/tests/25-fionread.t @@ -0,0 +1,34 @@ +# IPXWrapper test suite +# Copyright (C) 2023 Daniel Collins +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/lib/"; + +use IPXWrapper::Util; + +require "$FindBin::Bin/config.pm"; +our ($remote_mac_a, $remote_ip_a); + +reg_delete_key($remote_ip_a, "HKCU\\Software\\IPXWrapper"); + +# Unit tests implemented by fionread.exe, so run it on the test system and pass +# the (TAP) output/exit status to our parent. + +system("ssh", $remote_ip_a, "Z:\\tools\\fionread.exe", "00:00:00:01", $remote_mac_a); +exit($? >> 8); diff --git a/tests/fionread.c b/tests/fionread.c new file mode 100644 index 0000000..e5012a9 --- /dev/null +++ b/tests/fionread.c @@ -0,0 +1,134 @@ +/* IPXWrapper test suite + * Copyright (C) 2023 Daniel Collins + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include +#include + +#include +#include +#include + +#include "tap/basic.h" +#include "../tools/tools.h" + +static char buf[4096]; + +int main(int argc, char **argv) +{ + if(argc != 3) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + plan_lazy(); + + struct sockaddr_ipx addr1 = read_sockaddr(argv[1], argv[2], "1234"); + struct sockaddr_ipx addr2 = read_sockaddr(argv[1], argv[2], "1235"); + + WSADATA data; + WSAStartup(MAKEWORD(1, 1), &data); + + int sock1 = socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX); + assert(sock1 != SOCKET_ERROR); + + assert(bind(sock1, (struct sockaddr*)(&addr1), sizeof(addr1)) == 0); + + int sock2 = socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX); + assert(sock2 != SOCKET_ERROR); + + assert(bind(sock2, (struct sockaddr*)(&addr2), sizeof(addr2)) == 0); + + fd_set readfds; + struct timeval timeout = { 0, 0 }; + + unsigned long x = 123456789; + int r = ioctlsocket(sock1, FIONREAD, &x); + + is_int(0, r, "ioctlsocket(FIONREAD) succeeds when no packets are waiting"); + is_int(0, x, "ioctlsocket(FIONREAD) returns zero bytes when no packets are waiting"); + + FD_ZERO(&readfds); + FD_SET(sock1, &readfds); + is_int(select(-1, &readfds, NULL, NULL, &timeout), 0, "select() initially indicates socket is not ready to read"); + ok(!FD_ISSET(sock1, &readfds), "select() initially indicates socket is not ready to read"); + + assert(sendto(sock2, buf, 128, 0, (struct sockaddr*)(&addr1), sizeof(addr1)) == 128); + + /* Just in case there's any async going on */ + Sleep(100); + + FD_ZERO(&readfds); + FD_SET(sock1, &readfds); + is_int(select(-1, &readfds, NULL, NULL, &timeout), 1, "select() indicates socket is ready to read before call to ioctlsocket(FIONREAD)"); + ok(FD_ISSET(sock1, &readfds), "select() indicates socket is ready to read before call to ioctlsocket(FIONREAD)"); + + x = 123456789; + r = ioctlsocket(sock1, FIONREAD, &x); + + is_int(0, r, "ioctlsocket(FIONREAD) succeeds when packets are waiting"); + is_int(128, x, "ioctlsocket(FIONREAD) returns payload size when one packet is waiting"); + + FD_ZERO(&readfds); + FD_SET(sock1, &readfds); + is_int(select(-1, &readfds, NULL, NULL, &timeout), 1, "select() indicates socket is ready to read after call to ioctlsocket(FIONREAD)"); + ok(FD_ISSET(sock1, &readfds), "select() indicates socket is ready to read after call to ioctlsocket(FIONREAD)"); + + assert(sendto(sock2, buf, 256, 0, (struct sockaddr*)(&addr1), sizeof(addr1)) == 256); + + /* Just in case there's any async going on */ + Sleep(100); + + x = 123456789; + r = ioctlsocket(sock1, FIONREAD, &x); + + is_int(0, r, "ioctlsocket(FIONREAD) succeeds when packets are waiting"); + is_int(384, x, "ioctlsocket(FIONREAD) returns combined payload sizes when multiple packets are waiting"); + + assert(recv(sock1, buf, 4096, 0) == 128); + + FD_ZERO(&readfds); + FD_SET(sock1, &readfds); + is_int(select(-1, &readfds, NULL, NULL, &timeout), 1, "select() indicates socket is ready to read after reading first packet"); + ok(FD_ISSET(sock1, &readfds), "select() indicates socket is ready to read after reading first packet"); + + x = 123456789; + r = ioctlsocket(sock1, FIONREAD, &x); + + is_int(0, r, "ioctlsocket(FIONREAD) succeeds when packets are waiting"); + is_int(256, x, "ioctlsocket(FIONREAD) returns payload sizes when one packet is waiting"); + + assert(recv(sock1, buf, 4096, 0) == 256); + + FD_ZERO(&readfds); + FD_SET(sock1, &readfds); + is_int(select(-1, &readfds, NULL, NULL, &timeout), 0, "select() indicates socket is not ready to read after reading second packet"); + ok(!FD_ISSET(sock1, &readfds), "select() indicates socket is ready to read after reading second packet"); + + x = 123456789; + r = ioctlsocket(sock1, FIONREAD, &x); + + is_int(0, r, "ioctlsocket(FIONREAD) succeeds when no packets are waiting"); + is_int(0, x, "ioctlsocket(FIONREAD) returns zero bytes when no packets are waiting"); + + closesocket(sock2); + closesocket(sock1); + + WSACleanup(); + + return 0; +} diff --git a/tools/tools.h b/tools/tools.h index 021b0b3..c455854 100644 --- a/tools/tools.h +++ b/tools/tools.h @@ -8,7 +8,7 @@ #include #include -#include "addr.h" +#include "../src/addr.h" static struct sockaddr_ipx read_sockaddr(const char *net_s, const char *node_s, const char *socket_s) {