// termdemo.c -- SecLink terminal emulator demo // // A standalone DVX GUI application (NOT a DXE app -- this has its own main()) // that combines the ANSI terminal widget with the SecLink encrypted serial // stack to create a BBS terminal client. // // Unlike the DXE apps (progman, notepad, clock, dvxdemo) which run inside // the DVX Shell, this is a freestanding program that initializes the GUI // directly and manages its own event loop. It demonstrates how to use the // DVX widget system outside the shell framework. // // Data flow: // User keystrokes -> terminal widget -> commWrite -> secLink (encrypt) -> UART // UART -> secLink (decrypt) -> onRecv -> ring buffer -> commRead -> terminal widget // // The ring buffer decouples the secLink receive callback (which fires during // secLinkPoll) from the terminal widget's read cycle (which fires during // dvxUpdate). This is necessary because the callback can fire at any time // during polling, but the terminal widget expects to read data synchronously // during its paint cycle. // // Usage: termdemo [com_port] [baud_rate] // com_port -- 1-4 (default 1) // baud_rate -- baud rate (default 115200) #include "dvxApp.h" #include "dvxWidget.h" #include "secLink.h" #include "security.h" #include #include #include // ============================================================ // Constants // ============================================================ #define CMD_FILE_QUIT 100 #define RECV_BUF_SIZE 4096 #define CHANNEL_DATA 0 // ============================================================ // Types // ============================================================ // Bridges the secLink receive callback (asynchronous, fires during poll) // with the terminal widget's comm interface (synchronous, called during // widget paint). The ring buffer allows these two timing domains to // communicate without blocking. typedef struct { SecLinkT *link; AppContextT *app; WidgetT *term; uint8_t recvBuf[RECV_BUF_SIZE]; int32_t recvHead; // written by onRecv callback int32_t recvTail; // read by commRead during widget paint } TermContextT; // ============================================================ // Prototypes // ============================================================ static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen); static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len); static void idlePoll(void *ctx); static void onCloseCb(WindowT *win); static void onMenuCb(WindowT *win, int32_t menuId); static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel); // ============================================================ // commRead -- drain receive ring buffer for terminal widget // ============================================================ static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen) { TermContextT *tc = (TermContextT *)ctx; int32_t count = 0; while (count < maxLen && tc->recvTail != tc->recvHead) { buf[count++] = tc->recvBuf[tc->recvTail]; tc->recvTail = (tc->recvTail + 1) % RECV_BUF_SIZE; } return count; } // ============================================================ // commWrite -- send keystrokes via secLink (encrypted, channel 0) // ============================================================ static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len) { TermContextT *tc = (TermContextT *)ctx; int rc = secLinkSend(tc->link, data, len, CHANNEL_DATA, true, false); return (rc == SECLINK_SUCCESS) ? len : 0; } // ============================================================ // idlePoll -- poll serial link instead of yielding CPU // ============================================================ static void idlePoll(void *ctx) { TermContextT *tc = (TermContextT *)ctx; secLinkPoll(tc->link); } // ============================================================ // onCloseCb -- quit when the terminal window is closed // ============================================================ static void onCloseCb(WindowT *win) { AppContextT *ctx = (AppContextT *)win->userData; if (ctx) { dvxQuit(ctx); } } // ============================================================ // onMenuCb -- handle menu commands // ============================================================ static void onMenuCb(WindowT *win, int32_t menuId) { AppContextT *ctx = (AppContextT *)win->userData; switch (menuId) { case CMD_FILE_QUIT: if (ctx) { dvxQuit(ctx); } break; } } // ============================================================ // onRecv -- secLink receive callback, fills ring buffer // ============================================================ static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel) { (void)channel; TermContextT *tc = (TermContextT *)ctx; for (int i = 0; i < len; i++) { int32_t next = (tc->recvHead + 1) % RECV_BUF_SIZE; if (next == tc->recvTail) { break; } tc->recvBuf[tc->recvHead] = data[i]; tc->recvHead = next; } } // ============================================================ // main // ============================================================ int main(int argc, char *argv[]) { int comPort = 0; int32_t baudRate = 115200; // Parse command line if (argc >= 2) { if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { printf("Usage: termdemo [com_port] [baud_rate]\n"); printf(" com_port -- 1-4 (default 1)\n"); printf(" baud_rate -- baud rate (default 115200)\n"); return 0; } comPort = atoi(argv[1]) - 1; if (comPort < 0 || comPort > 3) { fprintf(stderr, "Invalid COM port (must be 1-4)\n"); return 1; } } if (argc >= 3) { baudRate = atol(argv[2]); if (baudRate <= 0) { fprintf(stderr, "Invalid baud rate\n"); return 1; } } printf("SecLink Terminal Demo\n"); printf("COM%d at %ld 8N1\n\n", comPort + 1, (long)baudRate); // Seed the RNG with hardware entropy printf("Gathering entropy...\n"); uint8_t entropy[16]; int got = secRngGatherEntropy(entropy, sizeof(entropy)); secRngSeed(entropy, got); // Initialize terminal context TermContextT tc; memset(&tc, 0, sizeof(tc)); // Open secLink on the specified COM port printf("Opening SecLink on COM%d...\n", comPort + 1); tc.link = secLinkOpen(comPort, baudRate, 8, 'N', 1, 0, onRecv, &tc); if (!tc.link) { fprintf(stderr, "Failed to open SecLink\n"); return 1; } // Perform DH key exchange (blocks until both sides complete) printf("Performing key exchange (waiting for remote side)...\n"); int rc = secLinkHandshake(tc.link); if (rc != SECLINK_SUCCESS) { fprintf(stderr, "Handshake failed (%d)\n", rc); secLinkClose(tc.link); return 1; } printf("Secure link established.\n\n"); // Initialize DVX GUI AppContextT ctx; printf("Initializing video...\n"); if (dvxInit(&ctx, 1024, 768, 16) != 0) { fprintf(stderr, "Failed to initialize DVX GUI\n"); secLinkClose(tc.link); return 1; } tc.app = &ctx; // Create the terminal window WindowT *win = dvxCreateWindow(&ctx, "SecLink Terminal", 40, 30, 680, 460, false); if (!win) { dvxShutdown(&ctx); secLinkClose(tc.link); return 1; } win->userData = &ctx; win->onClose = onCloseCb; win->onMenu = onMenuCb; // File menu MenuBarT *bar = wmAddMenuBar(win); if (bar) { MenuT *fileMenu = wmAddMenu(bar, "File"); if (fileMenu) { wmAddMenuItem(fileMenu, "Quit", CMD_FILE_QUIT); } } // Widget tree: terminal + status bar WidgetT *root = wgtInitWindow(&ctx, win); WidgetT *term = wgtAnsiTerm(root, 80, 25); term->weight = 100; wgtAnsiTermSetScrollback(term, 1000); tc.term = term; // The comm interface is the glue between the terminal widget and the // secLink transport. The widget calls commRead during its paint cycle // to get new data to display, and commWrite when the user types to send // keystrokes over the encrypted link. wgtAnsiTermSetComm(term, &tc, commRead, commWrite); // Status bar showing connection info WidgetT *sb = wgtStatusBar(root); char statusText[64]; snprintf(statusText, sizeof(statusText), "COM%d %ld 8N1 [Encrypted]", comPort + 1, (long)baudRate); wgtLabel(sb, statusText); // Fit window to widget tree dvxFitWindow(&ctx, win); // Register an idle callback so the GUI event loop polls secLink during // idle time (when there are no mouse/keyboard events to process). This // ensures incoming data is processed even when the user isn't interacting // with the terminal. Without this, data would only arrive during the // fixed-rate dvxUpdate calls. ctx.idleCallback = idlePoll; ctx.idleCtx = &tc; // Main loop: poll secLink for incoming data, then dvxUpdate processes // input events, repaints dirty widgets (including the terminal, which // calls commRead to consume the ring buffer), and flushes to the LFB. while (ctx.running) { secLinkPoll(tc.link); if (!dvxUpdate(&ctx)) { break; } } // Cleanup dvxShutdown(&ctx); secLinkClose(tc.link); printf("SecLink Terminal Demo ended.\n"); return 0; }