commit b5c3a29f37120387b993a9a0fe57e8cbd8638b9e Author: Stephen Heumann Date: Sat Mar 18 18:44:11 2017 -0500 Initial version, with test program. It can currently send a GetStatus request and get a response. 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