AmendHub

Download:

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