Listener/Listener/main.c

711 lines
17 KiB
C

/*
* main.c
* Listener
*
* Created by Jeremy Rand on 2021-07-16.
* Copyright (c) 2021 ___ORGANIZATIONNAME___. All rights reserved.
*
*/
#pragma nda NDAOpen NDAClose NDAAction NDAInit 2 0x03FF " Listener\\H**"
#include <orca.h>
#include <GSOS.h>
#include <QuickDraw.h>
#include <Window.h>
#include <Desk.h>
#include <Event.h>
#include <Resources.h>
#include <MiscTool.h>
#include <Memory.h>
#include <Loader.h>
#include <locator.h>
#include <tcpip.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "main.h"
// Defines
#define LISTEN_PORT 19026
#define LISTEN_STATE_MSG 1
#define LISTEN_TEXT_MSG 2
#define LISTEN_SEND_MORE 3
#define WINDOW_CHAR_WIDTH 50
// Typedefs
typedef enum tListenState {
LISTEN_STATE_START = 0,
LISTEN_STATE_NETWORK_UNCONNECTED,
LISTEN_STATE_NETWORK_CONNECTED,
LISTEN_STATE_AWAITING_CONNECTION,
LISTEN_STATE_AWAITING_ESTABLISH,
LISTEN_STATE_AWAITING_MSG_HEADER,
LISTEN_STATE_AWAITING_TEXT,
LISTEN_STATE_ERROR,
NUM_LISTEN_STATES
} tListenState;
typedef struct tMessageHeader {
uint16_t messageType;
uint16_t messageArg;
} tMessageHeader;
typedef struct tTextList tTextList;
typedef struct tTextListHeader {
struct tTextList * next;
uint16_t size;
uint16_t position;
} tTextListHeader;
typedef struct tTextList {
tTextListHeader header;
char text[1];
} tTextList;
typedef struct tGlobals {
BOOLEAN ndaActive;
BOOLEAN tcpipStarted;
BOOLEAN networkConnected;
BOOLEAN hasListenIpid;
BOOLEAN hasConnIpid;
GrafPortPtr winPtr;
tListenState state;
Word listenIpid;
Word connIpid;
Word position;
tTextList * textListHead;
tTextList * textListTail;
tMessageHeader messageHeader;
tTextList * textTransfer;
char line1[WINDOW_CHAR_WIDTH];
char line2[WINDOW_CHAR_WIDTH];
char line3[WINDOW_CHAR_WIDTH];
} tGlobals;
typedef void (*tStateHandler)(void);
// Forward declarations
void handleStartState(void);
void handleNetworkUnconnectedState(void);
void handleNetworkConnectedState(void);
void handleAwaitingConnectionState(void);
void handleAwaitingEstablishState(void);
void handleAwaitingMsgHeaderState(void);
void handleAwaitingTextState(void);
void handleErrorState(void);
// Globals
static tGlobals * globals = NULL;
static unsigned int userId;
static tStateHandler stateHandlers[NUM_LISTEN_STATES] = {
handleStartState,
handleNetworkUnconnectedState,
handleNetworkConnectedState,
handleAwaitingConnectionState,
handleAwaitingEstablishState,
handleAwaitingMsgHeaderState,
handleAwaitingTextState,
handleErrorState,
};
static char waitingMessage[48] = "Waiting for connection";
static char * stateMessages[NUM_LISTEN_STATES] = {
"Starting network tools",
"Connecting to network",
"Creating listen socket",
waitingMessage,
"Establishing connection",
"Connected to device",
"Receiving text",
""
};
// Implementation
void delayToNextTick(void)
{
LongWord oldTickCounter = GetTick();
while (oldTickCounter == GetTick())
;
}
void closeConnection(void)
{
static srBuff srBuffer;
int counter;
if (globals->hasConnIpid) {
TCPIPCloseTCP(globals->connIpid);
for (counter = 0; counter < 20; counter++)
{
TCPIPPoll();
delayToNextTick();
TCPIPStatusTCP(globals->connIpid, &srBuffer);
if ((srBuffer.srState == TCPSCLOSED) ||
(srBuffer.srState == TCPSTIMEWAIT))
break;
}
if ((srBuffer.srState != TCPSCLOSED) &
(srBuffer.srState != TCPSTIMEWAIT))
TCPIPAbortTCP(globals->connIpid);
TCPIPLogout(globals->connIpid);
}
globals->hasConnIpid = FALSE;
}
void teardownNetwork(void)
{
closeConnection();
if (globals->hasListenIpid) {
TCPIPCloseTCP(globals->listenIpid);
TCPIPLogout(globals->listenIpid);
}
globals->hasListenIpid = FALSE;
if (globals->networkConnected)
TCPIPDisconnect(TRUE, NULL);
globals->networkConnected = FALSE;
if (globals->tcpipStarted)
TCPIPShutDown();
globals->tcpipStarted = FALSE;
}
void freeMemory(void)
{
tTextList * textList = globals->textListHead;
if (globals->textTransfer != NULL)
free(globals->textTransfer);
while (textList != NULL) {
tTextList * prev = textList;
textList = textList->header.next;
free(prev);
}
free(globals);
globals = NULL;
}
void NDAClose(void)
{
if (globals == NULL)
return;
if (globals->ndaActive) {
CloseWindow(globals->winPtr);
globals->winPtr = NULL;
globals->ndaActive = FALSE;
ResourceShutDown();
}
teardownNetwork();
freeMemory();
}
void NDAInit(int code)
{
/* When code is 1, this is tool startup, otherwise tool
* shutdown.
*/
if (code) {
userId = MMStartUp();
}
}
void InvalidateWindow(void)
{
Rect frame;
SetPort(globals->winPtr);
GetPortRect(&frame);
frame.v2 -= frame.v1;
frame.h2 -= frame.h1;
frame.h1 = 0;
frame.v1 = 0;
InvalRect(&frame);
}
#pragma databank 1
void DrawContents(void)
{
Rect frame;
GetPortRect(&frame);
frame.v2 -= frame.v1;
frame.h2 -= frame.h1;
frame.h1 = 0;
frame.v1 = 0;
EraseRect(&frame);
PenNormal();
MoveTo(7,10);
DrawCString(globals->line1);
MoveTo(7,20);
DrawCString(globals->line2);
MoveTo(7,30);
DrawCString(globals->line3);
}
#pragma databank 0
GrafPortPtr NDAOpen(void)
{
unsigned int oldResourceApp;
LevelRecGS levelDCB;
unsigned int oldLevel;
SysPrefsRecGS prefsDCB;
unsigned int oldPrefs;
if (globals == NULL) {
globals = malloc(sizeof(*globals));
memset(globals, 0, sizeof(*globals));
}
if (globals->ndaActive)
return NULL;
levelDCB.pCount = 2;
GetLevelGS(&levelDCB);
oldLevel = levelDCB.level;
levelDCB.level = 0;
SetLevelGS(&levelDCB);
prefsDCB.pCount = 1;
GetSysPrefsGS(&prefsDCB);
oldPrefs = prefsDCB.preferences;
prefsDCB.preferences = (prefsDCB.preferences & 0x1fff) | 0x8000;
SetSysPrefsGS(&prefsDCB);
oldResourceApp = OpenResourceFileByID(readEnable, userId);
globals->winPtr = NewWindow2((Pointer)"\p Listener ", 0, DrawContents, NULL, 0x02, windowRes, rWindParam1);
SetSysWindow(globals->winPtr);
ShowWindow(globals->winPtr);
SelectWindow(globals->winPtr);
globals->ndaActive = TRUE;
prefsDCB.preferences = oldPrefs;
SetSysPrefsGS(&prefsDCB);
levelDCB.level = oldLevel;
SetLevelGS(&levelDCB);
SetCurResourceApp(oldResourceApp);
return globals->winPtr;
}
void enterErrorState(char * errorString, Word errorCode)
{
strcpy(globals->line1, errorString);
sprintf(globals->line2, " Error code = $%04x", errorCode);
InvalidateWindow();
teardownNetwork();
globals->state = LISTEN_STATE_ERROR;
}
void newState(tListenState state)
{
strcpy(globals->line1, stateMessages[state]);
InvalidateWindow();
globals->state = state;
}
void handleStartState(void)
{
LoadOneTool(54, 0x200); // Load Marinetti
if (toolerror()) {
enterErrorState("Unable to load Marinetti", toolerror());
return;
}
if (!TCPIPStatus()) {
TCPIPStartUp();
if (toolerror()) {
enterErrorState("Unable to start Marinetti", toolerror());
return;
}
globals->tcpipStarted = TRUE;
}
if (TCPIPGetConnectStatus()) {
newState(LISTEN_STATE_NETWORK_CONNECTED);
} else {
newState(LISTEN_STATE_NETWORK_UNCONNECTED);
}
}
void handleNetworkUnconnectedState(void)
{
TCPIPPoll();
TCPIPConnect(NULL);
if ((!toolerror()) &&
(TCPIPGetConnectStatus())) {
globals->networkConnected = TRUE;
newState(LISTEN_STATE_NETWORK_CONNECTED);
} else {
enterErrorState("Unable to connect to network", toolerror());
}
}
void handleNetworkConnectedState(void)
{
Word error;
TCPIPPoll();
Long ipAddress = TCPIPGetMyIPAddress();
if (!toolerror()) {
strcpy(waitingMessage, "Waiting for connection at ");
TCPIPConvertIPToCASCII(ipAddress, &(waitingMessage[26]), 0);
} else {
strcpy(waitingMessage, "Waiting for connection");
}
globals->listenIpid = TCPIPLogin(userId, 0, 0, 0, 64);
if (toolerror()) {
enterErrorState("Unable to create socket", toolerror());
return;
}
TCPIPSetSourcePort(globals->listenIpid, LISTEN_PORT);
if (toolerror()) {
enterErrorState("Unable to set port number", toolerror());
return;
}
error = TCPIPListenTCP(globals->listenIpid);
if (error != terrOK) {
TCPIPLogout(globals->listenIpid);
enterErrorState("Unable to listen on socket", error);
return;
}
globals->hasListenIpid = TRUE;
newState(LISTEN_STATE_AWAITING_CONNECTION);
}
void handleAwaitingConnectionState(void)
{
TCPIPPoll();
globals->connIpid = TCPIPAcceptTCP(globals->listenIpid, 0);
switch (toolerror()) {
case terrOK:
globals->hasConnIpid = TRUE;
newState(LISTEN_STATE_AWAITING_ESTABLISH);
break;
case terrNOINCOMING:
break;
default:
enterErrorState("Unable to accept connection", toolerror());
break;
}
}
void handleAwaitingEstablishState(void)
{
static srBuff srBuffer;
TCPIPPoll();
TCPIPStatusTCP(globals->connIpid, &srBuffer);
if (toolerror()) {
enterErrorState("Unable to get connection state", toolerror());
return;
}
switch (srBuffer.srState) {
case TCPSSYNSENT:
case TCPSSYNRCVD:
break;
case TCPSESTABLISHED:
newState(LISTEN_STATE_AWAITING_MSG_HEADER);
globals->position = 0;
break;
default:
closeConnection();
newState(LISTEN_STATE_AWAITING_CONNECTION);
break;
}
}
void handleAwaitingMsgHeaderState(void)
{
static srBuff srBuffer;
static rrBuff readResponseBuf;
tTextList * textTransfer;
TCPIPPoll();
TCPIPStatusTCP(globals->connIpid, &srBuffer);
if (toolerror()) {
enterErrorState("Unable to get connection state", toolerror());
return;
}
if (srBuffer.srState != TCPSESTABLISHED) {
closeConnection();
newState(LISTEN_STATE_AWAITING_CONNECTION);
globals->line2[0] = '\0';
return;
}
Word error = TCPIPReadTCP(globals->connIpid, 0,
((uint32_t)(&(globals->messageHeader))) + globals->position,
sizeof(globals->messageHeader) - globals->position,
&readResponseBuf);
if (error != tcperrOK) {
enterErrorState("Unable to read from connection", error);
return;
}
globals->position += readResponseBuf.rrBuffCount;
if (globals->position < sizeof(globals->messageHeader))
return;
switch (globals->messageHeader.messageType) {
case LISTEN_STATE_MSG:
if (globals->messageHeader.messageArg)
strcpy(globals->line2, " Listening...");
else
globals->line2[0] = '\0';
InvalidateWindow();
globals->position = 0;
break;
case LISTEN_TEXT_MSG:
textTransfer = malloc(sizeof(tTextListHeader) + globals->messageHeader.messageArg);
if (textTransfer == NULL) {
enterErrorState("Unable to get memory for text", 0);
return;
}
globals->textTransfer = textTransfer;
textTransfer->header.next = NULL;
textTransfer->header.size = globals->messageHeader.messageArg;
textTransfer->header.position = 0;
newState(LISTEN_STATE_AWAITING_TEXT);
if (FrontWindow() == globals->winPtr)
SendBehind((GrafPortPtr)toBottom, globals->winPtr);
break;
default:
enterErrorState("Unexpected message type", globals->messageHeader.messageType);
}
}
void handleAwaitingTextState(void)
{
static srBuff srBuffer;
static rrBuff readResponseBuf;
tTextList * textTransfer = globals->textTransfer;
TCPIPPoll();
TCPIPStatusTCP(globals->connIpid, &srBuffer);
if (toolerror()) {
enterErrorState("Unable to get connection state", toolerror());
return;
}
if (srBuffer.srState != TCPSESTABLISHED) {
closeConnection();
newState(LISTEN_STATE_AWAITING_CONNECTION);
globals->line2[0] = '\0';
return;
}
Word error = TCPIPReadTCP(globals->connIpid, 0,
(uint32_t)(&(textTransfer->text[textTransfer->header.position])),
textTransfer->header.size - textTransfer->header.position,
&readResponseBuf);
if (error != tcperrOK) {
enterErrorState("Unable to read text from connection", error);
return;
}
textTransfer->header.position += readResponseBuf.rrBuffCount;
if (textTransfer->header.position < textTransfer->header.size)
return;
if (globals->textListTail != NULL) {
globals->textListTail->header.next = textTransfer;
} else {
globals->textListHead = textTransfer;
strcpy(globals->line3, " Typing...");
InvalidateWindow();
}
textTransfer->header.position = 0;
globals->textListTail = textTransfer;
globals->textTransfer = NULL;
newState(LISTEN_STATE_AWAITING_MSG_HEADER);
globals->position = 0;
}
void handleErrorState(void)
{
// Do nothing. Once we have entered the error state, then nothing more to do.
}
void runStateMachine(void)
{
stateHandlers[globals->state]();
}
void sendKey(void)
{
EventRecord eventRecord;
// If there is still a key down waiting to be processed, then don't send another one.
if (EventAvail(keyDownMask, &eventRecord))
return;
tTextList * textList = globals->textListHead;
PostEvent(keyDownEvt, 0x00C00000 | textList->text[textList->header.position]);
textList->header.position++;
if (textList->header.position < textList->header.size)
return;
if (textList == globals->textListTail) {
globals->textListTail = NULL;
globals->line3[0] = '\0';
InvalidateWindow();
}
globals->textListHead = textList->header.next;
free(textList);
// If there is no more text to type, let the other end know we are ready for more.
if ((globals->textListHead == NULL) &&
((globals->state == LISTEN_STATE_AWAITING_TEXT) ||
(globals->state == LISTEN_STATE_AWAITING_MSG_HEADER))) {
uint16_t msg = LISTEN_SEND_MORE;
TCPIPWriteTCP(globals->connIpid, (Pointer)&msg, sizeof(msg), FALSE, FALSE);
}
}
void HandleRun(void)
{
if (globals == NULL)
return;
runStateMachine();
if (globals->textListHead != NULL)
sendKey();
}
void HandleControl(EventRecord *event)
{
}
void HandleKey(EventRecord *event)
{
}
void HandleCursor(void)
{
}
void HandleMenu(int menuItem)
{
}
BOOLEAN NDAAction(EventRecord *sysEvent, int code)
{
static EventRecord localEvent;
unsigned int eventCode;
BOOLEAN result = FALSE;
if (globals == NULL)
return result;
switch (code) {
case runAction:
HandleRun();
break;
case eventAction:
BlockMove((Pointer)sysEvent, (Pointer)&localEvent, 16);
localEvent.wmTaskMask = 0x001FFFFF;
eventCode = TaskMasterDA(0, &localEvent);
switch(eventCode) {
case updateEvt:
BeginUpdate(globals->winPtr);
DrawContents();
EndUpdate(globals->winPtr);
break;
case wInControl:
HandleControl(&localEvent);
break;
case keyDownEvt:
case autoKeyEvt:
HandleKey(&localEvent);
break;
}
break;
case cursorAction:
HandleCursor();
break;
case cutAction:
case copyAction:
case pasteAction:
case clearAction:
result = TRUE;
HandleMenu(code);
break;
}
return result;
}