Initial version, with test program.

It can currently send a GetStatus request and get a response.
This commit is contained in:
Stephen Heumann 2017-03-18 18:44:11 -05:00
commit b5c3a29f37
17 changed files with 798 additions and 0 deletions

11
Makefile.mk Normal file
View File

@ -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)

179
aspinterface.c Normal file
View File

@ -0,0 +1,179 @@
#pragma noroot
#include <AppleTalk.h>
#include <string.h>
#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));
}

20
aspinterface.h Normal file
View File

@ -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

3
atipmapping.c Normal file
View File

@ -0,0 +1,3 @@
#pragma noroot
struct ATIPMapping atipMapping;

17
atipmapping.h Normal file
View File

@ -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

182
dsi.c Normal file
View File

@ -0,0 +1,182 @@
#pragma noroot
#include <AppleTalk.h>
#include <stdlib.h>
#include <orca.h>
#include <tcpip.h>
#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;
}
}

10
dsi.h Normal file
View File

@ -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

42
dsiproto.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef DSIPROTO_H
#define DSIPROTO_H
#include <types.h>
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

72
dsitest.c Normal file
View File

@ -0,0 +1,72 @@
#include <AppleTalk.h>
#include <locator.h>
#include <tcpip.h>
#include <stdio.h>
#include <orca.h>
#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<commandRec.dataLength;i++) {
printf("%02x ", replyBuffer[i]);
if ((i+1)%16 == 0) printf("\n");
}
printf("\n");
for (i=0; i<commandRec.dataLength;i++) {
if (replyBuffer[i] >= ' ' && replyBuffer[i] <= 126)
printf("%c", replyBuffer[i]);
else
printf(" ");
}
printf("\n");
error:
if (startedTCP)
TCPIPShutDown();
if (loadedTCP)
UnloadOneTool(54);
TLShutDown();
}

32
endian.asm Normal file
View File

@ -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

17
endian.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef ENDIAN_H
#define ENDIAN_H
#include <types.h>
/* 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

8
endiantest.c Normal file
View File

@ -0,0 +1,8 @@
#include "endian.h"
#include <stdio.h>
int main(void)
{
printf("%lx\n", htonl(0x12345678));
printf("%x\n", htons(0xabcd));
}

38
readtcp.c Normal file
View File

@ -0,0 +1,38 @@
#pragma noroot
#include "readtcp.h"
#include "session.h"
#include <tcpip.h>
#include <orca.h>
#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;
}
}

15
readtcp.h Normal file
View File

@ -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

65
session.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef SESSION_H
#define SESSION_H
#include <types.h>
#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

78
tcpconnection.c Normal file
View File

@ -0,0 +1,78 @@
#pragma noroot
#include <tcpip.h>
#include <stdlib.h>
#include <AppleTalk.h>
#include <orca.h>
#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;
}
}

9
tcpconnection.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef TCPCONNECTION_H
#define TCPCONNECTION_H
#include "session.h"
BOOLEAN StartTCPConnection(Session *sess);
void EndTCPConnection(Session *sess);
#endif