Encryption and other serial fixes. Terminal cursor not being erased fixed. Ability to auto-size windows added.
This commit is contained in:
parent
79b2825a98
commit
3cc942a4d4
13 changed files with 579 additions and 57 deletions
|
|
@ -8,7 +8,7 @@ memsize = 64
|
||||||
quit warning = false
|
quit warning = false
|
||||||
|
|
||||||
[cpu]
|
[cpu]
|
||||||
core = auto
|
core = normal
|
||||||
cputype = 486
|
cputype = 486
|
||||||
cycles = 33445
|
cycles = 33445
|
||||||
|
|
||||||
|
|
@ -27,6 +27,9 @@ umb = true
|
||||||
xms = true
|
xms = true
|
||||||
ems = true
|
ems = true
|
||||||
|
|
||||||
|
[serial]
|
||||||
|
serial1 = nullmodem server:127.0.0.1 port:2323 transparent:1
|
||||||
|
|
||||||
[autoexec]
|
[autoexec]
|
||||||
mount c .
|
mount c .
|
||||||
c:
|
c:
|
||||||
|
|
|
||||||
43
dvx/dvxApp.c
43
dvx/dvxApp.c
|
|
@ -1,6 +1,8 @@
|
||||||
// dvx_app.c — Layer 5: Application API for DV/X GUI
|
// dvx_app.c — Layer 5: Application API for DV/X GUI
|
||||||
|
|
||||||
#include "dvxApp.h"
|
#include "dvxApp.h"
|
||||||
|
#include "dvxWidget.h"
|
||||||
|
#include "widgets/widgetInternal.h"
|
||||||
#include "dvxFont.h"
|
#include "dvxFont.h"
|
||||||
#include "dvxCursor.h"
|
#include "dvxCursor.h"
|
||||||
|
|
||||||
|
|
@ -355,6 +357,44 @@ void dvxDestroyWindow(AppContextT *ctx, WindowT *win) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// dvxFitWindow
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void dvxFitWindow(AppContextT *ctx, WindowT *win) {
|
||||||
|
if (!ctx || !win || !win->widgetRoot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measure the widget tree to get minimum content size
|
||||||
|
widgetCalcMinSizeTree(win->widgetRoot, &ctx->font);
|
||||||
|
|
||||||
|
int32_t contentW = win->widgetRoot->calcMinW;
|
||||||
|
int32_t contentH = win->widgetRoot->calcMinH;
|
||||||
|
|
||||||
|
// Compute chrome overhead
|
||||||
|
int32_t topChrome = CHROME_TOTAL_TOP;
|
||||||
|
if (win->menuBar) {
|
||||||
|
topChrome += CHROME_MENU_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t newW = contentW + CHROME_TOTAL_SIDE * 2;
|
||||||
|
int32_t newH = contentH + topChrome + CHROME_TOTAL_BOTTOM;
|
||||||
|
|
||||||
|
// Dirty old position
|
||||||
|
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
|
||||||
|
|
||||||
|
// Resize
|
||||||
|
win->w = newW;
|
||||||
|
win->h = newH;
|
||||||
|
wmUpdateContentRect(win);
|
||||||
|
wmReallocContentBuf(win, &ctx->display);
|
||||||
|
|
||||||
|
// Dirty new position
|
||||||
|
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// dvxGetBlitOps
|
// dvxGetBlitOps
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -506,8 +546,9 @@ bool dvxUpdate(AppContextT *ctx) {
|
||||||
|
|
||||||
if (ctx->dirty.count > 0) {
|
if (ctx->dirty.count > 0) {
|
||||||
compositeAndFlush(ctx);
|
compositeAndFlush(ctx);
|
||||||
|
} else if (ctx->idleCallback) {
|
||||||
|
ctx->idleCallback(ctx->idleCtx);
|
||||||
} else {
|
} else {
|
||||||
// Nothing to do — yield timeslice
|
|
||||||
__dpmi_yield();
|
__dpmi_yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@ typedef struct AppContextT {
|
||||||
int32_t lastCloseClickId; // window ID of last-clicked close gadget (-1 = none)
|
int32_t lastCloseClickId; // window ID of last-clicked close gadget (-1 = none)
|
||||||
int32_t iconRefreshIdx; // next minimized icon to refresh (staggered)
|
int32_t iconRefreshIdx; // next minimized icon to refresh (staggered)
|
||||||
int32_t frameCount; // frame counter for periodic tasks
|
int32_t frameCount; // frame counter for periodic tasks
|
||||||
|
void (*idleCallback)(void *ctx); // called instead of yield when non-NULL
|
||||||
|
void *idleCtx;
|
||||||
} AppContextT;
|
} AppContextT;
|
||||||
|
|
||||||
// Initialize the application (VESA mode, input, etc.)
|
// Initialize the application (VESA mode, input, etc.)
|
||||||
|
|
@ -60,6 +62,9 @@ WindowT *dvxCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t
|
||||||
// Destroy a window
|
// Destroy a window
|
||||||
void dvxDestroyWindow(AppContextT *ctx, WindowT *win);
|
void dvxDestroyWindow(AppContextT *ctx, WindowT *win);
|
||||||
|
|
||||||
|
// Resize a window to fit its widget tree's minimum size
|
||||||
|
void dvxFitWindow(AppContextT *ctx, WindowT *win);
|
||||||
|
|
||||||
// Invalidate a region of a window's content area (triggers repaint)
|
// Invalidate a region of a window's content area (triggers repaint)
|
||||||
void dvxInvalidateRect(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int32_t w, int32_t h);
|
void dvxInvalidateRect(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int32_t w, int32_t h);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,10 @@ typedef struct WidgetT {
|
||||||
int32_t scrollbackCount; // current number of lines stored
|
int32_t scrollbackCount; // current number of lines stored
|
||||||
int32_t scrollbackHead; // write position (circular index)
|
int32_t scrollbackHead; // write position (circular index)
|
||||||
int32_t scrollPos; // view position (scrollbackCount = live)
|
int32_t scrollPos; // view position (scrollbackCount = live)
|
||||||
|
// Dirty tracking for fast repaint
|
||||||
|
uint32_t dirtyRows; // bitmask of rows needing repaint
|
||||||
|
int32_t lastCursorRow; // cursor row at last repaint
|
||||||
|
int32_t lastCursorCol; // cursor col at last repaint
|
||||||
// Communications interface (all NULL = disconnected)
|
// Communications interface (all NULL = disconnected)
|
||||||
void *commCtx;
|
void *commCtx;
|
||||||
int32_t (*commRead)(void *ctx, uint8_t *buf, int32_t maxLen);
|
int32_t (*commRead)(void *ctx, uint8_t *buf, int32_t maxLen);
|
||||||
|
|
@ -495,6 +499,11 @@ void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines);
|
||||||
// Poll the comm interface for incoming data and process it. Returns bytes processed.
|
// Poll the comm interface for incoming data and process it. Returns bytes processed.
|
||||||
int32_t wgtAnsiTermPoll(WidgetT *w);
|
int32_t wgtAnsiTermPoll(WidgetT *w);
|
||||||
|
|
||||||
|
// Fast repaint: renders only dirty rows directly into the window's content
|
||||||
|
// buffer, bypassing the full widget paint pipeline. Returns number of rows
|
||||||
|
// repainted (0 if nothing was dirty).
|
||||||
|
int32_t wgtAnsiTermRepaint(WidgetT *w);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Operations
|
// Operations
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,9 @@ static const int32_t sAnsiToCga[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
|
||||||
|
|
||||||
static void ansiTermAddToScrollback(WidgetT *w, int32_t screenRow);
|
static void ansiTermAddToScrollback(WidgetT *w, int32_t screenRow);
|
||||||
static void ansiTermDeleteLines(WidgetT *w, int32_t count);
|
static void ansiTermDeleteLines(WidgetT *w, int32_t count);
|
||||||
|
static void ansiTermDirtyAll(WidgetT *w);
|
||||||
|
static void ansiTermDirtyRange(WidgetT *w, int32_t startCell, int32_t count);
|
||||||
|
static void ansiTermDirtyRow(WidgetT *w, int32_t row);
|
||||||
static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd);
|
static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd);
|
||||||
static void ansiTermEraseDisplay(WidgetT *w, int32_t mode);
|
static void ansiTermEraseDisplay(WidgetT *w, int32_t mode);
|
||||||
static void ansiTermEraseLine(WidgetT *w, int32_t mode);
|
static void ansiTermEraseLine(WidgetT *w, int32_t mode);
|
||||||
|
|
@ -93,6 +96,43 @@ static void ansiTermAddToScrollback(WidgetT *w, int32_t screenRow) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// ansiTermDirtyAll
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void ansiTermDirtyAll(WidgetT *w) {
|
||||||
|
w->as.ansiTerm.dirtyRows = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// ansiTermDirtyRange
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Mark rows dirty that are touched by a cell range [startCell, startCell+count).
|
||||||
|
|
||||||
|
static void ansiTermDirtyRange(WidgetT *w, int32_t startCell, int32_t count) {
|
||||||
|
int32_t cols = w->as.ansiTerm.cols;
|
||||||
|
int32_t startRow = startCell / cols;
|
||||||
|
int32_t endRow = (startCell + count - 1) / cols;
|
||||||
|
|
||||||
|
for (int32_t r = startRow; r <= endRow && r < 32; r++) {
|
||||||
|
w->as.ansiTerm.dirtyRows |= (1U << r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// ansiTermDirtyRow
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void ansiTermDirtyRow(WidgetT *w, int32_t row) {
|
||||||
|
if (row >= 0 && row < 32) {
|
||||||
|
w->as.ansiTerm.dirtyRows |= (1U << row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ansiTermDeleteLines
|
// ansiTermDeleteLines
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -122,6 +162,11 @@ static void ansiTermDeleteLines(WidgetT *w, int32_t count) {
|
||||||
for (int32_t r = rows - count; r < rows; r++) {
|
for (int32_t r = rows - count; r < rows; r++) {
|
||||||
ansiTermFillCells(w, r * cols, cols);
|
ansiTermFillCells(w, r * cols, cols);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All rows from cursorRow down are affected
|
||||||
|
for (int32_t r = row; r < rows; r++) {
|
||||||
|
ansiTermDirtyRow(w, r);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -383,6 +428,8 @@ static void ansiTermFillCells(WidgetT *w, int32_t start, int32_t count) {
|
||||||
cells[(start + i) * 2] = ' ';
|
cells[(start + i) * 2] = ' ';
|
||||||
cells[(start + i) * 2 + 1] = attr;
|
cells[(start + i) * 2 + 1] = attr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ansiTermDirtyRange(w, start, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -442,6 +489,11 @@ static void ansiTermInsertLines(WidgetT *w, int32_t count) {
|
||||||
for (int32_t r = row; r < row + count; r++) {
|
for (int32_t r = row; r < row + count; r++) {
|
||||||
ansiTermFillCells(w, r * cols, cols);
|
ansiTermFillCells(w, r * cols, cols);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All rows from cursorRow down are affected
|
||||||
|
for (int32_t r = row; r < rows; r++) {
|
||||||
|
ansiTermDirtyRow(w, r);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -610,6 +662,7 @@ static void ansiTermPutChar(WidgetT *w, uint8_t ch) {
|
||||||
int32_t idx = (row * cols + col) * 2;
|
int32_t idx = (row * cols + col) * 2;
|
||||||
w->as.ansiTerm.cells[idx] = ch;
|
w->as.ansiTerm.cells[idx] = ch;
|
||||||
w->as.ansiTerm.cells[idx + 1] = w->as.ansiTerm.curAttr;
|
w->as.ansiTerm.cells[idx + 1] = w->as.ansiTerm.curAttr;
|
||||||
|
ansiTermDirtyRow(w, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
w->as.ansiTerm.cursorCol++;
|
w->as.ansiTerm.cursorCol++;
|
||||||
|
|
@ -640,6 +693,8 @@ static void ansiTermScrollDown(WidgetT *w) {
|
||||||
|
|
||||||
// Clear the top line
|
// Clear the top line
|
||||||
ansiTermFillCells(w, 0, cols);
|
ansiTermFillCells(w, 0, cols);
|
||||||
|
|
||||||
|
ansiTermDirtyAll(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -672,6 +727,8 @@ static void ansiTermScrollUp(WidgetT *w) {
|
||||||
if (wasAtBottom) {
|
if (wasAtBottom) {
|
||||||
w->as.ansiTerm.scrollPos = w->as.ansiTerm.scrollbackCount;
|
w->as.ansiTerm.scrollPos = w->as.ansiTerm.scrollbackCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ansiTermDirtyAll(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -833,6 +890,119 @@ int32_t wgtAnsiTermPoll(WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtAnsiTermRepaint
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Fast repaint: renders only dirty rows directly into the window's
|
||||||
|
// content buffer, bypassing the full widget paint pipeline (no clear,
|
||||||
|
// no relayout, no other widgets). This keeps ACK turnaround fast
|
||||||
|
// for the serial link.
|
||||||
|
|
||||||
|
int32_t wgtAnsiTermRepaint(WidgetT *w) {
|
||||||
|
if (!w || w->type != WidgetAnsiTermE || !w->window) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always repaint the row the cursor was on last time (to erase it)
|
||||||
|
// and the row it's on now (to draw it)
|
||||||
|
int32_t prevRow = w->as.ansiTerm.lastCursorRow;
|
||||||
|
int32_t curRow = w->as.ansiTerm.cursorRow;
|
||||||
|
int32_t prevCol = w->as.ansiTerm.lastCursorCol;
|
||||||
|
int32_t curCol = w->as.ansiTerm.cursorCol;
|
||||||
|
|
||||||
|
if (prevRow != curRow || prevCol != curCol) {
|
||||||
|
if (prevRow >= 0 && prevRow < w->as.ansiTerm.rows) {
|
||||||
|
w->as.ansiTerm.dirtyRows |= (1U << prevRow);
|
||||||
|
}
|
||||||
|
if (curRow >= 0 && curRow < w->as.ansiTerm.rows) {
|
||||||
|
w->as.ansiTerm.dirtyRows |= (1U << curRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t dirty = w->as.ansiTerm.dirtyRows;
|
||||||
|
if (dirty == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowT *win = w->window;
|
||||||
|
if (!win->contentBuf || !win->widgetRoot) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppContextT *ctx = (AppContextT *)win->widgetRoot->userData;
|
||||||
|
if (!ctx) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up display context pointing at the content buffer
|
||||||
|
DisplayT cd = ctx->display;
|
||||||
|
cd.backBuf = win->contentBuf;
|
||||||
|
cd.width = win->contentW;
|
||||||
|
cd.height = win->contentH;
|
||||||
|
cd.pitch = win->contentPitch;
|
||||||
|
cd.clipX = 0;
|
||||||
|
cd.clipY = 0;
|
||||||
|
cd.clipW = win->contentW;
|
||||||
|
cd.clipH = win->contentH;
|
||||||
|
|
||||||
|
const BlitOpsT *ops = &ctx->blitOps;
|
||||||
|
const BitmapFontT *font = &ctx->font;
|
||||||
|
|
||||||
|
int32_t cols = w->as.ansiTerm.cols;
|
||||||
|
int32_t rows = w->as.ansiTerm.rows;
|
||||||
|
int32_t cellW = font->charWidth;
|
||||||
|
int32_t cellH = font->charHeight;
|
||||||
|
int32_t baseX = w->x + ANSI_BORDER;
|
||||||
|
int32_t baseY = w->y + ANSI_BORDER;
|
||||||
|
|
||||||
|
// Build palette
|
||||||
|
uint32_t palette[16];
|
||||||
|
for (int32_t i = 0; i < 16; i++) {
|
||||||
|
palette[i] = packColor(&cd, sCgaPalette[i][0], sCgaPalette[i][1], sCgaPalette[i][2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool viewingLive = (w->as.ansiTerm.scrollPos == w->as.ansiTerm.scrollbackCount);
|
||||||
|
int32_t repainted = 0;
|
||||||
|
|
||||||
|
for (int32_t row = 0; row < rows; row++) {
|
||||||
|
if (!(dirty & (1U << row))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t lineIndex = w->as.ansiTerm.scrollPos + row;
|
||||||
|
const uint8_t *lineData = ansiTermGetLine(w, lineIndex);
|
||||||
|
|
||||||
|
for (int32_t col = 0; col < cols; col++) {
|
||||||
|
uint8_t ch = lineData[col * 2];
|
||||||
|
uint8_t attr = lineData[col * 2 + 1];
|
||||||
|
|
||||||
|
uint32_t fg = palette[attr & 0x0F];
|
||||||
|
uint32_t bg = palette[(attr >> 4) & 0x0F];
|
||||||
|
|
||||||
|
if (viewingLive && w->as.ansiTerm.cursorVisible && w->focused &&
|
||||||
|
row == w->as.ansiTerm.cursorRow && col == w->as.ansiTerm.cursorCol) {
|
||||||
|
uint32_t tmp = fg;
|
||||||
|
fg = bg;
|
||||||
|
bg = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t cx = baseX + col * cellW;
|
||||||
|
int32_t cy = baseY + row * cellH;
|
||||||
|
|
||||||
|
drawChar(&cd, ops, font, cx, cy, (char)ch, fg, bg, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
repainted++;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.ansiTerm.dirtyRows = 0;
|
||||||
|
w->as.ansiTerm.lastCursorRow = w->as.ansiTerm.cursorRow;
|
||||||
|
w->as.ansiTerm.lastCursorCol = w->as.ansiTerm.cursorCol;
|
||||||
|
return repainted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// wgtAnsiTermSetComm
|
// wgtAnsiTermSetComm
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
#include <pc.h>
|
#include <pc.h>
|
||||||
#include "packet.h"
|
#include "packet.h"
|
||||||
#include "../rs232/rs232.h"
|
#include "../rs232/rs232.h"
|
||||||
|
|
@ -46,8 +47,8 @@
|
||||||
// Receive buffer must hold at least one max-size stuffed frame
|
// Receive buffer must hold at least one max-size stuffed frame
|
||||||
#define RX_BUF_SIZE (MAX_STUFFED_SIZE + 64)
|
#define RX_BUF_SIZE (MAX_STUFFED_SIZE + 64)
|
||||||
|
|
||||||
// Retransmit timeout in poll cycles (caller-dependent; ~50ms worth at typical poll rates)
|
// Retransmit timeout in milliseconds
|
||||||
#define RETRANSMIT_TIMEOUT 500
|
#define RETRANSMIT_TIMEOUT_MS 500
|
||||||
|
|
||||||
// Receive state machine
|
// Receive state machine
|
||||||
#define RX_STATE_HUNT 0 // scanning for FLAG_BYTE
|
#define RX_STATE_HUNT 0 // scanning for FLAG_BYTE
|
||||||
|
|
@ -64,7 +65,7 @@ typedef struct {
|
||||||
uint8_t data[PKT_MAX_PAYLOAD];
|
uint8_t data[PKT_MAX_PAYLOAD];
|
||||||
int len;
|
int len;
|
||||||
uint8_t seq;
|
uint8_t seq;
|
||||||
uint32_t timer;
|
clock_t timer;
|
||||||
} TxSlotT;
|
} TxSlotT;
|
||||||
|
|
||||||
// Connection state
|
// Connection state
|
||||||
|
|
@ -86,8 +87,6 @@ struct PktConnS {
|
||||||
uint8_t rxFrame[MAX_FRAME_SIZE];
|
uint8_t rxFrame[MAX_FRAME_SIZE];
|
||||||
int rxFrameLen;
|
int rxFrameLen;
|
||||||
|
|
||||||
// Poll counter (simple timer)
|
|
||||||
uint32_t pollCount;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -203,6 +202,9 @@ static void sendFrame(PktConnT *conn, uint8_t seq, uint8_t type, const uint8_t *
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trailing flag to close the frame immediately
|
||||||
|
stuffed[out++] = FLAG_BYTE;
|
||||||
|
|
||||||
// Send via serial port (blocking write)
|
// Send via serial port (blocking write)
|
||||||
rs232Write(conn->com, (const char *)stuffed, out);
|
rs232Write(conn->com, (const char *)stuffed, out);
|
||||||
}
|
}
|
||||||
|
|
@ -309,7 +311,7 @@ static void processFrame(PktConnT *conn, const uint8_t *frame, int len) {
|
||||||
for (int i = idx; i < conn->txCount; i++) {
|
for (int i = idx; i < conn->txCount; i++) {
|
||||||
TxSlotT *slot = &conn->txSlots[i];
|
TxSlotT *slot = &conn->txSlots[i];
|
||||||
sendFrame(conn, slot->seq, FRAME_DATA, slot->data, slot->len);
|
sendFrame(conn, slot->seq, FRAME_DATA, slot->data, slot->len);
|
||||||
slot->timer = conn->pollCount;
|
slot->timer = clock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -378,11 +380,14 @@ static void rxProcessByte(PktConnT *conn, uint8_t byte) {
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
static void retransmitCheck(PktConnT *conn) {
|
static void retransmitCheck(PktConnT *conn) {
|
||||||
|
clock_t now = clock();
|
||||||
|
clock_t timeout = (clock_t)RETRANSMIT_TIMEOUT_MS * CLOCKS_PER_SEC / 1000;
|
||||||
|
|
||||||
for (int i = 0; i < conn->txCount; i++) {
|
for (int i = 0; i < conn->txCount; i++) {
|
||||||
TxSlotT *slot = &conn->txSlots[i];
|
TxSlotT *slot = &conn->txSlots[i];
|
||||||
if (conn->pollCount - slot->timer >= RETRANSMIT_TIMEOUT) {
|
if (now - slot->timer >= timeout) {
|
||||||
sendFrame(conn, slot->seq, FRAME_DATA, slot->data, slot->len);
|
sendFrame(conn, slot->seq, FRAME_DATA, slot->data, slot->len);
|
||||||
slot->timer = conn->pollCount;
|
slot->timer = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -399,6 +404,14 @@ void pktClose(PktConnT *conn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool pktCanSend(PktConnT *conn) {
|
||||||
|
if (!conn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return conn->txCount < conn->windowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int pktGetPending(PktConnT *conn) {
|
int pktGetPending(PktConnT *conn) {
|
||||||
if (!conn) {
|
if (!conn) {
|
||||||
return PKT_ERR_INVALID_PARAM;
|
return PKT_ERR_INVALID_PARAM;
|
||||||
|
|
@ -432,7 +445,6 @@ PktConnT *pktOpen(int com, int windowSize, PktRecvCallbackT callback, void *call
|
||||||
conn->rxExpectSeq = 0;
|
conn->rxExpectSeq = 0;
|
||||||
conn->rxState = RX_STATE_HUNT;
|
conn->rxState = RX_STATE_HUNT;
|
||||||
conn->rxFrameLen = 0;
|
conn->rxFrameLen = 0;
|
||||||
conn->pollCount = 0;
|
|
||||||
|
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
@ -447,8 +459,6 @@ int pktPoll(PktConnT *conn) {
|
||||||
return PKT_ERR_INVALID_PARAM;
|
return PKT_ERR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn->pollCount++;
|
|
||||||
|
|
||||||
// Read available serial data and feed to state machine
|
// Read available serial data and feed to state machine
|
||||||
while ((nRead = rs232Read(conn->com, buf, sizeof(buf))) > 0) {
|
while ((nRead = rs232Read(conn->com, buf, sizeof(buf))) > 0) {
|
||||||
for (int i = 0; i < nRead; i++) {
|
for (int i = 0; i < nRead; i++) {
|
||||||
|
|
@ -460,6 +470,11 @@ int pktPoll(PktConnT *conn) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect disconnected socket/port
|
||||||
|
if (nRead < 0) {
|
||||||
|
return PKT_ERR_DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for retransmit timeouts
|
// Check for retransmit timeouts
|
||||||
retransmitCheck(conn);
|
retransmitCheck(conn);
|
||||||
|
|
||||||
|
|
@ -498,7 +513,9 @@ int pktSend(PktConnT *conn, const uint8_t *data, int len, bool block) {
|
||||||
// Wait for window space if blocking
|
// Wait for window space if blocking
|
||||||
if (block) {
|
if (block) {
|
||||||
while (conn->txCount >= conn->windowSize) {
|
while (conn->txCount >= conn->windowSize) {
|
||||||
pktPoll(conn);
|
if (pktPoll(conn) == PKT_ERR_DISCONNECTED) {
|
||||||
|
return PKT_ERR_DISCONNECTED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (conn->txCount >= conn->windowSize) {
|
if (conn->txCount >= conn->windowSize) {
|
||||||
|
|
@ -511,7 +528,7 @@ int pktSend(PktConnT *conn, const uint8_t *data, int len, bool block) {
|
||||||
memcpy(slot->data, data, len);
|
memcpy(slot->data, data, len);
|
||||||
slot->len = len;
|
slot->len = len;
|
||||||
slot->seq = conn->txNextSeq;
|
slot->seq = conn->txNextSeq;
|
||||||
slot->timer = conn->pollCount;
|
slot->timer = clock();
|
||||||
conn->txCount++;
|
conn->txCount++;
|
||||||
conn->txNextSeq++;
|
conn->txNextSeq++;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#define PKT_ERR_INVALID_PARAM -6
|
#define PKT_ERR_INVALID_PARAM -6
|
||||||
#define PKT_ERR_TX_FULL -7
|
#define PKT_ERR_TX_FULL -7
|
||||||
#define PKT_ERR_NO_DATA -8
|
#define PKT_ERR_NO_DATA -8
|
||||||
|
#define PKT_ERR_DISCONNECTED -9
|
||||||
|
|
||||||
// Callback for received packets
|
// Callback for received packets
|
||||||
// ctx: user context pointer
|
// ctx: user context pointer
|
||||||
|
|
@ -62,6 +63,9 @@ int pktPoll(PktConnT *conn);
|
||||||
// Sends a RST frame to the remote side.
|
// Sends a RST frame to the remote side.
|
||||||
int pktReset(PktConnT *conn);
|
int pktReset(PktConnT *conn);
|
||||||
|
|
||||||
|
// Check if there is room in the transmit window.
|
||||||
|
bool pktCanSend(PktConnT *conn);
|
||||||
|
|
||||||
// Get number of unacknowledged packets in the transmit window.
|
// Get number of unacknowledged packets in the transmit window.
|
||||||
int pktGetPending(PktConnT *conn);
|
int pktGetPending(PktConnT *conn);
|
||||||
|
|
||||||
|
|
|
||||||
273
proxy/proxy.c
273
proxy/proxy.c
|
|
@ -4,7 +4,7 @@
|
||||||
// secLink protocol plain telnet
|
// secLink protocol plain telnet
|
||||||
//
|
//
|
||||||
// Usage: proxy [listen_port] [bbs_host] [bbs_port]
|
// Usage: proxy [listen_port] [bbs_host] [bbs_port]
|
||||||
// Defaults: 2323 bbs.duensing.digital 23
|
// Defaults: 2323 10.1.0.244 2023
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -28,18 +28,46 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
#define DEFAULT_LISTEN_PORT 2323
|
#define DEFAULT_LISTEN_PORT 2323
|
||||||
#define DEFAULT_BBS_HOST "bbs.duensing.digital"
|
#define DEFAULT_BBS_HOST "10.1.0.244"
|
||||||
#define DEFAULT_BBS_PORT 23
|
#define DEFAULT_BBS_PORT 2023
|
||||||
|
|
||||||
#define CHANNEL_TERMINAL 0
|
#define CHANNEL_TERMINAL 0
|
||||||
#define POLL_TIMEOUT_MS 10
|
#define POLL_TIMEOUT_MS 10
|
||||||
|
|
||||||
|
// Telnet protocol bytes
|
||||||
|
#define TEL_IAC 255
|
||||||
|
#define TEL_DONT 254
|
||||||
|
#define TEL_DO 253
|
||||||
|
#define TEL_WONT 252
|
||||||
|
#define TEL_WILL 251
|
||||||
|
#define TEL_SB 250
|
||||||
|
#define TEL_SE 240
|
||||||
|
|
||||||
|
// Telnet options we accept
|
||||||
|
#define TELOPT_ECHO 1
|
||||||
|
#define TELOPT_SGA 3
|
||||||
|
#define TELOPT_TTYPE 24
|
||||||
|
#define TELOPT_NAWS 31
|
||||||
|
|
||||||
|
// Telnet parser states
|
||||||
|
#define TS_DATA 0
|
||||||
|
#define TS_IAC 1
|
||||||
|
#define TS_WILL 2
|
||||||
|
#define TS_WONT 3
|
||||||
|
#define TS_DO 4
|
||||||
|
#define TS_DONT 5
|
||||||
|
#define TS_SB 6
|
||||||
|
#define TS_SB_IAC 7
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// Static globals
|
// Static globals
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
static volatile bool sRunning = true;
|
static volatile bool sRunning = true;
|
||||||
|
static volatile bool sGotEnter = false;
|
||||||
|
static int sTelState = TS_DATA;
|
||||||
|
static int sClientFd = -1;
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
@ -48,9 +76,12 @@ static volatile bool sRunning = true;
|
||||||
|
|
||||||
static int connectToBbs(const char *host, int port);
|
static int connectToBbs(const char *host, int port);
|
||||||
static int createListenSocket(int port);
|
static int createListenSocket(int port);
|
||||||
|
static void hexDump(const char *label, const uint8_t *data, int len);
|
||||||
static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
||||||
static void seedRng(void);
|
static void seedRng(void);
|
||||||
static void sigHandler(int sig);
|
static void sigHandler(int sig);
|
||||||
|
static void telnetRespond(int bbsFd, uint8_t cmd, uint8_t opt);
|
||||||
|
static int telnetFilter(int bbsFd, const uint8_t *in, int inLen, uint8_t *out);
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
@ -121,11 +152,42 @@ static int createListenSocket(int port) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void hexDump(const char *label, const uint8_t *data, int len) {
|
||||||
|
printf("%s (%d bytes):", label, len);
|
||||||
|
for (int i = 0; i < len && i < 64; i++) {
|
||||||
|
if (i % 16 == 0) {
|
||||||
|
printf("\n ");
|
||||||
|
}
|
||||||
|
printf("%02X ", data[i]);
|
||||||
|
}
|
||||||
|
if (len > 64) {
|
||||||
|
printf("\n ...");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel) {
|
static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel) {
|
||||||
int bbsFd = *(int *)ctx;
|
int bbsFd = *(int *)ctx;
|
||||||
|
|
||||||
(void)channel;
|
(void)channel;
|
||||||
|
|
||||||
|
hexDump("DOS->proxy (decrypted)", data, len);
|
||||||
|
|
||||||
|
// Check for ENTER before BBS is connected
|
||||||
|
if (!sGotEnter) {
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (data[i] == '\r' || data[i] == '\n') {
|
||||||
|
sGotEnter = true;
|
||||||
|
printf("Got ENTER from terminal.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hexDump("DOS->BBS", data, len);
|
||||||
|
|
||||||
int sent = 0;
|
int sent = 0;
|
||||||
while (sent < len) {
|
while (sent < len) {
|
||||||
ssize_t n = write(bbsFd, data + sent, len - sent);
|
ssize_t n = write(bbsFd, data + sent, len - sent);
|
||||||
|
|
@ -156,6 +218,123 @@ static void seedRng(void) {
|
||||||
static void sigHandler(int sig) {
|
static void sigHandler(int sig) {
|
||||||
(void)sig;
|
(void)sig;
|
||||||
sRunning = false;
|
sRunning = false;
|
||||||
|
// Shutdown the client socket to break any blocking reads in the packet layer
|
||||||
|
if (sClientFd >= 0) {
|
||||||
|
shutdown(sClientFd, SHUT_RDWR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Send a telnet negotiation response
|
||||||
|
static void telnetRespond(int bbsFd, uint8_t cmd, uint8_t opt) {
|
||||||
|
uint8_t resp[3] = {TEL_IAC, cmd, opt};
|
||||||
|
ssize_t n = write(bbsFd, resp, 3);
|
||||||
|
(void)n;
|
||||||
|
printf(" TEL TX: %s %d\n",
|
||||||
|
cmd == TEL_WILL ? "WILL" : cmd == TEL_WONT ? "WONT" :
|
||||||
|
cmd == TEL_DO ? "DO" : "DONT", opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Filter telnet IAC sequences from BBS data.
|
||||||
|
// Handles negotiation by responding appropriately.
|
||||||
|
// Returns the number of clean data bytes written to 'out'.
|
||||||
|
static int telnetFilter(int bbsFd, const uint8_t *in, int inLen, uint8_t *out) {
|
||||||
|
int outLen = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < inLen; i++) {
|
||||||
|
uint8_t b = in[i];
|
||||||
|
|
||||||
|
switch (sTelState) {
|
||||||
|
case TS_DATA:
|
||||||
|
if (b == TEL_IAC) {
|
||||||
|
sTelState = TS_IAC;
|
||||||
|
} else {
|
||||||
|
out[outLen++] = b;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_IAC:
|
||||||
|
switch (b) {
|
||||||
|
case TEL_IAC:
|
||||||
|
// Escaped 0xFF — emit literal
|
||||||
|
out[outLen++] = 0xFF;
|
||||||
|
sTelState = TS_DATA;
|
||||||
|
break;
|
||||||
|
case TEL_WILL:
|
||||||
|
sTelState = TS_WILL;
|
||||||
|
break;
|
||||||
|
case TEL_WONT:
|
||||||
|
sTelState = TS_WONT;
|
||||||
|
break;
|
||||||
|
case TEL_DO:
|
||||||
|
sTelState = TS_DO;
|
||||||
|
break;
|
||||||
|
case TEL_DONT:
|
||||||
|
sTelState = TS_DONT;
|
||||||
|
break;
|
||||||
|
case TEL_SB:
|
||||||
|
sTelState = TS_SB;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Unknown command, skip
|
||||||
|
sTelState = TS_DATA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_WILL:
|
||||||
|
// Server offers to do something — accept ECHO and SGA, refuse others
|
||||||
|
printf(" TEL RX: WILL %d\n", b);
|
||||||
|
if (b == TELOPT_ECHO || b == TELOPT_SGA) {
|
||||||
|
telnetRespond(bbsFd, TEL_DO, b);
|
||||||
|
} else {
|
||||||
|
telnetRespond(bbsFd, TEL_DONT, b);
|
||||||
|
}
|
||||||
|
sTelState = TS_DATA;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_WONT:
|
||||||
|
printf(" TEL RX: WONT %d\n", b);
|
||||||
|
telnetRespond(bbsFd, TEL_DONT, b);
|
||||||
|
sTelState = TS_DATA;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_DO:
|
||||||
|
// Server asks us to do something — accept TTYPE and NAWS, refuse others
|
||||||
|
printf(" TEL RX: DO %d\n", b);
|
||||||
|
if (b == TELOPT_TTYPE || b == TELOPT_NAWS) {
|
||||||
|
telnetRespond(bbsFd, TEL_WILL, b);
|
||||||
|
} else {
|
||||||
|
telnetRespond(bbsFd, TEL_WONT, b);
|
||||||
|
}
|
||||||
|
sTelState = TS_DATA;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_DONT:
|
||||||
|
printf(" TEL RX: DONT %d\n", b);
|
||||||
|
telnetRespond(bbsFd, TEL_WONT, b);
|
||||||
|
sTelState = TS_DATA;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_SB:
|
||||||
|
// Inside subnegotiation — skip until IAC SE
|
||||||
|
if (b == TEL_IAC) {
|
||||||
|
sTelState = TS_SB_IAC;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_SB_IAC:
|
||||||
|
if (b == TEL_SE) {
|
||||||
|
sTelState = TS_DATA;
|
||||||
|
} else {
|
||||||
|
sTelState = TS_SB;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -193,8 +372,12 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
signal(SIGINT, sigHandler);
|
struct sigaction sa;
|
||||||
signal(SIGTERM, sigHandler);
|
sa.sa_handler = sigHandler;
|
||||||
|
sa.sa_flags = 0;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sigaction(SIGINT, &sa, NULL);
|
||||||
|
sigaction(SIGTERM, &sa, NULL);
|
||||||
|
|
||||||
// Listen for 86Box connection
|
// Listen for 86Box connection
|
||||||
listenFd = createListenSocket(listenPort);
|
listenFd = createListenSocket(listenPort);
|
||||||
|
|
@ -204,35 +387,34 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
printf("Listening on port %d...\n", listenPort);
|
printf("Listening on port %d...\n", listenPort);
|
||||||
|
|
||||||
// Accept connection from 86Box
|
// Accept connection from 86Box (poll so Ctrl+C works)
|
||||||
|
struct pollfd listenPoll = {listenFd, POLLIN, 0};
|
||||||
|
while (sRunning) {
|
||||||
|
int pr = poll(&listenPoll, 1, 500);
|
||||||
|
if (pr > 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
clientLen = sizeof(clientAddr);
|
clientLen = sizeof(clientAddr);
|
||||||
clientFd = accept(listenFd, (struct sockaddr *)&clientAddr, &clientLen);
|
clientFd = accept(listenFd, (struct sockaddr *)&clientAddr, &clientLen);
|
||||||
close(listenFd);
|
close(listenFd);
|
||||||
if (clientFd < 0) {
|
if (!sRunning || clientFd < 0) {
|
||||||
|
if (sRunning) {
|
||||||
fprintf(stderr, "Accept failed: %s\n", strerror(errno));
|
fprintf(stderr, "Accept failed: %s\n", strerror(errno));
|
||||||
return 1;
|
}
|
||||||
|
return sRunning ? 1 : 0;
|
||||||
}
|
}
|
||||||
printf("86Box connected.\n");
|
printf("86Box connected.\n");
|
||||||
|
sClientFd = clientFd;
|
||||||
|
|
||||||
// Associate socket with COM0 for the secLink stack
|
// Associate socket with COM0 for the secLink stack
|
||||||
sockShimSetFd(0, clientFd);
|
sockShimSetFd(0, clientFd);
|
||||||
|
|
||||||
// Connect to BBS
|
|
||||||
printf("Connecting to %s:%d...\n", bbsHost, bbsPort);
|
|
||||||
bbsFd = connectToBbs(bbsHost, bbsPort);
|
|
||||||
if (bbsFd < 0) {
|
|
||||||
fprintf(stderr, "Failed to connect to BBS: %s\n", strerror(errno));
|
|
||||||
close(clientFd);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("BBS connected.\n");
|
|
||||||
|
|
||||||
// Seed RNG from /dev/urandom and open secLink
|
// Seed RNG from /dev/urandom and open secLink
|
||||||
seedRng();
|
seedRng();
|
||||||
link = secLinkOpen(0, 115200, 8, 'N', 1, 0, onRecvFromDos, &bbsFd);
|
link = secLinkOpen(0, 115200, 8, 'N', 1, 0, onRecvFromDos, &bbsFd);
|
||||||
if (!link) {
|
if (!link) {
|
||||||
fprintf(stderr, "Failed to open secLink.\n");
|
fprintf(stderr, "Failed to open secLink.\n");
|
||||||
close(bbsFd);
|
|
||||||
close(clientFd);
|
close(clientFd);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -243,10 +425,38 @@ int main(int argc, char *argv[]) {
|
||||||
if (rc != SECLINK_SUCCESS) {
|
if (rc != SECLINK_SUCCESS) {
|
||||||
fprintf(stderr, "Handshake failed: %d\n", rc);
|
fprintf(stderr, "Handshake failed: %d\n", rc);
|
||||||
secLinkClose(link);
|
secLinkClose(link);
|
||||||
close(bbsFd);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("Handshake complete. Proxying traffic.\n");
|
printf("Handshake complete.\n");
|
||||||
|
|
||||||
|
// Wait for ENTER from terminal before connecting to BBS
|
||||||
|
printf("Waiting for terminal to send ENTER...\n");
|
||||||
|
while (sRunning) {
|
||||||
|
secLinkPoll(link);
|
||||||
|
if (sGotEnter) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
usleep(10000);
|
||||||
|
}
|
||||||
|
if (!sRunning) {
|
||||||
|
secLinkClose(link);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send test string to verify data path
|
||||||
|
const char *testMsg = "SecLink proxy ready.\r\n";
|
||||||
|
secLinkSend(link, (const uint8_t *)testMsg, (int)strlen(testMsg), CHANNEL_TERMINAL, true, false);
|
||||||
|
printf("Sent test message to terminal.\n");
|
||||||
|
|
||||||
|
// Now connect to BBS
|
||||||
|
printf("Connecting to %s:%d...\n", bbsHost, bbsPort);
|
||||||
|
bbsFd = connectToBbs(bbsHost, bbsPort);
|
||||||
|
if (bbsFd < 0) {
|
||||||
|
fprintf(stderr, "Failed to connect to BBS: %s\n", strerror(errno));
|
||||||
|
secLinkClose(link);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("BBS connected. Proxying traffic.\n");
|
||||||
|
|
||||||
// Set BBS socket non-blocking for the main loop
|
// Set BBS socket non-blocking for the main loop
|
||||||
int flags = fcntl(bbsFd, F_GETFL, 0);
|
int flags = fcntl(bbsFd, F_GETFL, 0);
|
||||||
|
|
@ -265,15 +475,28 @@ int main(int argc, char *argv[]) {
|
||||||
// (callback forwards decrypted data to BBS)
|
// (callback forwards decrypted data to BBS)
|
||||||
secLinkPoll(link);
|
secLinkPoll(link);
|
||||||
|
|
||||||
// Read from BBS and send encrypted to 86Box
|
// Read from BBS, filter telnet, send clean data to 86Box
|
||||||
if (fds[1].revents & POLLIN) {
|
if (fds[1].revents & POLLIN) {
|
||||||
uint8_t buf[SECLINK_MAX_PAYLOAD];
|
uint8_t raw[64];
|
||||||
ssize_t n = read(bbsFd, buf, sizeof(buf));
|
uint8_t clean[64];
|
||||||
|
ssize_t n = read(bbsFd, raw, sizeof(raw));
|
||||||
if (n <= 0) {
|
if (n <= 0) {
|
||||||
printf("BBS disconnected.\n");
|
printf("BBS disconnected.\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
secLinkSend(link, buf, (int)n, CHANNEL_TERMINAL, true, true);
|
|
||||||
|
int cleanLen = telnetFilter(bbsFd, raw, (int)n, clean);
|
||||||
|
if (cleanLen > 0) {
|
||||||
|
hexDump("BBS->DOS", clean, cleanLen);
|
||||||
|
|
||||||
|
// Retry with ACK processing if the send window is full
|
||||||
|
int rc = secLinkSend(link, clean, cleanLen, CHANNEL_TERMINAL, true, false);
|
||||||
|
while (rc != SECLINK_SUCCESS && sRunning) {
|
||||||
|
secLinkPoll(link);
|
||||||
|
usleep(1000);
|
||||||
|
rc = secLinkSend(link, clean, cleanLen, CHANNEL_TERMINAL, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for disconnects
|
// Check for disconnects
|
||||||
|
|
|
||||||
|
|
@ -53,13 +53,19 @@ int rs232Open(int com, int32_t bps, int dataBits, char parity, int stopBits, int
|
||||||
|
|
||||||
int rs232Read(int com, char *data, int len) {
|
int rs232Read(int com, char *data, int len) {
|
||||||
if (com < 0 || com >= MAX_PORTS || sFds[com] < 0) {
|
if (com < 0 || com >= MAX_PORTS || sFds[com] < 0) {
|
||||||
return 0;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t n = recv(sFds[com], data, len, MSG_DONTWAIT);
|
ssize_t n = recv(sFds[com], data, len, MSG_DONTWAIT);
|
||||||
if (n <= 0) {
|
if (n < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (n == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
return (int)n;
|
return (int)n;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1321,11 +1321,11 @@ int rs232SetParity(int com, char parity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (parity) {
|
switch (parity) {
|
||||||
case 'n': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_NONE); return RS232_SUCCESS;
|
case 'n': case 'N': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_NONE); return RS232_SUCCESS;
|
||||||
case 'e': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_EVEN); return RS232_SUCCESS;
|
case 'e': case 'E': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_EVEN); return RS232_SUCCESS;
|
||||||
case 'o': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_ODD); return RS232_SUCCESS;
|
case 'o': case 'O': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_ODD); return RS232_SUCCESS;
|
||||||
case 'm': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_MARK); return RS232_SUCCESS;
|
case 'm': case 'M': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_MARK); return RS232_SUCCESS;
|
||||||
case 's': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_SPACE); return RS232_SUCCESS;
|
case 's': case 'S': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_SPACE); return RS232_SUCCESS;
|
||||||
}
|
}
|
||||||
return RS232_ERR_INVALID_PARITY;
|
return RS232_ERR_INVALID_PARITY;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,6 @@ static void completeHandshake(SecLinkT *link) {
|
||||||
uint8_t txKey[SEC_XTEA_KEY_SIZE];
|
uint8_t txKey[SEC_XTEA_KEY_SIZE];
|
||||||
uint8_t rxKey[SEC_XTEA_KEY_SIZE];
|
uint8_t rxKey[SEC_XTEA_KEY_SIZE];
|
||||||
bool weAreLower;
|
bool weAreLower;
|
||||||
|
|
||||||
secDhComputeSecret(link->dh, link->remoteKey, SEC_DH_KEY_SIZE);
|
secDhComputeSecret(link->dh, link->remoteKey, SEC_DH_KEY_SIZE);
|
||||||
secDhDeriveKey(link->dh, masterKey, SEC_XTEA_KEY_SIZE);
|
secDhDeriveKey(link->dh, masterKey, SEC_XTEA_KEY_SIZE);
|
||||||
|
|
||||||
|
|
@ -212,7 +211,9 @@ int secLinkHandshake(SecLinkT *link) {
|
||||||
|
|
||||||
// Poll until the callback completes the handshake
|
// Poll until the callback completes the handshake
|
||||||
while (link->state != STATE_READY) {
|
while (link->state != STATE_READY) {
|
||||||
pktPoll(link->pkt);
|
if (pktPoll(link->pkt) == PKT_ERR_DISCONNECTED) {
|
||||||
|
return SECLINK_ERR_HANDSHAKE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SECLINK_SUCCESS;
|
return SECLINK_SUCCESS;
|
||||||
|
|
@ -286,6 +287,12 @@ int secLinkSend(SecLinkT *link, const uint8_t *data, int len, uint8_t channel, b
|
||||||
return SECLINK_ERR_PARAM;
|
return SECLINK_ERR_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For non-blocking sends, check window space BEFORE encrypting
|
||||||
|
// to avoid advancing the cipher counter on a failed send
|
||||||
|
if (!block && !pktCanSend(link->pkt)) {
|
||||||
|
return SECLINK_ERR_SEND;
|
||||||
|
}
|
||||||
|
|
||||||
// Build channel header byte
|
// Build channel header byte
|
||||||
buf[0] = channel & CHANNEL_MASK;
|
buf[0] = channel & CHANNEL_MASK;
|
||||||
if (encrypt) {
|
if (encrypt) {
|
||||||
|
|
|
||||||
|
|
@ -339,8 +339,8 @@ static void computeR2(BigNumT *r2, const BigNumT *m) {
|
||||||
bnSet(r2, 1);
|
bnSet(r2, 1);
|
||||||
|
|
||||||
for (int i = 0; i < 2 * BN_BITS; i++) {
|
for (int i = 0; i < 2 * BN_BITS; i++) {
|
||||||
bnShiftLeft1(r2);
|
int carry = bnShiftLeft1(r2);
|
||||||
if (bnCmp(r2, m) >= 0) {
|
if (carry || bnCmp(r2, m) >= 0) {
|
||||||
bnSub(r2, r2, m);
|
bnSub(r2, r2, m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -586,6 +586,7 @@ int secDhGenerateKeys(SecDhT *dh) {
|
||||||
|
|
||||||
// public = g^private mod p
|
// public = g^private mod p
|
||||||
bnModExp(&dh->publicKey, &sDhGenerator, &dh->privateKey, &sDhPrime, sDhM0Inv, &sDhR2);
|
bnModExp(&dh->publicKey, &sDhGenerator, &dh->privateKey, &sDhPrime, sDhM0Inv, &sDhR2);
|
||||||
|
|
||||||
dh->hasKeys = true;
|
dh->hasKeys = true;
|
||||||
dh->hasSecret = false;
|
dh->hasSecret = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "dvxWidget.h"
|
#include "dvxWidget.h"
|
||||||
#include "secLink.h"
|
#include "secLink.h"
|
||||||
#include "security.h"
|
#include "security.h"
|
||||||
|
#include "../rs232/rs232.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -46,6 +47,7 @@ typedef struct {
|
||||||
|
|
||||||
static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen);
|
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 int32_t commWrite(void *ctx, const uint8_t *data, int32_t len);
|
||||||
|
static void idlePoll(void *ctx);
|
||||||
static void onCloseCb(WindowT *win);
|
static void onCloseCb(WindowT *win);
|
||||||
static void onMenuCb(WindowT *win, int32_t menuId);
|
static void onMenuCb(WindowT *win, int32_t menuId);
|
||||||
static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
||||||
|
|
@ -80,6 +82,16 @@ static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 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
|
// onCloseCb — quit when the terminal window is closed
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -178,6 +190,15 @@ int main(int argc, char *argv[]) {
|
||||||
TermContextT tc;
|
TermContextT tc;
|
||||||
memset(&tc, 0, sizeof(tc));
|
memset(&tc, 0, sizeof(tc));
|
||||||
|
|
||||||
|
// Test rs232 directly first for diagnostics
|
||||||
|
printf("Testing rs232Open on COM%d...\n", comPort + 1);
|
||||||
|
int rsRc = rs232Open(comPort, baudRate, 8, 'N', 1, 0);
|
||||||
|
printf("rs232Open returned %d\n", rsRc);
|
||||||
|
if (rsRc == RS232_SUCCESS) {
|
||||||
|
printf("UART type: %d\n", rs232GetUartType(comPort));
|
||||||
|
rs232Close(comPort);
|
||||||
|
}
|
||||||
|
|
||||||
// Open secLink on the specified COM port
|
// Open secLink on the specified COM port
|
||||||
printf("Opening SecLink on COM%d...\n", comPort + 1);
|
printf("Opening SecLink on COM%d...\n", comPort + 1);
|
||||||
tc.link = secLinkOpen(comPort, baudRate, 8, 'N', 1, 0, onRecv, &tc);
|
tc.link = secLinkOpen(comPort, baudRate, 8, 'N', 1, 0, onRecv, &tc);
|
||||||
|
|
@ -213,7 +234,7 @@ int main(int argc, char *argv[]) {
|
||||||
tc.app = &ctx;
|
tc.app = &ctx;
|
||||||
|
|
||||||
// Create the terminal window
|
// Create the terminal window
|
||||||
WindowT *win = dvxCreateWindow(&ctx, "SecLink Terminal", 40, 30, 680, 460, true);
|
WindowT *win = dvxCreateWindow(&ctx, "SecLink Terminal", 40, 30, 680, 460, false);
|
||||||
|
|
||||||
if (!win) {
|
if (!win) {
|
||||||
dvxShutdown(&ctx);
|
dvxShutdown(&ctx);
|
||||||
|
|
@ -253,12 +274,27 @@ int main(int argc, char *argv[]) {
|
||||||
snprintf(statusText, sizeof(statusText), "COM%d %ld 8N1 [Encrypted]", comPort + 1, (long)baudRate);
|
snprintf(statusText, sizeof(statusText), "COM%d %ld 8N1 [Encrypted]", comPort + 1, (long)baudRate);
|
||||||
wgtLabel(sb, statusText);
|
wgtLabel(sb, statusText);
|
||||||
|
|
||||||
|
// Fit window to widget tree
|
||||||
|
dvxFitWindow(&ctx, win);
|
||||||
wgtInvalidate(root);
|
wgtInvalidate(root);
|
||||||
|
|
||||||
// Main loop — poll secLink and terminal each frame
|
// Poll serial during idle instead of yielding CPU
|
||||||
while (dvxUpdate(&ctx)) {
|
ctx.idleCallback = idlePoll;
|
||||||
|
ctx.idleCtx = &tc;
|
||||||
|
|
||||||
|
// Main loop — poll serial, render immediately, composite
|
||||||
|
while (ctx.running) {
|
||||||
secLinkPoll(tc.link);
|
secLinkPoll(tc.link);
|
||||||
wgtAnsiTermPoll(term);
|
wgtAnsiTermPoll(term);
|
||||||
|
|
||||||
|
if (wgtAnsiTermRepaint(term) > 0) {
|
||||||
|
win->contentDirty = true;
|
||||||
|
dvxInvalidateWindow(&ctx, win);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dvxUpdate(&ctx)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue