Added terminal app to test communications chain and ANSI widget.

This commit is contained in:
Scott Duensing 2026-03-10 22:20:55 -05:00
parent 27b5ad5a16
commit 79b2825a98
3 changed files with 464 additions and 0 deletions

43
termdemo/Makefile Normal file
View file

@ -0,0 +1,43 @@
# SecLink Terminal Demo Makefile for DJGPP cross-compilation
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../dvx -I../seclink -I../security
LDFLAGS = -L../lib -ldvx -lseclink -lpacket -lsecurity -lrs232 -lm
OBJDIR = ../obj/termdemo
BINDIR = ../bin
LIBDIR = ../lib
SRCS = termdemo.c
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
TARGET = $(BINDIR)/termdemo.exe
.PHONY: all clean libs
all: libs $(TARGET)
libs:
$(MAKE) -C ../dvx
$(MAKE) -C ../rs232
$(MAKE) -C ../packet
$(MAKE) -C ../security
$(MAKE) -C ../seclink
$(TARGET): $(OBJS) $(LIBDIR)/libdvx.a $(LIBDIR)/libseclink.a $(LIBDIR)/libpacket.a $(LIBDIR)/libsecurity.a $(LIBDIR)/librs232.a | $(BINDIR)
$(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS)
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR):
mkdir -p $(OBJDIR)
$(BINDIR):
mkdir -p $(BINDIR)
# Dependencies
$(OBJDIR)/termdemo.o: termdemo.c ../dvx/dvxApp.h ../dvx/dvxWidget.h ../seclink/secLink.h ../security/security.h
clean:
rm -rf $(OBJDIR) $(TARGET)

150
termdemo/README.md Normal file
View file

@ -0,0 +1,150 @@
# SecLink Terminal Demo
DOS terminal emulator combining the DV/X windowed GUI with SecLink
encrypted serial communication. Connects to a remote BBS through the
SecLink proxy, providing a full ANSI terminal in a DESQview/X-style
window with encrypted transport.
## Architecture
```
termdemo (DOS, 86Box)
|
+--- DV/X GUI windowed desktop, ANSI terminal widget
|
+--- SecLink encrypted serial link
| |
| +--- packet HDLC framing, CRC, retransmit
| +--- security DH key exchange, XTEA-CTR cipher
| +--- rs232 ISR-driven UART I/O
|
COM port (86Box emulated modem)
|
TCP:2323
|
secproxy (Linux)
|
TCP:23
|
Remote BBS
```
All traffic between the terminal and the proxy is encrypted via
XTEA-CTR on SecLink channel 0. The proxy decrypts and forwards
plaintext to the BBS over telnet.
## Usage
```
termdemo [com_port] [baud_rate]
```
| Argument | Default | Description |
|-------------|---------|------------------------------|
| `com_port` | 1 | COM port number (1-4) |
| `baud_rate` | 115200 | Serial baud rate |
```
termdemo # COM1 at 115200
termdemo 2 # COM2 at 115200
termdemo 1 57600 # COM1 at 57600
termdemo -h # show usage
```
## Startup Sequence
1. Seed the RNG from hardware entropy
2. Open SecLink on the specified COM port (8N1, no handshake)
3. Perform DH key exchange (blocks until the proxy completes its side)
4. Initialize the DV/X GUI (1024x768, 16bpp VESA)
5. Create a resizable terminal window with menu bar and status bar
6. Enter the main loop
The handshake completes in text mode before the GUI starts, so the
DOS console shows progress messages during connection setup.
## Main Loop
Each iteration:
1. `dvxUpdate()` — process mouse, keyboard, paint, and window events
2. `secLinkPoll()` — read serial data, decrypt, deliver to ring buffer
3. `wgtAnsiTermPoll()` — drain ring buffer into the ANSI parser
## Data Flow
```
BBS → proxy → serial → secLinkPoll() → onRecv() → ring buffer
→ commRead() → wgtAnsiTermWrite() → ANSI parser → screen
Keyboard → widgetAnsiTermOnKey() → commWrite()
→ secLinkSend() → serial → proxy → BBS
```
A 4KB ring buffer bridges the SecLink receive callback (which fires
during `secLinkPoll()`) and the terminal widget's comm read interface
(which is polled by `wgtAnsiTermPoll()`).
## GUI
- **Window**: resizable, titled "SecLink Terminal"
- **Menu bar**: File → Quit
- **Terminal**: 80x25 ANSI terminal widget with 1000-line scrollback
- **Status bar**: shows COM port, baud rate, and encryption status
The ANSI terminal widget supports standard escape sequences including
cursor control, SGR colors (16-color CGA palette), erase, scroll,
insert/delete lines, and DEC private modes (cursor visibility, line
wrap).
## Test Setup
1. Start the SecLink proxy on the Linux host:
```
secproxy 2323 bbs.example.com 23
```
2. Configure 86Box with a COM port pointing at the proxy's listen port
(TCP client mode, port 2323, no telnet negotiation)
3. Run the terminal inside 86Box:
```
termdemo
```
4. The handshake completes, the GUI appears, and BBS output is
displayed in the terminal window
## Building
```
make # builds ../bin/termdemo.exe
make clean # removes objects and binary
```
The Makefile builds all dependency libraries automatically. Objects
are placed in `../obj/termdemo/`, the binary in `../bin/`.
## Dependencies
All libraries are in `../lib/`:
| Library | Purpose |
|------------------|--------------------------------------|
| `libdvx.a` | DV/X windowed GUI and widget system |
| `libseclink.a` | Secure serial link wrapper |
| `libpacket.a` | HDLC framing and reliability |
| `libsecurity.a` | DH key exchange and XTEA cipher |
| `librs232.a` | ISR-driven UART serial driver |
Target: DJGPP cross-compiler, 486+ CPU, VESA VBE 2.0+ video.
## Files
```
termdemo/
termdemo.c terminal emulator program
Makefile DJGPP cross-compilation build
```

271
termdemo/termdemo.c Normal file
View file

@ -0,0 +1,271 @@
// termdemo.c — SecLink terminal emulator demo
//
// Uses DV/X GUI ANSI terminal widget with SecLink encrypted serial link
// to provide a BBS terminal over a secured serial connection.
//
// 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// Constants
// ============================================================
#define CMD_FILE_QUIT 100
#define RECV_BUF_SIZE 4096
#define CHANNEL_DATA 0
// ============================================================
// Types
// ============================================================
// Bridges secLink receive callback and terminal widget comm interface
typedef struct {
SecLinkT *link;
AppContextT *app;
WidgetT *term;
uint8_t recvBuf[RECV_BUF_SIZE];
int32_t recvHead;
int32_t recvTail;
} 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 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;
}
// ============================================================
// 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 DV/X GUI
AppContextT ctx;
printf("Initializing video...\n");
if (dvxInit(&ctx, 1024, 768, 16) != 0) {
fprintf(stderr, "Failed to initialize DV/X GUI\n");
secLinkClose(tc.link);
return 1;
}
tc.app = &ctx;
// Create the terminal window
WindowT *win = dvxCreateWindow(&ctx, "SecLink Terminal", 40, 30, 680, 460, true);
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;
// Connect terminal widget to secLink via comm interface
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);
wgtInvalidate(root);
// Main loop — poll secLink and terminal each frame
while (dvxUpdate(&ctx)) {
secLinkPoll(tc.link);
wgtAnsiTermPoll(term);
}
// Cleanup
dvxShutdown(&ctx);
secLinkClose(tc.link);
printf("SecLink Terminal Demo ended.\n");
return 0;
}