commit 56f1b1422f3faa7ec99359dce2716a68100841f4 Author: Kelvin Sherlock Date: Mon Nov 12 19:36:34 2018 -0500 initial check in. diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..c7d7974 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,17 @@ + +OBJ = o/main.a o/connection.a + + +dict : $(OBJ) + iix link o/main o/connection keep=$@ + + +o/main.a : main.c connection.h +o/connection.a : connection.c connection.h + + +o : + mkdir $@ + +o/%.a : %.c | o + iix compile $< keep=o/$* diff --git a/connection.c b/connection.c new file mode 100644 index 0000000..2796383 --- /dev/null +++ b/connection.c @@ -0,0 +1,270 @@ +#pragma optimize 79 +#pragma noroot +#pragma lint -1 + +#include +#include "Memory.h" + +#include "connection.h" +#include +//#include "s16debug.h" + + +static char pstring[256]; + + +static Word LoginAndOpen(Connection *buffer) +{ + Word ipid; + Word terr; + + if (buffer->displayPtr) + { + static char message[] = "\pConnecting to xxx.xxx.xxx.xxx:xxxxx"; + + Word length; + Word tmp; + + length = 15; + // first the ip addresss... + tmp = TCPIPConvertIPToCASCII(buffer->dnr.DNRIPaddress, message + length, 0); + length += tmp; + + message[length++] = ':'; + // now the port... + Int2Dec(buffer->port, message + length, 5, 0); + length += 5; + message[length] = 0; + message[0] = length; + buffer->displayPtr(message); + } + + ipid = TCPIPLogin( + buffer->memID, + buffer->dnr.DNRIPaddress, + buffer->port, + 0x0000, 0x0040); + + if (_toolErr) + { + buffer->state = kConnectionStateError; + return -1; + } + + terr = TCPIPOpenTCP(ipid); + if (_toolErr || terr) + { + TCPIPLogout(ipid); + buffer->state = kConnectionStateError; + buffer->terr = terr; + buffer->ipid = 0; + return -1; + } + + buffer->ipid = ipid; + buffer->state = kConnectionStateConnecting; + + return 0; +} + + +Word ConnectionPoll(Connection *buffer) +{ + + Word state; + if (!buffer) return -1; + state = buffer->state; + + if (state == 0) return -1; + if (state == kConnectionStateConnected) return 1; + if (state == kConnectionStateDisconnected) return 1; + if (state == kConnectionStateError) return -1; + + TCPIPPoll(); + + if (state == kConnectionStateDNR) + { + if (buffer->dnr.DNRstatus == DNR_OK) + { + return LoginAndOpen(buffer); + } + else if (buffer->dnr.DNRstatus != DNR_Pending) + { + buffer->state = kConnectionStateError; + if (buffer->displayPtr) + { + static char message[] = "\pDNR lookup failed: $xxxx"; + Int2Hex(buffer->dnr.DNRstatus, message + 21, 4); + buffer->displayPtr(message); + } + return -1; + } + } + + if (state == kConnectionStateConnecting || state == kConnectionStateDisconnecting) + { + Word terr; + static srBuff sr; + + terr = TCPIPStatusTCP(buffer->ipid, &sr); + + if (state == kConnectionStateDisconnecting) + { + // these are not errors. + if (terr == tcperrConClosing || terr == tcperrClosing) + terr = tcperrOK; + } + + if (terr || _toolErr) + { + //CloseAndLogout(buffer); + + //s16_debug_printf("terr = %04x tool error = %04x\n", terr, _toolErr); + //s16_debug_srbuff(&sr); + + TCPIPCloseTCP(buffer->ipid); + TCPIPLogout(buffer->ipid); + buffer->ipid = 0; + buffer->state = kConnectionStateError; + buffer->terr = terr; + return -1; + } + + if (sr.srState == TCPSESTABLISHED) // && state == kConnectionStateConnecting) + { + buffer->state = kConnectionStateConnected; + return 1; + } + + if (sr.srState == TCPSCLOSED || sr.srState == TCPSTIMEWAIT) + { + + //s16_debug_srbuff(&sr); + + TCPIPLogout(buffer->ipid); + buffer->ipid = 0; + buffer->state = kConnectionStateDisconnected; + return 1; + } + } + + return 0; +} + +Word ConnectionOpenC(Connection *buffer, const char *host, Word port) +{ + Word length; + + if (!host) return -1; + + length = strlen(host); + if (length > 255) return -1; + + pstring[0] = length & 0xff; + memcpy(pstring + 1, host, length); + + return ConnectionOpen(buffer, pstring, port); +} + +Word ConnectionOpenGS(Connection *buffer, const GSString255 *host, Word port) +{ + if (!host) return -1; + if (host->length > 255) return -1; + + pstring[0] = host->length & 0xff; + memcpy(pstring + 1, host->text, host->length); + + return ConnectionOpen(buffer, pstring, port); +} + +Word ConnectionOpen(Connection *buffer, const char *host, Word port) +{ + buffer->state = 0; + buffer->ipid = 0; + buffer->terr = 0; + buffer->port = port; + + if (!buffer || !*buffer || !host || !*host) return -1; + + // 1. check if we need to do DNR. + if (TCPIPValidateIPString(host)) + { + cvtRec cvt; + TCPIPConvertIPToHex(&cvt, host); + buffer->dnr.DNRIPaddress = cvt.cvtIPAddress; + buffer->dnr.DNRstatus = DNR_OK; // fake it. + + return LoginAndOpen(buffer); + } + // do dnr. + if (buffer->displayPtr) + { + static char message[256] = "\pDNR lookup: "; + BlockMove(host + 1, message + 13, host[0]); + message[0] = 13 + host[0]; + buffer->displayPtr(message); + } + + TCPIPDNRNameToIP(host, &buffer->dnr); + if (_toolErr) + { + buffer->state = kConnectionStateError; + if (buffer->displayPtr) + { + static char message[] = "\pDNR lookup tool error: $xxxx"; + Int2Hex(_toolErr, message + 25, 4); + buffer->displayPtr(message); + } + return -1; + } + buffer->state = kConnectionStateDNR; + return 0; +} + +void ConnectionInit(Connection *buffer, Word memID, ConnectionCallback displayPtr) +{ + buffer->memID = memID; + buffer->ipid = 0; + buffer->terr = 0; + buffer->state = 0; + buffer->port = 0; + buffer->dnr.DNRstatus = 0; + buffer->dnr.DNRIPaddress = 0; + buffer->displayPtr = displayPtr; +} + +Word ConnectionClose(Connection *buffer) +{ + Word state = buffer->state; + + // todo -- how do you close if not yet connected? + if (state == kConnectionStateConnected) + { + buffer->state = kConnectionStateDisconnecting; + buffer->terr = TCPIPCloseTCP(buffer->ipid); + + if (buffer->displayPtr) + { + static char message[] = "\pClosing connection: $0000"; + Int2Hex(buffer->terr, message + 22, 4); + buffer->displayPtr(message); + } + return 0; + } + + if (state == kConnectionStateDNR) + { + TCPIPCancelDNR(&buffer->dnr); + buffer->state = 0; + + if (buffer->displayPtr) + { + static char message[] = "\pDNR lookup canceled"; + buffer->displayPtr(message); + } + return 1; + } + + return -1; +} + diff --git a/connection.h b/connection.h new file mode 100644 index 0000000..ff260a9 --- /dev/null +++ b/connection.h @@ -0,0 +1,40 @@ +#ifndef __CONNECTION_H__ +#define __CONNECTION_H__ + +#ifndef __TCPIP__ +#include +#endif + +enum { + kConnectionStateDNR = 1, + kConnectionStateConnecting, + kConnectionStateConnected, + kConnectionStateDisconnecting, + kConnectionStateDisconnected, + kConnectionStateError +}; + +typedef void (*ConnectionCallback)(const char *message); +typedef struct Connection { + Word memID; + Word ipid; + Word terr; + Word state; + dnrBuffer dnr; + Word port; + ConnectionCallback displayPtr; +} Connection; + + + +void ConnectionInit(Connection *, Word memID, ConnectionCallback displayPtr); + +Word ConnectionOpen(Connection *, const char *host, Word port); +Word ConnectionOpenC(Connection *, const char *host, Word port); +Word ConnectionOpenGS(Connection *, const GSString255 *host, Word port); + +Word ConnectionClose(Connection *); +Word ConnectionPoll(Connection *); + + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..f20cbd6 --- /dev/null +++ b/main.c @@ -0,0 +1,371 @@ +#pragma optimize 79 +#pragma lint -1 + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "connection.h" + +#define IncBusy() asm { jsl 0xE10064 } +#define DecBusy() asm { jsl 0xE10068 } +#define Resched() asm { cop 0x7f } + +#define BusyFlag ((byte *)0xE100FFl) + + +// startup/shutdown flags. +enum { + kLoaded = 1, + kStarted = 2, + kConnected = 4, + + kLoadError = -1, + kVersionError = -2 +}; + +int StartUpTCP(displayPtr fx) +{ + word status; + word flags = 0; + + // TCPIP is an init, not a tool, so it should always + // be loaded. + + status = TCPIPStatus(); + if (_toolErr) + { + LoadOneTool(54, 0x0300); + if (_toolErr == toolVersionErr) return kVersionError; + if (_toolErr) return kLoadError; + + status = 0; + flags |= kLoaded; + } + + + // require 3.0b3 + if (TCPIPLongVersion() < 0x03006003) + { + if (flags & kLoaded) + UnloadOneTool(54); + + return kVersionError; + } + + if (!status) + { + TCPIPStartUp(); + if (_toolErr) return kLoadError; + flags |= kStarted; + } + + status = TCPIPGetConnectStatus(); + if (!status) + { + TCPIPConnect(fx); + flags |= kConnected; + } + + return flags; +} + +void ShutDownTCP(int flags, Boolean force, displayPtr fx) +{ + if (flags <= 0) return; + + if (flags & kConnected) + { + TCPIPDisconnect(force, fx); + if (_toolErr) return; + } + if (flags & kStarted) + { + TCPIPShutDown(); + if (_toolErr) return; + } + if (flags & kLoaded) + { + UnloadOneTool(54); + } +} + + +// #pragma databank [push | pop] would be nice... +#pragma databank 1 +pascal void DisplayCallback(const char *message) +{ + unsigned length; + + // message is a p-string. + length = message ? message[0] : 0; + if (!length) return; + + fprintf(stderr, "%.*s\n", length, message + 1); +} +#pragma databank 0 + + +int ConnectLoop(char *host, Word port, Connection *connection) +{ + LongWord qtick; + + ConnectionInit(connection, MMStartUp(), DisplayCallback); + ConnectionOpenC(connection, host, port); + + // 30 second timeout. + qtick = GetTick() + 30 * 60; + while (!ConnectionPoll(connection)) + { + if (GetTick() >= qtick) + { + fprintf(stderr, "Connection timed out.\n"); + + IncBusy(); + TCPIPAbortTCP(connection->ipid); + TCPIPLogout(connection->ipid); + DecBusy(); + + return 0; + } + } + + if (connection->state != kConnectionStateConnected) + { + fprintf(stderr, "Unable to open host: %s:%u\n", + host, + port); + return 0; + } + + return 1; +} + + +int CloseLoop(Connection *connection) +{ + ConnectionClose(connection); + + while (!ConnectionPoll(connection)) ; // wait for it to close. + + return 1; +} + + + +static char buffer[512]; + +int ReadLineSync(word ipid) { + + LongWord qtick; + static srBuff sr; + static rlrBuff rlr; + unsigned x; + word terr; + + + //asm { brk 0xea } + buffer[0] = 0; + + qtick = GetTick() + 30 * 60; + for(;;) { + Word terr; + TCPIPPoll(); + terr = TCPIPStatusTCP(ipid, &sr); + if (sr.srRcvQueued) break; + + if (terr) return -1; + if (GetTick() >= qtick) { + fprintf(stderr, "Read timed out.\n"); + return -1; + } + } + for(;;) { + terr = TCPIPReadLineTCP(ipid, "\p\r\n", 0x0000, (Ref)&buffer, sizeof(buffer) - 1, &rlr); + /* if (terr) return -1; */ /* nb - marinetti return bug */ + if (!rlr.rlrIsDataFlag) { + + if (GetTick() >= qtick) { + fprintf(stderr, "Read timed out.\n"); + return -1; + } + TCPIPPoll(); + continue; + } + break; + } + x = rlr.rlrBuffCount; + buffer[x] = 0; + return x; +} + +int status(void) { + unsigned x; + if (sscanf(buffer, "%u", &x) == 1) { + fprintf(stderr, "status: %d\n", x); + return x; + } + return -1; +} + +int client(word ipid) { + word terr; + int ok; + + /* also checks for 220 header. */ + + ok = ReadLineSync(ipid); + if (ok < 0) return ok; + ok = status(); + if (ok != 220) return -1; + + terr = TCPIPWriteTCP(ipid, "CLIENT dict-iigs\r\n", 18, 1, 0); + if (terr != 0) fprintf(stderr, "terr: %04x\n", terr); + + ok = ReadLineSync(ipid); + if (ok < 0) return ok; + +/* +3.6.2. Responses + + 250 ok (optional timing information here) +*/ + ok = status(); + if (ok != 250) return -1; + return 0; +} + +int quit(word ipid) { + word terr; + int ok; + + terr = TCPIPWriteTCP(ipid, "QUIT\r\n", 6, 1, 0); + if (terr != 0) fprintf(stderr, "terr: %04x\n", terr); + ok = ReadLineSync(ipid); + if (ok < 0) return ok; + +/* +3.9.2. Responses + + 221 Closing Connection +*/ + + + return 0; +} + +int one_def(Word ipid) { + int ok; + + for(;;) { + ok = ReadLineSync(ipid); + if (ok < 0) return ok; + ok = status(); + if (ok == 250) return 0; + if (ok != 151) return -1; + for(;;) { + ok = ReadLineSync(ipid); + if (ok < 0) return ok; + if (buffer[0] == '.') { + fputc('\n', stdout); + break; + } + fputs(buffer, stdout); + fputc('\n', stdout); + } + + + } + fputc('\n', stdout); + return 0; +} + +int define(Word ipid, const char *term, const char *dict) { + word terr; + int ok; + unsigned x; + static char buffer[512]; + + if (!dict || !*dict) dict = "!"; + if (!term || !*term) return -1; + + x = sprintf(buffer, "DEFINE %s \"%s\"\r\n", dict, term); + + terr = TCPIPWriteTCP(ipid, buffer, x, 1, 0); + if (terr != 0) fprintf(stderr, "terr: %04x\n", terr); + +/* +3.2.2. Responses + + 550 Invalid database, use "SHOW DB" for list of databases + 552 No match + 150 n definitions retrieved - definitions follow + 151 word database name - text follows + 250 ok (optional timing information here) +*/ + + ok = ReadLineSync(ipid); + if (ok < 0) return ok; + + ok = status(); + switch(status()) { + default: return -1; + + case 550: + fprintf(stdout, "Invalid database.\n"); + return -1; + + case 552: + fprintf(stdout, "No match.\n"); + return 0; + + case 150: + return one_def(ipid); + } +} + + +int main(int argc, char **argv) { + + + Connection connection; + int mf; + int ok; + word terr; + + mf = StartUpTCP(DisplayCallback); + + if (argc < 1) exit(1); + + if (mf < 0) { + fprintf(stderr, "Marinetti 3.0b3 or greater is required.\n"); + exit(1); + } + + ok = ConnectLoop("dict.org", 2628, &connection); + if (ok) { + unsigned i; + int ok; + + ok = client(connection.ipid); + if (ok == 0) { + for (i = 1; i < argc; ++i) { + ok = define(connection.ipid, argv[i], NULL); + if (ok < 0) break; + } + } + + quit(connection.ipid); + CloseLoop(&connection); + } + + + ShutDownTCP(mf, false, DisplayCallback); + return 0; +} \ No newline at end of file