The WinMock Library

An off-line tool for testing and debugging WinSock apps

Joseph Hlavaty

Joe works for IBM Software Solutions in Research Triangle Park, NC, although he currently lives in Warsaw, Poland. He can be contacted at JHlavaty@vnet.ibm.com.


Developers of distributed applications face many problems, not the least of which include internetwork errors ("The LAN's down!"), intermittent problems difficult to reproduce for a debugger, and even the lack of network access during development. To tackle these and other problems, I've developed a tool to test and debug Windows Sockets applications without a network connection. I call it "WinMock," or the "Windows Sockets Mockup Tool." The WinMock library is a 16-bit Windows DLL built with Microsoft C 6.0 and the small DLL library.

The WinMock library is not an emulation of Windows Sockets calls. Instead, it is intended to become a fully "Windows Sockets Compliant" interface, albeit without underlying network support. While this library is based on the Windows Sockets 1.1 spec, nothing should preclude testing Windows Sockets 1.0 applications with it. In this article, I'll discuss only those APIs necessary to use WinSock 1.1 applications. Source code for the WinMock library is available electronically. To build changes to the WinMock source, you will need a compatible version of winsock.h. (You may, of course, use the library as-is, without winsock.h.)

The WinSock API

The Windows Sockets API is a network-programming interface based on BSD sockets. It contains a number of BSD routines (socket(), for instance) and extensions that allow you to take advantage of Windows' message-driven architecture. While the Windows Sockets API is meant to be used with the Internet Protocol Suite (TCP/IP), some vendors have implemented Windows Sockets wrappers around other protocol stacks. Since the Windows Sockets API is the same in all cases, WinMock should let you test and debug non-TCP/IP Windows Sockets interface apps, as well.

A socket is a tag for a communication endpoint. Normally, each conversation (connection) would have two such endpoints, one on the client side and the other on the server. WinMock will generate sockets either from the client or the server side, as requested, but makes no attempt to associate any client socket with any existing server socket.

The two types of sockets-stream and datagram-are normally bidirectional. A stream socket is conceptually similar to file I/O: It guarantees that information is delivered in order, that duplicate packets have been removed, and that the information has not been corrupted in transmission.

A datagram socket is less reliable: Information is not guaranteed to be in order, and it may be duplicated. However, for record-oriented data, datagrams guarantee that record boundaries will be preserved if the underlying network supports it (in other words, if the record is not too large for the underlying packet). Datagram sockets can also be used to broadcast packets if the underlying network supports broadcasting (sending a packet to multiple hosts).

Network byte order is Big-endian and involves a particular order of contiguous bytes in memory that represent numbers. Any local number sent to a remote location should be translated to network byte order using the Windows Sockets functions htons() (for short integers) and htonl() (for long integers). All numbers sent across the Internet (port numbers, addresses, and the like) must be in network byte order.

Host byte order is the byte order of the local machine (which may or may not match that of the network). Numbers received from the Internet must be converted using ntohs() (for short integers) or ntohl() (for long integers). Machines with Intel processors are Little-endian, but in the interests of portability, you should always convert with ntohs() and ntohl(). As an added complication, certain processors (the PowerPC, for instance) can operate in either endian mode.

Blocking calls do not return to the caller until the underlying function has completed (for example, a recv() command, which may need to wait until data are sent by the remote system). Calls don't block just because they can. For example, a recv() call will return immediately if data are waiting on the socket. There is a subset of calls like htons() that, by their nature, don't block (they involve no remote requests).

Modifying WinSock API Return Codes

The WinMock DLL can give explicit success or failure return codes for supported WinSock APIs. The user can create a WinMock.ini file with a section for each WinSock API; see Listing Two. The contents of each section are API specific, but most sections can include an RC key of the form RC=<value>. Acceptable values are those found in the WinMock ErrorList[] data structure. The wmConvertErrorString() function translates the given value (assumed to be a text string, for example, "ZERO") to its integer equivalent (in this example, the integer 0).

Certain WinMock APIs that return structures can read data values for certain fields of these structures from the WinMock.ini file. For example, you can return a wVersion number of 0x101 (257 decimal) from WSAStartup() by placing wVersion=257 in the [wsastartup] section of your WinMock.ini file.

The [lasterror] Section

To return the API's default error-return code (specific to the particular WinMock API) and to set a specific last-error value, include in your WinMock.ini file a [lasterror] section that contains the key <apiname>=<value>. This section is used to set the mylasterror field of the task data structure. A Windows Sockets application would retrieve the error by calling the Windows Sockets function WSAGetLastError().

For example, you can fail all recv() calls made by any task with a SOCKET_ERROR RC and a lasterror value of WSAECONNABORTED by placing recv=WSAECONNABORTED in the [lasterror] section. This is an easy way of checking how well a Windows Sockets application responds to errors.

WinMock APIs first check for a [lasterror] key for the given API. If a key is found, then the task's mylasterror value is set to the key's value, and the function returns the default error-return code without checking for an RC key in the API's section. If no [lasterror] key is found for the given API, WinMock will check for the RC key, if applicable (see the sample WinMock.ini file in Listing One.).

Since the WinMock library is a work in progress, not all APIs currently support these return-code .ini file entries. All APIs will soon support the [lasterror] section and may be assumed to support the RC key unless the documentation specifically prohibits it. The RC key, for example, is prohibited in the case of the database functions such as GetHostByName(), where the return value is an address of a structure and not a numeric value.

Base Data Structures for WinMock

The WinMock task list is a simple array of MYTASK structures. The number of structures in the array is decided at compile time by the MAXTASKS #define in winmock.h. The wmInitializeTaskList() routine is responsible for verifying that all task list elements have a known (and available) state. It could easily be modified to use a singly linked list and a more flexible task-list size. The fields for a MYTASK structure are currently defined as: mytask, the Windows task handle for the application in question; myhostent, the host-entry structure used for this application (or NULL, if none has been allocated); myprotoent, the protocol-entry structure (or NULL); myservent, the service-entry structure (or NULL) used for this application; isblocking, a flag noting whether a blocking call is currently being made for this task; mylasterror, the task-level error code for this application; and WSAStartupCount, the number of times this task has called the WSAStartup() function that initializes the task to the WinMock library. This count can be greater than 1 because the Windows Sockets API specifically permits multiple calls to WSAStartup().

All structure memory handles (myhostent, myprotoent, and the like) are initialized to NULL. These memory blocks are only allocated if the application requests them by calling a function that returns a HOSTENT, PROTOENT, or SERVENT structure. These blocks of memory are allocated on a one-per-task basis, and this structure will be reused if the application again calls a function requiring this block. If the value is non-NULL, a memory block has been allocated. All blocks (if any) will be freed during final WSACleanup() processing for the application.

The isblocking flag is not used in this WinMock version. It notes whether or not the given task has begun a "blocking" operation. A Windows Sockets-compliant interface must not begin a second operation until the first has completed. The default value is False (not blocking).

The mylasterror field contains the value to be returned by WSAGetLastError(). It is kept on a per-task basis. Any value set should either be 0 (no error) or one of the WSA error codes defined in winsock.h. The WinSock specification defines exactly which error codes may occur for each Windows Sockets API. The default value is 0.

WSAStartupCount is the number of times that this application (or, possibly, a DLL for this application) has called WSAStartup(). If this count is not at least 1, the application will not be on our task list (and Windows Sockets API calls will fail). If this count falls to 0, any resources allocated for the application by the WinMock library are freed.

The key used to identify an application on the task list is the mytask field, which contains the HTASK returned by the GetCurrentTask() API. This value distinguishes any single currently running Windows task from any other.

The Socket List

The socket list is also a simple array of MYSOCKET structures. The number of structures is decided at compile time by the MAXSOCKETS #define in winmock.h. The wmInitializeSocketList() routine could also be modified to use a more flexible allocation strategy. The fields for a MYSOCKET structure are currently defined as: mytask, the task owning this socket; isconnected, a flag that marks whether or not this socket is currently part of a conversation; isstillreceiving, which marks whether or not this application has receive data waiting for it; socketblocking, which marks whether or not this socket is blockable; socketfamily, this socket's address family (as defined by Windows Sockets); socketprotocol, the application's protocol family (as defined by Windows Sockets); and sockettype, the type of socket (stream or datagram).

MYSOCKET has four additional fields: hRecv, the global-memory handle (or NULL) for the task-receive buffer; wRecvSize, the size of the information received; wReceiveOffset, the offset in the received block that the user has not yet read (initially, 0); and hSend, the global-memory handle for sent text.

The key used to distinguish a socket on the socket list is the mys field-a number generated by WinMock to distinguish any one socket from all those currently using the WinMock library. A socket mys field may be of any value other than SOCKET_ERROR. The value in the mys field is actually the index of the MYSOCKET structure in the socket list, if it is in use; otherwise the mys field will contain SOCKET_ERROR.

Base Functions for WinMock

Eventually, WinMock will support all Windows Sockets 1.1 APIs.

The current version supports a limited subset; see winmins.c (available electronically). This file contains mostly BSD sockets-like routines used by the World Wide Web browsers I've used as test "data."

The WinMock DLL begins execution in the main() procedure, an assembly-language stub (found in dllentry.asm) that does heap initialization and calls the LibMain() routine.

A General Note About WinMock Functions

Unless otherwise noted, two conditions will cause all WinMock APIs to fail with the error code specified as the API error-return code (with no further processing):

Keep both conditions in mind when reading the WinMock API descriptions that follow. For brevity, these rules are not repeated in the individual functional descriptions. The first condition does not apply during WSAStartup() processing, of course, nor for resource-free API calls (such as htons()). The second condition applies only to those APIs that take a socket as an input parameter.

LibMain

The LibMain routine initializes the socket and task arrays via calls to wmInitializeSocketList() and wmInitializeTaskList(); see Listing One. Both routines simply initialize their respective lists to a known, neutral state. Following are WinMock's function prototypes (see Table 2).

This implementation of gethostbyname() can return only one address-list entry. The h_name value cannot be changed by the user-it is copied from the hostname parameter given by the caller.

The default error-return code is NULL. Because this function returns an address, any rc= line in the [gethostbyname] section will be ignored. The user may not modify the return value other than through a [lasterror] key for this function. An online WinSock implementation usually resolves the host information using a name server. If a name cannot be resolved, the function may fail. The WinMock implementation of this function, however, cannot normally fail.

The s_name value cannot be changed by the user; it is copied from the service-name parameter given by the caller. The default error-return code is NULL. Because this function returns an address, any rc= line in the [getservbyname] section will be ignored. The user may not modify the return value other than through a [lasterror] key. An online WinSock implementation would usually resolve the service information using a service database, and may fail if the given service name cannot be found in the database. The WinMock implementation of this function cannot normally fail. (The s_aliases field is ignored in this version of the WinMock library.)

recv(). Reads data from the connected socket and is one of the most complicated commands in the WinMock library. It uses the socket flag isstillreceiving to see if data are available for the socket in question. A number of variables in the socket structure keep track of the data available on the socket: If it is not NULL, the hRecv variable contains a handle to a global-memory block that contains the data received on the socket. The wRecvSize variable contains the size of the data (in bytes), and the wRecvOffset variable contains the offset of the first byte in the hRecv buffer that the caller has not read yet. (If the caller has read no data on this socket, wRecvOffset will be 0 or a near pointer to the first byte in the buffer.)

After verifying that there are data to be returned to the caller on the socket, the equation: wLeftToRecv=pthissocket->wRecvSize-pthissocket->wRecvOffset calculates the number of bytes remaining in the buffer. Then, either all data will fit into the caller's buffer or only some of them will fit. If the number of bytes left to receive is smaller than the size of the buffer passed in by the caller (wLeftToRecv<len), you can return all of the data in the buffer. The function copies wLeftToReceive bytes starting at wRecvOffset into the caller's buffer, and sets the isstillreceiving flag to False (thus marking that all data have been received). The function will return wLeftToReceive, the number of characters copied to the caller's buffer.

If the number of bytes left to receive does not fit in the user buffer, the data must be returned in chunks to the caller, so WinMock:

1. Copies len (size of caller buffer) bytes starting at wRecvOffset into the user buffer.

2. Sets wRecvOffset to the next unread byte by adding len to the current value.

3. Returns len as the number of bytes written to the caller's buffer.

If no data are available (FALSE==isstillreceiving), WinMock:

1. Sets isstillreceving to True (so the next recv() call will find data available on this socket).

2. Sets wRecvOffset to 0 (so that the next recv() call will begin reading at the beginning of the buffer.

3. Returns 0 bytes copied to buffer.

The [recv] section of winmock.ini may contain a line of the format buffer= <string of received text> or buffer=.<file name containing text to be received>. If the first character of the string given as the value of the buffer key is a period, it is assumed that the rest of the string is a (possibly fully qualified) path to a file that contains text to be returned as the received text.

The default error-return code is SOCKET_ERROR. The user may not modify the return value of recv() other than through a [lasterror] key. This command will fail if the socket is not connected with a WSAENOTCONN error.

The socket() command also fills in the socket's appropriate family, type, and protocol fields. It marks the calling task as the owner of the socket by placing the task handle retrieved from GetCurrentTask() in the mytask field of the socket structure. The mys field contents are returned to the caller as the socket tag. The [socket] section of winmock.ini currently has no entries. The default error-return code is INVALID_ SOCKET. The user may not modify the return value of socket() other than through a [lasterror] key. A protocol parameter gives a last error of WSAEPROTONOSUPPORT, and a type parameter gives a last error of WSAEPROTOTYPE. (These parameters are not in Table 1.) If no free socket is available, INVALID_SOCKET is returned with a last error of WSAEMFILE.

Additional elements of the WSAData structure are: iMaxSockets, which is set to the value of the MAXSOCKETS #define; iMaxUdpDg, which is set to 512 (the minimum value permitted by the Windows Sockets specification); lpVendorInfo, which is NULL.

This routine will fail with a return code of WSAVERNOTSUPPORTED if the caller's requested version of the Windows Sockets cannot be supported.

Conclusion

The WinMock library is not yet complete and only supports a subset of the Windows Sockets 1.1 API. However, this subset is enough to bring up a number of available Web browsers and simulate online interaction. I am currently upgrading the WinMock library to a fully enabled, Windows Sockets-compliant interface that I'll release in the near future.

Table 1: Valid socket() type and protocol pairs. All other combinations of protocols and types are invalid in the WinMock library implementation.

      Protocol      0          UDP           TCP
     

      0             INVT       OK (DGRAM)    OK (STREAM)

      DGRAM         OK (UDP)   OK            INVT

      STREAM        OK (TCP)   INVT          OK



      OK = valid                                OK (DGRAM) = valid, type set to DGRAM
      OK (UDP) = valid, protocol set to UDP     OK (STREAM) = valid, type set to STREAM
      OK (TCP) = valid, protocol set to TCP     INVT = invalid, incompatible type



Table 2: WinMock library-function prototypes

int APIENTRY          closesocket (SOCKET s)
int APIENTRY          connect (SOCKET s, LPSOCKADDR name, int namelen)
LPHOSTENT APIENTRY    gethostbyname(LPSTR hostname)
int APIENTRY          gethostname (LPSTR hostname, int namelen)
LPPROTOENT APIENTRY   getprotobyname(LPSTR protocolname)
LPSERVENT APIENTRY    getservbyname(LPSTR servicename, LPSTR proto)
int APIENTRY          recv (SOCKET s, LPSTR buf, int len, int flags)
int APIENTRY          recvfrom (SOCKET s, LPSTR buf, int len, int flags, LPSOCKADDR from, LPINT fromlen)
int APIENTRY          send (SOCKET s, LPSTR buf, int len, int flags)
int APIENTRY          sendto (SOCKET s, LPSTR buf, int len, int flags, LPSOCKADDR to, int tolen)
SOCKET APIENTRY       socket (int af, int type, int protocol)
int APIENTRY          WSAGetLastError(void)
int APIENTRY          WSACleanup(void)
int APIENTRY          WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData)

Listing One

WORD wmInitializeTaskList()
{
   PMYTASK pthistask = pataskshead ;
   int i ;
   // mark all our tasks as unused
   for ( i = 0; i < MAXTASKS; i++)
   {
      pthistask[i].mytask = (HTASK) NULL ;
      pthistask[i].myhostent = NULL ;
      pthistask[i].myprotoent = NULL ;
      pthistask[i].myservent = NULL ;
      pthistask[i].isblocking = FALSE ;
      pthistask[i].mylasterror = 0 ;
      pthistask[i].WSAStartupCount = 0 ;
      pthistask[i].ptnext = NULL ;
   }
   return( TRUE ) ;
}
WORD wmInitializeSocketList()
{
   PMYSOCKET pthissocket = pasocketshead ;
   int i ;
   // mark all our sockets as unused...
   for ( i = 0; i < MAXSOCKETS; i++)
   {
      pthissocket[i].mys = SOCKET_ERROR ;
      pthissocket[i].mytask = (HTASK) NULL ;
      pthissocket[i].isconnected = FALSE ;
      pthissocket[i].isstillreceiving = FALSE ;
      pthissocket[i].socketblocking = FALSE ;
      pthissocket[i].socketfamily = 0 ;
      pthissocket[i].socketprotocol = 0 ;
      pthissocket[i].sockettype = 0 ;
      pthissocket[i].hRecvBuffer = NULL ;
      pthissocket[i].iRecvSize = 0 ;
      pthissocket[i].iRecvOffset = 0 ;
      pthissocket[i].hSendBuffer = NULL ;
      pthissocket[i].psnext = NULL ;
   }
  return( TRUE ) ;
}

Listing Two
; each section (application name) corresponds to the given Windows Sockets API 
; each keyname corresponds to the WinSock API parameter or structure field
; place this file in your WINDOWS directory

[closesocket]

[connect]

[gethostbyname]
 h_name=hostname
 h_addrtype=2
 h_length=4
 h_addr_list=11.22.33.44

[gethostname]
 hostname=host.company.com

[getprotobyname]
 p_proto=6

[getservbyname]
 p_proto=tcp
; the following is in network byte order
 p_port=8000 ;

[recv]
 buffer=This is received text.\r\n.\r\n
;buffer=.temp.fil

[recvfrom]
 sin_family=2
 sin_port=0
 buffer=This is received text.\r\n.\r\n

[send]

[sendto]

[socket]

[wsastartup]
; 257 decimal is 0x0101
 wVersion=257
 wHighVersion=257
 szDescription=WinMock Windows Sockets Off-Line Tester
 szSystemStatus=
;iMaxSockets= MAXSOCKETS

[lasterror]
;closesocket=WSAENOTSOCK
;connect=WSAEISCONN
;gethostbyname=WSAHOST_NOT_FOUND
;gethostname=WSAEINPROGRESS
;getprotobyname=WSANO_DATA
;getservbyname=WSANO_DATA
;recv=WSAECONNABORTED 
;recvfrom=WSAEFAULT
;send=WSAECONNRESET
;sendto=WSAEADDRNOTAVAIL
;socket=WSAENOBUFS
;wsacleanup=
;wsagetlasterror=
;wsastartup=