jcs
/detritus
/amendments
/3
*: Initial import, part 3
jcs made amendment 3 about 1 year ago
--- client.c Mon Sep 30 20:45:34 2024
+++ client.c Mon Sep 30 20:45:34 2024
@@ -0,0 +1,982 @@
+/*
+ * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "gemino.h"
+#include "client.h"
+#include "focusable.h"
+#include "util.h"
+
+#define PADDING 10
+#define CLIENT_FONT_SIZE 10
+#define CLIENT_FONT geneva
+
+bool client_close(struct focusable *focusable);
+void client_idle(struct focusable *focusable, EventRecord *event);
+void client_update_menu(struct client *client);
+void client_update(struct focusable *focusable, EventRecord *event);
+void client_key_down(struct focusable *focusable, EventRecord *event);
+void client_mouse_down(struct focusable *focusable, EventRecord *event);
+bool client_handle_menu(struct focusable *focusable, short menu,
+ short item);
+void client_atexit(struct focusable *focusable);
+bool client_avoid_te_overflow(struct client *client, TEHandle te,
+ short line_height);
+
+void client_cleanup(struct client *client);
+void client_connect(struct client *client);
+void client_shuffle_data(struct client *client);
+void shuffle_read_tls_ciphertext(struct client *client);
+void shuffle_read_tcp_ciphertext(struct client *client, short space);
+void shuffle_tls_send_plaintext(struct client *client, short space);
+void shuffle_tls_read_plaintext(struct client *client);
+
+size_t client_printf(struct client *client, const char *format, ...);
+size_t client_debugf(struct client *client, const char *format, ...);
+void client_parse_gemtext(struct client *client);
+
+Pattern fill_pattern;
+
+void
+client_idle(struct focusable *focusable, EventRecord *event)
+{
+ struct client *client = (struct client *)focusable->cookie;
+ size_t len;
+
+ TEIdle(client->active_te);
+
+ if (!client->req)
+ return;
+
+ switch (client->req->state) {
+ case REQ_STATE_IDLE:
+ break;
+ case REQ_STATE_CONNECTED:
+ client_shuffle_data(client);
+ break;
+ }
+}
+
+struct client *
+client_init(void)
+{
+ char title[64];
+ struct client *client;
+ struct focusable *focusable;
+ Rect bounds = { 0 }, te_bounds = { 0 };
+ Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
+ Point cell_size = { 0, 0 };
+ Cell cell = { 0 };
+ short n, width, height;
+
+ client = xmalloczero(sizeof(struct client));
+ if (client == NULL)
+ panic("Out of memory allocating client");
+
+ GetIndPattern(&fill_pattern, sysPatListID, 7);
+
+ /* main window */
+ width = screenBits.bounds.right - screenBits.bounds.left - PADDING;
+ if (width > 540)
+ width = 540;
+ height = screenBits.bounds.bottom - screenBits.bounds.top -
+ PADDING - (GetMBarHeight() * 2);
+ if (height > 340)
+ height = 300;
+ center_in_screen(width, height, true, &bounds);
+
+ snprintf(title, sizeof(title), "%s", PROGRAM_NAME);
+ CtoPstr(title);
+ client->win = NewWindow(0L, &bounds, title, false, noGrowDocProc,
+ (WindowPtr)-1L, true, 0);
+ if (!client->win)
+ panic("Can't create window");
+ SetPort(client->win);
+
+ /* uri TE */
+ bounds.top = PADDING;
+ bounds.left = PADDING;
+ bounds.right = client->win->portRect.right - PADDING - 50;
+ bounds.bottom = bounds.top + 16;
+ te_bounds = bounds;
+ InsetRect(&te_bounds, 2, 2);
+ TextFont(geneva);
+ TextSize(10);
+ client->uri_te = TENew(&te_bounds, &bounds);
+ if (client->uri_te == NULL)
+ panic("Out of memory allocating TE");
+ TEAutoView(false, client->uri_te);
+ TEActivate(client->uri_te);
+ client->active_te = client->uri_te;
+
+ bounds.left = bounds.right + PADDING;
+ bounds.right = client->win->portRect.right - PADDING;
+ client->go_button = NewControl(client->win, &bounds, "\pGo", true,
+ 1, 1, 1, pushButProc, 0L);
+
+ /* output TE */
+ bounds.left = PADDING;
+ bounds.right = client->win->portRect.right - SCROLLBAR_WIDTH - PADDING;
+ bounds.top = bounds.bottom + PADDING;
+ bounds.bottom = client->win->portRect.bottom - PADDING;
+ te_bounds = bounds;
+ InsetRect(&te_bounds, 2, 2);
+ client->output_te = TEStylNew(&te_bounds, &bounds);
+ if (client->output_te == NULL)
+ panic("Out of memory allocating TE");
+ TEAutoView(true, client->output_te);
+ (*(client->output_te))->caretHook = NullCaretHook;
+
+ /* scrollbar for output text */
+ bounds.right = client->win->portRect.right - PADDING;
+ bounds.left = bounds.right - SCROLLBAR_WIDTH;
+ bounds.bottom++;
+ bounds.top--;
+ client->output_te_scroller = NewControl(client->win, &bounds, "\p",
+ true, 1, 1, 1, scrollBarProc, 0L);
+
+ client_update_menu(client);
+ UpdateScrollbarForTE(client->win, client->output_te_scroller,
+ client->output_te, true);
+
+ TESetText("geminiprotocol.net", strlen("geminiprotocol.net"),
+ client->uri_te);
+
+ focusable = xmalloczero(sizeof(struct focusable));
+ if (focusable == NULL)
+ panic("Out of memory!");
+ focusable->cookie = client;
+ focusable->win = client->win;
+ focusable->idle = client_idle;
+ focusable->update = client_update;
+ focusable->mouse_down = client_mouse_down;
+ focusable->key_down = client_key_down;
+ focusable->menu = client_handle_menu;
+ focusable->close = client_close;
+ focusable->atexit = client_atexit;
+ focusable_add(focusable);
+
+ return client;
+}
+
+bool
+client_close(struct focusable *focusable)
+{
+ struct client *client = (struct client *)focusable->cookie;
+
+ TEDispose(client->uri_te);
+ TEDispose(client->output_te);
+ DisposeWindow(client->win);
+
+ client_cleanup(client);
+ xfree(&client);
+ focusable->cookie = NULL;
+
+ scsi_cleanup();
+
+ return true;
+}
+
+void
+client_cleanup(struct client *client)
+{
+ if (!client->req)
+ return;
+
+ if (client->req->tcp_stream)
+ _TCPAbort(&client->req->tcp_iopb, client->req->tcp_stream,
+ NULL, NULL, false);
+
+ xfree(&client->req);
+}
+
+void
+client_atexit(struct focusable *focusable)
+{
+ struct client *client = (struct client *)focusable->cookie;
+
+ if (client)
+ client_cleanup(client);
+
+ scsi_cleanup();
+}
+
+void
+client_update_menu(struct client *client)
+{
+ size_t vlines;
+ TERec *te;
+ Cell cell = { 0, 0 };
+
+ TextFont(systemFont);
+ TextSize(12);
+
+#if 0
+ te = *(client->te);
+
+ DisableItem(edit_menu, EDIT_MENU_CUT_ID);
+
+ if (te->selStart == te->selEnd)
+ DisableItem(edit_menu, EDIT_MENU_COPY_ID);
+ else
+ EnableItem(edit_menu, EDIT_MENU_COPY_ID);
+
+ DisableItem(edit_menu, EDIT_MENU_PASTE_ID);
+
+ if (te->nLines == 0) {
+ DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+ } else {
+ EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+ }
+#endif
+}
+
+void
+client_update(struct focusable *focusable, EventRecord *event)
+{
+ struct client *client = (struct client *)focusable->cookie;
+ Rect r;
+ short what = -1;
+
+ if (event != NULL)
+ what = event->what;
+
+ switch (what) {
+ case -1:
+ case updateEvt:
+ FillRect(&client->win->portRect, fill_pattern);
+
+ HLock(client->uri_te);
+ r = (*(client->uri_te))->viewRect;
+ HUnlock(client->uri_te);
+ FillRect(&r, white);
+ TEUpdate(&r, client->uri_te);
+ InsetRect(&r, -1, -1);
+ FrameRect(&r);
+
+ HLock(client->output_te);
+ r = (*(client->output_te))->viewRect;
+ HUnlock(client->output_te);
+ FillRect(&r, white);
+ TEUpdate(&r, client->output_te);
+ InsetRect(&r, -1, -1);
+ FrameRect(&r);
+
+ UpdtControl(client->win, client->win->visRgn);
+
+ client_update_menu(client);
+
+ break;
+ }
+}
+
+void
+client_mouse_down(struct focusable *focusable, EventRecord *event)
+{
+ struct client *client = (struct client *)focusable->cookie;
+ struct client_link *link;
+ Cell selected = { 0 };
+ Point p;
+ ControlHandle control;
+ Rect r;
+ short val, adj, page, len, part, off;
+ size_t n;
+
+ p = event->where;
+ GlobalToLocal(&p);
+
+ HLock(client->uri_te);
+ r = (*(client->uri_te))->viewRect;
+ HUnlock(client->uri_te);
+ if (PtInRect(p, &r)) {
+ if (client->active_te != client->uri_te) {
+ TEDeactivate(client->active_te);
+ client->active_te = client->uri_te;
+ TEActivate(client->uri_te);
+ }
+ TEClick(p, ((event->modifiers & shiftKey) != 0), client->uri_te);
+ client_update_menu(client);
+ return;
+ }
+
+ switch (part = FindControl(p, client->win, &control)) {
+ case inButton:
+ if (TrackControl(control, p, 0L) && control == client->go_button)
+ client_connect(client);
+ break;
+ case inUpButton:
+ case inDownButton:
+ case inPageUp:
+ case inPageDown:
+ if (control != client->output_te_scroller)
+ break;
+ SetTrackControlTE(client->output_te);
+ TrackControl(control, p, TrackMouseDownInControl);
+ break;
+ case inThumb:
+ val = GetCtlValue(control);
+ if (TrackControl(control, p, 0L) == 0)
+ break;
+ adj = val - GetCtlValue(control);
+ if (adj != 0) {
+ val -= adj;
+ if (control == client->output_te_scroller) {
+ TEScroll(0, adj * TEGetHeight(0, 0,
+ client->output_te), client->output_te);
+ }
+ SetCtlValue(control, val);
+ }
+ break;
+ }
+}
+
+void
+client_key_down(struct focusable *focusable, EventRecord *event)
+{
+ struct client *client = (struct client *)(focusable->cookie);
+ char k;
+
+ k = (event->message & charCodeMask);
+
+ if (k == '\r') {
+ client_connect(client);
+ return;
+ }
+
+ TEKey(k, client->uri_te);
+ TESelView(client->uri_te);
+}
+
+bool
+client_handle_menu(struct focusable *focusable, short menu, short item)
+{
+ struct client *client = (struct client *)focusable->cookie;
+
+ switch (menu) {
+ case EDIT_MENU_ID:
+ switch (item) {
+ case EDIT_MENU_COPY_ID:
+ TECopy(client->active_te);
+ return true;
+ case EDIT_MENU_SELECT_ALL_ID:
+ TESetSelect(0, 1024 * 32, client->active_te);
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+void
+client_connect(struct client *client)
+{
+ struct tls_init_request tls_req;
+ char ip_s[16];
+ Rect r;
+ TERec *te;
+ size_t len;
+ unsigned long time;
+ unsigned short ret;
+ ip_addr ip, local_ip;
+ tcp_port port, local_port;
+ short err, i, j, count;
+
+ client_cleanup(client);
+
+ client->req = xmalloczero(sizeof(struct tcp_request));
+ if (client->req == NULL)
+ panic("Out of memory allocating request");
+
+ client->req->state = REQ_STATE_DISCONNECTED;
+ client->req->gem_state = GEM_STATE_HEADER;
+
+ HLock(client->uri_te);
+ te = *(client->uri_te);
+ HLock(te->hText);
+ len = te->teLength;
+ if (len >= sizeof(client->req->uri))
+ len = sizeof(client->req->uri) - 1;
+ memcpy(client->req->uri, *(te->hText), len);
+ client->req->uri[len] = '\0';
+ HUnlock(te->hText);
+ HUnlock(client->uri_te);
+
+ if (count = 0, sscanf(client->req->uri, "gemini://%255[^/]/%*s%n",
+ &client->req->hostname, &count) == 1 && count > 10) {
+ /* gemini://host/path */
+ }
+ else if (count = 0, sscanf(client->req->uri, "gemini://%255[^/]%n",
+ &client->req->hostname, &count) == 1 && count > 10) {
+ /* gemini://host */
+ client->req->uri_len = strlcat(client->req->uri, "/",
+ sizeof(client->req->uri));
+ }
+ else if (count = 0, sscanf(client->req->uri, "%255[^/]/%*s%n",
+ &client->req->hostname, &count) == 1 && count > 3) {
+ /* host/path */
+ memmove(client->req->uri + 9, client->req->uri,
+ sizeof(client->req->uri) - 9);
+ client->req->uri[sizeof(client->req->uri) - 1] = '\0';
+ memcpy(client->req->uri, "gemini://", 9);
+ client->req->uri_len += 9;
+ }
+ else if (count = 0, sscanf(client->req->uri, "%255[^/]%n",
+ &client->req->hostname, &count) == 1 && count > 1) {
+ /* host */
+ client->req->uri_len = snprintf(client->req->uri,
+ sizeof(client->req->uri), "gemini://%s/", client->req->hostname);
+ }
+ else {
+ warn("Invalid URI");
+ return;
+ }
+
+ /* TODO: support URIs with port? */
+ client->req->port = 6667; //DEFAULT_GEMINI_PORT;
+
+ TESetText(client->req->uri, strlen(client->req->uri), client->uri_te);
+ HLock(client->uri_te);
+ r = (*(client->uri_te))->viewRect;
+ HUnlock(client->uri_te);
+ TEUpdate(&r, client->uri_te);
+
+ client->req->uri_len = strlcat(client->req->uri, "\r\n",
+ sizeof(client->req->uri));
+
+ if (client->req->host_ip == 0) {
+ client_printf(client, "[Resolving \"%s\"]\r",
+ client->req->hostname);
+
+ err = DNSResolveName(client->req->hostname,
+ &client->req->host_ip, NULL);
+ err = 0;
+ client->req->host_ip = ip2long("192.168.1.196");
+ if (err) {
+ client_printf(client, "[Failed resolving: %d]\r", err);
+ return;
+ }
+ }
+
+ long2ip(client->req->host_ip, (char *)&ip_s);
+ client_printf(client, "[Connecting to \"%s\" (%s) on port %d]\r",
+ client->req->hostname, ip_s, client->req->port);
+
+ err = _TCPCreate(&client->req->tcp_iopb, &client->req->tcp_stream,
+ (Ptr)client->req->tcp_buf, sizeof(client->req->tcp_buf), NULL, NULL,
+ NULL, false);
+ if (err) {
+ client_printf(client, "[TCPCreate failed: %d]\r", err);
+ goto error;
+ }
+
+ err = _TCPActiveOpen(&client->req->tcp_iopb, client->req->tcp_stream,
+ client->req->host_ip, client->req->port, &local_ip, &local_port,
+ NULL, NULL, false);
+ if (err) {
+ client_printf(client, "[Failed connecting to %s (%s) port %d: %d]\r",
+ client->req->hostname, ip_s, client->req->port, err);
+ goto error;
+ }
+
+ err = _TCPStatus(&client->req->tcp_iopb, client->req->tcp_stream,
+ &client->req->tcp_status_pb, NULL, NULL, false);
+ if (err) {
+ client_printf(client, "[Failed TCPStatus on connection to %s (%s) "
+ "port %d: %d]\r", client->req->hostname, ip_s, port, err);
+ goto error;
+ }
+
+ memset(&tls_req, 0, sizeof(tls_req));
+ strlcpy(tls_req.hostname, client->req->hostname,
+ sizeof(tls_req.hostname));
+ time = MAC_TO_UNIX_TIME(Time);
+ tls_req.unix_time[0] = (time >> 24) & 0xff;
+ tls_req.unix_time[1] = (time >> 16) & 0xff;
+ tls_req.unix_time[2] = (time >> 8) & 0xff;
+ tls_req.unix_time[3] = (time) & 0xff;
+
+ client->req->tls_id = 123;
+
+ client_printf(client, "[Creating TLS context]\r");
+ scsi_tls_init(client->req->tls_id, client->scsi_buf,
+ sizeof(client->scsi_buf), &tls_req);
+
+ client->req->state = REQ_STATE_CONNECTED;
+
+error:
+ return;
+}
+
+void
+client_shuffle_data(struct client *client)
+{
+ size_t len;
+ unsigned short slen;
+ short err, status;
+ short cipherspace, plainspace, error;
+
+ if (client->req->tcp_iopb.ioResult > 0) {
+ client->req->state = REQ_STATE_DISCONNECTED;
+ return;
+ }
+
+ status = scsi_tls_status(client->req->tls_id, client->scsi_buf,
+ sizeof(client->scsi_buf), &cipherspace, &plainspace, &error);
+
+ while (status != 0) {
+ if (status & 0x1) {
+ /* closed */
+ client_printf(client, "[TLS handshake failed: %d, 0x%x]\r",
+ error, status);
+ client->req->state = REQ_STATE_DISCONNECTED;
+ break;
+ }
+
+ if (status & 0x2) {
+ /* tls has ciphertext for tcp */
+ shuffle_read_tls_ciphertext(client);
+ status &= ~0x2;
+ }
+
+ if (status & 0x10) {
+ /* tls has plaintext data for us */
+ shuffle_tls_read_plaintext(client);
+ status &= ~0x10;
+ }
+
+ if (status & 0x8) {
+ /* tls can read plaintext from us */
+ shuffle_tls_send_plaintext(client, plainspace);
+ status &= ~0x8;
+ }
+
+ if (status & 0x4) {
+ /* tls can read ciphertext from tcp */
+ shuffle_read_tcp_ciphertext(client, cipherspace);
+ status &= ~0x4;
+ }
+
+ if (status) {
+ client_printf(client, "[Status is 0x%x?]\r", status);
+ return;
+ }
+ }
+}
+
+void
+shuffle_read_tls_ciphertext(struct client *client)
+{
+ size_t len;
+ short err;
+
+ /* read ciphertext from TLS and send it out TCP */
+ len = scsi_tls_read(client->req->tls_id, client->scsi_buf,
+ sizeof(client->scsi_buf), true);
+ if (len == 0) {
+ client_printf(client, "[No ciphertext read from TLS]\r");
+ return;
+ }
+
+ client_debugf(client, "[Read %lu bytes of ciphertext from TLS, "
+ "forwarding to TCP]\r", len);
+
+ memset(&client->req->tcp_wds, 0, sizeof(client->req->tcp_wds));
+ client->req->tcp_wds[0].ptr = (Ptr)client->scsi_buf;
+ client->req->tcp_wds[0].length = len;
+
+ err = _TCPSend(&client->req->tcp_iopb, client->req->tcp_stream,
+ client->req->tcp_wds, NULL, NULL, false);
+ if (err) {
+ client_printf(client, "[TCPSend failed: %d]\r", err);
+ client->req->state = REQ_STATE_DISCONNECTED;
+ return;
+ }
+}
+
+void
+shuffle_read_tcp_ciphertext(struct client *client, short space)
+{
+ size_t len;
+ short err;
+ unsigned short slen;
+
+ /* read ciphertext from TCP and send it to TLS */
+ if (client->req->tcp_input_len < sizeof(client->req->tcp_input)) {
+ err = _TCPStatus(&client->req->tcp_iopb, client->req->tcp_stream,
+ &client->req->tcp_status_pb, NULL, NULL, false);
+ if (err) {
+ client_printf(client, "[Failed TCPStatus: %d]\r", err);
+ client->req->state = REQ_STATE_DISCONNECTED;
+ return;
+ }
+
+ if (client->req->tcp_status_pb.amtUnreadData > 0) {
+ slen = MIN(client->req->tcp_status_pb.amtUnreadData,
+ sizeof(client->req->tcp_input) - client->req->tcp_input_len);
+ if (slen) {
+ err = _TCPRcv(&client->req->tcp_iopb,
+ client->req->tcp_stream,
+ (Ptr)(client->req->tcp_input + client->req->tcp_input_len),
+ &slen, NULL, NULL, false);
+ if (err) {
+ client_printf(client, "[Failed TCPRcv: %d]\r", err);
+ client->req->state = REQ_STATE_DISCONNECTED;
+ return;
+ }
+ client->req->tcp_input_len += slen;
+ client_debugf(client, "[Read %d bytes of ciphertext from "
+ "TCP, forwarding to TLS]\r", slen);
+ } else {
+ client_printf(client, "[No buffer space available in "
+ "tcp_input]\r");
+ }
+ }
+ }
+
+ if (client->req->tcp_input_len && space) {
+ slen = client->req->tcp_input_len;
+ if (slen > space)
+ slen = space;
+ client_debugf(client, "[Writing %d bytes of ciphertext to TLS]\r",
+ slen);
+ len = scsi_tls_write(client->req->tls_id, client->req->tcp_input,
+ slen, true);
+ if (len > 0) {
+ if (len == client->req->tcp_input_len)
+ client->req->tcp_input_len = 0;
+ else {
+ size_t n;
+
+ /* TODO: why does memmove fail? */
+ //memmove(client->req->readbuf, client->req->readbuf + len,
+ // client->req->readbuflen - len);
+ for (n = 0; n < client->req->tcp_input_len - len; n++)
+ client->req->tcp_input[n] =
+ client->req->tcp_input[len + n];
+
+ client->req->tcp_input_len -= len;
+ client_debugf(client, "[Wrote %ld bytes of "
+ "ciphertext to TLS, %ld left]\r", len,
+ client->req->tcp_input_len);
+ }
+ } else {
+ client_printf(client, "[Failed sending %d bytes of ciphertext "
+ "to TLS]\r", slen);
+ }
+ }
+}
+
+void
+shuffle_tls_send_plaintext(struct client *client, short space)
+{
+ size_t slen, len;
+
+ /* send any plaintext from us to TLS */
+ if (client->req->uri_len == 0)
+ return;
+
+ slen = client->req->uri_len;
+ if (slen > space)
+ slen = space;
+
+ len = scsi_tls_write(client->req->tls_id,
+ (unsigned char *)client->req->uri, slen, false);
+ if (len) {
+ if (len == client->req->uri_len) {
+ client->req->uri_len = 0;
+ client_debugf(client, "[Sent %ld bytes of plaintext to TLS]\r",
+ len);
+ } else {
+ memmove(client->req->uri,
+ client->req->uri + len,
+ client->req->uri_len - len);
+ client->req->uri_len -= len;
+ client_debugf(client, "[Wrote %ld bytes of plaintext to "
+ "TLS, %ld left]\r", len, client->req->uri_len);
+ }
+ } else {
+ client_printf(client, "[Failed sending %ld bytes of plaintext "
+ "to TLS]\r", slen);
+ }
+}
+
+void
+shuffle_tls_read_plaintext(struct client *client)
+{
+ size_t len;
+
+ /* read as much plaintext from TLS as we can buffer */
+ len = sizeof(client->req->input) - client->req->input_len;
+ if (len > sizeof(client->scsi_buf))
+ len = sizeof(client->scsi_buf);
+ if (len > 0) {
+ len = scsi_tls_read(client->req->tls_id, client->scsi_buf, len,
+ false);
+ if (len > 0) {
+ client_debugf(client, "[Read %ld bytes of plaintext from TLS]\r", len);
+ if (len > sizeof(client->req->input) + client->req->input_len)
+ panic("input overflow!");
+ memcpy(client->req->input + client->req->input_len,
+ client->scsi_buf, len);
+ client->req->input_len += len;
+ }
+ }
+
+ if (client->req->input_len)
+ client_parse_gemtext(client);
+}
+
+size_t
+client_printf(struct client *client, const char *format, ...)
+{
+ static char fmt[1024];
+ va_list argptr;
+ size_t len;
+
+ va_start(argptr, format);
+ len = vsnprintf(fmt, sizeof(fmt), format, argptr);
+ if (len > sizeof(fmt))
+ len = sizeof(fmt);
+ va_end(argptr);
+
+ client_print(client, fmt, len);
+}
+
+size_t
+client_debugf(struct client *client, const char *format, ...)
+{
+#if 0
+ static char fmt[1024];
+ va_list argptr;
+ size_t len;
+
+ va_start(argptr, format);
+ len = vsnprintf(fmt, sizeof(fmt), format, argptr);
+ if (len > sizeof(fmt))
+ len = sizeof(fmt);
+ va_end(argptr);
+
+ client_print(client, fmt, len);
+#endif
+}
+
+size_t
+client_print(struct client *client, const char *str, size_t len)
+{
+ static char tstr[1024];
+ short line_height;
+ short was_len;
+ size_t n = 0;
+
+ line_height = CLIENT_FONT_SIZE + 3;
+
+ HLock(client->output_te);
+ was_len = (*(client->output_te))->teLength;
+ HUnlock(client->output_te);
+
+ client_avoid_te_overflow(client, client->output_te, line_height);
+
+ while (len) {
+ if (*str == '\r' && *(str + 1) == '\n') {
+ tstr[n++] = '\r';
+ str++;
+ len--;
+ } else if (*str == '\n')
+ tstr[n++] = '\r';
+ else
+ tstr[n++] = *str;
+
+ str++;
+ len--;
+
+ if (n == sizeof(tstr) || len == 0) {
+ TESetSelect(SHRT_MAX, SHRT_MAX, client->output_te);
+ TEInsert(tstr, n, client->output_te);
+ if (len == 0)
+ break;
+ n = 0;
+ }
+ }
+
+ if (was_len == 0) {
+ SetCtlValue(client->output_te_scroller,
+ GetCtlMin(client->output_te_scroller));
+ }
+ UpdateScrollbarForTE(client->win, client->output_te_scroller,
+ client->output_te, false);
+
+ HUnlock(client->output_te);
+
+ return len;
+}
+
+bool
+client_avoid_te_overflow(struct client *client, TEHandle te,
+ short line_height)
+{
+ RgnHandle savergn;
+ Rect zerorect = { 0, 0, 0, 0 };
+
+ HLock(te);
+
+ /* too many lines */
+ if ((*te)->nLines >= (nitems((*te)->lineStarts) - 10))
+ goto te_overflow;
+
+ /* too many characters */
+ if ((*te)->teLength >= (SHRT_MAX - 500))
+ goto te_overflow;
+
+ /* rect of all lines is too tall */
+ if ((*te)->nLines * line_height >= (SHRT_MAX - 100))
+ goto te_overflow;
+
+ HUnlock(te);
+
+ return false;
+
+te_overflow:
+ savergn = NewRgn();
+ GetClip(savergn);
+ /* create an empty clip region so all TE updates are hidden */
+ ClipRect(&zerorect);
+
+ /* select some lines at the start, delete them */
+ TESetSelect(0, (*te)->lineStarts[5], te);
+ TEDelete(te);
+
+ /* scroll up, causing a repaint */
+ TEPinScroll(0, INT_MAX, te);
+
+ /* then scroll back down to what it looked like before we did anything */
+ TEPinScroll(0, -INT_MAX, te);
+
+ /* resume normal drawing */
+ SetClip(savergn);
+ DisposeRgn(savergn);
+
+ HUnlock(te);
+
+ return true;
+}
+
+void
+client_clear(struct client *client)
+{
+ size_t n;
+
+ TEPinScroll(0, -SHRT_MAX, client->uri_te);
+ TESetText("", 0, client->uri_te);
+
+ TEPinScroll(0, -SHRT_MAX, client->output_te);
+ TESetText("", 0, client->output_te);
+ UpdateScrollbarForTE(client->win, client->output_te_scroller,
+ client->output_te, true);
+}
+
+void
+client_parse_gemtext(struct client *client)
+{
+ size_t n;
+
+ switch (client->req->gem_state) {
+ case GEM_STATE_HEADER: {
+ short status;
+
+ for (n = 0; n < client->req->input_len; n++) {
+ if (!(client->req->input[n] == '\n' && n &&
+ client->req->input[n - 1] == '\r'))
+ continue;
+
+ client->req->input[n] = '\0';
+ client->req->input[n - 1] = '\0';
+
+ client_debugf(client, "[Received header: %d]\r", status);
+ if (!(client->req->input[0] >= '0' &&
+ client->req->input[0] <= '9' &&
+ client->req->input[1] >= '0' &&
+ client->req->input[1] <= '9' &&
+ client->req->input[2] == ' ')) {
+ client_printf(client, "[Malformed response %s]\r",
+ client->req->input);
+ client->req->state = REQ_STATE_DISCONNECTED;
+ return;
+ }
+
+ status = ((client->req->input[0] - '0') * 10) +
+ (client->req->input[1] - '0');
+
+ if (status >= 10 && status <= 19) {
+ /* input, not supported */
+ client_printf(client, "[Input not supported (%d)]\r",
+ status);
+ client->req->state = REQ_STATE_DISCONNECTED;
+ return;
+ } else if (status >= 20 && status <= 29) {
+ /* success */
+ client->req->gem_state = GEM_STATE_BODY;
+ } else if (status >= 30 && status <= 39) {
+ /* redirect */
+ /* TODO */
+ } else if (status >= 40 && status <= 49) {
+ /* temp fail */
+ client_printf(client, "[Temporary server failure (%d)]\r",
+ status);
+ client->req->state = REQ_STATE_DISCONNECTED;
+ return;
+ } else if (status >= 50 && status <= 59) {
+ /* perm fail */
+ client_printf(client, "[Permanent server failure (%d)]\r",
+ status);
+ client->req->state = REQ_STATE_DISCONNECTED;
+ return;
+ } else if (status >= 60 && status <= 69) {
+ /* auth, not supported */
+ client_printf(client, "[Auth not supported (%d)]\r",
+ status);
+ client->req->state = REQ_STATE_DISCONNECTED;
+ return;
+ } else {
+ client_printf(client, "[Unsupported status %d]\r", status);
+ client->req->state = REQ_STATE_DISCONNECTED;
+ return;
+ }
+
+ strlcpy(client->req->mime_type, client->req->input + 3,
+ sizeof(client->req->mime_type));
+
+ if (n == client->req->input_len - 1)
+ client->req->input_len = 0;
+ else {
+ memmove(client->req->input, client->req->input + n + 1,
+ sizeof(client->req->input) - (n + 1));
+ client->req->input_len -= (n + 1);
+ }
+ break;
+ }
+ break;
+ }
+ case GEM_STATE_BODY:
+ /* TODO */
+ client_print(client, client->req->input, client->req->input_len);
+ client->req->input_len = 0;
+ break;
+ }
+}
--- client.h Mon Sep 30 17:56:27 2024
+++ client.h Mon Sep 30 17:56:27 2024
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __BROWSER_H__
+#define __BROWSER_H__
+
+#include <stdlib.h>
+#include "tcp.h"
+#include "util.h"
+
+enum {
+ REQ_STATE_IDLE,
+ REQ_STATE_CONNECTED,
+ REQ_STATE_DISCONNECTED
+};
+
+enum {
+ GEM_STATE_HEADER,
+ GEM_STATE_BODY
+};
+
+struct tcp_request {
+ short tls_id;
+
+ unsigned char tcp_buf[(4 * 1500) + 2048]; /* 4*MTU + tcp_input */
+ unsigned char tcp_input[2048];
+ size_t tcp_input_len;
+ short state;
+
+ char hostname[256];
+ ip_addr host_ip;
+ unsigned short port;
+
+ TCPiopb tcp_iopb;
+ StreamPtr tcp_stream;
+ wdsEntry tcp_wds[2];
+ TCPStatusPB tcp_status_pb;
+
+ char uri[1024 + 2];
+ size_t uri_len;
+
+ short gem_state;
+ char input[1024];
+ size_t input_len;
+
+ char mime_type[64];
+};
+
+struct client {
+ WindowPtr win;
+ TEHandle uri_te;
+ ControlHandle go_button;
+ TEHandle output_te;
+ ControlHandle output_te_scroller;
+
+ TEHandle active_te;
+ struct tcp_request *req;
+
+ unsigned char scsi_buf[2048];
+};
+
+struct client * client_init(void);
+size_t client_print(struct client *client, const char *str, size_t len);
+void client_clear(struct client *client);
+
+#endif
\ No newline at end of file
--- gemino.π.r Mon Sep 30 20:58:34 2024
+++ gemino.π.r Mon Sep 30 20:58:34 2024
@@ -0,0 +1,75 @@
+data 'MENU' (128) {
+ $"0080 0000 0000 0000 0000 FFFF FFFB 0114" /* .Ä.............. */
+ $"1241 626F 7574 2057 696B 6970 6564 6961" /* .About Wikipedia */
+ $"2E2E 2E00 0000 0001 2D00 0000 0000" /* ........-..... */
+};
+
+data 'MENU' (129) {
+ $"0081 0000 0000 0000 0000 FFFF FFFF 0446" /* .Å.............F */
+ $"696C 6504 5175 6974 0051 0000 00" /* ile.Quit.Q... */
+};
+
+data 'MENU' (130) {
+ $"0082 0000 0000 0000 0000 FFFF FFFF 0445" /* .Ç.............E */
+ $"6469 7403 4375 7400 5800 0004 436F 7079" /* dit.Cut.X...Copy */
+ $"0043 0000 0550 6173 7465 0056 0000 0A53" /* .C...Paste.V...S */
+ $"656C 6563 7420 416C 6C00 4100 0000" /* elect All.A... */
+};
+
+data 'MENU' (131) {
+ $"0083 0000 0000 0000 0000 FFFF FFFF 0456" /* .É.............V */
+ $"6965 770B 5669 6577 2053 6F75 7263 6500" /* iew.View Source. */
+ $"5500 0000" /* U... */
+};
+
+data 'MBAR' (128) {
+ $"0004 0080 0081 0082 0083" /* ...Ä.Å.Ç.É */
+};
+
+data 'DITL' (130, "ASK") {
+ $"0003 0000 0000 004E 00FA 0064 0134 0403" /* .......N...d.4.. */
+ $"5965 7321 0000 0000 004E 00B4 0064 00EE" /* Yes!.....N.¥.d.. */
+ $"0402 4E6F 0000 0000 000D 004E 0041 0136" /* ..No.....¬.N.A.6 */
+ $"0802 5E30 0000 0000 000D 0017 002D 0037" /* ..^0.....¬...-.7 */
+ $"A002 0001" /* †... */
+};
+
+data 'vers' (1) {
+ $"0010 6000 0000 0330 2E31 2630 2E31 20A9" /* ..`....0.1&0.1 © */
+ $"2032 3032 342C 206A 6F73 6875 6120 7374" /* 2024, joshua st */
+ $"6569 6E20 3C6A 6373 406A 6373 2E6F 7267" /* ein <jcs@jcs.org */
+ $"3E" /* > */
+};
+
+data 'ICN#' (128) {
+ $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */
+ $"0000 0000 0C00 0000 1800 0060 3000 06F0" /* ...........`0... */
+ $"2007 C790 23E6 4490 202D C490 2028 0410" /* .«ê#.Dê -ƒê (.. */
+ $"1048 0410 0FC4 0000 0007 F000 0000 0000" /* .H...ƒ.......... */
+ $"0000 0000 0040 0000 0006 07C0 000B 0440" /* .....@.....¿...@ */
+ $"0048 8840 0048 9040 0050 9040 0010 9040" /* .Hà@.Hê@.Pê@..ê@ */
+ $"0000 18C0 0000 0700 0000 0000 0000 0000" /* ...¿............ */
+ $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */
+ $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */
+ $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */
+ $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */
+ $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */
+ $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */
+ $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */
+ $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */
+ $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */
+};
+
+data 'BNDL' (128) {
+ $"474D 4E4F 0000 0001 4652 4546 0000 0000" /* GMNO....FREF.... */
+ $"0080 4943 4E23 0000 0000 0080" /* .ÄICN#.....Ä */
+};
+
+data 'FREF' (128) {
+ $"4150 504C 0000 00" /* APPL... */
+};
+
+data 'GMNO' (0, "Owner resource") {
+ $"00" /* . */
+};
+
--- gemino.h Mon Sep 30 13:51:05 2024
+++ gemino.h Mon Sep 30 13:51:05 2024
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __TCPUTILITY_H__
+#define __TCPUTILITY_H__
+
+#include "client.h"
+#include "util.h"
+
+#define PROGRAM_NAME "Gemino"
+
+#define MBAR_ID 128
+
+#define APPLE_MENU_ID 128
+#define APPLE_MENU_ABOUT_ID 1
+
+#define FILE_MENU_ID 129
+#define FILE_MENU_QUIT_ID 1
+
+#define EDIT_MENU_ID 130
+#define EDIT_MENU_CUT_ID 1
+#define EDIT_MENU_COPY_ID 2
+#define EDIT_MENU_PASTE_ID 3
+#define EDIT_MENU_SELECT_ALL_ID 4
+
+#define DEFAULT_GEMINI_PORT 1965
+
+struct tls_init_request {
+ uint8_t flags[2];
+#define TLS_INIT_REQUEST_FLAG_NO_VERIFY (1 << 0)
+ uint8_t unix_time[4];
+ char hostname[256];
+};
+
+extern MenuHandle file_menu, edit_menu;
+
+void menu_defaults(void);
+
+short scsi_find_tls(void);
+bool scsi_tls_init(char tls_id, unsigned char *buf, size_t buf_size,
+ struct tls_init_request *req);
+bool scsi_tls_close(char tls_id, unsigned char *buf, size_t buf_size);
+short scsi_tls_status(char tls_id, unsigned char *buf, size_t buf_size,
+ short *cipherspace, short *plainspace, short *error);
+size_t scsi_tls_read(char tls_id, unsigned char *buf, size_t buf_size,
+ bool cipher);
+size_t scsi_tls_write(char tls_id, unsigned char *buf, size_t buf_size,
+ bool cipher);
+void scsi_cleanup(void);
+
+#endif
\ No newline at end of file
--- main.c Mon Sep 30 10:29:36 2024
+++ main.c Mon Sep 30 10:29:36 2024
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "gemino.h"
+#include "client.h"
+#include "focusable.h"
+#include "tcp.h"
+#include "util.h"
+
+MenuHandle apple_menu, file_menu, edit_menu;
+bool quitting = false;
+
+bool handle_menu(long menu_id);
+void handle_exit(void);
+
+short
+main(void)
+{
+ Handle mbar;
+ EventRecord event;
+ WindowPtr event_win;
+ GrafPtr old_port;
+ struct focusable *found_focusable;
+ short event_in, n;
+ char key;
+
+ InitGraf(&thePort);
+ InitFonts();
+ FlushEvents(everyEvent, 0);
+ InitWindows();
+ InitMenus();
+ TEInit();
+ InitDialogs(0);
+ InitCursor();
+ MaxApplZone();
+
+ util_init();
+ _atexit(handle_exit);
+
+ mbar = GetNewMBar(MBAR_ID);
+ SetMenuBar(mbar);
+ apple_menu = GetMHandle(APPLE_MENU_ID);
+ AddResMenu(apple_menu, 'DRVR');
+ file_menu = GetMHandle(FILE_MENU_ID);
+ edit_menu = GetMHandle(EDIT_MENU_ID);
+ menu_defaults();
+ DrawMenuBar();
+
+ progress("Finding BlueSCSI TLS...");
+ if (scsi_find_tls() < 0) {
+ progress(NULL);
+ panic("No BlueSCSI TLS device found");
+ }
+ progress(NULL);
+
+ if (_TCPInit() != 0)
+ panic("Failed initializing MacTCP");
+
+ client_init();
+
+ while (!quitting) {
+ WaitNextEvent(everyEvent, &event, 5L, 0L);
+
+ switch (event.what) {
+ case nullEvent:
+ for (n = 0; n < nfocusables; n++) {
+ if (focusables[n]->idle)
+ focusables[n]->idle(focusables[n], &event);
+ }
+ break;
+ case keyDown:
+ case autoKey:
+ key = event.message & charCodeMask;
+ if ((event.modifiers & cmdKey) != 0 &&
+ handle_menu(MenuKey(key)) == true)
+ break;
+ if (nfocusables && focusables[0]->visible &&
+ focusables[0]->key_down)
+ focusables[0]->key_down(focusables[0], &event);
+ break;
+ case mouseDown:
+ event_in = FindWindow(event.where, &event_win);
+ found_focusable = focusable_find(event_win);
+
+ if (found_focusable)
+ focusable_show(found_focusable);
+
+ switch (event_in) {
+ case inMenuBar:
+ handle_menu(MenuSelect(event.where));
+ break;
+ case inSysWindow:
+ SystemClick(&event, event_win);
+ break;
+ case inDrag:
+ DragWindow(event_win, event.where, &screenBits.bounds);
+ break;
+ case inGrow:
+ if (found_focusable && found_focusable->resize)
+ found_focusable->resize(found_focusable, &event);
+ break;
+ case inGoAway:
+ if (TrackGoAway(event_win, event.where) && found_focusable)
+ focusable_close(found_focusable);
+ break;
+ case inContent:
+ if (found_focusable && found_focusable->mouse_down)
+ found_focusable->mouse_down(found_focusable, &event);
+ break;
+ }
+
+ break;
+ case updateEvt:
+ case activateEvt:
+ event_win = (WindowPtr)event.message;
+ found_focusable = focusable_find(event_win);
+
+ GetPort(&old_port);
+ SetPort(event_win);
+
+ if (event.what == updateEvt)
+ BeginUpdate(event_win);
+
+ if (found_focusable && found_focusable->update)
+ found_focusable->update(found_focusable, &event);
+
+ if (event.what == updateEvt)
+ EndUpdate(event_win);
+
+ SetPort(old_port);
+ break;
+ case app4Evt:
+ if (HiWord(event.message) & (1 << 8)) {
+ /* multifinder suspend/resume */
+ switch (event.message & (1 << 0)) {
+ case 0:
+ /* suspend */
+ for (n = 0; n < nfocusables; n++) {
+ if (focusables[n]->suspend)
+ focusables[n]->suspend(focusables[n], &event);
+ }
+ break;
+ case 1:
+ /* resume */
+ for (n = 0; n < nfocusables; n++) {
+ if (focusables[n]->resume)
+ focusables[n]->resume(focusables[n], &event);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+bool
+handle_menu(long menu_id)
+{
+ struct focusable *focused;
+ short menu, item;
+
+ menu = HiWord(menu_id);
+ item = LoWord(menu_id);
+
+ if ((focused = focusable_focused()) && focused->menu &&
+ focused->menu(focused, menu, item))
+ goto handled;
+
+ switch (menu) {
+ case APPLE_MENU_ID:
+ switch (item) {
+ case APPLE_MENU_ABOUT_ID:
+ about(PROGRAM_NAME);
+ break;
+ default: {
+ Str255 da;
+ GrafPtr save_port;
+
+ GetItem(apple_menu, LoWord(menu_id), &da);
+ GetPort(&save_port);
+ OpenDeskAcc(da);
+ SetPort(save_port);
+ break;
+ }
+ }
+ break;
+ case FILE_MENU_ID:
+ switch (item) {
+ case FILE_MENU_QUIT_ID:
+ if (focusables_quit())
+ quitting = true;
+ break;
+ }
+ break;
+ }
+
+handled:
+ HiliteMenu(0);
+ return true;
+}
+
+void
+menu_defaults(void)
+{
+ DisableItem(edit_menu, EDIT_MENU_CUT_ID);
+ DisableItem(edit_menu, EDIT_MENU_COPY_ID);
+ DisableItem(edit_menu, EDIT_MENU_PASTE_ID);
+ DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+}
+
+void
+handle_exit(void)
+{
+ focusables_quit();
+}
--- scsi.c Mon Sep 30 20:46:09 2024
+++ scsi.c Mon Sep 30 20:46:09 2024
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2023 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <SCSI.h>
+#include <string.h>
+#include <stdio.h>
+#include "gemino.h"
+
+struct scsi_inquiry {
+ unsigned char deviceType;
+ unsigned char deviceQualifier;
+ unsigned char version;
+ unsigned char responseFormat;
+ unsigned char additionalLength;
+ unsigned char vendor;
+ short reserved;
+ unsigned char vendorID[8];
+ unsigned char productID[16];
+ unsigned char revision[4];
+ unsigned char vendor2[20];
+ unsigned char reserved2[42];
+ unsigned char vendor3[158];
+};
+
+enum {
+ SCSI_READ,
+ SCSI_READ_UNKNOWN_SIZE,
+ SCSI_WRITE
+};
+
+#define BLUESCSI_TLS_CMD 0x1d
+#define BLUESCSI_TLS_CMD_INIT 0x01
+#define BLUESCSI_TLS_CMD_STATUS 0x02
+#define BLUESCSI_TLS_CMD_READ_PLAIN 0x03
+#define BLUESCSI_TLS_CMD_WRITE_PLAIN 0x04
+#define BLUESCSI_TLS_CMD_READ_CIPHER 0x05
+#define BLUESCSI_TLS_CMD_WRITE_CIPHER 0x06
+#define BLUESCSI_TLS_CMD_CLOSE 0x07
+
+static short bluescsi_tls_scsi_id = -1;
+static struct SCSIInstr tib[2];
+
+short scsi_io(unsigned char *cdb, unsigned long cdb_len, short io_type,
+ unsigned char *buf, unsigned long len);
+
+short
+scsi_find_tls(void)
+{
+ struct scsi_inquiry inq;
+ unsigned char cdb[16] = { 0 };
+ short stat, msg, err;
+ short i, scsi_id;
+
+ cdb[0] = 0x12; /* inquiry */
+ cdb[4] = 5; /* length */
+
+ tib[0].scOpcode = scNoInc;
+ tib[0].scParam1 = (unsigned long)&inq;
+ tib[0].scParam2 = 5;
+ tib[1].scOpcode = scStop;
+ tib[1].scParam1 = 0;
+ tib[1].scParam2 = 0;
+
+ SCSIReset();
+
+ for (scsi_id = 0; scsi_id <= 7; scsi_id++) {
+ for (i = 0; i <= 1; i++) {
+ if (SCSIGet() != noErr)
+ goto scan_failed;
+
+ if (SCSISelect(scsi_id) != noErr)
+ break;
+
+ if (SCSICmd((Ptr)&cdb, 6) != noErr) {
+ SCSIComplete(&stat, &msg, 300);
+ goto scan_failed;
+ }
+
+ memset(&inq, 0, sizeof(inq));
+
+ if (SCSIRead((Ptr)&tib) != noErr) {
+ SCSIComplete(&stat, &msg, 300);
+ goto scan_failed;
+ }
+
+ if (i == 0) {
+ cdb[4] += inq.additionalLength;
+ tib[0].scParam2 += inq.additionalLength;
+ }
+
+ if (SCSIComplete(&stat, &msg, 300) != noErr)
+ goto scan_failed;
+
+ if (memcmp(inq.vendorID, "BlueSCSI", 8) == 0 &&
+ memcmp(inq.productID, "TLS", 3) == 0) {
+ bluescsi_tls_scsi_id = scsi_id;
+ return noErr;
+ }
+ }
+ }
+
+scan_failed:
+ return -1;
+}
+
+bool
+scsi_tls_init(char tls_id, unsigned char *buf, size_t buf_size,
+ struct tls_init_request *req)
+{
+ unsigned char cdb[6];
+ unsigned short size;
+
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = BLUESCSI_TLS_CMD;
+ cdb[1] = BLUESCSI_TLS_CMD_INIT;
+ cdb[2] = tls_id;
+
+ size = sizeof(struct tls_init_request);
+ cdb[3] = (size >> 8) & 0xff;
+ cdb[4] = size & 0xff;
+
+ memcpy(buf, req, size);
+
+ return (scsi_io(cdb, sizeof(cdb), SCSI_WRITE, buf, size) != -1);
+}
+
+bool
+scsi_tls_close(char tls_id, unsigned char *buf, size_t buf_size)
+{
+ unsigned char cdb[6];
+
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = BLUESCSI_TLS_CMD;
+ cdb[1] = BLUESCSI_TLS_CMD_CLOSE;
+ cdb[2] = tls_id;
+
+ return (scsi_io(cdb, sizeof(cdb), SCSI_WRITE, NULL, 0) != -1);
+}
+
+short
+scsi_tls_status(char tls_id, unsigned char *buf, size_t buf_size,
+ short *cipherspace, short *plainspace, short *error)
+{
+ unsigned char cdb[6];
+ size_t size;
+ short ret;
+
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = BLUESCSI_TLS_CMD;
+ cdb[1] = BLUESCSI_TLS_CMD_STATUS;
+ cdb[2] = tls_id;
+
+ ret = scsi_io(cdb, sizeof(cdb), SCSI_READ, buf, 8);
+ if (ret != 8)
+ return 0;
+
+ *cipherspace = ((short)buf[2] << 8) | buf[3];
+ *plainspace = ((short)buf[4] << 8) | buf[5];
+ *error = ((short)buf[6] << 8) | buf[7];
+
+ return (((short)buf[0] << 8) | buf[1]);
+}
+
+size_t
+scsi_tls_read(char tls_id, unsigned char *buf, size_t buf_size, bool cipher)
+{
+ unsigned char cdb[6];
+ short ret;
+
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = BLUESCSI_TLS_CMD;
+ if (cipher)
+ cdb[1] = BLUESCSI_TLS_CMD_READ_CIPHER;
+ else
+ cdb[1] = BLUESCSI_TLS_CMD_READ_PLAIN;
+ cdb[2] = tls_id;
+ cdb[3] = (buf_size >> 8) & 0xff;
+ cdb[4] = buf_size & 0xff;
+
+ ret = scsi_io(cdb, sizeof(cdb), SCSI_READ_UNKNOWN_SIZE, buf, buf_size);
+ if (ret < 0)
+ return 0;
+
+ return ret;
+}
+
+size_t
+scsi_tls_write(char tls_id, unsigned char *buf, size_t buf_size,
+ bool cipher)
+{
+ unsigned char cdb[6];
+ short ret;
+
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = BLUESCSI_TLS_CMD;
+ if (cipher)
+ cdb[1] = BLUESCSI_TLS_CMD_WRITE_CIPHER;
+ else
+ cdb[1] = BLUESCSI_TLS_CMD_WRITE_PLAIN;
+ cdb[2] = tls_id;
+ cdb[3] = (buf_size >> 8) & 0xff;
+ cdb[4] = buf_size & 0xff;
+
+ ret = scsi_io(cdb, sizeof(cdb), SCSI_WRITE, buf, buf_size);
+ if (ret < 0)
+ return 0;
+
+ return ret;
+}
+
+short
+scsi_io(unsigned char *cdb, unsigned long cdb_len, short io_type,
+ unsigned char *buf, unsigned long len)
+{
+ short ret, ioret, stat, msg, err, tlen;
+
+ if (SCSIGet() != noErr) {
+ warn("SCSIGet failed");
+ return -1;
+ }
+
+ if (SCSISelect(bluescsi_tls_scsi_id) != noErr) {
+ warn("SCSISelect failed");
+ return -1;
+ }
+
+ if (SCSICmd((Ptr)cdb, cdb_len) != noErr) {
+ SCSIComplete(&stat, &msg, 300);
+ warn("SCSICmd failed");
+ return -1;
+ }
+
+ if (len) {
+ switch (io_type) {
+ case SCSI_READ:
+ memset(buf, 0, len);
+
+ tib[0].scOpcode = scNoInc;
+ tib[0].scParam1 = (long)buf;
+ tib[0].scParam2 = len;
+ tib[1].scOpcode = scStop;
+ tib[1].scParam1 = 0;
+ tib[1].scParam2 = 0;
+
+ ioret = SCSIRead((Ptr)&tib);
+ break;
+ case SCSI_READ_UNKNOWN_SIZE:
+ memset(buf, 0, len);
+
+ tib[0].scOpcode = scNoInc;
+ tib[0].scParam1 = (long)buf;
+ tib[0].scParam2 = 2;
+ tib[1].scOpcode = scStop;
+ tib[1].scParam1 = 0;
+ tib[1].scParam2 = 0;
+
+ ioret = SCSIRead((Ptr)&tib);
+ tlen = ((short)buf[0] << 8) + buf[1];
+
+ if (tlen > len) {
+ warn("SCSI trying to read %ld bytes but buf is only %ld",
+ tlen, len);
+ tlen = len;
+ }
+
+ len = tlen;
+
+ if (tlen) {
+ memset(buf, 0, tlen);
+ tib[0].scOpcode = scNoInc;
+ tib[0].scParam1 = (long)buf;
+ tib[0].scParam2 = tlen;
+ tib[1].scOpcode = scStop;
+ tib[1].scParam1 = 0;
+ tib[1].scParam2 = 0;
+
+ ioret = SCSIRead((Ptr)&tib);
+ }
+
+ break;
+ case SCSI_WRITE:
+ tib[0].scOpcode = scNoInc;
+ tib[0].scParam1 = (long)buf;
+ tib[0].scParam2 = len;
+ tib[1].scOpcode = scStop;
+ tib[1].scParam1 = 0;
+ tib[1].scParam2 = 0;
+
+ ioret = SCSIWrite((Ptr)&tib);
+ break;
+ }
+ } else {
+ tib[0].scOpcode = scStop;
+ tib[0].scParam1 = 0;
+ tib[0].scParam2 = 0;
+
+ ioret = SCSIWrite((Ptr)&tib);
+ }
+
+ /* complete and free the bus before responding to the read/write */
+ if ((ret = SCSIComplete(&stat, &msg, 300 /* 1/60 ticks */)) != noErr) {
+ warn("SCSIComplete failed");
+ return -1;
+ }
+
+ if (ioret != noErr && ioret != scPhaseErr)
+ warn("SCSIRead/Write failed");
+
+ if (stat != noErr)
+ return -1;
+
+ return len;
+}
+
+void
+scsi_cleanup(void)
+{
+ short stat, msg;
+
+ SCSIComplete(&stat, &msg, 60);
+}
\ No newline at end of file