From b5c3a29f37120387b993a9a0fe57e8cbd8638b9e Mon Sep 17 00:00:00 2001 From: Stephen Heumann Date: Sat, 18 Mar 2017 18:44:11 -0500 Subject: [PATCH] Initial version, with test program. It can currently send a GetStatus request and get a response. --- Makefile.mk | 11 +++ aspinterface.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++ aspinterface.h | 20 ++++++ atipmapping.c | 3 + atipmapping.h | 17 +++++ dsi.c | 182 ++++++++++++++++++++++++++++++++++++++++++++++++ dsi.h | 10 +++ dsiproto.h | 42 +++++++++++ dsitest.c | 72 +++++++++++++++++++ endian.asm | 32 +++++++++ endian.h | 17 +++++ endiantest.c | 8 +++ readtcp.c | 38 ++++++++++ readtcp.h | 15 ++++ session.h | 65 +++++++++++++++++ tcpconnection.c | 78 +++++++++++++++++++++ tcpconnection.h | 9 +++ 17 files changed, 798 insertions(+) create mode 100644 Makefile.mk create mode 100644 aspinterface.c create mode 100644 aspinterface.h create mode 100644 atipmapping.c create mode 100644 atipmapping.h create mode 100644 dsi.c create mode 100644 dsi.h create mode 100644 dsiproto.h create mode 100644 dsitest.c create mode 100644 endian.asm create mode 100644 endian.h create mode 100644 endiantest.c create mode 100644 readtcp.c create mode 100644 readtcp.h create mode 100644 session.h create mode 100644 tcpconnection.c create mode 100644 tcpconnection.h diff --git a/Makefile.mk b/Makefile.mk new file mode 100644 index 0000000..beef887 --- /dev/null +++ b/Makefile.mk @@ -0,0 +1,11 @@ +CFLAGS = -i -w + +OBJS = dsitest.o aspinterface.o dsi.o readtcp.o endian.o tcpconnection.o atipmapping.o +PROG = dsitest + +$(PROG): $(OBJS) + occ $(CFLAGS) -o $@ $(OBJS) + +.PHONY: clean +clean: + $(RM) $(OBJS) $(PROG) diff --git a/aspinterface.c b/aspinterface.c new file mode 100644 index 0000000..0e1cd0b --- /dev/null +++ b/aspinterface.c @@ -0,0 +1,179 @@ +#pragma noroot + +#include +#include + +#include "session.h" +#include "aspinterface.h" +#include "dsi.h" +#include "tcpconnection.h" +#include "endian.h" +#include "readtcp.h" + +void DoSPGetStatus(Session *sess, ASPGetStatusRec *commandRec); +void DoSPOpenSession(Session *sess, ASPOpenSessionRec *commandRec); +void DoSPCloseSession(Session *sess, ASPCloseSessionRec *commandRec); +void DoSPCommand(Session *sess, ASPCommandRec *commandRec); +void DoSPWrite(Session *sess, ASPWriteRec *commandRec); +void CompleteCommand(Session *sess); + + +Session sessionTbl[MAX_SESSIONS]; + +void DispatchASPCommand(SPCommandRec *commandRec) { + Session *sess; + unsigned int i; + + if (commandRec->command == aspGetStatusCommand + || commandRec->command==aspOpenSessionCommand) + { + for (i = 0; i < MAX_SESSIONS; i++) { + if (sessionTbl[i].dsiStatus == unused) + break; + } + if (i == MAX_SESSIONS) { + commandRec->result = aspTooManySessions; + CompleteCommand(sess); + return; + } + sess = &sessionTbl[i]; + sess->spCommandRec = commandRec; + + if (!StartTCPConnection(sess)) { + // Error code was set in TCPIPConnect + CompleteCommand(sess); + return; + } + sess->dsiStatus = awaitingHeader; + InitReadTCP(sess, DSI_HEADER_SIZE, &sess->reply); + } else { + if (commandRec->refNum < SESSION_NUM_START) { + // TODO call original AppleTalk routine (or do it earlier) + return; + } + i = commandRec->refNum - SESSION_NUM_START; + sess = &sessionTbl[i]; + sess->spCommandRec = commandRec; + } + + // TODO properly handle all cases of getting a command while + // one is in progress + if (commandRec->command != aspCloseSessionCommand) { + if (sess->commandStatus != noCommand) { + commandRec->result = aspSessNumErr; + CompleteCommand(sess); + return; + } + sess->commandStatus = commandPending; + } + + switch (commandRec->command) { + case aspGetStatusCommand: + DoSPGetStatus(sess, (ASPGetStatusRec *)commandRec); + break; + case aspOpenSessionCommand: + DoSPOpenSession(sess, (ASPOpenSessionRec *)commandRec); + break; + case aspCloseSessionCommand: + DoSPCloseSession(sess, (ASPCloseSessionRec *)commandRec); + break; + case aspCommandCommand: + DoSPCommand(sess, (ASPCommandRec *)commandRec); + break; + case aspWriteCommand: + DoSPWrite(sess, (ASPWriteRec *)commandRec); + break; + } + + if (commandRec->async & AT_ASYNC) { + if (sess->commandStatus == commandDone) { + CompleteCommand(sess); + } else { + commandRec->result = aspBusyErr; // indicate call in process + } + return; + } + + // if we're here, the call is synchronous -- we must complete it + + while (sess->commandStatus != commandDone) { + PollForData(sess); + } +} + +void DoSPGetStatus(Session *sess, ASPGetStatusRec *commandRec) { + sess->request.flags = DSI_REQUEST; + sess->request.command = DSIGetStatus; + sess->request.requestID = htons(sess->nextRequestID++); + sess->request.writeOffset = 0; + sess->request.totalDataLength = 0; + sess->replyBuf = (void*)commandRec->bufferAddr; + sess->replyBufLen = commandRec->bufferLength; + + SendDSIMessage(sess, &sess->request, NULL); +} + +void DoSPOpenSession(Session *sess, ASPOpenSessionRec *commandRec) { + // TODO +} + +void DoSPCloseSession(Session *sess, ASPCloseSessionRec *commandRec) { + // TODO +} + +void DoSPCommand(Session *sess, ASPCommandRec *commandRec) { + // TODO +} + +void DoSPWrite(Session *sess, ASPWriteRec *commandRec) { + // TODO +} + +// Fill in any necessary data in the ASP command rec for a successful return +void FinishASPCommand(Session *sess) { + LongWord dataLength; + + dataLength = ntohl(sess->reply.totalDataLength); + if (dataLength > 0xFFFF) { + // The IIgs ASP interfaces can't represent lengths over 64k. + // This should be detected as an error earlier, but let's make sure. + sess->spCommandRec->result = aspSizeErr; + return; + } + + switch(sess->spCommandRec->command) { + case aspGetStatusCommand: + ((ASPGetStatusRec*)(sess->spCommandRec))->dataLength = dataLength; + break; + case aspOpenSessionCommand: + // TODO set session ref num + break; + case aspCloseSessionCommand: + ((ASPCloseSessionRec*)(sess->spCommandRec))->refNum = + (sess - &sessionTbl[0]) + SESSION_NUM_START; + break; + case aspCommandCommand: + ((ASPCommandRec*)(sess->spCommandRec))->cmdResult = + sess->reply.errorCode; + ((ASPCommandRec*)(sess->spCommandRec))->replyLength = dataLength; + break; + case aspWriteCommand: + // TODO + break; + } + + CompleteCommand(sess); +} + +/* Actions to complete a command, whether successful or not */ +void CompleteCommand(Session *sess) { + if (sess->spCommandRec->command == aspGetStatusCommand + || sess->spCommandRec->command == aspCloseSessionCommand) + { + EndTCPConnection(sess); + } + + // TODO call completion routine + + memset(sess, 0, sizeof(*sess)); +} diff --git a/aspinterface.h b/aspinterface.h new file mode 100644 index 0000000..c1fbd11 --- /dev/null +++ b/aspinterface.h @@ -0,0 +1,20 @@ +#ifndef ASPINTERFACE_H +#define ASPINTERFACE_H + +#include "session.h" + +/* async flag values */ +#define AT_SYNC 0 +#define AT_ASYNC 0x80 + +#define aspBusyErr 0x07FF /* temp result code for async call in process */ + +#define SESSION_NUM_START 0xF8 +#define MAX_SESSIONS (256 - SESSION_NUM_START) + +extern Session sessionTbl[MAX_SESSIONS]; + +void FinishASPCommand(Session *sess); +void DispatchASPCommand(SPCommandRec *commandRec); + +#endif diff --git a/atipmapping.c b/atipmapping.c new file mode 100644 index 0000000..50beed9 --- /dev/null +++ b/atipmapping.c @@ -0,0 +1,3 @@ +#pragma noroot + +struct ATIPMapping atipMapping; diff --git a/atipmapping.h b/atipmapping.h new file mode 100644 index 0000000..024cb3c --- /dev/null +++ b/atipmapping.h @@ -0,0 +1,17 @@ +#ifndef ATIPMAPPING_H +#define ATIPMAPPING_H + +typedef struct ATIPMapping { + /* AppleTalk address/socket */ + Word networkNumber; /* in network byte order */ + Byte node; + Byte socket; + + /* IP address/port */ + LongWord ipAddr; + Word port; +} ATIPMapping; + +extern struct ATIPMapping atipMapping; + +#endif diff --git a/dsi.c b/dsi.c new file mode 100644 index 0000000..b4185c4 --- /dev/null +++ b/dsi.c @@ -0,0 +1,182 @@ +#pragma noroot + +#include +#include +#include +#include +#include "dsiproto.h" +#include "session.h" +#include "readtcp.h" +#include "dsi.h" +#include "endian.h" +#include "aspinterface.h" + +static DSIRequestHeader tickleRequestRec = { + DSI_REQUEST, /* flags */ + DSITickle, /* command */ + 0, /* requestID - set later */ + 0, /* writeOffset */ + 0, /* totalDataLength */ + 0 /* reserved */ +}; + +/* Actually a reply, but use DSIRequestHeader type so we can send it. */ +static DSIRequestHeader attentionReplyRec = { + DSI_REPLY, /* flags */ + DSIAttention, /* command */ + 0, /* requestID - set later */ + 0, /* errorCode */ + 0, /* totalDataLength */ + 0 /* reserved */ +}; + +void FlagFatalError(Session *sess, Word errorCode) { + sess->dsiStatus = error; + if (errorCode) { + if (sess->commandStatus == commandPending) { + sess->spCommandRec->result = errorCode; + } + } else { + // TODO deduce error code from Marinetti errors + } + // TODO close TCP connection, anything else? + + // call completion routing if needed + + sess->commandStatus = commandDone; +} + + +void SendDSIMessage(Session *sess, DSIRequestHeader *header, void *payload) { + Boolean hasData; + + hasData = header->totalDataLength != 0; + + sess->tcperr = TCPIPWriteTCP(sess->ipid, (void*)header, + DSI_HEADER_SIZE, + !hasData, FALSE); + sess->toolerr = toolerror(); + if (sess->tcperr || sess->toolerr) { + FlagFatalError(sess, 0); + return; + } + + if (hasData) { + sess->tcperr = TCPIPWriteTCP(sess->ipid, payload, + ntohl(header->totalDataLength), + TRUE, FALSE); + sess->toolerr = toolerror(); + if (sess->tcperr || sess->toolerr) { + FlagFatalError(sess, 0); + return; + } + } +} + + +void PollForData(Session *sess) { + ReadStatus rs; + LongWord dataLength; + + if (sess->dsiStatus != awaitingHeader && sess->dsiStatus != awaitingPayload) + return; + +top: + rs = TryReadTCP(sess); + if (rs != rsDone) { + if (rs == rsError) FlagFatalError(sess, 0); + return; + } + + // If we're here, we successfully read something. + + if (sess->dsiStatus == awaitingHeader) { + if (sess->reply.totalDataLength != 0) { + dataLength = ntohl(sess->reply.totalDataLength); + + if (sess->commandStatus == commandPending + && sess->reply.flags == DSI_REPLY + && sess->reply.requestID == sess->request.requestID + && sess->reply.command == sess->request.command) + { + if (dataLength <= sess->replyBufLen) { + InitReadTCP(sess, dataLength, sess->replyBuf); + sess->dsiStatus = awaitingPayload; + goto top; + } else { + // handle data too long + sess->junkBuf = malloc(dataLength); + if (sess->junkBuf == NULL) { + FlagFatalError(sess, atMemoryErr); + return; + } else { + InitReadTCP(sess, dataLength, sess->junkBuf); + sess->dsiStatus = awaitingPayload; + goto top; + } + } + } + else if (sess->reply.flags == DSI_REQUEST + && sess->reply.command == DSIAttention + && dataLength <= DSI_ATTENTION_QUANTUM) + { + InitReadTCP(sess, DSI_ATTENTION_QUANTUM, &sess->attentionCode); + sess->dsiStatus = awaitingPayload; + goto top; + } + else + { + FlagFatalError(sess, aspSizeErr); + return; + } + } + } + + // If we're here, we got a full message. + + // Handle a command that is now done, if any. + if (sess->commandStatus == commandPending + && sess->reply.flags == DSI_REPLY + && sess->reply.requestID == sess->request.requestID + && sess->reply.command == sess->request.command) + { + if (sess->junkBuf != NULL) { + free(sess->junkBuf); + sess->junkBuf = NULL; + if (sess->reply.command == DSIOpenSession) { + // We ignore the DSIOpenSession options for now. + // Maybe we should do something with them? + FinishASPCommand(sess); + } else { + sess->spCommandRec->result = aspSizeErr; + } + } + + // TODO correct logic for all cases + FinishASPCommand(sess); + sess->commandStatus = commandDone; + return; + } + //Handle a request from the server + else if (sess->reply.flags == DSI_REQUEST) + { + if (sess->reply.command == DSIAttention) { + attentionReplyRec.requestID = sess->reply.requestID; + SendDSIMessage(sess, &attentionReplyRec, NULL); + //TODO call attention routine. + } else if (sess->reply.command == DSICloseSession) { + // TODO handle close + } else if (sess->reply.command == DSITickle) { + tickleRequestRec.requestID = htons(sess->nextRequestID++); + SendDSIMessage(sess, &tickleRequestRec, NULL); + } else { + FlagFatalError(sess, aspNetworkErr); + return; + } + } + else + { + FlagFatalError(sess, aspNetworkErr); + return; + } +} diff --git a/dsi.h b/dsi.h new file mode 100644 index 0000000..e2a5ffa --- /dev/null +++ b/dsi.h @@ -0,0 +1,10 @@ +#ifndef DSI_H +#define DSI_H + +#include "dsiproto.h" +#include "session.h" + +void SendDSIMessage(Session *sess, DSIRequestHeader *header, void *payload); +void PollForData(Session *sess); + +#endif diff --git a/dsiproto.h b/dsiproto.h new file mode 100644 index 0000000..8cc764f --- /dev/null +++ b/dsiproto.h @@ -0,0 +1,42 @@ +#ifndef DSIPROTO_H +#define DSIPROTO_H + +#include + +typedef struct DSIRequestHeader { + Byte flags; + Byte command; + Word requestID; + LongWord writeOffset; + LongWord totalDataLength; + LongWord reserved; +} DSIRequestHeader; + +typedef struct DSIReplyHeader { + Byte flags; + Byte command; + Word requestID; + LongWord errorCode; + LongWord totalDataLength; + LongWord reserved; +} DSIReplyHeader; + +#define DSI_HEADER_SIZE 16 + +/* flags values */ +#define DSI_REQUEST 0 +#define DSI_REPLY 1 + +/* DSI command codes */ +#define DSICloseSession 1 +#define DSICommand 2 +#define DSIGetStatus 3 +#define DSIOpenSession 4 +#define DSITickle 5 +#define DSIWrite 6 +#define DSIAttention 8 + +/* The attention quantum supported by this implementation */ +#define DSI_ATTENTION_QUANTUM 2 + +#endif diff --git a/dsitest.c b/dsitest.c new file mode 100644 index 0000000..d3c6452 --- /dev/null +++ b/dsitest.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include "aspinterface.h" +#include "atipmapping.h" + +ASPGetStatusRec commandRec; +Byte replyBuffer[1024]; + +int main(int argc, char **argv) +{ + Boolean loadedTCP = FALSE; + Boolean startedTCP = FALSE; + cvtRec myCvtRec; + int i; + + TLStartUp(); + if (!TCPIPStatus()) { + LoadOneTool(54, 0x0300); /* load Marinetti 3.0+ */ + if (toolerror()) + goto error; + loadedTCP = TRUE; + TCPIPStartUp(); + if (toolerror()) + goto error; + startedTCP = TRUE; + } + + atipMapping.networkNumber = 0xFFFF; + atipMapping.node = 0xFF; + atipMapping.socket = 0xFF; + + TCPIPConvertIPCToHex(&myCvtRec, argv[1]); + atipMapping.ipAddr = myCvtRec.cvtIPAddress; + atipMapping.port = 548; + + // Do the call + commandRec.async = AT_SYNC; + commandRec.command = aspGetStatusCommand; + commandRec.completionPtr = 0; + commandRec.slsNet = atipMapping.networkNumber; + commandRec.slsNode = atipMapping.node; + commandRec.slsSocket = atipMapping.socket; + commandRec.bufferLength = sizeof(replyBuffer); + commandRec.bufferAddr = (LongWord)&replyBuffer; + + DispatchASPCommand((SPCommandRec *)&commandRec); + + for (i=0; i= ' ' && replyBuffer[i] <= 126) + printf("%c", replyBuffer[i]); + else + printf(" "); + } + printf("\n"); + + +error: + if (startedTCP) + TCPIPShutDown(); + if (loadedTCP) + UnloadOneTool(54); + TLShutDown(); + +} diff --git a/endian.asm b/endian.asm new file mode 100644 index 0000000..5e180e3 --- /dev/null +++ b/endian.asm @@ -0,0 +1,32 @@ + case on + +htons start +ntohs entry + ply + phb + plx + pla + xba + phx + plb + phy + rtl + end + +htonl start +ntohl entry + lda 4,s + xba + tax + lda 6,s + xba + tay + phb + pla + sta 3,s + pla + sta 3,s + plb + tya + rtl + end diff --git a/endian.h b/endian.h new file mode 100644 index 0000000..c901a40 --- /dev/null +++ b/endian.h @@ -0,0 +1,17 @@ +#ifndef ENDIAN_H +#define ENDIAN_H + +#include + +/* Undefine these in case they're defined as macros */ +#undef htons +#undef ntohs +#undef htonl +#undef ntohl + +Word htons(Word); +Word ntohs(Word); +LongWord htonl(LongWord); +LongWord ntohl(LongWord); + +#endif diff --git a/endiantest.c b/endiantest.c new file mode 100644 index 0000000..805402e --- /dev/null +++ b/endiantest.c @@ -0,0 +1,8 @@ +#include "endian.h" +#include + +int main(void) +{ + printf("%lx\n", htonl(0x12345678)); + printf("%x\n", htons(0xabcd)); +} diff --git a/readtcp.c b/readtcp.c new file mode 100644 index 0000000..f156495 --- /dev/null +++ b/readtcp.c @@ -0,0 +1,38 @@ +#pragma noroot + +#include "readtcp.h" +#include "session.h" +#include +#include + +#define buffTypePointer 0x0000 /* For TCPIPReadTCP() */ +#define buffTypeHandle 0x0001 +#define buffTypeNewHandle 0x0002 + +void InitReadTCP(Session *sess, LongWord readCount, void *readPtr) { + sess->readCount = readCount; + sess->readPtr = readPtr; +} + + +ReadStatus TryReadTCP(Session *sess) { + rrBuff rrBuff; + + TCPIPPoll(); + sess->tcperr = TCPIPReadTCP(sess->ipid, buffTypePointer, (Ref)sess->readPtr, + sess->readCount, &rrBuff); + sess->toolerr = toolerror(); + if (sess->tcperr || sess->toolerr) { + sess->dsiStatus = error; + return rsError; + } + + sess->readCount -= rrBuff.rrBuffCount; + sess->readPtr += rrBuff.rrBuffCount; + + if (sess->readCount == 0) { + return rsDone; + } else { + return rsWaiting; + } +} diff --git a/readtcp.h b/readtcp.h new file mode 100644 index 0000000..e1b58ed --- /dev/null +++ b/readtcp.h @@ -0,0 +1,15 @@ +#ifndef READTCP_H +#define READTCP_H + +#include "session.h" + +typedef enum ReadStatus { + rsDone, + rsWaiting, + rsError +} ReadStatus; + +void InitReadTCP(Session *sess, LongWord readCount, void *readPtr); +ReadStatus TryReadTCP(Session *sess); + +#endif diff --git a/session.h b/session.h new file mode 100644 index 0000000..9e6809c --- /dev/null +++ b/session.h @@ -0,0 +1,65 @@ +#ifndef SESSION_H +#define SESSION_H + +#include +#include "dsiproto.h" + +/* Common portion of records for the various SP... commands */ +typedef struct SPCommandRec { + Byte async; + Byte command; + Word result; + /* Below are in records for several ASP commands, but not all. */ + LongWord completionPtr; + Byte refNum; +} SPCommandRec; + +typedef enum DSISessionStatus { + unused = 0, + awaitingHeader, + awaitingPayload, + error +} DSISessionStatus; + +typedef enum CommandStatus { + noCommand = 0, + commandPending, + commandDone +} CommandStatus; + +typedef struct Session { + /* Marinetti TCP connection status */ + Word ipid; + Boolean tcpLoggedIn; + + DSISessionStatus dsiStatus; + + /* Information on command currently being processed, if any. */ + CommandStatus commandStatus; + SPCommandRec *spCommandRec; + DSIRequestHeader request; + + DSIReplyHeader reply; + + Word nextRequestID; + + /* Buffer to hold reply payload data */ + void *replyBuf; + Word replyBufLen; + + /* Buffer to hold unusable payload data that doesn't fit in replyBuf */ + void *junkBuf; + + /* ReadTCP status */ + LongWord readCount; + Byte *readPtr; + + /* Set by DSIAttention */ + Word attentionCode; + + /* Marinetti error codes, both the tcperr* value and any tool error */ + Word tcperr; + Word toolerr; +} Session; + +#endif diff --git a/tcpconnection.c b/tcpconnection.c new file mode 100644 index 0000000..a4327f8 --- /dev/null +++ b/tcpconnection.c @@ -0,0 +1,78 @@ +#pragma noroot + +#include +#include +#include +#include +#include "atipmapping.h" +#include "session.h" + +/* Make a TCP connection to the address mapped to the specified AT address. + * sess->spCommandRec should be an SPGetStatus or SPOpenSession command. + * + * On success, returns TRUE and sets sess->ipid. + * On failure, returns FALSE and sets commandRec->result to an error code. + */ +BOOLEAN StartTCPConnection(Session *sess) { + Word tcperr; + ASPOpenSessionRec *commandRec; + srBuff mySRBuff; + + commandRec = (ASPOpenSessionRec *)sess->spCommandRec; + + if (commandRec->slsNet != atipMapping.networkNumber + || commandRec->slsNode != atipMapping.node + || commandRec->slsSocket != atipMapping.socket) + { + commandRec->result = aspNetworkErr; + return FALSE; + } + + if (TCPIPGetConnectStatus() == FALSE) { + TCPIPConnect(NULL); + if (toolerror()) { + commandRec->result = aspNetworkErr; + return FALSE; + } + } + + sess->ipid = + TCPIPLogin(userid(), atipMapping.ipAddr, atipMapping.port, 0, 0x40); + if (toolerror()) { + commandRec->result = aspNetworkErr; + return FALSE; + } + + tcperr = TCPIPOpenTCP(sess->ipid); + if (toolerror()) { + TCPIPLogout(sess->ipid); + commandRec->result = aspNetworkErr; + return FALSE; + } else if (tcperr != tcperrOK) { + TCPIPLogout(sess->ipid); + commandRec->result = aspNoRespErr; + return FALSE; + } + + do { + TCPIPPoll(); + TCPIPStatusTCP(sess->ipid, &mySRBuff); + } while (mySRBuff.srState == TCPSSYNSENT); + if (mySRBuff.srState != TCPSESTABLISHED) { + TCPIPAbortTCP(sess->ipid); + TCPIPLogout(sess->ipid); + commandRec->result = aspNoRespErr; + return FALSE; + } + + sess->tcpLoggedIn = TRUE; + return TRUE; +} + +void EndTCPConnection(Session *sess) { + if (sess->tcpLoggedIn) { + TCPIPAbortTCP(sess->ipid); + TCPIPLogout(sess->ipid); + sess->tcpLoggedIn = FALSE; + } +} diff --git a/tcpconnection.h b/tcpconnection.h new file mode 100644 index 0000000..e993673 --- /dev/null +++ b/tcpconnection.h @@ -0,0 +1,9 @@ +#ifndef TCPCONNECTION_H +#define TCPCONNECTION_H + +#include "session.h" + +BOOLEAN StartTCPConnection(Session *sess); +void EndTCPConnection(Session *sess); + +#endif