APPCLIB.C

#include <windows.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winappc.h>
#include <wincsv.h>
#include "appclib.h"
#include "queue.h"

#define memalloc(size) LocalAlloc(LMEM_FIXED, size)
#define memfree(pointer) LocalFree(pointer)

// --- internal functions used by the module ---
void appcerror(vcb_t vcb);
BOOL callappc(vcb_t *vcb);
void conv_a_to_e(char *string, int length);
void memcpypad(char *dest, TCHAR *source, int length);
void memcpya2e(char *dest, TCHAR *source, int length);
int do_receive_and_wait(char *tp_id, unsigned long int conv_id,
void *data, int maxlen, BOOL *dealloc);
void do_send_data(char *tp_id, unsigned long int conv_id, void *data, int len);
void do_confirm(char *tp_id, unsigned long int conv_id);
void do_prepare_to_receive(char *tp_id, unsigned long int conv_id);
void do_get_fqplu_name(char *tp_id, unsigned long int conv_id,
char *fqplu_name);
void do_tp_started(TCHAR *lu_alias, TCHAR *tp_name, char *tp_id);
unsigned long int do_mc_allocate(TCHAR *lu_alias, TCHAR *tp_name,
TCHAR *mode_name, char *tp_id);
void do_deallocate(char *tp_id, unsigned long int conv_id);
void do_tp_ended(char *tp_id);

queue *pairq; // queue of sessions that have
// both read and write established
HANDLE hPairqSem; // semaphore of number of objects
// in pairq;
HANDLE hListenThread = NULL; // handle to listenthread()
HANDLEhShutdownEvent = NULL;// event to signal that the application
// is shutting down
intoutstanding;// number of outstanding APPC reqs
HANDLEhOutstandingZero = NULL;// event set when outstanding goes to
// zero
CRITICAL_SECTION csPairq; // critical section for pairq

/*
* convert string from ascii to ebcdic (overwriting the original string).
*/
void conv_a_to_e(char *string, int length) {
struct convert cnvt;

memset(&cnvt, 0, sizeof(cnvt));
cnvt.opcode = SV_CONVERT;
cnvt.direction = SV_ASCII_TO_EBCDIC;
cnvt.char_set = SV_AE;
cnvt.len = length;
cnvt.source = string;
cnvt.target = string;

ACSSVC_C((long) &cnvt);
}

/*
* copy a string from <source> to <dest>, padding the end of it with spaces so
* it takes <length> bytes.
*
* input is assumed to be Unicode if UNICODE is defined, output is always Ansi
*/
void memcpypad(char *dest, TCHAR *source, int length) {
#ifdef UNICODE
char ansisrc[1024];

// convert the source string from Unicode to ANSI
wcstombs(ansisrc, source, 1024);

// copy the ANSI string into the destination
memset(dest, ' ', length);
memcpy(dest, ansisrc, strlen(ansisrc));
#else
memset(dest, ' ', length);
memcpy(dest, source, strlen(source));
#endif
}

/*
* copy a string from <source> to <dest>, padding the end of it with spaces
* so it takes <length> bytes. convert the <dest> string from ascii to ebcdic
*
* input is assumed to be Unicode if UNICODE is defined, output is always Ansi
*/
void memcpya2e(char *dest, TCHAR *source, int length) {
#ifdef UNICODE
char ansisrc[1024];

// convert the source string from Unicode to ANSI
wcstombs(ansisrc, source, 1024);

// copy the ANSI string into the destination
memset(dest, ' ', length);
memcpy(dest, ansisrc, strlen(ansisrc));

// convert ansi to ebcdic
conv_a_to_e(dest, length);
#else
memset(dest, ' ', length);
memcpy(dest, source, strlen(source));
conv_a_to_e(dest, length);
#endif
}

/*
* a standard error handler for APPC errors. prints out the last verb and the
* two return codes
*/
void appcerror(vcb_t vcb) {
char errorbuf[1024];

printf("\nAPPC Error: Verb = 0x%0x PriRC = 0x%0x SecRC = 0x%0x\n",
APPC_FLIPI(vcb.hdr.opcode), APPC_FLIPI(vcb.hdr.primary_rc),
APPC_FLIPL(vcb.hdr.secondary_rc));

GetAppcReturnCode((struct appc_hdr *) &(vcb.hdr), 1024, errorbuf);

printf("\n%s\n", errorbuf);

appcdestroy();

ExitProcess(1);
}

/*
* initialize APPC. all programs should call this before using any
* APPC functions
*/
int appcinit(void) {
WAPPCDATA APPCData;

// create the shutdown event
hShutdownEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (hShutdownEvent == NULL) return -1;

// create the event that is triggered when outstanding goes to zero
// this only gets set when hShutdownEvent has been set and all requests
// are being cancelled
hOutstandingZero = CreateEvent(NULL, FALSE, FALSE, NULL);
if (hOutstandingZero == NULL) return -1;

return WinAPPCStartup(WINAPPCVERSION, &APPCData);
}

/*
* kill APPC. this should be called when a program is done using APPC
*/
BOOL WINAPI appcdestroy(void) {
// if there are any outstanding requests
if (outstanding > 0) {
// tell all threads to cancel their outstanding requests. when the last
// one cancels its request it will set hOutstandingZero.
SetEvent(hShutdownEvent);
// wait up to 60 seconds for all outstanding requests to be canceled
while
((WaitForSingleObject(hOutstandingZero, 20000) == WAIT_OBJECT_0) &&
(outstanding > 0));
}

// tell WinAPPC that we're done with it
return WinAPPCCleanup();
}

// a wrapper around APPC which handles hShutdownEvent for clean shutdowns
BOOL callappc(vcb_t *vcb) {
HANDLE hAsync, objs[2];
DWORD event;

// create an event handle
objs[0] = CreateEvent(NULL, FALSE, FALSE, NULL);

InterlockedIncrement(&outstanding);

// make the APPC call
ASYNCAPPC(objs[0], vcb, hAsync);
// see if the async APPC call failed
if (hAsync == 0) {
InterlockedDecrement(&outstanding);
SetLastError(APPCLIB_ASYNC_FAILED);
return FALSE;
}

// wait for either the APPC call to complete or the hShutdownEvent to be
// triggered
objs[1] = hShutdownEvent;
event = WaitForMultipleObjects(2, objs, FALSE, INFINITE) - WAIT_OBJECT_0;
switch (event) {
case 0:// APPC call completed
InterlockedDecrement(&outstanding);
break;
case 1:// shutdown event triggered
// cancel the outstanding request
WinAPPCCancelAsyncRequest(hAsync);
// if this is the last cancel than let appcdestroy() know that
if (InterlockedDecrement(&outstanding) <= 0) {
SetEvent(hOutstandingZero);
}
break;
case WAIT_FAILED:
SetLastError(APPCLIB_NO_INIT);
return FALSE;
}
CloseHandle(objs[0]);
CloseHandle(hAsync);

return TRUE;
}

/*
* execute a MC_RECEIVE_AND_WAIT verb with the given TP ID and Conversation ID
* data is copied into the buffer at <data> for a maximum of <maxlen> bytes.
* Returns the number of bytes actually read.
*
* If the sender requests confirmation a MC_CONFIRM verb is send to them.
* if the conversation gets deallocated then dealloc is set to true.
*/
int do_receive_and_wait(char *tp_id, unsigned long int conv_id,
void *data, int maxlen, BOOL *dealloc) {
vcb_t vcb;
int read;

CLEARVCB(vcb);

*dealloc = FALSE;

vcb.rcvwait.opcode = AP_M_RECEIVE_AND_WAIT;
vcb.rcvwait.opext = AP_MAPPED_CONVERSATION;
memcpy(&(vcb.rcvwait.tp_id), tp_id, 8);
vcb.rcvwait.conv_id = conv_id;
vcb.rcvwait.rtn_status = AP_YES;
vcb.rcvwait.max_len = maxlen;
vcb.rcvwait.dptr = data;

callappc(&vcb);
APPCDBG(printf("vcb.rcvwait.what_rcvd = 0x%x\n", APPC_FLIPI(vcb.rcvwait.what_rcvd)));
APPCDBG(printf("vcb.rcvwait.primary_rc = 0x%x\n", APPC_FLIPI(vcb.rcvwait.primary_rc)));
APPCDBG(printf("vcb.rcvwait.dlen = %i\n", vcb.rcvwait.dlen));
read = vcb.rcvwait.dlen;

if ((vcb.rcvwait.primary_rc == AP_DEALLOC_NORMAL) ||
(vcb.rcvwait.primary_rc == AP_DEALLOC_ABEND)) {
do_tp_ended(tp_id);
*dealloc = TRUE;
} else if (vcb.rcvwait.primary_rc != AP_OK) {
appcerror(vcb);
} else if ((vcb.rcvwait.what_rcvd == AP_CONFIRM_DEALLOCATE) ||
(vcb.rcvwait.what_rcvd == AP_DATA_COMPLETE_CONFIRM_DEALL)) {
/* confirm deallocation */
CLEARVCB(vcb);
vcb.confirmed.opcode = AP_M_CONFIRMED;
vcb.confirmed.opext = AP_MAPPED_CONVERSATION;
memcpy(vcb.confirmed.tp_id, tp_id, 8);
vcb.confirmed.conv_id = conv_id;
callappc(&vcb);
if (vcb.confirmed.primary_rc != AP_OK) appcerror(vcb);

do_tp_ended(tp_id);
*dealloc = TRUE;
} else if ((vcb.rcvwait.what_rcvd == AP_CONFIRM_WHAT_RECEIVED) ||
(vcb.rcvwait.what_rcvd == AP_CONFIRM_SEND) ||
(vcb.rcvwait.what_rcvd == AP_DATA_COMPLETE_CONFIRM) ||
(vcb.rcvwait.what_rcvd == AP_DATA_CONFIRM)) {
/* do a confirmation if one was requested */
CLEARVCB(vcb);
vcb.confirmed.opcode = AP_M_CONFIRMED;
vcb.confirmed.opext = AP_MAPPED_CONVERSATION;
memcpy(vcb.confirmed.tp_id, tp_id, 8);
vcb.confirmed.conv_id = conv_id;
callappc(&vcb);
if (vcb.confirmed.primary_rc != AP_OK) appcerror(vcb);
}

return read;
}

/*
* do a MC_SEND_DATA verb with the given TP ID, Conversation ID and <len>
* bytes of data at the <data> pointer. asks for a confirmation upon
* sending.
*/
void do_send_data(char *tp_id, unsigned long int conv_id, void *data, int len) {
vcb_t vcb;

CLEARVCB(vcb);

vcb.snddata.opcode = AP_M_SEND_DATA;
vcb.snddata.opext = AP_MAPPED_CONVERSATION;
memcpy(vcb.snddata.tp_id, tp_id, 8);
vcb.snddata.conv_id = conv_id;
vcb.snddata.dlen = len;
vcb.snddata.dptr = data;
vcb.snddata.type = AP_SEND_DATA_CONFIRM;

callappc(&vcb);

if (vcb.snddata.primary_rc != AP_OK) appcerror(vcb);
}

/*
* ask for a confirmation on a given TP ID and Conversation ID. used for
* syncronizing data flow.
*/
void do_confirm(char *tp_id, unsigned long int conv_id) {
vcb_t vcb;

CLEARVCB(vcb);
vcb.confirm.opcode = AP_M_CONFIRM;
vcb.confirm.opext = AP_MAPPED_CONVERSATION;
memcpy(vcb.confirm.tp_id, tp_id, 8);
vcb.confirm.conv_id = conv_id;

callappc(&vcb);

if (vcb.confirm.primary_rc != AP_OK) appcerror(vcb);
}

/*
* switch the conversation into a receive state and ask the partner TP
* to confirm this.
*/
void do_prepare_to_receive(char *tp_id, unsigned long int conv_id) {
vcb_t vcb;

CLEARVCB(vcb);
vcb.torec.opcode = AP_M_PREPARE_TO_RECEIVE;
vcb.torec.opext = AP_MAPPED_CONVERSATION;
vcb.torec.ptr_type = AP_SYNC_LEVEL;
vcb.torec.locks = AP_SHORT;
memcpy(vcb.torec.tp_id, tp_id, 8);
vcb.torec.conv_id = conv_id;

callappc(&vcb);

if (vcb.confirm.primary_rc != AP_OK) appcerror(vcb);
}

/*
* wait on a TP name for a MC_ALLOCATE request. the TP ID is written into
* the buffer at <tp_id>, the conversation ID is returned.
*/
void do_get_fqplu_name(char *tp_id, unsigned long int conv_id,
char *fqplu_name) {
vcb_t vcb;

CLEARVCB(vcb);
vcb.getattrib.opcode = AP_M_GET_ATTRIBUTES;
vcb.getattrib.opext = AP_MAPPED_CONVERSATION;
memcpy(vcb.getattrib.tp_id, tp_id, 8);
vcb.getattrib.conv_id = conv_id;

callappc(&vcb);
if (vcb.rcvalloc.primary_rc != AP_OK) appcerror(vcb);

memcpy(fqplu_name, vcb.getattrib.fqplu_name, 17);
}

/*
* start a new TP. this registers the LU alias and TP name for your TP
* to use and returns a TP ID
*/
void do_tp_started(TCHAR *lu_alias, TCHAR *tp_name, char *tp_id) {
vcb_t vcb;

CLEARVCB(vcb);
vcb.tpstart.opcode = AP_TP_STARTED;
memcpypad(vcb.tpstart.lu_alias, lu_alias, 8);
memcpya2e(vcb.tpstart.tp_name, tp_name, 64);

callappc(&vcb);
if (vcb.tpstart.primary_rc != AP_OK) appcerror(vcb);

memcpy(tp_id, vcb.tpstart.tp_id, 8);
}

/*
* allocate a conversation with a partner TP. This takes the partner TP's
* alias, tp name, mode name, and your TP ID and returns a conversation ID.
*
* it does a MC_CONFIRM directly afterwords to see if the conversation was
* really allocated. if it wasn't it sleeps for 100ms and tries again for
* up to 20 retries (about 2 seconds).
*/
unsigned long int do_mc_allocate(TCHAR *lu_alias, TCHAR *tp_name,
TCHAR *mode_name, char *tp_id) {
vcb_t vcb;
int tries = 0;
unsigned long int conv_id;

do {
CLEARVCB(vcb);
vcb.allocate.opcode = AP_M_ALLOCATE;
vcb.allocate.opext = AP_MAPPED_CONVERSATION;
memcpy(vcb.allocate.tp_id, tp_id, 8);
vcb.allocate.sync_level = AP_CONFIRM_SYNC_LEVEL;
vcb.allocate.rtn_ctl = AP_WHEN_SESSION_ALLOCATED;

memcpypad(vcb.allocate.plu_alias, lu_alias, 8);
memcpya2e(vcb.allocate.mode_name, mode_name, 8);
memcpya2e(vcb.allocate.tp_name, tp_name, 64);
vcb.allocate.security = AP_NONE;

callappc(&vcb);
conv_id = vcb.allocate.conv_id;
if (vcb.allocate.primary_rc != AP_OK) appcerror(vcb);

if (vcb.allocate.primary_rc == AP_ALLOCATION_ERROR) Sleep(100);
} while ((tries++ < 20) &&
(vcb.allocate.primary_rc == AP_ALLOCATION_ERROR));

if (vcb.allocate.primary_rc != AP_OK) appcerror(vcb);

return conv_id;
}

/*
* deallocate a conversation. should only be called with a TP ID and
* conversation ID that are in the send state.
*/
void do_deallocate(char *tp_id, unsigned long int conv_id) {
vcb_t vcb;

CLEARVCB(vcb);
vcb.dealloc.opcode = AP_M_DEALLOCATE;
vcb.dealloc.opext = AP_MAPPED_CONVERSATION;
memcpy(vcb.confirm.tp_id, tp_id, 8);
vcb.confirm.conv_id = conv_id;
vcb.dealloc.dealloc_type = AP_SYNC_LEVEL;

callappc(&vcb);

if (vcb.dealloc.primary_rc != AP_OK) appcerror(vcb);
}

void do_tp_ended(char *tp_id) {
vcb_t vcb;

CLEARVCB(vcb);
vcb.tpend.opcode = AP_TP_ENDED;
vcb.tpend.type = AP_SOFT;
memcpy(vcb.confirm.tp_id, tp_id, 8);

callappc(&vcb);

if (vcb.dealloc.primary_rc != AP_OK) appcerror(vcb);
}

DWORD WINAPI listenthread(TCHAR *tpname) {
vcb_t vcb_r, vcb_w; // verb control block for each sess
TCHAR tpname_r[9], tpname_w[9]; // tp name for each session
HANDLE h_event_r, h_event_w; // event handles for each session
HANDLE h_async_r, h_async_w; // async task handles for each sess
HANDLE objs[3]; // array of handles to wait for
DWORD event; // which event returned
tpconvid_t tpconv; // temporary tpconv pointer
name_time_t name_time; // logical unit of work & time key
int dread;
BOOL dealloc; // deallocation flag on session
queue *readq; // queue of sessions that have
// read establish, but not write
readnode_t readnode; // node for readq
unsigned long int w_conv_id; // conversation id for write sess
unsigned char w_tp_id[8]; // tp_id for write session
qnode *n; // temp node for searching queues

// create the event handles for each session
h_event_r = CreateEvent(NULL, FALSE, FALSE, NULL);
h_event_w = CreateEvent(NULL, FALSE, FALSE, NULL);
if ((h_event_r == NULL) || (h_event_w == NULL)) {
printf("appclib: CreateEvent() failed near line %i\n", __LINE__);
return FALSE;
}

// create the q
readq = newq();
if (readq == NULL) {
printf("appclib: newq() failed near line %i\n", __LINE__);
return FALSE;
}

// we actually use two different TP names, one for each session.
// the name that the server reads on ends in a R, the name that the
// server writes on ends in a W

//
// last minute change to ensure we work as an autostarted TP where
// the tp name we issue in the rcv_alloc must match the TP name we
// registered as with Service Control Manager. On the Server read
// tp we do not alter the name. The server write TP is still altered.
// will most likely not work if the TP name is 8 chars long and ends
// in a W.
//
memcpy(tpname_r, tpname, 8 * sizeof(TCHAR));
// tpname_r[7] = 0;
// lstrcat(tpname_r, TEXT("R"));

memcpy(tpname_w, tpname, 7 * sizeof(TCHAR));
tpname_w[7] = 0;
lstrcat(tpname_w, TEXT("W"));

// start an receive_allocate on the read tp
InterlockedIncrement(&outstanding);
CLEARVCB(vcb_r);
vcb_r.rcvalloc.opcode = AP_RECEIVE_ALLOCATE;
memcpya2e(vcb_r.rcvalloc.tp_name, tpname_r, 64);
ASYNCAPPC(h_event_r, &vcb_r, h_async_r);
if (h_async_r == 0) {
InterlockedDecrement(&outstanding);
return FALSE;
}

// start an receive_allocate on the write tp
InterlockedIncrement(&outstanding);
CLEARVCB(vcb_w);
vcb_w.rcvalloc.opcode = AP_RECEIVE_ALLOCATE;
memcpya2e(vcb_w.rcvalloc.tp_name, tpname_w, 64);
ASYNCAPPC(h_event_w, &vcb_w, h_async_w);
if (h_async_w == 0) {
InterlockedDecrement(&outstanding);
printf("appclib: WinAsyncAPPCEx failed with verb 0x%x near line %i\n",
APPC_FLIPI(vcb_r.rcvalloc.opcode), __LINE__);
return FALSE;
}

while (TRUE) {
objs[0] = h_event_r;
objs[1] = h_event_w;
objs[2] = hShutdownEvent;
event = WaitForMultipleObjects(3, objs, FALSE, INFINITE)-WAIT_OBJECT_0;
switch (event) {
case 0:// someone connected to reader
InterlockedDecrement(&outstanding);

// check the APPC return codes
if (vcb_r.rcvalloc.primary_rc != AP_OK) appcerror(vcb_r);

// allocate a new readnode struct and the tpconv structure
// inside it. save the tp_id and conv_id into the tpconv
// structure
readnode = (readnode_t) memalloc(sizeof(struct readnode_st));
readnode->tpconv =
(tpconvid_t) memalloc(sizeof(struct tpconvid_st));
memcpy(readnode->tpconv->r_tp_id, vcb_r.rcvalloc.tp_id, 8);
readnode->tpconv->r_conv_id = vcb_r.rcvalloc.conv_id;

// start another receive_allocate on the read TP
InterlockedIncrement(&outstanding);
CLEARVCB(vcb_r);
vcb_r.rcvalloc.opcode = AP_RECEIVE_ALLOCATE;
memcpya2e(vcb_r.rcvalloc.tp_name, tpname_r, 64);
ASYNCAPPC(h_event_r, &vcb_r, h_async_r);
if (h_async_r == 0) {
InterlockedDecrement(&outstanding);
return FALSE;
}

// receive_and_wait for the name_time key for this session
dread = do_receive_and_wait(readnode->tpconv->r_tp_id,
readnode->tpconv->r_conv_id, &(readnode->name_time),
sizeof(name_time_t), &dealloc);
if ((dread != sizeof(name_time_t)) || (dealloc)) {
// close session
memfree(readnode->tpconv);
memfree(readnode);
break;
}

// add all of this to the readq
addtoq(readq, (void *) readnode);
break;
case 1:// someone connected to writer
InterlockedDecrement(&outstanding);

// check the APPC return codes
if (vcb_w.rcvalloc.primary_rc != AP_OK) appcerror(vcb_w);

// save the tp id and conversation id for this session
memcpy(w_tp_id, vcb_w.rcvalloc.tp_id, 8);
w_conv_id = vcb_w.rcvalloc.conv_id;

// start another receive_allocate on the write TP
InterlockedIncrement(&outstanding);
CLEARVCB(vcb_w);
vcb_w.rcvalloc.opcode = AP_RECEIVE_ALLOCATE;
memcpya2e(vcb_w.rcvalloc.tp_name, tpname_w, 64);
ASYNCAPPC(h_event_w, &vcb_w, h_async_w);
if (h_async_w == 0) {
InterlockedDecrement(&outstanding);
return FALSE;
}

// receive and wait for the name_time key for this session
dread = do_receive_and_wait(w_tp_id, w_conv_id, &name_time,
sizeof(name_time_t), &dealloc);
if ((dread != sizeof(name_time_t)) || (dealloc)) {
// close session
break;
}

// find the matching name_time key in the readq
for (n = readq->head; n != NULL; n = n->next) {
if (memcmp(&name_time,
&(((readnode_t) (n->ptr))->name_time),
sizeof(name_time_t)) == 0) break;
}
if (n == NULL) {
// close session
break;
}

readnode = (readnode_t) removeqnodefromq(readq, n);
tpconv = readnode->tpconv;
memfree(readnode);
memcpy(tpconv->w_tp_id, w_tp_id, 8);
tpconv->w_conv_id = w_conv_id;
tpconv->r_valid = TRUE;
tpconv->w_valid = TRUE;

// receive and wait to confirm data turnaround
do_receive_and_wait(tpconv->w_tp_id, tpconv->w_conv_id,
NULL, 0, &dealloc);

// add it to the pairq and signal that there is one more
// pair available
EnterCriticalSection(&csPairq);
addtoq(pairq, (void *) tpconv);
ReleaseSemaphore(hPairqSem, 1, NULL);
LeaveCriticalSection(&csPairq);
break;
case 2:// shutdown event
WinAPPCCancelAsyncRequest(h_async_r);
InterlockedDecrement(&outstanding);
WinAPPCCancelAsyncRequest(h_async_w);
if (InterlockedDecrement(&outstanding) <= 0)
SetEvent(hOutstandingZero);
return FALSE;
}
}
}

/*
* ---- functions to setup easy two-way APPC communication ----
*/

/*
* listen on a TP name for incoming conversations.
*
* each TP can only listen to one tpname at a time (because this only
* spawns one listenthread). If a TP needs to be written that can handle
* listening to multiple TP names in one run then this will need to be
* modified to make one listenthread() for each appclisten() that gets
* called with a unique TP name.
*
* returns a tpconvid that can be used with appcread, appcwrite and
* appcclose.
*/
tpconvid_t appclisten(TCHAR *tpname) {
tpconvid_t tpconv;
DWORD tid;

APPCDBG(printf("appclisten(%ws) starting\n", tpname));

if (hListenThread == NULL) {
APPCDBG(printf("appclisten: creating listenthread\n"));
// create the semaphore for the pairq
hPairqSem = CreateSemaphore(NULL, 0, 100000, NULL);
if (hPairqSem == NULL) return NULL;

// create the pairq
pairq = newq();
if (pairq == NULL) return NULL;

// create a critical section for dealing with the pairq
InitializeCriticalSection(&csPairq);

// create the listen thread
hListenThread = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE) listenthread, tpname, 0, &tid);
if (hListenThread == NULL) return NULL;
}

// wait for the listen thread to put a tpconv pair into the pairq
WaitForSingleObject(hPairqSem, INFINITE);

// remove the first tpconv pair from the pairq and return it
EnterCriticalSection(&csPairq);
tpconv = removeheadfromq(pairq);
LeaveCriticalSection(&csPairq);

APPCDBG(printf("appclisten done\n", tpname));

return tpconv;
}

/*
* connect to a remote server. this allocates two conversations, one
* to write on and one to read on.
*
* the LU's should be listed as partners under SNA Admin with a mode name of
* <mode_name> between them.
*
* returns a tpconvid which can be used with appcread, appcwrite, and
* appcclose.
*/
tpconvid_t appcconnect(TCHAR *lu_alias, TCHAR *plu_alias,
TCHAR *tp_name, TCHAR *loc_tp_name, TCHAR *mode_name) {
tpconvid_t tpconv;
name_time_t name_time;
TCHAR tpname_w[9], tpname_r[9];
TCHAR loctpname_w[9], loctpname_r[9];


APPCDBG(printf("appcconnect(%ws, %ws, %ws, %ws) starting\n", lu_alias, plu_alias, tp_name, mode_name));

// we actually use two different TP names, one for each session.
// the name that the server reads on ends in a R, the name that the
// server writes on ends in a W
memcpy(tpname_r, tp_name, 8 * sizeof(TCHAR));
// tpname_r[7] = 0;
// lstrcat(tpname_r, TEXT("R"));

memcpy(tpname_w, tp_name, 7 * sizeof(TCHAR));
tpname_w[7] = 0;
lstrcat(tpname_w, TEXT("W"));

memcpy(loctpname_r, loc_tp_name, 8 * sizeof(TCHAR));
// loctpname_r[7] = 0;
// lstrcat(loctpname_r, TEXT("R"));

memcpy(loctpname_w, loc_tp_name, 7 * sizeof(TCHAR));
loctpname_w[7] = 0;
lstrcat(loctpname_w, TEXT("W"));

tpconv = (tpconvid_t) memalloc(sizeof(struct tpconvid_st));
if (tpconv == NULL) {
printf("appclib: couldn't allocate memory for tpconvid_t\n");
exit(1);
}

// connect the first session
do_tp_started(lu_alias, loctpname_w, tpconv->w_tp_id);
tpconv->w_conv_id = do_mc_allocate(plu_alias, tpname_r, mode_name,
tpconv->w_tp_id);
// fill in the logical unit of work/time structure
do_get_fqplu_name(tpconv->w_tp_id, tpconv->w_conv_id, name_time.fqplu_name);
name_time.time = GetTickCount();
// send the fqplu name/time information
do_send_data(tpconv->w_tp_id, tpconv->w_conv_id, (void *) &name_time,
sizeof(name_time_t));
tpconv->w_valid = TRUE;

// connect the second session
do_tp_started(lu_alias, loctpname_r, tpconv->r_tp_id);
tpconv->r_conv_id = do_mc_allocate(plu_alias, tpname_w, mode_name,
tpconv->r_tp_id);
// send the fqplu name/time information
do_send_data(tpconv->r_tp_id, tpconv->r_conv_id, (void *) &name_time,
sizeof(name_time_t));
// reverse the conversation
do_prepare_to_receive(tpconv->r_tp_id, tpconv->r_conv_id);
tpconv->r_valid = TRUE;

APPCDBG(printf("appcconnect done\n"));

return tpconv;
}

/*
* read from a conversation setup by appcconnect() or appclisten().
* will read at most <maxlen> bytes into the buffer at <data>.
*/
int appcread(tpconvid_t tpconv, void *data, int maxlen) {
BOOL dealloc;
int read;

APPCDBG(printf("appcread(tpconv, data, %i) starting\n", maxlen));

if (!tpconv->r_valid) return -1;
read = do_receive_and_wait(tpconv->r_tp_id, tpconv->r_conv_id, data, maxlen,
&dealloc);
if (dealloc) tpconv->r_valid = FALSE;

APPCDBG(printf("appcread done, returning %i, dealloc = %i, tpconv->r_valid = %i\n", read, dealloc, tpconv->r_valid));

return read;
}

/*
* write to a conversation setup by appcconnect() or appclisten().
* will write at most <maxlen> bytes into the buffer at <data>
*/
void appcwrite(tpconvid_t tpconv, void *data, int len) {
APPCDBG(printf("appcwrite(tpconv, data, %i) starting\n", len));
do_send_data(tpconv->w_tp_id, tpconv->w_conv_id, data, len);
APPCDBG(printf("appcwrite done\n"));
}

int appcvalid(tpconvid_t tpconv) {
int rc;

APPCDBG(printf("appcvalid starting and done, returning %i\n", (tpconv->r_valid && tpconv->w_valid)));
rc = (tpconv->r_valid && tpconv->w_valid);
return rc;
}

void appcclose(tpconvid_t tpconv) {
APPCDBG(printf("appcclose(tpconv) starting\n"));

/* deallocate the write channel */
if (tpconv->w_valid) {
do_deallocate(tpconv->w_tp_id, tpconv->w_conv_id);
do_tp_ended(tpconv->w_tp_id);
tpconv->w_valid = FALSE;
}

APPCDBG(printf("appcclose done\n"));
}