From 516e60887fc5502576d0d290f8c74325c7184eb0 Mon Sep 17 00:00:00 2001 From: cy384 Date: Mon, 20 Jul 2020 00:14:27 -0400 Subject: [PATCH] split code up into more files, create network config/read thread --- CMakeLists.txt | 2 +- ssheven-console.c | 175 +++++++++++ ssheven-console.h | 28 ++ ssheven-debug.h => ssheven-debug.c | 7 + ssheven.c | 468 +++++++++-------------------- ssheven.h | 59 ++++ 6 files changed, 419 insertions(+), 320 deletions(-) create mode 100644 ssheven-console.c create mode 100644 ssheven-console.h rename ssheven-debug.h => ssheven-debug.c (98%) create mode 100644 ssheven.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 83eef06..b2c4230 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.9) -add_application(ssheven CREATOR "SSH7" ssheven.c ssheven.r) +add_application(ssheven CREATOR "SSH7" ssheven.c ssheven-console.c ssheven.r) set_target_properties(ssheven PROPERTIES COMPILE_OPTIONS -ffunction-sections) diff --git a/ssheven-console.c b/ssheven-console.c new file mode 100644 index 0000000..3f49215 --- /dev/null +++ b/ssheven-console.c @@ -0,0 +1,175 @@ +/* + * ssheven + * + * Copyright (c) 2020 by cy384 + * See LICENSE file for details + */ + +#include "ssheven-console.h" + + +void draw_char(int x, int y, Rect* r, char c) +{ + TextFont(kFontIDMonaco); + TextSize(9); + TextFace(normal); + + int cell_height = 12; + int cell_width = CharWidth('M'); + + MoveTo(r->left + x * cell_width + 2, r->top + ((y+1) * cell_height) - 2); + DrawChar(c); +} + +void draw_screen(Rect* r) +{ + EraseRect(r); + for (int x = 0; x < 80; x++) + for (int y = 0; y < 24; y++) + draw_char(x, y, r, con.data[x][y]); +} + +void ruler(Rect* r) +{ + char itoc[] = {'0','1','2','3','4','5','6','7','8','9'}; + + for (int x = 0; x < 80; x++) + for (int y = 0; y < 24; y++) + draw_char(x, y, r, itoc[x%10]); +} + +void bump_up_line() +{ + for (int y = 0; y < 23; y++) + { + for (int x = 0; x < 80; x++) + { + con.data[x][y] = con.data[x][y+1]; + } + } + + for (int x = 0; x < 80; x++) con.data[x][23] = ' '; +} + +int is_printable(char c) +{ + if (c >= 32 && c <= 126) return 1; else return 0; +} + +void print_char(char c) +{ + // backspace + if ('\b' == c) + { + // erase current location + con.data[con.cursor_x][con.cursor_y] = ' '; + + // wrap back to the previous line if possible and necessary + if (con.cursor_x == 0 && con.cursor_y != 0) + { + con.cursor_x = 79; + con.cursor_y--; + } + // otherwise just move back a spot + else if (con.cursor_x > 0) + { + con.cursor_x--; + } + + return; + } + + // got a bell, give em a system beep (value of 30 recommended by docs) + if ('\a' == c) SysBeep(30); + + if ('\n' == c) + { + con.cursor_y++; + con.cursor_x = 0; + } + + if (is_printable(c)) + { + con.data[con.cursor_x][con.cursor_y] = c; + con.cursor_x++; + } + + if (con.cursor_x == 80) + { + con.cursor_x = 0; + con.cursor_y++; + } + + if (con.cursor_y == 24) + { + bump_up_line(); + con.cursor_y = 23; + } +} + +void print_string_i(const char* c) +{ + print_string(c); + // TODO invalidate only the correct region + InvalRect(&(con.win->portRect)); +} + +void print_string(const char* c) +{ + for (int i = 0; i < strlen(c); i++) + { + print_char(c[i]); + } +} + +void set_window_title(WindowPtr w, const char* c_name) +{ + Str255 pascal_name; + strncpy((char *) &pascal_name[1], c_name, 255); + pascal_name[0] = strlen(c_name); + + SetWTitle(w, pascal_name); +} + + +void console_setup(void) +{ + TextFont(kFontIDMonaco); + TextSize(9); + TextFace(normal); + + int cell_height = 12; + int cell_width = CharWidth('M'); + + Rect initial_window_bounds = qd.screenBits.bounds; + InsetRect(&initial_window_bounds, 20, 20); + initial_window_bounds.top += 40; + + initial_window_bounds.bottom = initial_window_bounds.top + cell_height * 24 + 2; + initial_window_bounds.right = initial_window_bounds.left + cell_width * 80 + 4; + + // limits on window size changes: + // top = min vertical + // bottom = max vertical + // left = min horizontal + // right = max horizontal + //Rect window_limits = { .top = 100, .bottom = 200, .left = 100, .right = 200 }; + + ConstStr255Param title = "\pssheven " SSHEVEN_VERSION; + + WindowPtr win = NewWindow(NULL, &initial_window_bounds, title, true, noGrowDocProc, (WindowPtr)-1, true, 0); + + Rect portRect = win->portRect; + + SetPort(win); + EraseRect(&portRect); + + int exit_main_loop = 0; + + con.win = win; + memset(con.data, ' ', sizeof(char) * 24*80); + + con.cursor_x = 0; + con.cursor_y = 0; +} + diff --git a/ssheven-console.h b/ssheven-console.h new file mode 100644 index 0000000..253442e --- /dev/null +++ b/ssheven-console.h @@ -0,0 +1,28 @@ +/* + * ssheven + * + * Copyright (c) 2020 by cy384 + * See LICENSE file for details + */ + +#pragma once + +#include "ssheven.h" +#include + +void draw_char(int x, int y, Rect* r, char c); +void draw_screen(Rect* r); + +void ruler(Rect* r); + +void bump_up_line(); + +int is_printable(char c); + +void print_char(char c); +void print_string(const char* c); +void print_string_i(const char* c); + +void set_window_title(WindowPtr w, const char* c_name); + +void console_setup(void); diff --git a/ssheven-debug.h b/ssheven-debug.c similarity index 98% rename from ssheven-debug.h rename to ssheven-debug.c index 48fa472..3bde068 100644 --- a/ssheven-debug.h +++ b/ssheven-debug.c @@ -1,3 +1,10 @@ +/* + * ssheven + * + * Copyright (c) 2020 by cy384 + * See LICENSE file for details + */ + /* handy debugging string conversions */ /* convert libssh2 errors into strings */ diff --git a/ssheven.c b/ssheven.c index bef0c1c..f0ab530 100644 --- a/ssheven.c +++ b/ssheven.c @@ -5,64 +5,26 @@ * See LICENSE file for details */ -// retro68 stdio/console library -//#include - -// open transport -#include -#include - -// mac os stuff -#include -#include -#include -#include -#include -#include - -// libssh2 -#include +#include "ssheven.h" +#include "ssheven-console.h" // functions to convert error and status codes to strings -#include "ssheven-debug.h" - -// version string -#define SSHEVEN_VERSION "0.1.0" - -// size for recv and send thread buffers -#define BUFFER_SIZE 4096 - -// terminal type to send over ssh, determines features etc. -// "vanilla" supports basically nothing, which is good for us here -#define TERMINAL_TYPE "vanilla" +#include "ssheven-debug.c" // error checking convenience macros #define OT_CHECK(X) err = (X); if (err != noErr) { print_string("" #X " failed\n"); return; }; #define SSH_CHECK(X) rc = (X); if (rc != LIBSSH2_ERROR_NONE) { print_string("" #X " failed: "); print_string(libssh2_error_string(rc)); print_string("\n"); return;}; // sinful globals -struct ssheven_console -{ - WindowPtr win; +struct ssheven_console con = { NULL, {0}, 0, 0 }; +struct ssheven_ssh_connection ssh_con = { NULL, NULL, kOTInvalidEndpointRef, NULL, NULL }; - char data[80][24]; +enum { WAIT, READ, EXIT } read_thread_command = WAIT; +enum { UNITIALIZED, OPEN, CLEANUP, DONE } read_thread_state = UNITIALIZED; - int cursor_x; - int cursor_y; -} con = { NULL, {0}, 0, 0 }; - -struct ssheven_ssh_connection -{ - LIBSSH2_CHANNEL* channel; - LIBSSH2_SESSION* session; - - EndpointRef endpoint; - - char* recv_buffer; - char* send_buffer; -} ssh_con = { NULL, NULL, kOTInvalidEndpointRef, NULL, NULL }; - -enum { wait, read, exit } read_thread_state = wait; +char hostname[256] = {0}; +char username[256] = {0}; +char password[256] = {0}; // borrowed from Retro68 sample code // draws the "default" indicator around a button @@ -92,126 +54,9 @@ static pascal void yield_notifier(void* contextPtr, OTEventCode code, OTResult r } } -void draw_char(int x, int y, Rect* r, char c) -{ - TextFont(kFontIDMonaco); - TextSize(9); - TextFace(normal); - - int cell_height = 12; - int cell_width = CharWidth('M'); - - MoveTo(r->left + x * cell_width + 2, r->top + ((y+1) * cell_height) - 2); - DrawChar(c); -} - -void draw_screen(Rect* r) -{ - EraseRect(&(con.win->portRect)); - for (int x = 0; x < 80; x++) - for (int y = 0; y < 24; y++) - draw_char(x, y, r, con.data[x][y]); -} - -char itoc[] = {'0','1', '2','3','4','5','6','7','8','9'}; - -void ruler(Rect* r) -{ - for (int x = 0; x < 80; x++) - for (int y = 0; y < 24; y++) - draw_char(x, y, r, itoc[x%10]); -} - -void bump_up_line() -{ - for (int y = 0; y < 23; y++) - { - for (int x = 0; x < 80; x++) - { - con.data[x][y] = con.data[x][y+1]; - } - } - - for (int x = 0; x < 80; x++) con.data[x][23] = ' '; -} - -int is_printable(char c) -{ - if (c >= 32 && c <= 126) return 1; else return 0; -} - -void print_char(char c) -{ - // backspace - if ('\b' == c) - { - // erase current location - con.data[con.cursor_x][con.cursor_y] = ' '; - - // wrap back to the previous line if possible and necessary - if (con.cursor_x == 0 && con.cursor_y != 0) - { - con.cursor_x = 79; - con.cursor_y--; - } - // otherwise just move back a spot - else if (con.cursor_x > 0) - { - con.cursor_x--; - } - - return; - } - - // got a bell, give em a system beep (value of 30 recommended by docs) - if ('\a' == c) SysBeep(30); - - if ('\n' == c) - { - con.cursor_y++; - con.cursor_x = 0; - } - - if (is_printable(c)) - { - con.data[con.cursor_x][con.cursor_y] = c; - con.cursor_x++; - } - - if (con.cursor_x == 80) - { - con.cursor_x = 0; - con.cursor_y++; - } - - if (con.cursor_y == 24) - { - bump_up_line(); - con.cursor_y = 23; - } -} - -void print_string(const char* c) -{ - for (int i = 0; i < strlen(c); i++) - { - print_char(c[i]); - } -} - -#include -void set_window_title(WindowPtr w, const char* c_name) -{ - Str255 pascal_name; - strncpy((char *) &pascal_name[1], c_name, 255); - pascal_name[0] = strlen(c_name); - - SetWTitle(w, pascal_name); -} - +// read from the channel and print to console void ssh_read(void) { - // read from the channel int rc = libssh2_channel_read(ssh_con.channel, ssh_con.recv_buffer, BUFFER_SIZE); if (rc == 0) return; @@ -219,8 +64,6 @@ void ssh_read(void) if (rc > 0) { for(int i = 0; i < rc; ++i) print_char(ssh_con.recv_buffer[i]); - InvalRect(&(con.win->portRect)); - //print_string("\n"); } else { @@ -228,11 +71,15 @@ void ssh_read(void) print_string(libssh2_error_string(rc)); print_string("\n"); } -} + // TODO invalidate only the correct region + InvalRect(&(con.win->portRect)); +} void end_connection(void) { + read_thread_state = CLEANUP; + OSStatus err = noErr; if (ssh_con.channel) @@ -289,74 +136,73 @@ void end_connection(void) err = OTCloseProvider(ssh_con.endpoint); if (err != noErr) print_string("OTCloseProvider failed\n"); } + + read_thread_state = DONE; +} + +void check_network_events(void) +{ + OSStatus err = noErr; + + // check if we have any new network events + OTResult look_result = OTLook(ssh_con.endpoint); + + switch (look_result) + { + case T_DATA: + case T_EXDATA: + // got data + ssh_read(); + break; + + case T_RESET: + // connection reset? close it/give up + end_connection(); + break; + + case T_DISCONNECT: + // got disconnected + OTRcvDisconnect(ssh_con.endpoint, nil); + end_connection(); + break; + + case T_ORDREL: + // nice tcp disconnect requested by remote + err = OTRcvOrderlyDisconnect(ssh_con.endpoint); + if (err == noErr) + { + err = OTSndOrderlyDisconnect(ssh_con.endpoint); + } + end_connection(); + break; + + default: + // something weird or irrelevant: ignore it + break; + } + + return; } void event_loop(void) { int exit_event_loop = 0; - OTResult look_result = 0; - OSStatus err = noErr; + EventRecord event; + WindowPtr eventWin; + + // maximum length of time to sleep (in ticks) + // GetCaretTime gets the number of ticks between caret on/off time + long int sleep_time = GetCaretTime(); do { - // system task yields to run drivers and other stuff! - //SystemTask(); // don't need to call if we use waitnextevent - //Idle(); - EventRecord event; - WindowPtr eventWin; - -/* - while(!GetNextEvent(everyEvent, &event)) + // wait to get a GUI event + while (!WaitNextEvent(everyEvent, &event, sleep_time, NULL)) { - SystemTask(); - YieldToAnyThread(); - //Idle(); - } -*/ + // timed out without any GUI events - // alternately we can use: - long int ct = GetCaretTime(); // should probably make this a smaller number, but eh. - - // wait for some length of time to get an event - // runs the loop every time we timeout waiting for a mac event - while (!WaitNextEvent(everyEvent, &event, ct, NULL)) - { - // check if we have any new network events - look_result = OTLook(ssh_con.endpoint); - - switch (look_result) - { - case T_DATA: - case T_EXDATA: - // got data - ssh_read(); - break; - - case T_RESET: - // connection reset? close it/give up - end_connection(); - break; - - case T_DISCONNECT: - // got disconnected - OTRcvDisconnect(ssh_con.endpoint, nil); - end_connection(); - break; - - case T_ORDREL: - // nice tcp disconnect requested by remote - err = OTRcvOrderlyDisconnect(ssh_con.endpoint); - if (err == noErr) - { - err = OTSndOrderlyDisconnect(ssh_con.endpoint); - } - end_connection(); - break; - - default: - // something weird or irrelevant: ignore it - break; - } + // process any network events + //check_network_events(); // let any other threads run before we wait for events again YieldToAnyThread(); @@ -381,7 +227,7 @@ void event_loop(void) { if ('\r' == c) c = '\n'; ssh_con.send_buffer[0] = c; - libssh2_channel_write(ssh_con.channel, ssh_con.send_buffer, 1); + if (read_thread_state == OPEN) libssh2_channel_write(ssh_con.channel, ssh_con.send_buffer, 1); } case mouseDown: @@ -396,7 +242,7 @@ void event_loop(void) case inGrow: { - //don't allow resize right now + //not allowing resize right now break; /*long growResult = GrowWindow(eventWin, event.where, &window_limits); SizeWindow(eventWin, growResult & 0xFFFF, growResult >> 16, true); @@ -428,47 +274,6 @@ void event_loop(void) } while (!exit_event_loop); } -void console_setup(void) -{ - TextFont(kFontIDMonaco); - TextSize(9); - TextFace(normal); - - int cell_height = 12; - int cell_width = CharWidth('M'); - - Rect initial_window_bounds = qd.screenBits.bounds; - InsetRect(&initial_window_bounds, 20, 20); - initial_window_bounds.top += 40; - - initial_window_bounds.bottom = initial_window_bounds.top + cell_height * 24 + 2; - initial_window_bounds.right = initial_window_bounds.left + cell_width * 80 + 4; - - // limits on window size changes: - // top = min vertical - // bottom = max vertical - // left = min horizontal - // right = max horizontal - //Rect window_limits = { .top = 100, .bottom = 200, .left = 100, .right = 200 }; - - ConstStr255Param title = "\pssheven " SSHEVEN_VERSION; - - WindowPtr win = NewWindow(NULL, &initial_window_bounds, title, true, noGrowDocProc, (WindowPtr)-1, true, 0); - - Rect portRect = win->portRect; - - SetPort(win); - EraseRect(&portRect); - - int exit_main_loop = 0; - - con.win = win; - memset(con.data, ' ', sizeof(char) * 24*80); - - con.cursor_x = 0; - con.cursor_y = 0; -} - void init_connection(char* hostname) { int rc; @@ -479,6 +284,8 @@ void init_connection(char* hostname) DNSAddress hostDNSAddress; OSStatus result; + print_string_i("opening and configuring endpoint... "); YieldToAnyThread(); + // open TCP endpoint ssh_con.endpoint = OTOpenEndpoint(OTCreateConfiguration(kTCPName), 0, nil, &err); @@ -494,28 +301,41 @@ void init_connection(char* hostname) OT_CHECK(OTBind(ssh_con.endpoint, nil, nil)); + print_string_i("done.\n"); YieldToAnyThread(); + // set up address struct, do the DNS lookup, and connect OTMemzero(&sndCall, sizeof(TCall)); sndCall.addr.buf = (UInt8 *) &hostDNSAddress; sndCall.addr.len = OTInitDNSAddress(&hostDNSAddress, (char *) hostname); + print_string_i("connecting endpoint... "); YieldToAnyThread(); OT_CHECK(OTConnect(ssh_con.endpoint, &sndCall, nil)); - print_string("OT setup done\n"); + print_string_i("done.\n"); YieldToAnyThread(); + print_string_i("initializing SSH... "); YieldToAnyThread(); // init libssh2 SSH_CHECK(libssh2_init(0)); + print_string_i("done.\n"); YieldToAnyThread(); + + print_string_i("opening SSH session... "); YieldToAnyThread(); ssh_con.session = libssh2_session_init(); if (ssh_con.session == 0) { print_string("failed to initialize SSH library\n"); return; } + print_string_i("done.\n"); YieldToAnyThread(); + print_string_i("beginning SSH session handshake... "); YieldToAnyThread(); SSH_CHECK(libssh2_session_handshake(ssh_con.session, ssh_con.endpoint)); + print_string_i("done.\n"); YieldToAnyThread(); + + read_thread_state = OPEN; + return; } @@ -536,30 +356,6 @@ void ssh_setup_terminal(void) SSH_CHECK(libssh2_channel_shell(ssh_con.channel)); } -// TODO: unused -void* read_thread(void* arg) -{ - while(1) - { - switch(read_thread_state) - { - case wait: - break; - - case read: - ssh_read(); - InvalRect(&(con.win->portRect)); - break; - - case exit: - return 0; - break; - } - - YieldToAnyThread(); - } -} - void intro_dialog(char* hostname, char* username, char* password) { @@ -610,12 +406,48 @@ void intro_dialog(char* hostname, char* username, char* password) FlushEvents(everyEvent, -1); } +//enum { WAIT, READ, EXIT } read_thread_command = WAIT; +//enum { UNITIALIZED, OPEN, CLEANUP, DONE } read_thread_state = UNITIALIZED; + +// TODO: threads +void* read_thread(void* arg) +{ + while (read_thread_command == WAIT) YieldToAnyThread(); + + if (read_thread_command == EXIT) + { + return 0; + } + + // connect and log in + init_connection(hostname+1); + YieldToAnyThread(); + + print_string_i("authenticating... "); YieldToAnyThread(); + ssh_password_auth(username+1, password+1); + print_string_i("done.\n"); YieldToAnyThread(); + + ssh_setup_terminal(); + YieldToAnyThread(); + + // if we failed, exit + if (read_thread_state != OPEN) return 0; + + // loop as long until we've failed or are asked to EXIT + while (read_thread_command == READ && read_thread_state == OPEN) + { + check_network_events(); + YieldToAnyThread(); + } + + // if we still have a connection, close it + if (read_thread_state != DONE) end_connection(); + + return 0; +} + int main(int argc, char** argv) { - char hostname[256] = {0}; - char username[256] = {0}; - char password[256] = {0}; - OSStatus err = noErr; // expands the application heap to its maximum requested size @@ -643,6 +475,10 @@ int main(int argc, char** argv) print_string(logo); print_string("by cy384, version " SSHEVEN_VERSION "\n"); + BeginUpdate(con.win); + draw_screen(&(con.win->portRect)); + EndUpdate(con.win); + int ok = 1; if (InitOpenTransport() != noErr) @@ -663,10 +499,11 @@ int main(int argc, char** argv) } } -/* - read_thread_state = wait; + // create the network read/print thread + read_thread_command = WAIT; int read_thread_result = 0; ThreadID read_thread_id = 0; + if (ok) { err = NewThread(kCooperativeThread, read_thread, NULL, 0, kCreateIfNeeded, NULL, &read_thread_id); @@ -677,28 +514,21 @@ int main(int argc, char** argv) print_string("failed to create network read thread\n"); } } -*/ - if (ok) - { - // those strings are pascal strings, so we skip the first char - init_connection(hostname+1); - - ssh_password_auth(username+1, password+1); - ssh_setup_terminal(); - - read_thread_state = read; - } + // if we got the thread, tell it to begin operation + if (ok) read_thread_command = READ; + // procede into our main event loop event_loop(); -/* - read_thread_state = exit; - OTCancelSynchronousCalls(ssh_con.endpoint, kOTCanceledErr); - YieldToThread(read_thread_id); -// err = DisposeThread(read_thread_id, (void*)&read_thread_result, 0); - err = DisposeThread(read_thread_id, NULL, 0); -*/ + // tell the read thread to quit, then let it run to actually do so + read_thread_command = EXIT; + YieldToAnyThread(); + + //OTCancelSynchronousCalls(ssh_con.endpoint, kOTCanceledErr); + //YieldToThread(read_thread_id); + // err = DisposeThread(read_thread_id, (void*)&read_thread_result, 0); + //err = DisposeThread(read_thread_id, NULL, 0); end_connection(); diff --git a/ssheven.h b/ssheven.h new file mode 100644 index 0000000..2c61704 --- /dev/null +++ b/ssheven.h @@ -0,0 +1,59 @@ +/* + * ssheven + * + * Copyright (c) 2020 by cy384 + * See LICENSE file for details + */ + +#pragma once + +// open transport +#include +#include + +// mac os stuff +#include +#include +#include +#include +#include +#include + +// libssh2 +#include + +// version string +#define SSHEVEN_VERSION "0.1.0" + +// size for recv and send thread buffers +#define BUFFER_SIZE 4096 + +// terminal type to send over ssh, determines features etc. +// "vanilla" supports basically nothing, which is good for us here +#define TERMINAL_TYPE "vanilla" + +// sinful globals +struct ssheven_console +{ + WindowPtr win; + + char data[80][24]; + + int cursor_x; + int cursor_y; +}; + +extern struct ssheven_console con; + +struct ssheven_ssh_connection +{ + LIBSSH2_CHANNEL* channel; + LIBSSH2_SESSION* session; + + EndpointRef endpoint; + + char* recv_buffer; + char* send_buffer; +}; + +extern struct ssheven_ssh_connection ssh_con;