Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

926 lines
24 KiB

/* --------------------------------------------------------------------
File : spxclnt.c
Title : client loadable transport for DOS SPX
Description :
History :
6-26-91 Jim Teague Initial version.
3-04-92 Jim Teague Cloned from Windows for NT.
4-13-93 Alex Mitchell Ifdefed to support both TCP and SPX winsock.
4-22-93 Alex Mitchell Copied and modified to support DOS SPX without
winsock.
5-10-94 Von Jones Changed a call from I_RpcAllocate to
I_RpcRegisteredBufferAllocate and another
from I_RpcFree to I_RpcRegisteredBufferFree
to indicate buffers which are registered
to SPX. This change allows the Microsoft
Exchange DOS Client's Shell-to-DOS function to
recognize these buffers and avoid swapping them.
-------------------------------------------------------------------- */
#include "sysinc.h"
#include <stdlib.h>
#include "rpc.h"
#include "rpcdcep.h"
#include "regalloc.h"
#include "rpctran.h"
#include "rpcerrp.h"
#include "dos.h"
#include "novell.h"
#include "gethost.h"
/********************************************************************/
/* Defines. */
#ifdef DBG
#define OPTIONAL_STATIC
#else
#define OPTIONAL_STATIC static
#endif
#define NO_CONNECTION 0xff
// Zero indicates that SPX should choose the retry count.
#define RETRY_COUNT 0
#define MAX_CONN 16
#define NUM_SPX_BUF 5
#define ENDIAN_MASK 16
#define ENDPOINT_MAPPER_EP "34280"
/* bugbug - This should be from some global header file. */
#define TRANSPORTID 0x0c
#define TRANSPORTHOSTID 0x0d
#define TOWERFLOORS 5
/*Endpoint = 2 bytes, HostId = 10 bytes*/
#define TOWEREPSIZE 10
#define TOWERSIZE (TOWEREPSIZE+2)
#define PROTSEQ "ncacn_spx"
/* The minimum size RPC will allow for the MaximumPacketSize field of
TransInfo. */
#define RPC_MIN_MAX_PACKET 2048
//
// Slop space at end of receive buffer.
//
#define RECEIVE_PAD 2
/********************************************************************/
/* Structures. */
typedef enum RECEIVE_EN
{
first_r,
next_r,
last_r,
fail_r
} RECEIVE_EN;
#pragma pack(1)
typedef struct _FLOOR_234 {
unsigned short ProtocolIdByteCount;
unsigned char FloorId;
unsigned short AddressByteCount;
unsigned char Data[2];
} FLOOR_234, PAPI * PFLOOR_234;
#pragma pack()
typedef struct CONTROL_BLOCK
{
// The ecb field must be the first field in this structure.
ECB ecb;
SPX_HEADER spx;
int saved_ds;
} CONTROL_BLOCK;
typedef struct BUFFER
{
CONTROL_BLOCK header;
char data[1];
} BUFFER;
typedef struct
{
WORD conn;
volatile int closed;
CONTROL_BLOCK send[1];
} CONNECTION, *PCONNECTION;
typedef struct CONN_IDS
{
CONNECTION *rpc;
WORD spx;
} CONN_IDS;
typedef struct
{
unsigned char rpc_vers;
unsigned char rpc_vers_minor;
unsigned char PTYPE;
unsigned char pfc_flags;
unsigned char drep[4];
unsigned short frag_length;
unsigned short auth_length;
unsigned long call_id;
} message_header;
/********************************************************************/
/* Macros. */
#define NEXTFLOOR(t,x) (t)((unsigned char PAPI *)x +((t)x)->ProtocolIdByteCount\
+ ((t)x)->AddressByteCount\
+ sizeof(((t)x)->ProtocolIdByteCount)\
+ sizeof(((t)x)->AddressByteCount))
#define WAIT_TILL(cond) while (!(cond)) IPXRelinquishControl();
#define MIN( x, y ) ( (x) < (y) ? (x) : (y) )
/********************************************************************/
/* Globals. */
/* The maximum buffer size to transmit and receive from SPX. */
int packet_size;
/* The number of pieces to break a RPC packet into before giving it
to SPX. */
int max_num_send;
/* This array is used to find the CONNECTION block associated with a
SPX connection id when a connection closes asyncronously. This
structure cannot be used for anything else since a SPX connection
id can be reused after the connection closes. */
CONN_IDS conn_lookup[MAX_CONN];
/* Socket is the id of the socket used for all connections. */
WORD socket;
/* These globals are shared between ClientRecv and RecvESR. */
volatile RECEIVE_EN receive_state;
BUFFER *volatile receive_head; /* Full buffer list from ESR. */
BUFFER *volatile receive_tail; /* Full buffer list from ESR. */
/* Debugging variables. Filled in by ReceiveESR. */
volatile BYTE receive_code;
volatile WORD receive_fragmentCount;
volatile WORD receive_fragmentSize;
volatile WORD receive_spx_length; /* High - low. */
volatile BYTE receive_data;
volatile BYTE send_data;
volatile unsigned char receive_seq;
volatile unsigned char send_seq;
volatile int receive_failed_cnt;
volatile int conn_failed_cnt;
volatile int nfy_failure_cnt;
/* Memory allocated at initialization that must be freed on exit. */
void *chunk;
/********************************************************************/
/* Prototypes. */
OPTIONAL_STATIC void InitializeCB ( CONTROL_BLOCK *, void *, int );
OPTIONAL_STATIC void ReceiveESR ( void );
OPTIONAL_STATIC void my_itoa ( int, char * );
int __loadds ClientCleanup ( void );
/********************************************************************/
// This routine converts a two byte integer to an ascii string.
OPTIONAL_STATIC void my_itoa( int i, char * s )
{
int j = 0;
int k;
int d = 10000;
// Is the number zero?
if (i == 0)
s[j++] = '0';
else
{
// If the number negative?
if (i < 0)
{
s[j++] = '-';
i = -i;
}
// Skip leading zeros.
while (i < d)
d = d / 10;
// Insert digits.
while (d > 0)
{
k = i / d;
s[j++] = k + '0';
i = i - k*d;
d = d / 10;
}
}
s[j] = '\0';
return;
}
/********************************************************************/
OPTIONAL_STATIC void InitializeCB( CONTROL_BLOCK *cb, void *esr, int num )
{
/* Zero the control block. */
memset( cb, 0, sizeof(*cb) );
/* Initialize some fields. */
cb->ecb.socketNumber = socket;
cb->ecb.ESRAddress = esr;
cb->ecb.fragmentCount = num;
cb->ecb.fragmentDescriptor[0].size = sizeof( cb->spx );
cb->ecb.fragmentDescriptor[0].address = &cb->spx;
cb->ecb.fragmentDescriptor[1].size = packet_size;
/* Save DS. */
__asm
{
les bx, cb
mov es:[bx].saved_ds, ds
}
}
/********************************************************************/
RPC_STATUS RPC_ENTRY
ClientOpen (
IN RPC_TRANSPORT_CONNECTION ConnArg,
IN RPC_CHAR * NetworkAddress,
IN RPC_CHAR * Endpoint,
IN RPC_CHAR * NetworkOptions,
IN RPC_CHAR * TransportAddress,
IN RPC_CHAR * RpcProtocolSequence,
IN unsigned int Timeout
)
// Open a client connection
{
PCONNECTION pConn = ConnArg;
int status;
unsigned int length;
int i;
int index;
UNUSED(NetworkOptions);
UNUSED(TransportAddress);
UNUSED(RpcProtocolSequence);
UNUSED(Timeout);
// Verify the NetworkAddress and Endpoint.
length = strlen(Endpoint);
if (length <= 0 || length > 5 || length != strspn( Endpoint, "0123456789" ))
return( RPC_S_INVALID_ENDPOINT_FORMAT );
// Find an entry in the connection association table.
for (index = 0; index < MAX_CONN; index++)
if (conn_lookup[index].rpc == NULL)
break;
if (index >= MAX_CONN)
return RPC_S_OUT_OF_RESOURCES;
// Initialize the Connection structure.
pConn->closed = FALSE;
for (i = 0; i < max_num_send; i++)
InitializeCB( &pConn->send[i], NULL, 2 );
// Convert the network address.
memset((char *)&pConn->send[0].spx.ipx.Destination, 0,
sizeof (pConn->send[0].spx.ipx.Destination));
retry:
status = IpxGetHostByName( NetworkAddress, &pConn->send[0].spx.ipx.Destination,
Endpoint, Timeout );
if (status != 0)
return status;
// Establish the connection.
pConn->send[0].ecb.fragmentCount = 1;
status = SPXEstablishConnection( RETRY_COUNT, TRUE, &pConn->conn,
&pConn->send[0].ecb );
if (status != 0)
return RPC_S_OUT_OF_RESOURCES;
// Wait for connection establishment.
WAIT_TILL( pConn->send[0].ecb.inUseFlag == 0 );
if (pConn->send[0].ecb.completionCode != 0)
{
if (TRUE == CachedServerNotContacted(NetworkAddress))
{
goto retry;
}
return RPC_S_SERVER_UNAVAILABLE;
}
CachedServerContacted(NetworkAddress);
pConn->send[0].ecb.fragmentCount = 2;
// Save the SPX connection vs RPC connection association.
conn_lookup[index].rpc = pConn;
conn_lookup[index].spx = pConn->conn;
return (RPC_S_OK);
}
/********************************************************************/
RPC_STATUS RPC_ENTRY
ClientClose (
IN RPC_TRANSPORT_CONNECTION ConnArg
)
// Close a client connection
{
PCONNECTION pConn = ConnArg;
int i;
CONTROL_BLOCK *cb;
// Clear the connection association.
for (i = 0; i < MAX_CONN; i++)
if (conn_lookup[i].rpc == pConn)
{
conn_lookup[i].rpc = NULL;
conn_lookup[i].spx = NO_CONNECTION;
break;
}
// If the connection has been terminated asynchronously, just return.
if (pConn->closed)
return RPC_S_OK;
// Disconnect.
cb = &pConn->send[0];
cb->ecb.fragmentCount = 1;
SPXTerminateConnection( pConn->conn, &cb->ecb );
// Wait for the disconnect to complete.
WAIT_TILL( cb->ecb.inUseFlag == 0 );
return (RPC_S_OK);
}
/********************************************************************/
RPC_STATUS RPC_ENTRY
ClientSend (
IN RPC_TRANSPORT_CONNECTION ConnArg,
IN void PAPI * Buffer,
IN unsigned int BufferLength
)
// Write a message to a connection. This operation is retried in case
// the server is "busy".
{
PCONNECTION pConn = ConnArg;
int i;
CONTROL_BLOCK *cb;
int fragments;
// If the connection closed asynchronously, fail.
if (pConn->closed)
{
ClientClose( pConn );
return RPC_P_SEND_FAILED;
}
// Send the data as up to max_num_send fragments.
fragments = 0;
send_data = *((char *) Buffer + 24);
// ((message_header *) Buffer)->rpc_vers_minor = ++send_seq;
while (BufferLength > 0)
{
cb = &pConn->send[fragments++];
cb->ecb.fragmentDescriptor[1].address = Buffer;
i = MIN( BufferLength, packet_size );
cb->ecb.fragmentDescriptor[1].size = i;
BufferLength -= i;
Buffer = ((char *) Buffer) + i;
SPXSendSequencedPacket( pConn->conn, &cb->ecb );
}
// Wait for the last fragment to complete.
WAIT_TILL( cb->ecb.inUseFlag == 0 );
// Verify that all fragments were successful.
for (i = 0; i < fragments; i++)
{
cb = &pConn->send[i];
if (cb->ecb.completionCode != 0)
{
if (cb->ecb.completionCode == ECB_CONN_TERMINATED ||
cb->ecb.completionCode == ECB_CONN_ABORTED ||
cb->ecb.completionCode == ECB_CONN_INVALID)
pConn->closed = TRUE;
ClientClose( pConn );
return RPC_P_SEND_FAILED;
}
}
return(RPC_S_OK);
}
/********************************************************************/
OPTIONAL_STATIC void ReceiveESR()
{
BUFFER *cb;
int i;
WORD conn;
// Restore DS.
__asm mov ds, es:[si]CONTROL_BLOCK.saved_ds
// Get the ECB pointer.
__asm
{
mov ax, es
mov word ptr [cb+2], ax
mov word ptr [cb], si
}
// Switch on the completion code.
receive_code = cb->header.ecb.completionCode;
receive_fragmentCount = cb->header.ecb.fragmentCount;
receive_fragmentSize = cb->header.ecb.fragmentDescriptor[1].size;
receive_spx_length = cb->header.spx.ipx.Length;
switch (cb->header.ecb.completionCode)
{
// Receive.
case ECB_SUCCESSFUL:
// If the data stream type indicates connection closed, find out
// which connection closed.
if (cb->header.spx.DataType == 0xFE)
{
conn_failed_cnt += 1;
conn = cb->header.spx.DestConnId;
for (i = 0; i < MAX_CONN; i++)
if (conn_lookup[i].spx == conn)
{
conn_lookup[i].rpc->closed = TRUE;
conn_lookup[i].rpc = NULL;
conn_lookup[i].spx = NO_CONNECTION;
nfy_failure_cnt += 1;
break;
}
// Give the buffer back to SPX.
SPXListenForSequencedPacket( &cb->header.ecb );
}
// Pass the buffer to ClientRecv.
else
{
cb->header.ecb.linkAddress = NULL;
if (receive_head == NULL)
receive_head = cb;
else
receive_tail->header.ecb.linkAddress = cb;
receive_tail = cb;
}
break;
// Some connection failed.
case ECB_CONN_ABORTED:
// Find the connection in the connection association and flag
// it closed.
conn_failed_cnt += 1;
conn = *((WORD *) &cb->header.ecb.IPXWorkspace);
for (i = 0; i < MAX_CONN; i++)
if (conn_lookup[i].spx == conn)
{
conn_lookup[i].rpc->closed = TRUE;
conn_lookup[i].rpc = NULL;
conn_lookup[i].spx = NO_CONNECTION;
nfy_failure_cnt += 1;
break;
}
// Give the buffer back to SPX.
SPXListenForSequencedPacket( &cb->header.ecb );
break;
// The receive overflowed.
case ECB_PACKET_OVERFLOW:
// Find the connection in the connection association and flag
// it closed.
conn_failed_cnt += 1;
conn = cb->header.spx.DestConnId;
for (i = 0; i < MAX_CONN; i++)
if (conn_lookup[i].spx == conn)
{
conn_lookup[i].rpc->closed = TRUE;
conn_lookup[i].rpc = NULL;
conn_lookup[i].spx = NO_CONNECTION;
nfy_failure_cnt += 1;
break;
}
// Give the buffer back to SPX.
SPXListenForSequencedPacket( &cb->header.ecb );
break;
// Something failed.
case ECB_CANCELLED:
case ECB_MISC_FAILURE:
default:
receive_failed_cnt += 1;
receive_state = fail_r;
SPXListenForSequencedPacket( &cb->header.ecb );
break;
}
}
/********************************************************************/
RPC_TRANS_STATUS RPC_ENTRY
ClientRecv (
IN RPC_TRANSPORT_CONNECTION ConnArg,
IN OUT void PAPI * PAPI * Buffer,
IN OUT unsigned int PAPI * BufferLength
)
// Read a message from a connection.
{
PCONNECTION pConn = ConnArg;
RPC_STATUS RpcStatus;
message_header *mh;
unsigned int receive_len;
int receive_left;
int spx_len;
BUFFER *buf;
char *put_data;
// Set up the receive globals.
receive_state = first_r;
// If the connection closed asynchronously then fail.
if (pConn->closed)
{
receive_state = fail_r;
ClientClose( pConn );
return RPC_P_RECEIVE_FAILED;
}
// Receive buffers one at a time till the whole frame arrives or the
// receive fails.
while (TRUE)
{
// Wait for the next buffer to arrive (it may already be waiting).
while (!pConn->closed && receive_state != fail_r && receive_head == NULL);
// Do nothing.
// If the receive failed, close the connection.
if (receive_state == fail_r || pConn->closed)
{
ClientClose( pConn );
return RPC_P_RECEIVE_FAILED;
}
// Get the buffer.
__asm cli
buf = receive_head;
receive_head = receive_head->header.ecb.linkAddress;
__asm sti
// If the buffer belongs to another connection, it shouldn't have
// arrived, so toss it.
if (buf->header.spx.DestConnId != pConn->conn)
{
SPXListenForSequencedPacket( &buf->header.ecb );
continue;
}
// First fragment.
if (receive_state == first_r)
{
// Compute the size of the RPC packet.
mh = (message_header *) buf->data;
if ( (mh->drep[0] & ENDIAN_MASK) == 0)
// Big endian...swap
//
receive_len = ByteSwapShort( mh->frag_length );
else
// Little endian, just like us...
//
receive_len = mh->frag_length;
// The RPC buffer must be reallocated.
if (receive_len > *BufferLength)
{
RpcStatus = I_RpcTransClientReallocBuffer (pConn, Buffer,
0, receive_len );
if (RpcStatus != RPC_S_OK)
{
receive_state = fail_r;
return RPC_S_OUT_OF_MEMORY;
}
}
*BufferLength = receive_len;
receive_left = receive_len;
receive_state = next_r;
put_data = *Buffer;
}
// Copy the data.
spx_len = ByteSwapShort( buf->header.spx.ipx.Length ) - sizeof(SPX_HEADER);
memcpy( put_data, buf->data, spx_len );
put_data += spx_len;
receive_left -= spx_len;
// Give the empty back to SPX.
SPXListenForSequencedPacket( &buf->header.ecb );
// If the whole RPC packet has been received, return.
if (receive_left <= 0)
{
receive_data = *((char *) *Buffer + 24);
receive_seq = ((message_header *) *Buffer)->rpc_vers_minor;
// ((message_header *) *Buffer)->rpc_vers_minor = 0;
receive_state = fail_r;
return RPC_S_OK;
}
}
}
/********************************************************************/
#pragma pack(1)
RPC_STATUS RPC_ENTRY
ClientTowerConstruct(
IN char PAPI * Endpoint,
IN char PAPI * NetworkAddress,
OUT unsigned short PAPI * Floors,
OUT unsigned long PAPI * ByteCount,
OUT unsigned char PAPI * PAPI * Tower,
IN char PAPI * Protseq
)
{
unsigned int TowerSize;
unsigned short portnum;
PFLOOR_234 Floor;
IPX_ADDRESS netaddr;
UNUSED(Protseq);
/* Compute the memory size of the tower. */
*Floors = TOWERFLOORS;
TowerSize = TOWERSIZE;
TowerSize += 2*sizeof(FLOOR_234) - 4;
/* Allocate memory for the tower. */
*ByteCount = TowerSize;
if ((*Tower = (unsigned char PAPI*)I_RpcAllocate(TowerSize)) == NULL)
{
return (RPC_S_OUT_OF_MEMORY);
}
/* Put the endpoint address and transport protocol id in the first floor. */
Floor = (PFLOOR_234) *Tower;
Floor->ProtocolIdByteCount = 1;
Floor->FloorId = (unsigned char)(TRANSPORTID & 0xFF);
Floor->AddressByteCount = 2;
if (Endpoint == NULL || *Endpoint == '\0')
{
Endpoint = ENDPOINT_MAPPER_EP;
}
portnum = atoi (Endpoint);
Floor->Data[0] = portnum / 0x100;
Floor->Data[1] = portnum % 0x100;
/* Put the network address and the transport host protocol id in the
** second floor.
*/
Floor = NEXTFLOOR(PFLOOR_234, Floor);
Floor->ProtocolIdByteCount = 1;
Floor->FloorId = (unsigned char)(TRANSPORTHOSTID & 0xFF);
Floor->AddressByteCount = 4+6;
memset(Floor->Data, '\0', 10);
return RPC_S_OK;
}
/********************************************************************/
RPC_STATUS RPC_ENTRY
ClientTowerExplode(
IN unsigned char PAPI * Tower,
OUT char PAPI * PAPI * Protseq,
OUT char PAPI * PAPI * Endpoint,
OUT char PAPI * PAPI * NetworkAddress
)
{
PFLOOR_234 Floor = (PFLOOR_234) Tower;
RPC_STATUS Status = RPC_S_OK;
unsigned short *Port;
if (Protseq != NULL)
{
*Protseq = I_RpcAllocate(strlen(PROTSEQ) + 1);
if (*Protseq == NULL)
Status = RPC_S_OUT_OF_MEMORY;
else
memcpy(*Protseq, PROTSEQ, strlen(PROTSEQ) + 1);
}
if ((Endpoint == NULL) || (Status != RPC_S_OK))
{
return (Status);
}
*Endpoint = I_RpcAllocate(6); //Ports are all <64K [5 decimal dig +1]
if (*Endpoint == NULL)
{
Status = RPC_S_OUT_OF_MEMORY;
if (Protseq != NULL)
{
I_RpcFree(*Protseq);
}
}
else
{
Port = (unsigned short *)&Floor->Data[0];
my_itoa(ByteSwapShort(*Port), *Endpoint);
}
return(Status);
}
#pragma pack()
/********************************************************************/
int __loadds
ClientCleanup(void)
// DOS Destructor function.
{
BUFFER *buf = chunk;
char *data;
int i;
// Close the socket.
IPXCloseSocket( socket );
if (chunk == NULL)
return 0;
// Make sure no buffers are in use.
for (i = 0; i < NUM_SPX_BUF; i++)
{
if (buf->header.ecb.inUseFlag != 0)
{
IPXCancelEvent( &buf->header.ecb );
WAIT_TILL( buf->header.ecb.inUseFlag == 0 );
}
data = (char *) (buf + 1);
buf = (BUFFER *) (data + packet_size + RECEIVE_PAD);
}
// Free memory.
// 5/10/94 - Changed from I_RpcFree to I_RpcRegisteredBufferFree
// since this buffer was registered to SPX. This change allows
// the Microsoft Exchange DOS Client's Shell-to-DOS function to
// recognize these buffers and avoid swapping them.
I_RpcRegisteredBufferFree(chunk);
return 0;
}
/********************************************************************/
RPC_CLIENT_TRANSPORT_INFO TransInfo =
{
RPC_TRANSPORT_INTERFACE_VERSION,
TRANSPORTID,
ClientTowerConstruct,
ClientTowerExplode,
0,
sizeof (CONNECTION),
ClientOpen,
ClientClose,
ClientSend,
ClientRecv,
NULL,
0,
0,
0
};
RPC_CLIENT_TRANSPORT_INFO * RPC_ENTRY TransPortLoad (
IN RPC_CHAR * RpcProtocolSequence
)
// Loadable transport initialization function
{
BYTE majv;
BYTE minv;
WORD maxc;
WORD availc;
int i;
BUFFER *buf;
char *data;
/* Breakpoint for debugging. */
// __asm int 3
/* Initialize IPX and SPX. */
if (IPXInitialize() != 0)
return NULL;
if (SPXInitialize( &majv, &minv, &maxc, &availc ) != SPX_INSTALLED)
return NULL;
/* Create a socket. */
socket = 0;
chunk = NULL;
if (IPXOpenSocket( &socket, 0 ) != 0)
return NULL;
I_DosAtExit( ClientCleanup );
/* Initialize the global variables. */
receive_state = fail_r;
receive_head = NULL;
receive_tail = NULL;
receive_code = 0;
receive_fragmentCount = 0;
receive_fragmentSize = 0;
receive_spx_length = 0;
receive_failed_cnt = 0;
conn_failed_cnt = 0;
nfy_failure_cnt = 0;
for (i = 0; i < MAX_CONN; i++)
{
conn_lookup[i].rpc = NULL;
conn_lookup[i].spx = NO_CONNECTION;
}
/* Allocate some ECBs and data. */
packet_size = IPXGetMaxPacketSize() - sizeof(SPX_HEADER);
// Changed from I_RpcAllocate to I_RpcRegisteredBufferAllocate
// since this buffer was registered to SPX. This change allows
// the Microsoft Exchange DOS Client's Shell-to-DOS function to
// recognize these buffers and avoid swapping them.
//
buf = I_RpcRegisteredBufferAllocate( NUM_SPX_BUF * (sizeof(BUFFER) + packet_size + RECEIVE_PAD) );
if (buf == NULL)
return NULL;
chunk = buf;
/* Initialize and post some ECBs for SPX to play with. */
for (i = 0; i < NUM_SPX_BUF; i++)
{
data = (char *) (buf + 1);
InitializeCB( &buf->header, ReceiveESR, 2 );
buf->header.ecb.fragmentDescriptor[1].address = &buf->data;
SPXListenForSequencedPacket( &buf->header.ecb );
buf = (BUFFER *) (data + packet_size + RECEIVE_PAD);
}
/* Compute the maximum packet size to give to runtime. */
max_num_send = (RPC_MIN_MAX_PACKET + packet_size - 1) / packet_size;
TransInfo.MaximumPacketSize = (max_num_send * packet_size)
& 0xFFF8;
/* Compute the size of the connection structure. */
TransInfo.SizeOfConnection = sizeof(CONNECTION) +
(max_num_send - 1) * sizeof(CONTROL_BLOCK);
/* Return sucess. */
return(&TransInfo);
}