-/* $Id: macnet.c,v 1.1.2.1 1999/04/01 21:26:03 ben Exp $ */
+/* $Id: macnet.c,v 1.1.2.2 1999/04/03 21:53:29 ben Exp $ */
/*
* Copyright (c) 1999 Ben Harris
* All rights reserved.
*/
#include <MacTypes.h>
-#include <AddresXlation.h>
-#incldue <MacTCP.h>
+#include <AddressXlation.h>
+#include <Devices.h>
+#include <MacTCP.h>
#include <MixedMode.h>
+#include <OSUtils.h>
#include <Processes.h>
+#include <assert.h>
#include <stdlib.h>
+#include <string.h>
#include "putty.h"
+/*
+ * The theory behind this stuff:
+ *
+ * net_recv attempts to deliver any incoming data waiting on the
+ * queue. Since MacTCP maintains a buffer for incoming data, there's
+ * no need for us to run asynchronous TCPRcvs, and we just do a
+ * synchronous one if we detect some data waiting. Since TCPRcv can't
+ * be given a timeout of zero, we use TCPStatus to work out if there's
+ * anything waiting first.
+ *
+ * Sending data is trickier. TCPSend reserves the right to block
+ * until everything we've sent is ACKed, which means we have to use it
+ * asynchronously. In order to make life easier for backends, and to
+ * save a proliferation of circular buffers, we guarantee to take data
+ * off the hands of the backend as soon as it gives it to us. This is
+ * reasonable because currently there's no way for the backend to say
+ * it can't take data, and once it's got them, it may as well give
+ * them to us.
+ *
+ * Anyway, in order to avoid a fixed-size buffer overflowing, the send
+ * buffer is kept as a queue of blocks. When net_send is called, we
+ * malloc a new block and stick it on the queue. If the queue was
+ * empty, we kick off a new asynchronous TCPSend to handle our block.
+ *
+ */
+
+typedef struct Socket {
+ TCPiopb iopb; /* current MacTCP operation */
+ TCPiopb spareiopb; /* for closing etc */
+ hostInfo hostinfo;
+ int port;
+// unsigned char *inbuf;
+// int inbuf_head, inbuf_reap, inbuf_size;
+// unsigned char *outbuf;
+// int outbuf_head, outbuf_reap, outbuf_size;
+ ProcessSerialNumber psn;
+ Session *s;
+ UInt32 a5;
+ qHdr sendq; /* Blocks waiting to be sent */
+ qHdr freeq; /* Blocks sent, waiting to be freed */
+} Socket;
+
+typedef struct {
+ QElem qelem;
+ int flags;
+ int len;
+} Send_Buffer;
+
+/*
+ * Yes, I know the struct QElem has a short[1] to represent the user
+ * data. I'm ignoring it because it makes my code prettier and
+ * improves the alignment.
+ */
+
+typedef struct {
+ QElem qelem;
+ Socket *sock;
+ Net_Event_Type type;
+} NetEvent;
+
+#define TCPBUF_SIZE 8192
+
+static QHdr macnet_eventq;
+static QHdr macnet_freeq;
+
static short mtcp_refnum;
-statis OSErr mtcp_initted = FALSE;
+static int mtcp_initted = FALSE;
-static void macnet_init(void);
-static pascal void macnet_resolved(struct HostInfo *, char *);
+static OSErr macnet_init(void);
+static pascal void macnet_resolved(hostInfo *, char *);
+static void macnet_opened(TCPiopb*);
+static void macnet_sent(TCPiopb*);
+static void macnet_closed(TCPiopb*);
+static pascal void macnet_asr(StreamPtr, unsigned short, Ptr, unsigned short,
+ ICMPReport *);
+static void macnet_sendevent(Socket *, Net_Event_Type);
-#ifdef TARGET_RT_MAC_CFM
+#if TARGET_RT_MAC_CFM
static RoutineDescriptor macnet_resolved_upp =
BUILD_ROUTINE_DESCRIPTOR(uppResultProcInfo, (ProcPtr)macnet_resolved);
+static RoutineDescriptor macnet_opened_upp =
+ BUILD_ROUTINE_DESCRIPTOR(uppTCPIOCompletionProcInfo,
+ (ProcPtr)macnet_opened);
+static RoutineDescriptor macnet_sent_upp =
+ BUILD_ROUTINE_DESCRIPTOR(uppTCPIOCompletionProcInfo, (ProcPtr)macnet_sent);
+static RoutineDescriptor macnet_closed_upp =
+ BUILD_ROUTINE_DESCRIPTOR(uppTCPIOCompletionProcInfo,
+ (ProcPtr)macnet_closed);
+static RoutineDescriptor macnet_asr_upp =
+ BUILD_ROUTINE_DESCRIPTOR(uppTCPNotifyProcInfo, (ProcPtr)macnet_asr);
#else
#define macnet_resolved_upp macnet_resolved
+#define macnet_opened_upp macnet_opened
+#define macnet_sent_upp macnet_sent
+#define macnet_closed_upp macnet_closed
+#define macnet_asr_upp macnet_asr
#endif
+/*
+ * Number of outstanding network events allowed.
+ */
+#define NUM_EVENTS 16
+
/*
* Initialise networking. Set mtcp_initted if it goes OK.
*/
static OSErr macnet_init(void) {
OSErr err;
+ NetEvent *eventblock;
+ int i;
- err = OpenDriver(".IPP", &mtcp_refnum);
+ err = opendriver(".IPP", &mtcp_refnum);
if (err != noErr)
return err;
err = OpenResolver(NULL);
if (err != noErr)
return err;
+ /* Set up the event queues, and fill the free queue with events */
+ macnet_eventq.qFlags = 0;
+ macnet_eventq.qHead = macnet_eventq.qTail = NULL;
+ macnet_freeq.qFlags = 0;
+ macnet_freeq.qHead = macnet_eventq.qTail = NULL;
+ eventblock = smalloc(NUM_EVENTS * sizeof(NetEvent));
+ for (i = 0; i < NUM_EVENTS; i++)
+ Enqueue(&eventblock[i].qelem, &macnet_freeq);
mtcp_initted = TRUE;
-
- /* XXX: otherwise report an error */
}
-Socket *tcp_open(const char *host, int port, char **realhost) {
+Socket *net_open(Session *s, char *host, int port) {
ip_addr a;
- OSError err = noErr;
- Socket *s;
+ OSErr err = noErr;
+ Socket *sock;
+ void *tcpbuf;
- s = smalloc(sizeof(struct Socket));
+ /*
+ * First, get hold of all the memory we'll need (a lot of the
+ * later stuff happens at interrupt time)
+ */
+ sock = smalloc(sizeof(struct Socket));
+ memset(sock, 0, sizeof(*sock));
+ tcpbuf = smalloc(TCPBUF_SIZE);
+
+ /* Make a note of anything we don't want to forget */
+ sock->port = port;
+ GetCurrentProcess(&sock->psn);
+ sock->a5 = SetCurrentA5();
+
+ /* Get MacTCP running if it's not already */
if (!mtcp_initted)
if ((err = macnet_init()) != noErr)
fatalbox("Couldn't init network (%d)", err);
- s->port = port;
- GetCurrentProcess(&s->psn);
- err = StrToAddr(host, &s->host_info, &macnet_resolved_upp, (char *)s);
+
+ /* Get ourselves a TCP stream to play with */
+ sock->iopb.ioCRefNum = mtcp_refnum;
+ sock->iopb.csCode = TCPCreate;
+ sock->iopb.csParam.create.rcvBuff = tcpbuf;
+ sock->iopb.csParam.create.rcvBuffLen = TCPBUF_SIZE;
+ sock->iopb.csParam.create.notifyProc = macnet_asr_upp;
+ sock->iopb.csParam.create.userDataPtr = (Ptr)sock;
+ /* This could be done asynchronously, but I doubt it'll take long. */
+ err = PBControlSync((ParmBlkPtr)&sock->iopb);
+ if (err != noErr)
+ fatalbox("TCP stream open failed (%d)", err);
+
+ err = StrToAddr(host, &sock->hostinfo, &macnet_resolved_upp, (char *)sock);
if (err != noErr)
fatalbox("Host lookup failed (%d)", err);
- if (s->host_info.rtnCode != cacheFault)
- macnet_resolved(&s->host_info, s);
- return s;
+ if (sock->hostinfo.rtnCode != cacheFault)
+ macnet_resolved(&sock->hostinfo, (char *)sock);
+ return sock;
+}
+
+static pascal void macnet_resolved(hostInfo *hi, char *cookie) {
+ Socket *sock = (Socket *)cookie;
+ OSErr err;
+ UInt32 olda5;
+
+ olda5 = SetA5(sock->a5);
+ /*
+ * We've resolved a name, so now we'd like to connect to it (or
+ * report an error).
+ */
+ switch (sock->hostinfo.rtnCode) {
+ case noErr:
+ /* Open a connection */
+ sock->iopb.ioCompletion = macnet_opened_upp;
+ sock->iopb.csCode = TCPActiveOpen;
+ sock->iopb.csParam.open.validityFlags = typeOfService;
+ sock->iopb.csParam.open.commandTimeoutValue = 0; /* unused */
+ sock->iopb.csParam.open.remoteHost = sock->hostinfo.addr[0]; /*XXX*/
+ sock->iopb.csParam.open.remotePort = sock->port;
+ /* localHost is set by MacTCP. */
+ sock->iopb.csParam.open.localPort = 0;
+ sock->iopb.csParam.open.tosFlags = lowDelay;
+ sock->iopb.csParam.open.dontFrag = 0;
+ sock->iopb.csParam.open.timeToLive = 0; /* default */
+ sock->iopb.csParam.open.security = 0;
+ sock->iopb.csParam.open.optionCnt = 0;
+ sock->iopb.csParam.open.userDataPtr = (char *)sock;
+ err = PBControlSync((ParmBlkPtr)&sock->iopb);
+ if (err != noErr)
+ macnet_sendevent(sock, NE_NOOPEN);
+ break;
+ default: /* Something went wrong */
+ macnet_sendevent(sock, NE_NOHOST);
+ break;
+ }
+ SetA5(olda5);
+}
+
+static void macnet_opened(TCPiopb *iopb) {
+ Socket *sock = (Socket *)iopb->csParam.open.userDataPtr;
+ UInt32 olda5;
+
+ olda5 = SetA5(sock->a5);
+ switch (iopb->ioResult) {
+ case noErr:
+ macnet_sendevent(sock, NE_OPEN);
+ break;
+ default:
+ macnet_sendevent(sock, NE_NOOPEN);
+ break;
+ }
+ SetA5(olda5);
+}
+
+static pascal void macnet_asr(StreamPtr tcpstream, unsigned short eventcode,
+ Ptr cookie, unsigned short terminreason,
+ ICMPReport *icmpmsg) {
+ Socket *sock = (Socket *)cookie;
+ UInt32 olda5;
+
+ olda5 = SetA5(sock->a5);
+ switch (eventcode) {
+ case TCPClosing:
+ macnet_sendevent(sock, NE_CLOSING);
+ break;
+ case TCPULPTimeout:
+ macnet_sendevent(sock, NE_TIMEOUT);
+ break;
+ case TCPTerminate:
+ switch (terminreason) {
+ case TCPRemoteAbort:
+ macnet_sendevent(sock, NE_ABORT);
+ break;
+ default:
+ macnet_sendevent(sock, NE_DIED);
+ break;
+ }
+ break;
+ case TCPDataArrival:
+ macnet_sendevent(sock, NE_DATA);
+ break;
+ case TCPUrgent:
+ macnet_sendevent(sock, NE_URGENT);
+ break;
+ case TCPICMPReceived:
+ switch (icmpmsg->reportType) {
+ case portUnreach:
+ macnet_sendevent(sock, NE_REFUSED);
+ break;
+ }
+ break;
+ }
+ SetA5(olda5);
+}
+
+/*
+ * Send a block of data.
+ */
+
+int net_send(Socket *sock, void *buf, int buflen, int flags) {{
+ OSErr err;
+ Send_Buffer *buff;
+
+ buff = smalloc(sizeof(Send_Buffer) + buflen);
+ buff->flags = flags;
+ buff->len = buflen;
+ memcpy(buff + 1, buf, buflen);
+ Enqueue(&buff->qelem, &sock->sendq);
+ macnet_start(sock);
+}
+
+int net_recv(Socket *sock, void *buf, int buflen, int flags) {
+ TCPiopb iopb;
+ OSErr err;
+ int avail, want, got;
+
+ memcpy(&iopb, &sock->iopb, sizeof(TCPiopb));
+ /* Work out if there's anything to recieve (we don't want to block) */
+ iopb.csCode = TCPStatus;
+ err = PBControlSync((ParmBlkPtr)&iopb);
+ if (err != noErr)
+ return 0; /* macnet_asr should catch it anyway */
+ avail = iopb.csParam.status.amtUnreadData;
+ if (avail == 0)
+ return 0;
+ want = avail < buflen ? avail : buflen;
+ iopb.csCode = TCPRcv;
+ iopb.csParam.receive.buffPtr = buf;
+ iopb.csParam.receive.buffLen = want;
+ err = PBControlSync((ParmBlkPtr)&iopb);
+ if (err != noErr)
+ return 0;
+ return iopb.csParam.receive.buffLen;
+}
+
+
+void net_close(Socket *sock) {
+ OSErr err;
+
+ /*
+ * This might get called in the middle of processing another
+ * request on the socket, so we have a spare parameter block for
+ * this purpose (allocating one dynamically would mean having to
+ * free it, which we can't do at interrupt time).
+ */
+ memcpy(&sock->spareiopb, &sock->iopb, sizeof(TCPiopb));
+ sock->spareiopb.ioCompletion = macnet_closed_upp;
+ sock->spareiopb.csCode = TCPClose;
+ sock->spareiopb.csParam.close.validityFlags = 0;
+ sock->spareiopb.csParam.close.userDataPtr = (char *)sock;
+ err = PBControlAsync((ParmBlkPtr)&sock->spareiopb);
+ switch (err) {
+ case noErr:
+ case connectionClosing:
+ case connectionTerminated: /* We'll get an ASR */
+ break;
+ default:
+ macnet_sendevent(sock, NE_DIED);
+ break;
+ }
+}
+
+static void macnet_closed(TCPiopb* iopb) {
+ Socket *sock = (Socket *)iopb->csParam.close.userDataPtr;
+ UInt32 olda5;
+
+ olda5 = SetA5(sock->a5);
+ switch (iopb->ioResult) {
+ case noErr:
+ macnet_sendevent(sock, NE_CLOSED);
+ break;
+ case connectionClosing:
+ case connectionTerminated:
+ break;
+ default:
+ macnet_sendevent(sock, NE_DIED);
+ break;
+ }
+ SetA5(olda5);
+}
+
+/*
+ * Free all the data structures associated with a socket and tear down
+ * any connection through it.
+ */
+void net_destroy(Socket *sock) {
+ TCPiopb iopb;
+ OSErr err;
+
+ /*
+ * Yes, we need _another_ iopb, as there may be a send _and_ a
+ * close outstanding. Luckily, destroying a socket is
+ * synchronous, so we can allocate this one dynamically.
+ */
+ memcpy(&iopb, &sock->iopb, sizeof(TCPiopb));
+ iopb.csCode = TCPRelease;
+ err = PBControlSync((ParmBlkPtr)&iopb);
+ sfree(iopb.csParam.create.rcvBuff);
+ sfree(sock);
}
-static pascal void macnet_resolved(struct hostInfo *hi, char *cookie) {
- Socket *s = (Socket *)cookie;
+static void macnet_sendevent(Socket *sock, Net_Event_Type type) {
+ NetEvent *ne;
- /* We should probably tell the process what's going on here. */
- /* Alternatively, we should kick off the next stage in the process */
- WakeUpProcess(&s->psn);
+ ne = (NetEvent *)macnet_freeq.qHead;
+ assert (ne != NULL);
+ Dequeue(&ne->qelem, &macnet_freeq);
+ ne->sock = sock;
+ ne->type = type;
+ Enqueue(&ne->qelem, &macnet_eventq);
+ WakeUpProcess(&sock->psn);
}
/*
} VT_Mode;
typedef struct Session Session;
+typedef struct Socket Socket;
+
+/* Types of network event */
+
+typedef enum {
+ NE_NULL, /* Nothing happened */
+ NE_OPEN, /* Connection successfully opened */
+ NE_NOHOST, /* DNS lookup failed for some reason */
+ NE_REFUSED, /* Port unreachable */
+ NE_NOOPEN, /* Connection failed to open for some other reason */
+ NE_DATA, /* Incoming normal data */
+ NE_URGENT, /* Incoming urgent data */
+ NE_CLOSING, /* Connection closed by remote host */
+ NE_CLOSED, /* Connection close completed */
+ NE_TIMEOUT, /* Remote host vanished */
+ NE_ABORT, /* Remote host reset connection */
+ NE_DIED, /* Connection has failed for some other reason */
+} Net_Event_Type;
+
typedef struct {
-#ifdef macintosh
- char *(*init) (Session *, char *host, int port, char **realhost);
- int (*msg)(Session *);
-#else /* not macintosh */
- char *(*init) (HWND hwnd, char *host, int port, char **realhost);
- int (*msg) (WPARAM wParam, LPARAM lParam);
-#endif /* not macintosh */
+ char *(*init) (Session *, char *host, int port);
+ int (*msg)(Session *, Socket *, Net_Event_Type);
void (*send) (Session *, char *buf, int len);
void (*size) (Session *);
void (*special) (Session *, Telnet_Special code);
+ void (*shutdown) (Session *);
} Backend;
typedef struct {
#endif
} Session;
+typedef struct Socket Socket;
/*
* Exports from display system
#pragma noreturn (fatalbox)
#endif
extern void beep (Session *s);
-#define OPTIMISE_IS_SCROLL 1
+
+/*
+ * Exports from the network system
+ */
+
+extern Socket *net_open(Session *, char *host, int port);
+extern char *net_realname(Socket *);
+extern int net_recv(Socket *, void *, int, int);
+extern int net_send(Socket *, void *, int, int);
+extern void net_close(Socket *); /* ask the remote end to close */
+extern void net_destroy(Socket *); /* Tidy up */
/*
* Exports from noise.c.