CHATSOCK.C

/*++ 

Copyright (c) 1995 Intel Corp

Module Name:

chatsock.c

Abstract:

Socket-related functions for the WinSock2 Chat sample application.
--*/

#include "nowarn.h" /* turn off benign warnings */
#ifndef _WINSOCKAPI_
#define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */
#endif
#include <windows.h>
#include "nowarn.h" /* some warnings may have been turned back on */
#include <stdlib.h>
#include <malloc.h>
#include <stdio.h>
#include <assert.h>
#include <ws2atm.h>
#include "ws2chat.h"
#include "chatsock.h"
#include "chatdlg.h"



//
// Static Globals
//

// points to an array of WSAPROTOCOL_INFO structs
static LPWSAPROTOCOL_INFO InstalledProtocols = NULL;

// number of WSAPROTOCOL_INFO structs in the InstalledProtocols buffer
static int NumProtocols = 0;

// static array of sockets, one for each listening socket
static LISTENDATA ListeningSockets[MAX_LISTENING_SOCKETS];

// number of meaningful entries in ListeningSockets
static int NumFound = 0;




//
// Function Prototypes -- Internal Functions
//

DWORD
IOThreadFunc(
IN LPVOID ParamPtr);

BOOL
HandleSocketEvent(
IN OUT PCONNDATA ConnData);

BOOL
HandleOutputEvent(
IN OUT PCONNDATA);

BOOL
HandleOtherEvent(
IN DWORD WaitStatus,
IN OUT PCONNDATA ConnData);

int
HandleEvents(
IN PCONNDATA ConnData,
IN LPWSANETWORKEVENTS NetworkEvents);

BOOL
FillLocalAddress(
IN LPVOID SockAddr);

int
DoRecv(
IN PCONNDATA ConnData);

int
DoOverlappedCallbackSend(
IN POUTPUT_REQUEST OutReq,
IN PCONNDATA ConnData);

int
DoOverlappedEventSend(
IN POUTPUT_REQUEST OutReq,
IN PCONNDATA ConnData);

int
DoSend(
IN POUTPUT_REQUEST OutReq,
IN PCONNDATA ConnData);

void CALLBACK
SendCompFunc(
IN DWORD Error,
IN DWORD BytesTransferred,
IN LPWSAOVERLAPPED OverlappedPtr,
IN DWORD Flags);

int CALLBACK
AcceptCondFunc(
IN LPWSABUF CallerId,
IN LPWSABUF CallerData,
IN LPQOS CallerSQos,
IN LPQOS CallerGQos,
IN LPWSABUF CalleeId,
OUT LPWSABUF CalleeData,
OUT GROUP FAR *Group,
IN DWORD CallbackData);

LPWSAPROTOCOL_INFO
GetProtoFromSocket(
IN SOCKET Socket);

BOOL
GetMaxMsgSize(
IN OUT PCONNDATA ConnData);




//
// Function Definitions
//


BOOL
InitWS2(void)
/*++

Routine Description:

Calls WSAStartup, makes sure we have a good version of WinSock2

Arguments:

None.

Return Value:

TRUE - WinSock 2 DLL successfully started up

FALSE - Error starting up WinSock 2 DLL.

--*/

{
int Error; // catches return value of WSAStartup
WORD VersionRequested; // passed to WSAStartup
WSADATA WsaData; // receives data from WSAStartup
BOOL ReturnValue = TRUE; // return value

// Start WinSock 2. If it fails, we don't need to call
// WSACleanup().
VersionRequested = MAKEWORD(VERSION_MAJOR, VERSION_MINOR);
Error = WSAStartup(VersionRequested, &WsaData);
if (Error) {
MessageBox(GlobalFrameWindow,
"Could not find high enough version of WinSock",
"Error", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
ReturnValue = FALSE;
} else {

// Now confirm that the WinSock 2 DLL supports the exact version
// we want. If not, make sure to call WSACleanup().
if (LOBYTE(WsaData.wVersion) != VERSION_MAJOR ||
HIBYTE(WsaData.wVersion) != VERSION_MINOR) {
MessageBox(GlobalFrameWindow,
"Could not find the correct version of WinSock",
"Error", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
WSACleanup();
ReturnValue = FALSE;
}
}
return(ReturnValue);

} // InitWS2()





BOOL
FindProtocols(void)
/*++

Routine Description:

Finds out about all transport protocols installed on the local
machine and saves the information into global variables.

Implementation:

This function uses WSAEnumProtocols to find out about all
installed protocols on the local machine. It stores this
information in two variables which are global to this file;
InstalledProtocols is a pointer to a buffer of WSAPROTOCOL_INFO
structs, while NumProtocols is the number of protocols in that
buffer. This function is the only function in the file allowed to
touch these variables.

Arguments:

None.

Return Value:

TRUE - Successfully initialized the protocol buffer.

FALSE - Some kind of problem arose. The user is informed of the
error.

--*/
{

DWORD BufferSize = 0; // size of InstalledProtocols buffer
char MsgText[MSG_LEN]; // holds message strings


// Call WSAEnumProtocols to figure out how big of a buffer we need.
NumProtocols = WSAEnumProtocols(NULL,
NULL,
&BufferSize);

if ((NumProtocols != SOCKET_ERROR) && (WSAGetLastError() != WSAENOBUFS)) {
// We're in trouble!!
MessageBox(GlobalFrameWindow, "WSAEnumProtocols is broken.", "Error",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
goto Fail;
}

// Allocate a buffer, call WSAEnumProtocols to get an array of
// WSAPROTOCOL_INFO structs.
InstalledProtocols = (LPWSAPROTOCOL_INFO)malloc(BufferSize);
if (InstalledProtocols == NULL) {
MessageBox(GlobalFrameWindow, "malloc failed.", "Error",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
goto Fail;
}
NumProtocols = WSAEnumProtocols(NULL,
(LPVOID)InstalledProtocols,
&BufferSize);
if (NumProtocols == SOCKET_ERROR) {
// uh-oh
wsprintf(MsgText, "WSAEnumProtocols failed. Error Code: %d",
WSAGetLastError());
MessageBox(GlobalFrameWindow, MsgText, "Error",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
goto Fail;
}
return(TRUE);

Fail:

WSACleanup();
return(FALSE);

} // FindProtocols()





BOOL
FillLocalAddress(
IN struct sockaddr *pSockAddr)
/*++

Routine Description:

Fills in the struct sockaddr with a local address appropriate for
the address family, for use in a call to bind.

Arguments:

SockAddr -- A pointer to a struct sockaddr which will be filled in
appropriately, as determined by the particular address family.
The address family field (sa_family) must already be filled into
the structure before being passed into this function.

Return Value:

TRUE -- FillLocalAddress recognized the address family and
successfully filled in the struct sockaddr. This structure is
ready to be passed to a bind() call.

FALSE -- FillLocalAddress didn't recognize the address family and
didn't touch the struct sockaddr.

--*/
{

struct sockaddr_in *pInetSockAddr; // used to cast SockAddr to an INET address
struct sockaddr_atm *pATMSockAddr; // used to cast SockAddr to an ATM address
BOOL ReturnValue = TRUE; // holds the return value

switch (pSockAddr->sa_family) {

case AF_INET:

// Cast the pointer so we can now access it's fields as a
// struct sockaddr_in, the address structure for internet
// addresses.
pInetSockAddr = (struct sockaddr_in *)pSockAddr;
if (!DialogBoxParam(GlobalInstance,
"InetListenPortDlg",
GlobalFrameWindow,
InetListenPortDlgProc,
(LPARAM)pInetSockAddr)) {
ReturnValue = FALSE;
}
break;

case AF_ATM:
pATMSockAddr = (struct sockaddr_atm *)pSockAddr;

pATMSockAddr->satm_number.AddressType = SAP_FIELD_ANY;
pATMSockAddr->satm_blli.Layer2Protocol = SAP_FIELD_ANY;
pATMSockAddr->satm_blli.Layer3Protocol = SAP_FIELD_ANY;
pATMSockAddr->satm_bhli.HighLayerInfoType = BHLI_UserSpecific;

ReturnValue = DialogBoxParam(GlobalInstance,
"ATMSOCKADDRDLG",
GlobalFrameWindow,
(DLGPROC)ATMSockAddrProc,
(LPARAM)pATMSockAddr);
break;

default:

ReturnValue = FALSE;
break;
}
return(ReturnValue);

} // FillLocalAddress()





BOOL
ListenAll(void)

/*++

Routine Description:

For each installed, connection-oriented protocol, this function
creates a socket, binds to a local address, and listens on the
created socket. The socket is set up for Windows message
notification for any connection attempts.

Arguments:

None.

Return Value:

TRUE - Successfully listened on all installed protocols.

FALSE - Error listening on all installed protocols. It is not an
error if there are more connection-oriented protocols than
MAX_LISTENING_SOCKETS installed; in this case we just ignore any
extra protocols.

--*/

{
struct sockaddr *SockAddr; // holds socket addresses
int SockAddrLen; // length, in bytes, of SockAddrLen
LPWSAPROTOCOL_INFO COProtocolInfo; // current protocol info to examine
int i; // counting variable
char MsgText[MSG_LEN]; // holds message strings
int Error; // holds return values
int Index; // indexes into text messages


// Find all protocols that support connection-oriented data
// transfer; create a socket, bind to a local address and listen
// for connections on that socket for each such protocol found.
// Also fill in an entry in the ListeningSockets array.
for (i = 0; i < NumProtocols; i++) {

COProtocolInfo = &InstalledProtocols[i];
assert(COProtocolInfo != NULL);
if (UseProtocol(COProtocolInfo)) {

// We've found a suitable protocol. Create a socket, fill
// in the next entry in ListeningSockets
ListeningSockets[NumFound].Socket = WSASocket(FROM_PROTOCOL_INFO,
FROM_PROTOCOL_INFO,
FROM_PROTOCOL_INFO,
COProtocolInfo,
0,
WSA_FLAG_OVERLAPPED);
if (ListeningSockets[NumFound].Socket == INVALID_SOCKET) {
UCHAR textBuf[MAX_ERROR_TEXT];
sprintf(textBuf, "Could not open a socket. [%x]", WSAGetLastError());
MessageBox(GlobalFrameWindow, textBuf,
"Non-fatal error.",
MB_OK | MB_SETFOREGROUND);
continue;
}
ListeningSockets[NumFound].ProtocolInfo = COProtocolInfo;

// Allocate a block of memory for the socket address and
// zero it out.
SockAddrLen = COProtocolInfo->iMaxSockAddr;
SockAddr = (struct sockaddr *)malloc(SockAddrLen);

if (!SockAddr) {
ChatSysError("malloc()",
"ListenAll()",
TRUE);
}
memset((char *)SockAddr, 0, SockAddrLen);

// Get a local address to bind the socket to
SockAddr->sa_family = (u_short)COProtocolInfo->iAddressFamily;
FillLocalAddress(SockAddr);

// Bind the socket to SockAddr.
Error = bind(ListeningSockets[NumFound].Socket,
SockAddr,
SockAddrLen);
if (Error == SOCKET_ERROR) {
// bind() failed
UCHAR textBuf[MAX_ERROR_TEXT];
sprintf(textBuf, "Could not bind the socket. [%x]", WSAGetLastError());
MessageBox(GlobalFrameWindow, textBuf,
"Non-fatal error", MB_OK | MB_SETFOREGROUND);
free(SockAddr);
continue;
}

free(SockAddr);

// Set up the socket for windows message event notification.
// Note that this call automatically puts the socket into
// non-blocking mode, as if we had called WSAIoctl with
// the FIONBIO flag.
Error = WSAAsyncSelect(ListeningSockets[NumFound].Socket,
GlobalFrameWindow,
USMSG_ACCEPT,
FD_ACCEPT);
if (Error == SOCKET_ERROR) {
MessageBox(GlobalFrameWindow, "Error: WSAAsyncSelect()",
"Non-fatal error", MB_OK | MB_SETFOREGROUND);
continue;
}

// Listen for incoming connection requests on the socket.
Error = listen(ListeningSockets[NumFound].Socket,
SOMAXCONN);
if (Error == SOCKET_ERROR) {
MessageBox(GlobalFrameWindow, "Error: listen()",
"Non-fatal error",
MB_OK | MB_SETFOREGROUND);
continue;
}

// Looks good -- increase the count, check for overflow of
// the max amount, re-iterate if we're ok.
if (++NumFound == MAX_LISTENING_SOCKETS) {
wsprintf(MsgText,
"More than %d useable protocols. Ignoring extras.",
MAX_LISTENING_SOCKETS);
MessageBox(GlobalFrameWindow, MsgText, "Alert",
MB_OK | MB_SETFOREGROUND);
break;
}

} // if (UseProtocol(COProtocolInfo))
} // for(; ; ;)

if (NumFound == 0) {

Index = wsprintf(MsgText,
"Couldn't find a suitable protocol ");
Index += wsprintf(MsgText + Index,
"and/or no listening sockets could be opened.\r\n");
wsprintf(MsgText + Index, "Shall we continue anyway?");

if (MessageBox(GlobalFrameWindow, MsgText, "No protocols.",
MB_ICONQUESTION | MB_YESNO | MB_SETFOREGROUND)
== IDNO) {

WSACleanup();
return(FALSE);
}
}

return(TRUE);

} // ListenAll()





BOOL
UseProtocol(
IN LPWSAPROTOCOL_INFO Proto)
/*++

Routine Description:

Returns true if Proto is suitable for use by Chat.

Arguments:

Proto -- Points to a protocol information struct.

Return Value:

TRUE -- Chat likes it.

FALSE -- Get this chintzy protocol out of here!

--*/
{

if (!(Proto->dwServiceFlags1 & XP1_CONNECTIONLESS) &&
//(Proto->dwServiceFlags1 & XP1_GUARANTEED_DELIVERY) &&
(Proto->dwServiceFlags1 & XP1_GUARANTEED_ORDER)) {

return(TRUE);

} else {

return(FALSE);
}
} // UseProtocol





void
CleanUpSockets(void)
/*++

Routine Description:

This function closes all listening sockets.

Arguments:

None.

Return Value:

None.

--*/
{

int i; // counting variable

for (i = 0; i < NumFound; i++) {
closesocket(ListeningSockets[i].Socket);
}
} // CleanUpSockets()





int CALLBACK
AcceptCondFunc(
IN LPWSABUF CallerId,
IN LPWSABUF CallerData,
IN LPQOS CallerSQos,
IN LPQOS CallerGQos,
IN LPWSABUF CalleeId,
OUT LPWSABUF CalleeData,
OUT GROUP FAR *Group,
IN DWORD CallbackData)
/*++

Routine Description:

Condition function called when an incoming connection request is
handled.

Implementation:

This function allows the user to accept or reject an incoming
connection request after examining the caller's name and the
subject of the call. If the call is accepted, and
connection-time data transfer is supported by the particular
protocol on which the connection is made, a dialog box comes up
which prompts the user for his/her name.

Arguments:

CallerId -- Supplies the address of the caller.

CallerData -- Supplies the caller's user data. Chat uses this
parameter to send the caller's name and the subject of the call.

CallerSQos -- Supplies the forward and backward QOS.

CallerGQos -- Supplies the forward and backward flow specs for the
socket group the caller is to create. Not used by chat.

CalleeId -- Supplies the local address.

CalleeData -- Returns user data back to the caller (Chat uses this
parameter to return the callee's name)

Group -- Returns the appropriate group action to take on the
connecting socket. Not used by chat. Always returns NULL.

CallbackData -- Supplies a pointer the CONNDATA structure
associated with this connection.

Return Value:

CF_ACCEPT - Accept the connection request from the caller.

CF_REJECT - Reject the connection request from the caller.

--*/

{
char MsgText[MSG_LEN]; // build message string here
char TitleText[TITLE_LEN + 1]; // build title string here
PCONNDATA ConnData; // connection-specific data
int Index; // index into MsgText
int ReturnValue = CF_ACCEPT; // return value

ConnData = (PCONNDATA)CallbackData;

// CallerId contains the socket address of the connecting entity.
// Copy this into the connection-specific data.
ConnData->RemoteSockAddr.len = CallerId->len;
ConnData->RemoteSockAddr.buf = malloc(ConnData->RemoteSockAddr.len);
if (ConnData->RemoteSockAddr.buf == NULL) {
ChatSysError("malloc()",
"AcceptCondFunc()",
TRUE);
}
memcpy((char *)ConnData->RemoteSockAddr.buf, (char *)CallerId->buf,
CallerId->len);

// Translate the RemoteSockAddr into a human readable form, and
// store it in ConnData->PeerAddress
GetAddressString(ConnData->PeerAddress,
ConnData->RemoteSockAddr.buf,
ConnData->RemoteSockAddr.len,
ConnData->ProtocolInfo);

Index = wsprintf(MsgText, "Someone is attempting a chat connection.\r\n");

if (CallerData != NULL) {

// The connection request has come with some caller data. Use
// it to inform the user of who is trying to connect
ExtractTwoStrings(CallerData->buf,
ConnData->PeerName,
NAME_LEN + 1,
ConnData->Subject,
SUB_LEN + 1);

// Build the strings for the message box and the title of the
// connection window
Index += wsprintf(MsgText + Index,
"From: %s\r\nSubject: %s\r\n", ConnData->PeerName,
ConnData->Subject);
wsprintf(TitleText, "Connected to: %s @ %s", ConnData->PeerName,
ConnData->PeerAddress);

} else {

// There is no caller data...build a string for the title.
wsprintf(TitleText, "Connected to: %s", ConnData->PeerAddress);
}

// Continue building MsgText.
Index += wsprintf(MsgText + Index, "Address: %s\r\n",
ConnData->PeerAddress);
Index += wsprintf(MsgText + Index, "Would you like to accept it?");

// Prompt the user to accept or reject the connection.
//
// ****NOTE****
// This is NOT the right way to do this. The application should
// not hold up this thread by putting up this message box, or the
// dialog box below. As mentioned in the API spec, this function
// should return "as soon as possible", and clearly this is not
// what's being done here. Please look to future versions of chat
// for a fix. Thanks.
if (MessageBox(ConnData->ConnectionWindow, MsgText,
"Connection Request",
MB_ICONQUESTION | MB_YESNO | MB_SETFOREGROUND) == IDYES) {

// The user has accepted the connection request.
SetWindowText(ConnData->ConnectionWindow, TitleText);
if (CalleeData != NULL) {

// We can try to pass user data back. Call up a dialog box
// to get a name string and put it into CalleeData.
if (!DialogBoxParam(GlobalInstance,
"AcceptConnectionDlg",
ConnData->ConnectionWindow,
AcceptConnectionDlgProc,
(LPARAM)CalleeData)) {

CalleeData->len = 0;
}
}
ReturnValue = CF_ACCEPT;

} // if (MessageBox(...))
else {

// The user has rejected the connection request.
ReturnValue = CF_REJECT;
}
return(ReturnValue);

} // AcceptCondFunc()





BOOL
GetAddressString(
OUT char *String,
IN LPVOID SockAddr,
IN int SockAddrLen,
IN LPWSAPROTOCOL_INFO ProtocolInfo)
/*++

Routine Description:

This function translates the SockAddr into a human-readable
string, if possible. If chat doesn't recognize the protocol, then
the string "(unknown)" is returned in String.

Arguments:

String -- Returns the string representing the SockAddr in a
human-readable form.

SockAddr -- An address.

SockAddrLen -- The length, in bytes, of SockAddr.

ProtocolInfo -- Pointer to the protocol information structure.

Return Value:

TRUE -- Chat recognized the protocol family and successfully
translated the address.

FALSE -- Chat did not recognize the protocol family and returned
the string "(unknown)".

--*/
{

char *TempString; // string returned by inet_ntoa
struct sockaddr_in *SockAddrInet; // casts the address to a sockaddr_in
BOOL ReturnValue; // holds the return value

ReturnValue = TRUE;

switch (ProtocolInfo->iAddressFamily) {

case AF_INET:

// It's an Internet-style address.
SockAddrInet = (struct sockaddr_in *)SockAddr;
TempString = inet_ntoa(SockAddrInet->sin_addr);
if (TempString == NULL) {
MessageBox(NULL, "inet_ntoa() failed.", "Error.",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
} else {
strcpy(String, TempString);
}
ReturnValue = TRUE;
break;

default:

strcpy(String, "(unknown)");
ReturnValue = FALSE;
break;
}
return(ReturnValue);

} // GetAddressString()





int
HandleEvents(
IN PCONNDATA ConnData,
IN LPWSANETWORKEVENTS NetworkEvents)

/*++

Routine Description:

Handles network events that may occur on a connected socket.
The events handled by this function are FD_CLOSE, FD_READ, and
FD_WRITE.

Arguments:

ConnData - Supplies a pointer to data for the connection on which
the event happened.

NetworkEvents - Supplies a WSANETWORKEVENT structure, which is a
record of the network events that have occurred as well as any
accompanying error-codes.

Return Value:

CHAT_OK -- The network event was successfully handled.

CHAT_ERROR -- Some kind of error occurred while handling the
event, and the connection should be closed.

CHAT_CLOSED -- The connection has been gracefully closed.

--*/

{
int Result; // holds the result of DoRecv
int ReturnValue = CHAT_OK; // return value

// The following three if statements all execute unless one gets
// an error or closed socket, in which case we return immediately.
if (NetworkEvents->lNetworkEvents & FD_READ) {

// An FD_READ event has occurred on the connected socket.
if (NetworkEvents->iErrorCode[FD_READ_BIT] == WSAENETDOWN) {

// There is an error.
ReturnValue = CHAT_ERROR;
goto Done;

} else {

// Read data off the socket...
Result = DoRecv(ConnData);
if ((Result == CHAT_ERROR) || (Result == CHAT_CLOSED)) {
ReturnValue = Result;
goto Done;
}
}
}

if (NetworkEvents->lNetworkEvents & FD_WRITE) {

// An FD_WRITE event has occurred on the connected socket.
if (NetworkEvents->iErrorCode[FD_WRITE_BIT] == WSAENETDOWN) {

// There is an error.
ReturnValue = CHAT_ERROR;
goto Done;

} else {

// Allow chat to send on this socket, and signal the
// OuputEventObject in case there is pending output that is
// not completed due to WSAEWOULDBLOCK.
ConnData->WriteOk = TRUE;
SetEvent(ConnData->OutputEventObject);
}
}

if (NetworkEvents->lNetworkEvents & FD_CLOSE) {

if (NetworkEvents->iErrorCode[FD_CLOSE_BIT] == 0) {

// A graceful shutdown has occurred...
ReturnValue = CHAT_CLOSED;
goto Done;

} else {

// This is some other type of abortive close or failure...
ReturnValue = CHAT_ABORTED;
goto Done;
}

}

Done:
return(ReturnValue);

} // HandleEvents()





DWORD
IOThreadFunc(
IN LPVOID ParamPtr)

/*++

Routine Description:

This routine is invoked as a separate thread to handle all input
and output for a connection.

Implementation:

This thread sits in a loop, waiting for one of several things to
occur. These can be:

1. The user interface thread has some input ready to be sent,
and has signaled the ouput event object.

2. WinSock 2 has indicated there is a network event associated
with the socket.

3. Callback notification. This can be via an event or via a
queued callback function, depending on whether or not we've
compiled with the CALLBACK_NOTIFICATION flag.

The return value from the wait indicates what has
happened, and a switch statement handles all the possible cases.
When there is an error or the connection is being closed, the loop
is broken and the thread will exit and the connection window will
be closed (and sent a WM_DESTROY message).

Arguments:

ParamPtr - Supplies a pointer to a ConnData structure that holds
data specific to this connection.

Return Value:

0 - Always returns 0. The return value is not needed.

--*/
{
char MsgText[MSG_LEN]; // holds message strings
DWORD WaitStatus; // holds return value of the wait
PCONNDATA ConnData; // connection-specific data
BOOL KeepGoing = TRUE; // keep processing output requests?
BOOL Forever = TRUE; // constant to avoid warning

ConnData = (PCONNDATA)ParamPtr;

// Initialize the EventArray. The only two events in it, for now,
// are the Socket and Ouput events; if this is the event
// notification version of chat, then there will be an additional
// event for each overlapped send waiting for a completion
// notification.
ConnData->NumEvents = 2;
ConnData->EventArray[0] = ConnData->SocketEventObject;
ConnData->EventArray[1] = ConnData->OutputEventObject;

// Initialize the array of output requests, which is indexed in
// parallel to the above event array. When an event is signaled,
// we can figure out which output request and overlapped
// structures to free by indexing into this array.
// These first two entries should never be referenced, because
// their parallel entries in EventArray (see above) are for the

// permanent Socket and Output event objects. 
ConnData->OutReqArray[0] = ConnData->OutReqArray[1] = NULL;

while (Forever) {

// Wait for an event (or a queued callback function) to wake
// us up. This is an alertable wait state (fAlertable == TRUE).
WaitStatus = WSAWaitForMultipleEvents(ConnData->NumEvents,
ConnData->EventArray,
FALSE, // fWaitAll
WSA_INFINITE, // dwTimeout
TRUE); // fAlertable

// Determine why we woke up and act accordingly. Note that
// breaking out of the switch causes us to break out of the
// while loop as well. When we don't want to break out of the
// loop, case statements end with the continue statement.
switch (WaitStatus) {

case WSA_WAIT_FAILED:

// A fatal error. Pop up a message box and break out of
// the while loop to end the thread.

wsprintf(MsgText,
"WSAWaitForMultipleEvents() failed. Error code: %d",
WSAGetLastError());
MessageBox(ConnData->ConnectionWindow, MsgText, "Fatal Error",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
break;

case WAIT_IO_COMPLETION:

// An I/O completion routine has been executed. Cleanup
// has already occurred in SendCompFunc, so there is
// nothing left to do. Just reiterate through the loop.

continue;

case WSA_WAIT_EVENT_0:

// The SocketEventObject has been signaled. Handle it in
// a separate function. Break out of the thread if
// HandleSocketEvent returns FALSE, indicating error.
if (HandleSocketEvent(ConnData)) {
continue;
} else {
break;
}

// Please note: WSA_WAIT_EVENT_[1,2,3] are defined in
// chatsock.h, not winsock2.h like WSA_WAIT_EVENT_0

case WSA_WAIT_EVENT_1:

// The OuputEventObject has been signaled. Handle it in a
// separate function, and break out of the thread if it
// returns FALSE
if (HandleOutputEvent(ConnData)) {
continue;
} else {
break;
}

default:

// Some other event has been signaled. Handle it in a
// separate function, and break out of the thread if it
// returns FALSE.
if (HandleOtherEvent(WaitStatus, ConnData)) {
continue;
} else {
break;
}

} // switch (WaitStatus)

// Break out of the while loop.
break;

} // while (1)

// Thread is ending because the connection was closed or an error
// occurred
PostMessage(ConnData->ConnectionWindow,
WM_CLOSE,
0,
0);
return(0);

} // IOThreadFunc()





BOOL
HandleSocketEvent(
IN OUT PCONNDATA ConnData)
/*++

Routine Description:

Handles the case in IOThreadFunc where the thread is woken up by a
signal to the socket event object.

Arguments:

ConnData - Supplies a pointer to data for the connection on which
the event happened.

Return Value:

TRUE -- Chat successfully handled the event and the thread should
continue on.

FALSE -- An error occurred and Chat should kill the thread and the
connection.

--*/
{
int Result; // holds return values
WSANETWORKEVENTS NetworkEvents; // tells us what events happened
char MsgText[MSG_LEN]; // holds text strings
BOOL ReturnValue; // holds the return value

// Find out what happened and act accordingly.
Result = WSAEnumNetworkEvents(ConnData->Socket,
ConnData->SocketEventObject,
&NetworkEvents);
if (Result == SOCKET_ERROR) {

// Handle the fatal error.
wsprintf(MsgText,
"WSAEnumNetworkEvents failed. Error code: %d",
Result);
MessageBox(GlobalFrameWindow, MsgText, "Fatal Error",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
ReturnValue = FALSE;

} else {

// Handle all of the network events on the given socket
Result = HandleEvents(ConnData, &NetworkEvents);

if (Result == CHAT_CLOSED) {

// HandleEvents() has determined that the remote party
// has terminated the connection. Inform the user, and
// return.
if (ConnData->PeerName[0] != 0) {
wsprintf(MsgText,
"%s @ %s has terminated the connection.",
ConnData->PeerName, ConnData->PeerAddress);
} else {
wsprintf(MsgText,
"The party at %s has terminated the connection.",
ConnData->PeerAddress);
}
MessageBox(ConnData->ConnectionWindow, MsgText, "Sorry!",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
ReturnValue = FALSE;

} else if (Result == CHAT_ABORTED) {

// HandleEvents has determined that the connection has
// been aborted due to an undetermined error.
MessageBox(ConnData->ConnectionWindow,
"The connection has been broken.",
"Connection aborted.",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
ReturnValue = FALSE;

} else if (Result == CHAT_ERROR) {

// HandleEvents() has returned an error. Inform the
// user and break to exit the thread and kill the window.
MessageBox(ConnData->ConnectionWindow,
"An unidentified network or system error occurred.",
"Error.", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
ReturnValue = FALSE;

} else if (Result == CHAT_OK) {

// Inform the caller that everything went fine.
ReturnValue = TRUE;


} else {

// This case should only occur if there is a
// programming error. Break out of the while loop to
// kill the thread, the window, and therefore the
// connection.
MessageBox(ConnData->ConnectionWindow,
"HandleEvents() returned an unexpected value.",
"Error.", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
ReturnValue = FALSE;
}
}

return(ReturnValue);

} // HandleSocketEvent()





BOOL
HandleOutputEvent(
IN OUT PCONNDATA ConnData)
/*++

Routine Description:

Handles the case in IOThreadFunc where the thread is woken up by a
signal to the output event object.

Arguments:

ConnData - Supplies a pointer to data for the connection on which
the event happened.

Return Value:

TRUE -- Chat successfully handled the event and the thread should
continue on.

FALSE -- An error occurred and Chat should kill the thread and the
connection.

--*/
{
BOOL ReturnValue; // holds the return value
BOOL KeepGoing; // keep pulling requests off the queue?
POUTPUT_REQUEST OutReq; // pointer to the output request
int Result; // holds results of functions

ReturnValue = TRUE;

// First we check to see if a previous send attempt failed with
// WSAEWOULDBLOCK; if so, we don't have to bother trying to send
// data until we get an FD_WRITE network event, so just return
// with TRUE to indicate that the thread should wait again.
if (ConnData->WriteOk) {

// This loop pulls output requests off the queue and hands
// them to DoSend.
KeepGoing = TRUE;
while (KeepGoing) {

OutReq = (POUTPUT_REQUEST)QRemove(ConnData->OutputQueue);
if (OutReq == NULL) {

// Nothing is left on the queue.
KeepGoing = FALSE;
ReturnValue = TRUE;

} else {

// Do the output
Result = DoSend(OutReq, ConnData);
if (Result == CHAT_WOULD_BLOCK) {

// The send would have blocked; we need to
// requeue the output request and wait for an
// FD_WRITE network event.
KeepGoing = FALSE;
QInsertAtHead(ConnData->OutputQueue, (LPVOID)OutReq);
ReturnValue = TRUE;

} else if (Result == CHAT_ERROR) {

// An error occurred in DoSend. Set KeepGoing to
// FALSE in order to get us out of the loop.
KeepGoing = FALSE;
ReturnValue = FALSE;
MessageBox(ConnData->ConnectionWindow,
"Error sending data.",
"Error.",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);

} else {

// DoSend returned CHAT_OK. Keep looping.
continue;
}
}
} // while (KeepGoing)
} // if (ConnData->WriteOk)

return(ReturnValue);

} // HandleOutputEvent()





BOOL
HandleOtherEvent(
IN DWORD WaitStatus,
IN OUT PCONNDATA ConnData)
/*++

Routine Description:

Handles the case in IOThreadFunc where the thread is woken up by
an event that is either an overlapped I/O event or was not
specified in the event array (which is an error!).

Arguments:

WaitStatus -- Supplies the value returned by
WSAWaitForMultipleEvents.

ConnData - Supplies a pointer to data for the connection on which
the event happened.

Return Value:

TRUE -- Chat successfully handled the event and the thread should
continue on.

FALSE -- An error occurred and Chat should kill the thread and the
connection.

--*/
{
POUTPUT_REQUEST OutReq; // points to an output request
BOOL ReturnValue; // holds the return value
DWORD Count; // counting variable
char MsgText[MSG_LEN]; // holds message strings

// First do a sanity check to make sure the index returned is
// within the bounds of what WE think is the event array.
if ((WaitStatus >= WSA_WAIT_EVENT_0) &&
(WaitStatus <= (WSA_WAIT_EVENT_0 + ConnData->NumEvents - 1))) {

// Free the data buffer, the overlapped structure, the output
// request itself, and the event
OutReq = ConnData->OutReqArray[WaitStatus - WSA_WAIT_EVENT_0];
free(OutReq->Buffer.buf);
free(OutReq->Overlapped);
free(OutReq);

CloseHandle(ConnData->EventArray[WaitStatus - WSA_WAIT_EVENT_0]);

// Update all our event and output request arrays to
// reflect that the overlapped send has completed.
ConnData->NumEvents--;
for (Count = (WaitStatus - WSA_WAIT_EVENT_0);
Count < ConnData->NumEvents;
Count++) {
ConnData->EventArray[Count] = ConnData->EventArray[Count + 1];
ConnData->OutReqArray[Count] = ConnData->OutReqArray[Count + 1];
}
ReturnValue = TRUE;

} else {

// WSAWaitForMultipleEvents returned an unexpected
// value...
wsprintf(MsgText, "WSAWaitForMultipleEvents() returned %d.",
WaitStatus);
MessageBox(GlobalFrameWindow, MsgText, "Error.",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
ReturnValue = FALSE;
}

return(ReturnValue);

} // HandleOtherEvent()





void
HandleAcceptMessage(
IN HWND ConnectionWindow,
IN SOCKET Socket,
IN LPARAM LParam)
/*++

Routine Description:

Handles the reception of a USMSG_ACCEPT message, which indicates a
connection attempt is incoming.

Implementation:

This function lets the user decide whether to accept the
connection; if he or she does, the function initializes
connection-specific data, calls WSAEventSelect to register
interest in certain network events, and starts a network event
handling thread.

Arguments:

ConnectionWindow -- Handle to the connection window associated
with this connection request.

Socket -- Contains the handle to the listening socket to which the
connection request has been made.

LParam -- The LParam that was delivered with the USMSG_ACCEPT
message; contains the error code.

Return Value:

None.

--*/
{

int Error; // gets error code if necessary
char MsgText[MSG_LEN]; // holds message strings
DWORD ThreadId; // needed for CreateThread
PCONNDATA ConnData; // connection-specific data

ConnData = GetConnData(ConnectionWindow);

Error = WSAGETSELECTERROR(LParam);

// Check to see if there was an error on the connection attempt.
if (Error) {

// Some kind of error occurred.
if (Error == WSAENETDOWN) {
MessageBox(ConnectionWindow,
"The network is down!", "Uh-oh",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
} else {
MessageBox(ConnectionWindow,
"Unknown error on FD_ACCEPT", "Error",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
}
goto Fail;
}

// Get a pointer to the associated protocol information structure
// for the socket we are listening on (Socket). This will be the
// same as the protocol info for the new socket. Note that we
// could have allocated a new buffer and called getsockopt with
// the SO_PROTOCOL_INFO option. But since we've already allocated
// the memory, this way is more efficient.
ConnData->ProtocolInfo = GetProtoFromSocket(Socket);
assert(ConnData->ProtocolInfo != NULL);

// Accept the connection (this calls AcceptCondFunc, of course,
// before actually accepting the connection).
{
struct sockaddr address;
int address_len;

address_len = sizeof (address);
ConnData->Socket = WSAAccept(Socket,
&address,
&address_len,
NULL,
(DWORD)NULL);
// ConnData->Socket = WSAAccept(Socket,
// NULL,
// NULL,
// AcceptCondFunc,
// (DWORD)ConnData);
}

if (ConnData->Socket == INVALID_SOCKET) {

// WSAAccept failed -- inform the user and that's all.
Error = WSAGetLastError();
if (Error != WSAECONNREFUSED) {

// An unexpected error code.
wsprintf(MsgText, "WSAAccept failed. Error code: %d",
Error);
MessageBox(ConnectionWindow, MsgText, "Error",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
goto Fail;

} else {

// AcceptCondFunc returned CF_REJECT...
MessageBox(GlobalFrameWindow,
"The connection attempt has been refused.",
"Connection refused.", MB_OK | MB_SETFOREGROUND);
goto Fail;
}
}

WSAAsyncSelect(ConnData->Socket,
GlobalFrameWindow,
0,
0);

// Put Connection in Event Object Notification Mode.
WSAEventSelect(ConnData->Socket,
ConnData->SocketEventObject,
FD_READ | FD_WRITE | FD_CLOSE);

// Determine the maximum message size, if any.
if (!GetMaxMsgSize(ConnData)) {
goto Fail;
}

// Start the I/O thread, and save the thread handle.
ConnData->IOThreadHandle =
CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)IOThreadFunc,
ConnData,
0,
&ThreadId);
if (ConnData->IOThreadHandle == NULL) {
ChatSysError("CreateThread()",
"HandleAcceptMessage()",
TRUE);
}

return;

Fail:

DestroyWindow(ConnectionWindow);
return;

} // HandleAcceptMessage()





void
HandleConnectMessage(
IN HWND ConnectionWindow,
IN LPARAM LParam)
/*++

Routine Description:

Handles the reception of a USMSG_CONNECT message, which indicates
a connection attempt is complete (though not necessarily
successful).

Implementation:

As with HandleAcceptMessages, this function initializes
connection-specific data, calls WSAEventSelect to register
interest in certain network events, and starts a network event
handling thread.

Arguments:

ConnectionWindow -- Handle to the connection window associated
with this connection request.

LParam -- Contains the LParam that was a parameter of the
USMSG_CONNECT message.

Return Value:

None.

--*/
{

PCONNDATA ConnData; // connection-specific data
char TitleText[TITLE_LEN]; // text buffer for window title
DWORD ThreadId; // needed for CreateThread()
int Error; // holds error codes

Error = WSAGETSELECTERROR(LParam);

// Check to see if there was an error on the connection attempt.
if (Error) {
char textBuf[80];
sprintf(textBuf, "Connect error code: %x", Error);
MessageBox(NULL, textBuf, NULL, MB_OK);

// Some kind of error occurred.
if (Error == WSAECONNREFUSED) {

MessageBox(ConnectionWindow,
"Your connection attempt has been refused",
"Connection Refused",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
} else {

MessageBox(ConnectionWindow,
"Couldn't connect",
"Error",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
}

goto Fail;

}

// Connection has been accepted. Change the title of the
// connection window to reflect who the user has connected to
ConnData = GetConnData(ConnectionWindow);

if (ConnData->CalleeBuffer.len != 0) {

// The callee buffer contains the connection-time data
// sent by the callee. -- a string containing the name of
// the callee.
wsprintf(TitleText, "Connected To: %s @ %s",
ConnData->CalleeBuffer.buf, ConnData->PeerAddress);
} else {

// ConnData->PeerAddress just contains the address that
// the user typed in before the connection attempt.
wsprintf(TitleText, "Connected To: %s", ConnData->PeerAddress);
}
SetWindowText(ConnectionWindow, TitleText);

WSAAsyncSelect(ConnData->Socket,
GlobalFrameWindow,
0,
0);

// Put Connection in Event Object Notification Mode.
WSAEventSelect(ConnData->Socket,
ConnData->SocketEventObject,
FD_READ | FD_WRITE | FD_CLOSE);

// Determine the maximum message size, if any.
if (!GetMaxMsgSize(ConnData)) {
goto Fail;
}

// Start the I/O thread, and save the thread handle.
ConnData->IOThreadHandle =
CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)IOThreadFunc,
ConnData,
0,
&ThreadId);
if (ConnData->IOThreadHandle == NULL) {
ChatSysError("CreateThread()",
"HandleConnectMessage()",
TRUE);
}

return;

Fail:

DestroyWindow(ConnectionWindow);
return;

} // HandleConnectMessage()





BOOL
GetMaxMsgSize(
IN OUT PCONNDATA ConnData)
/*++

Routine Description:

Determines the maximum message size (if any) of a connected
socket. The connection must already be established, i.e. the
socket must be bound to a local address, for this function to
work. Fills the correct value into a field of the
connection-specific data.

Arguments:

ConnData -- Connection data for a connected socket.

Return Value:

TRUE -- The maximum message size was succesfully determined and
stored in ConnData->MaxMsgSize.

FALSE -- There was an error calling getsockopt to get the maximum
message size.

--*/
{
BOOL ReturnValue = TRUE; // return value
int DwordLen = sizeof(DWORD); // sizeof a DWORD!
int Error; // return value of getsockopt

if ((ConnData->ProtocolInfo->dwMessageSize == 0) ||
(ConnData->ProtocolInfo->dwMessageSize == 0xffffffff)) {

// Either the protocol isn't message-oriented, or there is no
// maximum message size.
ConnData->MaxMsgSize = NO_MAX_MSG_SIZE;

} else {

// There is a maximum message size. Note it.
if (ConnData->MaxMsgSize == 0x1) {

// The actual maximum message size was not stored in the
// protocol information structure -- rather, we need to
// get it using getsockopt().
Error = getsockopt(ConnData->Socket,
SOL_SOCKET,
SO_MAX_MSG_SIZE,
(char *)&ConnData->MaxMsgSize,
&DwordLen);
if (Error) {
ReturnValue = FALSE;
}

} // if (ConnData->MaxMsgSize == 0x1)
else {

// The message size is stored in the protocol information
// structure. Use it.
ConnData->MaxMsgSize = ConnData->ProtocolInfo->dwMessageSize;
}


} // else

return(ReturnValue);

} // GetMaxMsgSize()





BOOL
IsSendable(
char Char)
/*++

Routine Description:

Determines whether a certain character value is ok to send over
the socket to the far end. Eliminates most non-printable
characters except for newline and backspace.

Arguments:

Char -- Supplies the character to be tested.

Return Value:

TRUE -- The character should be sent as is.

FALSE -- The character should not be sent.

--*/
{

if (isprint(Char) || (Char == '\b') ||
(Char == '\r') || (Char == '\t') ||
(Char == '\n') ){
return(TRUE);
} else {
return(FALSE);
}

} // IsSendable()





int
DoRecv(
IN PCONNDATA ConnData)

/*++

Routine Description:

Receives as much available data as possible up to the size of the
receive buffer-1 (BUFFER_LENGTH-1). A single byte is reserved at
the end of the receive buffer to append a terminating NULL.

Arguments:

ConnData -- Points to the data for the connection that is ready to
receive data.

Return Value:

CHAT_OK -- Received data was successfully sent to the receive edit
control.

CHAT_ERROR -- Error receiving data.

CHAT_CLOSED -- The socket was gracefully closed.

--*/

{
DWORD NumBytes; // stores how many bytes we received
int Error; // gets error values
int Result; // gets return value from WSARecv
char Buf[BUFFER_LENGTH]; // buffer to receive data
int ReturnValue = CHAT_OK; // returnValue
WSABUF RecvBuffer; // WSABuf to pass to WSARecv
DWORD Flags; // flags for WSARecv

RecvBuffer.buf = Buf;
RecvBuffer.len = BUFFER_LENGTH - 1;

{
int socket_type;

socket_type = ConnData->ProtocolInfo->iSocketType;
if ((socket_type == SOCK_DGRAM) ||
(socket_type == SOCK_RDM) ||
(socket_type == SOCK_SEQPACKET)) {
Flags = MSG_PARTIAL;
// Allow message-oriented delivery to be received within
// our preferred buffer length without losing the trailing
// part of longer messages.
}
else {
Flags = 0;
// The MSG_PARTIAL flag is not required and may not be
// allowed for stream-oriented protocols.
}
}

// Do the receive
Result = WSARecv(ConnData->Socket,
&RecvBuffer,
1,
&NumBytes,
&Flags,
NULL,
NULL);

// Check for errors.
if (Result == SOCKET_ERROR) {

Error = WSAGetLastError();

switch (Error) {

case WSAENETRESET: // flow through
case WSAECONNRESET:

// The remote party has reset the connection.
ReturnValue = CHAT_CLOSED;
goto Done;

case WSAEWOULDBLOCK:

// No data received; return to wait for another read event.
ReturnValue = CHAT_OK;

default:

// Some other error...hit the panic button.
ReturnValue = CHAT_ERROR;
goto Done;
}

}

// Append a NULL to the text buffer.
RecvBuffer.buf[NumBytes] = '\0';

// Output the received text into the receive edit control.
OutputString(ConnData->RecvWindow, RecvBuffer.buf);

Done:
return(ReturnValue);

} // DoRecv()





int
DoOverlappedCallbackSend(
IN POUTPUT_REQUEST OutReq,
IN PCONNDATA ConnData)

/*++

Routine Description:

Sends a buffer of data over a connected socket using overlapped
I/O with callback function completion notification.

Implementation:

This function just does an overlapped send, giving WinSock 2 a
completion function which will be called when the operation has
finished, and within which cleanup will occur.

Arguments:

OutReq -- Points to a OUTPUT_REQUEST structure, which specifies an
output request.

ConnData -- Supplies a pointer to a CONNDATA structure, which
identifies a Chat connection.

Return Value:

CHAT_WOULD_BLOCK -- The send operation failed with WSAEWOULDBLOCK.

CHAT_ERROR -- A unexpected error occurred.

CHAT_OK -- The send was successfully initiated. WinSock 2 will
execute the callback function when the send has completed.

--*/

{
int Size = 0; // how many bytes we send
int Error; // return value of WSASend
int Errno; // result of WSAGetLastError
LPWSABUF Buffers; // points to an array of WSABUFs
DWORD BytesSent; // needed in WSASend
int ReturnValue; // the return value

ReturnValue = CHAT_OK;

// Allocate an OVERLAPPED structure.
OutReq->Overlapped = (LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED));
if (OutReq->Overlapped == NULL) {
ChatSysError("malloc()",
"DoOverlappedCallbackSend()",
TRUE);
}
Buffers = &OutReq->Buffer;

// The hEvent field of the Overlapped structure is not used
// for callback notification...thus we can use it any way we
// want to pass information to the callback routine. We use
// it to store a pointer to the OutReq associated with this
// overlapped send operation, so the callback function can
// free the memory.
OutReq->Overlapped->hEvent = (WSAEVENT)OutReq;

Error = WSASend(ConnData->Socket,
Buffers,
1,
&BytesSent,
0,
OutReq->Overlapped,
SendCompFunc);

if (Error == SOCKET_ERROR) {

// There is an error...
Errno = WSAGetLastError();
if (Errno == WSAEWOULDBLOCK) {


// WSAEWOULDBLOCK means we have to wait for an FD_WRITE
// before we can send.
ConnData->WriteOk = FALSE;
ReturnValue = CHAT_WOULD_BLOCK;

} else if (Errno == WSA_IO_PENDING) {

// Overlapped send successfully initiated.
ReturnValue = CHAT_OK;
}
else {

// An unexpected error occurred.
ReturnValue = CHAT_ERROR;
}

}

// No error -- the I/O request was completed immediately...
return(ReturnValue);

} // DoOverlappedCallbackSend()





int
DoOverlappedEventSend(
IN POUTPUT_REQUEST OutReq,
IN PCONNDATA ConnData)

/*++

Routine Description:

Sends a buffer of data over a connected socket using overlapped
I/O with event notification.

Implementation:

If the overlapped send is succesfully initiated, this function
increases the event count and adds an entry to the event array of
the associated connection. During IOThreadFunc, the thread will
thus wait on the new event as well, and will perform the
appropriate cleanup action when awoken.

Arguments:

OutReq -- Points to a OUTPUT_REQUEST structure, which specifies an
output request.

ConnData -- Supplies a pointer to a CONNDATA structure, which
identifies a Chat connection.

Return Value:

CHAT_WOULD_BLOCK -- The send operation failed with WSAEWOULDBLOCK.

CHAT_ERROR -- A unexpected error occurred.

CHAT_OK -- The send was successfully initiated and the new event
placed in the event array; it will be signaled by WinSock 2 when
the send operation has completed.

--*/

{
int Size = 0; // how many bytes we send
int Error; // return value of WSASend
int Errno; // result of WSAGetLastError
LPWSABUF Buffers; // points to an array of WSABUFs
DWORD BytesSent; // needed in WSASend
int ReturnValue; // the return value

ReturnValue = CHAT_OK;

// Allocate an OVERLAPPED structure.

// Note that the pointer to an overlapped structure is kept in
// OutReq. We use an array of OutReq pointers that is parallel to
// the event array. With that one pointer, then, we can find the
// associated Overlapped structure and free it, and then free the
// OutReq structure itself.
OutReq->Overlapped = (LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED));
if (OutReq->Overlapped == NULL) {
ChatSysError("malloc()",
"DoOverlappedEventSend()",
TRUE);
}
Buffers = &OutReq->Buffer;

OutReq->Overlapped->hEvent =
(WSAEVENT)CreateEvent(NULL,
FALSE,
FALSE,
NULL);
if (OutReq->Overlapped->hEvent == NULL) {
ChatSysError("CreateEvent()",
"DoOverlappedEventSend()",
TRUE);
}

// Do the send.
Error = WSASend(ConnData->Socket,
Buffers,
1,
&BytesSent,
0,
OutReq->Overlapped,
NULL);

if (Error == SOCKET_ERROR) {

// There is an error...
Errno = WSAGetLastError();
if (Errno == WSAEWOULDBLOCK) {

// WSAEWOULDBLOCK means we have to wait for an FD_WRITE
// before we can send.
ConnData->WriteOk = FALSE;
ReturnValue = CHAT_WOULD_BLOCK;

} else if (Errno == WSA_IO_PENDING) {

// Overlapped send successfully initiated.
// Increase the event count, and update the event and
// output request arrays to hold the entries for this
// overlapped send.
ConnData->NumEvents++;
if (ConnData->NumEvents > WSA_MAXIMUM_WAIT_EVENTS) {

// We're trying to wait on too many events at
// once. It's very unlikely this could happen --
// you'd have to get about 62 overlapped sends, in
// row, before one of them is completed and
// signaled. So just return CHAT_ERROR which will
// break the connection.
ReturnValue = CHAT_ERROR;

} else {

ConnData->EventArray[ConnData->NumEvents - 1] =
OutReq->Overlapped->hEvent;

ConnData->OutReqArray[ConnData->NumEvents - 1] = OutReq;
ReturnValue = CHAT_OK;
}
} // else if (...)
else {

// An unexpected error occurred.
ReturnValue = CHAT_ERROR;
}

} // if (Error == SOCKET_ERROR)

// No error -- the I/O request was completed immediately...
ReturnValue = CHAT_OK;

return(ReturnValue);

} // DoOverlappedEventSend()





int
DoSend(
IN POUTPUT_REQUEST OutReq,
IN PCONNDATA ConnData)

/*++

Routine Description:

Sends a buffer of data over a connected socket.

Implementation:

This function uses the information contained in the output request
structure to determine whether to use overlapped or non-overlapped
I/O. If using overlapped I/O, it calls an appropriate function
depending on whether the symbol CALLBACK_NOTIFICATION has been
defined. Otherwise, the non-overlapped send in performed
immediately.

Arguments:

OutReq -- Points to a OUTPUT_REQUEST structure, which specifies an
output request.

ConnData -- Supplies a pointer to a CONNDATA structure, which
identifies a Chat connection.

Return Value:

CHAT_WOULD_BLOCK -- The send operation failed with WSAEWOULDBLOCK.

CHAT_ERROR -- A unexpected error occurred.

CHAT_OK -- The send was successfully initiated. If the send was
overlapped, either a callback function is forthcoming or another
event was placed in the ConnData->EventArray for IOThreadFunc to
wait on until it's signaled by WinSock 2.

--*/

{
int Size = 0; // how many bytes we send
int Error; // return value of WSASend
int Errno; // result of WSAGetLastError
WSABUF Buffers[1]; // points to the data to be sent
DWORD BytesSent; // needed in WSASend
int ReturnValue; // the return value

if (OutReq->Type == NON_OVERLAPPED_IO) {

// Do a non-overlapped send by setting lpOverlapped and
// lpCompletionRoutine to NULL.
Buffers[0].len = OutReq->Buffer.len;
Buffers[0].buf = OutReq->Buffer.buf;
Error = WSASend(ConnData->Socket,
Buffers,
1,
&BytesSent,
0,
NULL,
NULL);

if (Error == SOCKET_ERROR) {

// There's an error...
Errno = WSAGetLastError();
if (Errno == WSAEWOULDBLOCK) {

// WSAEWOULDBLOCK means we have to wait for an FD_WRITE
// before we can send.
ConnData->WriteOk = FALSE;
ReturnValue = CHAT_WOULD_BLOCK;

} else {

// It's an unexpected error.
ReturnValue = CHAT_ERROR;
}
} else {

// There's no error.
ReturnValue = CHAT_OK;
free(OutReq);
}

} else if (OutReq->Type == OVERLAPPED_IO) {

#ifdef CALLBACK_NOTIFICATION

ReturnValue = DoOverlappedCallbackSend(OutReq, ConnData);

#else

ReturnValue = DoOverlappedEventSend(OutReq, ConnData);

#endif

} else {

// Unknown output type...
MessageBox(ConnData->ConnectionWindow,
"Unknown output type given to IOThread. Aborting.",
"Error.", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
ReturnValue = CHAT_ERROR;
}

return(ReturnValue);

} // DoSend()





void CALLBACK
SendCompFunc(
IN DWORD Error,
IN DWORD BytesTransferred,
IN LPWSAOVERLAPPED Overlapped,
IN DWORD Flags)
/*++

Routine Description:

Completion routine called after a successfully initiated overlapped
send operation completes; cleans up data structures associated
with the send.

Arguments:

Error -- Supplies the completion status for the overlapped
operation.

BytesTransferred -- Supplies the actual number of bytes sent.

Overlapped -- Supplies a pointer to a WSAOVERLAPPED structure.
The hEvent field in the structure contains a pointer to the
OUTPUT_REQUEST structure associated with this send operation.

Flags -- Not yet used.

Return Value:

None

--*/

{
POUTPUT_REQUEST OutReq; // The output request

OutReq = (POUTPUT_REQUEST)(Overlapped->hEvent);

if (Error) {
MessageBox(NULL,
"Error during overlapped send.",
"Error",
MB_OK | MB_SETFOREGROUND);
}

// Free the data buffer, the output request structure, and the
// overlapped I/O structure.
free(OutReq->Buffer.buf);
free(OutReq);
free(Overlapped);

} // SendCompFunc()





PCONNDATA
GetConnData(
IN HWND ConnectionWindow
)

/*++

Routine Description:

Retrieves a pointer to the PCONNDATA data structure associated
with a connection window

Arguments:

ConnectionWindow - Supplies the handle to a connection window.

Return Value:

Returns the PCONNDATA associated with the given connection window
handle.

--*/

{
return((PCONNDATA)GetWindowLong(ConnectionWindow, GWL_CONNINFO));

} // GetConnData()





BOOL
MakeConnection(
IN HWND ConnectionWindow)

/* ++

Routine Description:

Initiates a call to another instance of chat.
1) Creates a socket
2) Set the socket up for windows message notification of FD_CONNECT
events.
3) Allocate a buffer for caller name & subject, and fill it in.
4) Allocate a buffer to receive callee name.
5) Set up quality of service for the connection.
6) Attempt a connection

Arguments:

ConnectionWindow - Handle to the window that receives
notification when an FD_CONNECT event occurs.

Return Value:

TRUE - A connection attempt was successfully initiated.

FALSE - Error occurred while attempting to initiate a connection.

--*/

{
int ConnectStatus; // the return value of WSAConnect
int Error; // gets error values
BOOL ReturnValue; // holds the return value
PCONNDATA ConnData; // connection-specific data
LPWSABUF CallerBuffer; // user data we will send
LPWSABUF CalleeBuffer; // user data we will receive
QOS QualityOfService; // QOS structure, used by WSAConnect
LPFLOWSPEC FlowSpec; // dummy pointer for code readability
char MsgText[MSG_LEN]; // holds message strings
BOOL QOSSupported; // does the protocol support QOS?
BOOL CTDTSupported; // support for conn-time data xfer?
struct sockaddr *SockAddr; // socket address for WSAConnect
int SockAddrLen; // the length of the above


ReturnValue = TRUE;
ConnData = GetConnData(ConnectionWindow);
QOSSupported = (ConnData->ProtocolInfo->dwServiceFlags1 &
XP1_QOS_SUPPORTED);
CTDTSupported = (ConnData->ProtocolInfo->dwServiceFlags1 &
XP1_CONNECT_DATA);
if (CTDTSupported) {

// CallerBuffer and CallerBuffer->buf (allocated within the
// NameAndSubject dialog box procedure) both get freed by the
// end of this function. CalleeBuffer.buf gets freed upon
// connection shutdown -- it needs to be valid after this
// function has exited.
CallerBuffer = (LPWSABUF)malloc(sizeof(WSABUF));
if (CallerBuffer == NULL) {
ChatSysError("malloc()",
"MakeConnection()",
TRUE);
}
CalleeBuffer = &ConnData->CalleeBuffer;

// The connection-time data sent back when/if this connection
// is accepted will just contain the callee's name.
CalleeBuffer->len = NAME_LEN + 1;
CalleeBuffer->buf = (char *)malloc(CalleeBuffer->len);
if (CallerBuffer->buf == NULL) {
ChatSysError("malloc()",
"MakeConnection()",
TRUE);
}

// Prompt the user for his name and the subject of the call.
// a pointer to the CallerBuffer is passed to the dialog box
// procedure and the data is packed into this memory.
if (!DialogBoxParam(GlobalInstance,
"NameAndSubjectDlg",
ConnectionWindow,
NameAndSubjectDlgProc,
(LPARAM)CallerBuffer)) {
ReturnValue = FALSE;
goto Done;
}
} // if (CTDTSupported)

// Create a socket for this connection.
ConnData->Socket = WSASocket(FROM_PROTOCOL_INFO,
FROM_PROTOCOL_INFO,
FROM_PROTOCOL_INFO,
ConnData->ProtocolInfo,
0,
WSA_FLAG_OVERLAPPED);

if (ConnData->Socket == INVALID_SOCKET) {

MessageBox(ConnectionWindow, "Could not open a socket.", "Error",
MB_OK | MB_SETFOREGROUND);
ReturnValue = FALSE;
goto Done;

} else {

// Set up socket for windows message event notification.
WSAAsyncSelect(ConnData->Socket,
ConnectionWindow,
USMSG_CONNECT,
FD_CONNECT);
}

if (QOSSupported) {

// Set up quality of service info we want. Do not include any
// provider-specific information.
QualityOfService.ProviderSpecific.len = 0;
QualityOfService.ProviderSpecific.buf = NULL;

// FlowSpec is used only to make this code more readable.
FlowSpec = & QualityOfService.SendingFlowspec;
FlowSpec->TokenRate = TOKENRATE;
FlowSpec->TokenBucketSize = QOS_UNSPECIFIED;
FlowSpec->PeakBandwidth = QOS_UNSPECIFIED;
FlowSpec->Latency = QOS_UNSPECIFIED;
FlowSpec->DelayVariation = QOS_UNSPECIFIED;
FlowSpec->ServiceType = SERVICETYPE_BESTEFFORT;

// again, the extra FlowSpec variable is for readability
FlowSpec = & QualityOfService.ReceivingFlowspec;
FlowSpec->TokenRate = TOKENRATE;
FlowSpec->TokenBucketSize = QOS_UNSPECIFIED;
FlowSpec->PeakBandwidth = QOS_UNSPECIFIED;
FlowSpec->Latency = QOS_UNSPECIFIED;
FlowSpec->DelayVariation = QOS_UNSPECIFIED;
FlowSpec->ServiceType = SERVICETYPE_BESTEFFORT;
}

// Reminder: WinSock 2 requires that we pass a struct sockaddr *
// to WSAConnect; however, the service provider is free to
// interpret the pointer as an arbitrary chunk of data of size
// SockAddrLen.
SockAddr = (struct sockaddr *)ConnData->RemoteSockAddr.buf;
SockAddrLen = ConnData->RemoteSockAddr.len;


// Try to connect; depending on whether QOS or Connection Time
// Data Transfer are supported, pass the appropriate data.
if (QOSSupported && CTDTSupported) {

// Both are supported...
ConnectStatus = WSAConnect(ConnData->Socket,
SockAddr,
SockAddrLen,
CallerBuffer,
CalleeBuffer,
&QualityOfService,
NULL);
} else if (!QOSSupported && CTDTSupported) {

// Only CTDT is supported...
ConnectStatus = WSAConnect(ConnData->Socket,
SockAddr,
SockAddrLen,
CallerBuffer,
CalleeBuffer,
NULL,
NULL);

} else if (QOSSupported && !CTDTSupported) {
// Only QOS is supported... case for ATM
ConnectStatus = WSAConnect(ConnData->Socket,
SockAddr,
SockAddrLen,
NULL,
NULL,
&QualityOfService,
NULL);
} else {

// Neither is supported... case for TCP
ConnectStatus = WSAConnect(ConnData->Socket,
SockAddr,
SockAddrLen,
NULL,
NULL,
NULL,
NULL);
}

// Check for errors.
if (ConnectStatus == SOCKET_ERROR) {
Error = WSAGetLastError();
if (Error != WSAEWOULDBLOCK) {
wsprintf(MsgText, "WSAConnect failed. Error code: %d",
Error);
MessageBox(ConnectionWindow, MsgText, "Error",
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
ReturnValue = FALSE;
}
} else {
MessageBox(ConnectionWindow,
"WSAConnect should have returned SOCKET_ERROR.",
"Error", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
ReturnValue = FALSE;
}

Done:
// Free allocated memory, except CalleeBuffer which will be
// freed when connection window is destroyed.
if (CTDTSupported) {
free(CallerBuffer->buf);
free(CallerBuffer);
}
return(ReturnValue);
} // MakeConnection()





BOOL
FillInFamilies(
IN HWND DialogWindow,
IN DWORD FamilyLB)
/*++

Routine Description:

Fills in the listbox with a string for each protocol on which Chat
has a listening socket.

Implementation:

For each socket in the ListeningSockets array, this function:

1. Gets the associated protocol information structure.

2. If Chat recognizes the address family, it prints it in a
string; if not, it prints "unknown".

3. In the second half of the string, prints out the
human-readable string contained in the protocol information.

4. Sends the string to be an entry in the listbox identified
by FamilyLB.

Arguments:

DialogWindow -- Window handle for the dialog box.

FamilyLB -- Integer identifier for a listbox in the dialog box.

Return Value:

TRUE - All messages were successfully sent to the listbox.

FALSE - An LB_ADDSTRING message failed.

--*/

{

int i; // counting variable
LRESULT Result; // result of SendMessage calls
char LBString[MSG_LEN]; // string to send to the listbox
int Offset; // index into the string

// Iterate through all members of the ListeningSockets array.
for (i = 0; i < NumFound; i++){

// We are prepending a number to the list box string to be displayed
// since the list box does us the favor of alphabetizing the
// the sting and we are depending on the list entries appearing in the
// list box in the order that we added them to the list.

Offset = wsprintf(LBString, "%02i ",i);

switch (ListeningSockets[i].ProtocolInfo->iAddressFamily) {

case AF_INET:

Offset += wsprintf(LBString+Offset, "INET/");
wsprintf(LBString + Offset, "%s",
ListeningSockets[i].ProtocolInfo->szProtocol);
Result = SendMessage(GetDlgItem(DialogWindow, FamilyLB),
LB_ADDSTRING, 0, (LPARAM)LBString);
break;

case AF_ATM:
Offset += wsprintf(LBString+Offset, "ATM/");
wsprintf(LBString + Offset, "%s",
ListeningSockets[i].ProtocolInfo->szProtocol);
Result = SendMessage(GetDlgItem(DialogWindow, FamilyLB),
LB_ADDSTRING, 0, (LPARAM)LBString);
break;

case AF_IPX:
Offset += wsprintf(LBString+Offset, "IPX/");
wsprintf(LBString + Offset, "%s",
ListeningSockets[i].ProtocolInfo->szProtocol);
Result = SendMessage(GetDlgItem(DialogWindow, FamilyLB),
LB_ADDSTRING, 0, (LPARAM)LBString);
break;

case AF_NETBIOS:
Offset += wsprintf(LBString+Offset, "NETBIOS/");
wsprintf(LBString + Offset, "%s",
ListeningSockets[i].ProtocolInfo->szProtocol);
Result = SendMessage(GetDlgItem(DialogWindow, FamilyLB),
LB_ADDSTRING, 0, (LPARAM)LBString);
break;
default:

Offset += wsprintf(LBString+ Offset, "(unknown)/");
wsprintf(LBString + Offset, "%s",
ListeningSockets[i].ProtocolInfo->szProtocol);
Result = SendMessage(GetDlgItem(DialogWindow, FamilyLB),
LB_ADDSTRING, 0, (LPARAM)LBString);
break;

}
if ((Result == LB_ERR) || (Result == LB_ERRSPACE)) {
return(FALSE);
}
}
return(TRUE);

} // FillInFamilies()





LPWSAPROTOCOL_INFO
GetProtoFromIndex(
IN int LBIndex)
/*++

Routine Description:

Takes an index into the ChooseFamily dialog box and returns the
protocol associated with that index. This works because when the
dialog box is set up, the strings representing the protocols are
added to the listbox in the order they fall in ListeningSockets.

Arguments:

LBIndex -- The index of the user's selection.

Return Value:

NULL -- The index doesn't correspond to an element in the
ListeningSockets array.

LPWSAPROTOCOL_INFO -- A pointer to the protocol information struct
corresponding to LBIndex.

--*/
{
if (LBIndex > (NumFound - 1)) {
return(NULL);
}
return(ListeningSockets[LBIndex].ProtocolInfo);

} // GetProtoFromIndex()





LPWSAPROTOCOL_INFO
GetProtoFromSocket(
IN SOCKET Socket)
/*++

Routine Description:

Searches the ListeningSockets array for the first entry with a
Socket field equal to the Socket parameter. When found, it
returns a pointer to the associated protocol information
structure. If not found, return NULL.

Arguments:

Socket -- The socket who's associated WSAPROTOCOL_INFO struct we are
looking for.

Return Value:

NULL -- The socket was not found in the ListeningSockets array.

LPWSAPROTOCOL_INFO -- A pointer to the protocol information struct
for the socket.

--*/
{

int i; // counting variable

for (i = 0; i < NumFound; i++) {
if (ListeningSockets[i].Socket == Socket) {
return(ListeningSockets[i].ProtocolInfo);
}
}
return(NULL);

} // GetProtoFromSocket()





void
CleanupConnection(
IN PCONNDATA ConnData)
/*++

Routine Description:

Frees all memory and objects allocated for this connection.

Arguments:

ConnData -- Pointer to the connection-specific data structure.

Return Value:

None.

--*/
{

// Clean up connection-specific data. To keep this code
// readable, we ignore any errors.
if (ConnData->SocketEventObject != NULL) {
CloseHandle(ConnData->SocketEventObject);
ConnData->SocketEventObject = NULL;
}
if (ConnData->OutputEventObject != NULL) {
CloseHandle(ConnData->OutputEventObject);
ConnData->OutputEventObject = NULL;
}
if (ConnData->OutputQueue != NULL) {
QFree(ConnData->OutputQueue);
ConnData->OutputQueue = NULL;
}
if (ConnData->IOThreadHandle != NULL) {
CloseHandle(ConnData->IOThreadHandle);
ConnData->IOThreadHandle = NULL;
}
if (ConnData->Socket != INVALID_SOCKET) {
closesocket(ConnData->Socket);
}
if (ConnData->RemoteSockAddr.buf != NULL) {
free(ConnData->RemoteSockAddr.buf);
ConnData->RemoteSockAddr.buf = NULL;
}
if (ConnData->CalleeBuffer.buf != NULL) {
free(ConnData->CalleeBuffer.buf);
ConnData->CalleeBuffer.buf = NULL;
}
free(ConnData);

} // CleanupConnection()