From 8e8ab22d30648e77b9f38204cf603e47d4f9320e Mon Sep 17 00:00:00 2001 From: Sebastian Jeong Date: Sun, 16 Aug 2026 15:51:42 +0900 Subject: [PATCH] feat: replace serial automation with resident TCP agent --- .qemu/reset.ps1 | 68 ++++++++++ .qemu/run.ps1 | 15 +-- .qemu/serial-relay.mjs | 110 ---------------- .qemu/share/dosagent.c | 240 ----------------------------------- .qemu/tcp-agent-relay.mjs | 71 +++++++++++ fec/tcp-stage.bat | 19 --- tools/dos_stage.py | 67 ++++++++-- tools/dosagent.c | 243 ------------------------------------ tools/tcp_stage.py | 68 ---------- tools/tcpagent/BUILD.BAT | 6 + tools/tcpagent/INSTALL.BAT | 15 +++ tools/tcpagent/Makefile | 38 ++++++ tools/tcpagent/README.md | 32 +++++ tools/tcpagent/WPP.RSP | 11 ++ tools/tcpagent/tcpagent.cfg | 12 ++ tools/tcpagent/tcpagent.cpp | 205 ++++++++++++++++++++++++++++++ 16 files changed, 521 insertions(+), 699 deletions(-) create mode 100644 .qemu/reset.ps1 delete mode 100644 .qemu/serial-relay.mjs delete mode 100644 .qemu/share/dosagent.c create mode 100644 .qemu/tcp-agent-relay.mjs delete mode 100644 fec/tcp-stage.bat delete mode 100644 tools/dosagent.c delete mode 100644 tools/tcp_stage.py create mode 100644 tools/tcpagent/BUILD.BAT create mode 100644 tools/tcpagent/INSTALL.BAT create mode 100644 tools/tcpagent/Makefile create mode 100644 tools/tcpagent/README.md create mode 100644 tools/tcpagent/WPP.RSP create mode 100644 tools/tcpagent/tcpagent.cfg create mode 100644 tools/tcpagent/tcpagent.cpp diff --git a/.qemu/reset.ps1 b/.qemu/reset.ps1 new file mode 100644 index 0000000..f2465ac --- /dev/null +++ b/.qemu/reset.ps1 @@ -0,0 +1,68 @@ +param( + [int] $ReadyTimeoutSeconds = 45 +) + +$ErrorActionPreference = 'Stop' +$root = Split-Path -Parent $MyInvocation.MyCommand.Path + +function Stop-Listener([int] $Port) { + $listeners = Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue + foreach ($listener in $listeners) { + Stop-Process -Id $listener.OwningProcess -Force -ErrorAction SilentlyContinue + } +} + +# Quit through QEMU's monitor so qcow2/FAT writes are flushed. Forced VM +# termination is deliberately forbidden because it previously lost files. +$monitor = Get-NetTCPConnection -State Listen -LocalPort 4444 -ErrorAction SilentlyContinue +if ($monitor) { + & (Join-Path $root 'monitor.ps1') 'quit' | Out-Null + $deadline = (Get-Date).AddSeconds(10) + do { + Start-Sleep -Milliseconds 250 + $monitor = Get-NetTCPConnection -State Listen -LocalPort 4444 -ErrorAction SilentlyContinue + } while ($monitor -and (Get-Date) -lt $deadline) + if ($monitor) { throw 'QEMU did not quit cleanly; refusing a forced reset.' } +} +Stop-Listener 5555 +Start-Sleep -Milliseconds 500 + +$runner = Join-Path $root 'run.ps1' +Start-Process powershell -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File',$runner) -WorkingDirectory $root +$deadline = (Get-Date).AddSeconds(15) +do { + Start-Sleep -Milliseconds 250 + $monitor = Get-NetTCPConnection -State Listen -LocalPort 4444 -ErrorAction SilentlyContinue +} while (-not $monitor -and (Get-Date) -lt $deadline) +if (-not $monitor) { throw 'QEMU monitor did not start.' } + +# Select the default boot entry. TCPAGENT is started by FDAUTO.BAT after the +# packet driver, so readiness is the agent connection/PONG rather than sleep. +Start-Sleep -Seconds 2 +& (Join-Path $root 'monitor.ps1') 'sendkey ret' | Out-Null +$deadline = (Get-Date).AddSeconds($ReadyTimeoutSeconds) +$ready = $false +do { + Start-Sleep -Milliseconds 500 + $connection = Get-NetTCPConnection -State Established -LocalPort 5558 -ErrorAction SilentlyContinue + if ($connection) { + try { + $client = [Net.Sockets.TcpClient]::new('127.0.0.1',5555) + $stream = $client.GetStream() + $stream.ReadTimeout = 1500 + $bytes = [Text.Encoding]::ASCII.GetBytes("PING`n") + $stream.Write($bytes,0,$bytes.Length) + $buffer = New-Object byte[] 128 + $reply = '' + while ($reply -notmatch "`n") { + $count = $stream.Read($buffer,0,$buffer.Length) + if ($count -le 0) { break } + $reply += [Text.Encoding]::ASCII.GetString($buffer,0,$count) + } + $client.Dispose() + $ready = $reply -match '^OK 504F4E47' + } catch { $ready = $false } + } +} while (-not $ready -and (Get-Date) -lt $deadline) +if (-not $ready) { throw 'TCPAGENT did not become ready.' } +Write-Host 'QEMU reset complete; TCPAGENT answered PONG.' diff --git a/.qemu/run.ps1 b/.qemu/run.ps1 index 865e496..eefe499 100644 --- a/.qemu/run.ps1 +++ b/.qemu/run.ps1 @@ -4,26 +4,21 @@ $ErrorActionPreference = 'Stop' $qemu = Get-Command qemu-system-i386.exe -ErrorAction Stop $node = Get-Command node.exe -ErrorAction Stop $disk = Join-Path $PSScriptRoot 'freedos.qcow2' -$share = Join-Path $PSScriptRoot 'share' -$relay = Join-Path $PSScriptRoot 'serial-relay.mjs' - -# QEMU's FAT-directory drive is a convenient exchange volume, but it is not a -# reliable live-sync mechanism. Restart QEMU after host-side source changes. -New-Item -ItemType Directory -Force -Path $share | Out-Null +$relay = Join-Path $PSScriptRoot 'tcp-agent-relay.mjs' if (-not (Test-Path -LiteralPath $disk)) { throw "Missing $disk. Run .qemu\\setup.ps1, then .qemu\\install.ps1." } -$relayListening = Get-NetTCPConnection -State Listen -LocalPort 5556 -ErrorAction SilentlyContinue +$relayListening = Get-NetTCPConnection -State Listen -LocalPort 5558 -ErrorAction SilentlyContinue if (-not $relayListening) { Start-Process -FilePath $node.Source -ArgumentList @($relay) -WorkingDirectory $PSScriptRoot -WindowStyle Hidden $deadline = [DateTime]::UtcNow.AddSeconds(5) do { Start-Sleep -Milliseconds 100 - $relayListening = Get-NetTCPConnection -State Listen -LocalPort 5556 -ErrorAction SilentlyContinue + $relayListening = Get-NetTCPConnection -State Listen -LocalPort 5558 -ErrorAction SilentlyContinue } while (-not $relayListening -and [DateTime]::UtcNow -lt $deadline) - if (-not $relayListening) { throw 'Serial relay did not start.' } + if (-not $relayListening) { throw 'TCP agent relay did not start.' } } $monitorListening = Get-NetTCPConnection -State Listen -LocalPort 4444 -ErrorAction SilentlyContinue @@ -36,9 +31,7 @@ if ($monitorListening) { -smp 1 ` -m 64 ` -drive "file=$disk,format=qcow2,if=ide,index=0,media=disk" ` - -hdb "fat:rw:$share" ` -nic user,model=ne2k_isa ` -monitor "tcp:127.0.0.1:4444,server=on,wait=off" ` - -serial "tcp:127.0.0.1:5556" ` -boot order=c ` -display default diff --git a/.qemu/serial-relay.mjs b/.qemu/serial-relay.mjs deleted file mode 100644 index de44160..0000000 --- a/.qemu/serial-relay.mjs +++ /dev/null @@ -1,110 +0,0 @@ -import net from "node:net"; - -const HOST = "127.0.0.1"; -const QEMU_PORT = 5556; -const TOOL_PORT = 5555; -const OBSERVER_PORT = 5557; - -let qemu = null; -let controller = null; -const observers = new Set(); - -function safeWrite(socket, data) { - if (socket && !socket.destroyed && socket.writable) socket.write(data); -} - -function decodeHex(hex) { - if (!hex || hex.length % 2 || !/^[0-9a-f]+$/i.test(hex)) return null; - return Buffer.from(hex, "hex").toString("utf8"); -} - -function visible(text) { - const shortened = text.length > 400 ? `${text.slice(0, 400)}…` : text; - return shortened - .replace(/\r\n?/g, "\n") - .replace(/[^\x20-\x7E\n]/g, (char) => `[0x${char.charCodeAt(0).toString(16).padStart(2, "0")}]`) - .replace(/\n/g, "\r\n "); -} - -function formatLine(source, line) { - if (!line) return null; - if (line === "PING" || line === "QUIT" || line === "DOSAGENT READY") return `${source} ${line}`; - - const [command, ...args] = line.split(" "); - if (["READ", "LIST"].includes(command) && args[0]) { - return `${source} ${command} ${visible(decodeHex(args[0]) ?? args[0])}${args[1] ? ` @${args[1]}` : ""}`; - } - if (command === "WRITE" && args.length >= 3) { - const path = decodeHex(args[0]) ?? args[0]; - const body = decodeHex(args.slice(2).join(" ")) ?? args.slice(2).join(" "); - return `${source} WRITE ${visible(path)} (${args[1] === "T" ? "replace" : "append"}) ${visible(body)}`; - } - if (command === "EXEC" && args[0]) return `${source} EXEC ${visible(decodeHex(args.join(" ")) ?? args.join(" "))}`; - if (command === "ERR" && args[0]) return `${source} ERROR ${visible(decodeHex(args.join(" ")) ?? args.join(" "))}`; - if (command === "OK") { - const singlePayload = args.length === 1 ? decodeHex(args[0]) : null; - const payload = singlePayload ?? (args.length > 1 ? decodeHex(args.slice(1).join(" ")) : null); - const status = singlePayload !== null ? "" : (args[0] ? ` (${args[0]})` : ""); - return `${source} OK${status}${payload !== null ? ` ${visible(payload)}` : ""}`; - } - return `${source} ${line}`; -} - -const traceBuffers = new Map(); -function trace(source, data) { - const pending = (traceBuffers.get(source) ?? "") + data.toString("ascii"); - const parts = pending.split(/\r?\n/); - traceBuffers.set(source, parts.pop()); - for (const line of parts) { - const formatted = formatLine(source, line); - if (formatted) for (const observer of observers) safeWrite(observer, `${formatted}\r\n`); - } -} - -const qemuServer = net.createServer((socket) => { - if (qemu && !qemu.destroyed) { - socket.end(); - return; - } - qemu = socket; - socket.setNoDelay(true); - socket.on("data", (data) => { - safeWrite(controller, data); - trace("DOS -> Pi", data); - }); - socket.on("close", () => { - if (qemu === socket) qemu = null; - if (controller && !controller.destroyed) controller.destroy(); - }); - socket.on("error", () => {}); -}); - -const toolServer = net.createServer((socket) => { - if (controller && !controller.destroyed) { - socket.end("ERR 53455249414C20434F4E54524F4C4C45522042555359\r\n"); - return; - } - controller = socket; - socket.setNoDelay(true); - socket.on("data", (data) => { - safeWrite(qemu, data); - trace("Pi -> DOS", data); - }); - socket.on("close", () => { - if (controller === socket) controller = null; - }); - socket.on("error", () => {}); -}); - -const observerServer = net.createServer((socket) => { - observers.add(socket); - socket.setNoDelay(true); - socket.write("DOS serial observer - decoded protocol (read-only)\r\n"); - socket.on("data", () => {}); - socket.on("close", () => observers.delete(socket)); - socket.on("error", () => observers.delete(socket)); -}); - -qemuServer.listen(QEMU_PORT, HOST); -toolServer.listen(TOOL_PORT, HOST); -observerServer.listen(OBSERVER_PORT, HOST); diff --git a/.qemu/share/dosagent.c b/.qemu/share/dosagent.c deleted file mode 100644 index 01095b4..0000000 --- a/.qemu/share/dosagent.c +++ /dev/null @@ -1,240 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define LINE_SIZE 9000 -#define CHUNK_SIZE 4096 - -static char linebuf[LINE_SIZE]; -static unsigned char data[CHUNK_SIZE + 1]; - -#define COM1_BASE 0x3F8 - -static void serial_init(void) { - outp(COM1_BASE + 1, 0x00); - outp(COM1_BASE + 3, 0x80); - outp(COM1_BASE + 0, 12); - outp(COM1_BASE + 1, 0); - outp(COM1_BASE + 3, 0x03); - outp(COM1_BASE + 2, 0xC7); - outp(COM1_BASE + 4, 0x0B); -} - -static int console_break_requested(void) { - unsigned short key = _bios_keybrd(_KEYBRD_READY); - if ((key & 0xFF) != 3) return 0; - _bios_keybrd(_KEYBRD_READ); - return 1; -} - -static int serial_getc(void) { - while ((inp(COM1_BASE + 5) & 0x01) == 0) { - if (console_break_requested()) return -1; - } - return inp(COM1_BASE); -} - -static void serial_putc(int c) { - while ((inp(COM1_BASE + 5) & 0x20) == 0) { } - outp(COM1_BASE, c); -} - -static void serial_write(const char *text) { - while (*text) serial_putc((unsigned char)*text++); -} - -static int serial_readline(char *buffer, int cap) { - int used = 0; - int c; - for (;;) { - c = serial_getc(); - if (c < 0) return -1; - if (c == '\r' || c == '\n') { - if (used == 0) continue; - buffer[used] = '\0'; - return used; - } - if (used < cap - 1) buffer[used++] = (char)c; - } -} - -static int hexval(int c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - return -1; -} - -static int decode_hex(const char *src, unsigned char *dst, int cap) { - int n = 0; - int hi, lo; - while (*src && src[1]) { - if (n >= cap) return -1; - hi = hexval((unsigned char)src[0]); - lo = hexval((unsigned char)src[1]); - if (hi < 0 || lo < 0) return -1; - dst[n++] = (unsigned char)((hi << 4) | lo); - src += 2; - } - return *src ? -1 : n; -} - -static void print_hex(const unsigned char *src, unsigned count) { - static const char digits[] = "0123456789ABCDEF"; - unsigned i; - for (i = 0; i < count; ++i) { - serial_putc(digits[src[i] >> 4]); - serial_putc(digits[src[i] & 15]); - } -} - -static void ok_data(const unsigned char *src, unsigned count) { - serial_write("OK "); - print_hex(src, count); - serial_write("\r\n"); -} - -static void error_text(const char *message) { - serial_write("ERR "); - print_hex((const unsigned char *)message, strlen(message)); - serial_write("\r\n"); -} - -static int decode_path(const char *hex, char *path, int cap) { - int n = decode_hex(hex, (unsigned char *)path, cap - 1); - if (n < 0) return 0; - path[n] = '\0'; - return 1; -} - -static void command_read(char *args) { - char path[260]; - char *offset_text = strchr(args, ' '); - FILE *file; - long offset; - size_t count; - int eof; - - if (!offset_text) { error_text("READ requires path and offset"); return; } - *offset_text++ = '\0'; - if (!decode_path(args, path, sizeof(path))) { error_text("Invalid path encoding"); return; } - offset = atol(offset_text); - file = fopen(path, "rb"); - if (!file) { error_text("Cannot open file"); return; } - if (fseek(file, offset, SEEK_SET) != 0) { fclose(file); error_text("Cannot seek file"); return; } - count = fread(data, 1, CHUNK_SIZE, file); - eof = feof(file) ? 1 : 0; - if (count < CHUNK_SIZE) eof = 1; - fclose(file); - sprintf(linebuf, "OK %d ", eof); - serial_write(linebuf); - print_hex(data, count); - serial_write("\r\n"); -} - -static void command_write(char *args) { - char path[260]; - char *mode_text = strchr(args, ' '); - char *payload; - FILE *file; - int count; - - if (!mode_text) { error_text("WRITE requires path, mode and data"); return; } - *mode_text++ = '\0'; - payload = strchr(mode_text, ' '); - if (!payload) { error_text("WRITE requires data"); return; } - *payload++ = '\0'; - if (!decode_path(args, path, sizeof(path))) { error_text("Invalid path encoding"); return; } - count = decode_hex(payload, data, CHUNK_SIZE); - if (count < 0) { error_text("Invalid data encoding"); return; } - file = fopen(path, mode_text[0] == 'A' ? "ab" : "wb"); - if (!file) { error_text("Cannot write file"); return; } - if (count && fwrite(data, 1, count, file) != (size_t)count) { - fclose(file); error_text("Short write"); return; - } - fclose(file); - ok_data((const unsigned char *)"", 0); -} - -static void command_list(char *args) { - char path[260]; - char pattern[300]; - char output[CHUNK_SIZE]; - struct find_t found; - unsigned used = 0; - int rc; - - if (!decode_path(args, path, sizeof(path))) { error_text("Invalid path encoding"); return; } - strcpy(pattern, path); - if (pattern[0] && pattern[strlen(pattern) - 1] != '\\' && pattern[strlen(pattern) - 1] != '/') strcat(pattern, "\\"); - strcat(pattern, "*.*"); - rc = _dos_findfirst(pattern, _A_NORMAL | _A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_SUBDIR | _A_ARCH, &found); - while (rc == 0) { - char entry[100]; - int len; - if (strcmp(found.name, ".") && strcmp(found.name, "..")) { - sprintf(entry, "%s\t%lu\t%s\n", found.name, found.size, (found.attrib & _A_SUBDIR) ? "DIR" : "FILE"); - len = strlen(entry); - if (used + len >= sizeof(output)) break; - memcpy(output + used, entry, len); - used += len; - } - rc = _dos_findnext(&found); - } - ok_data((const unsigned char *)output, used); -} - -static void command_exec(char *args) { - char command[700]; - char shell_command[800]; - const char *temp_path = "C:\\PIEXEC.TMP"; - FILE *file; - int n, exit_code; - size_t count; - - n = decode_hex(args, (unsigned char *)command, sizeof(command) - 1); - if (n < 0) { error_text("Invalid command encoding"); return; } - command[n] = '\0'; - sprintf(shell_command, "COMMAND.COM /C %s > %s", command, temp_path); - exit_code = system(shell_command); - file = fopen(temp_path, "rb"); - count = file ? fread(data, 1, CHUNK_SIZE, file) : 0; - if (file) fclose(file); - remove(temp_path); - sprintf(linebuf, "OK %d ", exit_code); - serial_write(linebuf); - print_hex(data, count); - serial_write("\r\n"); -} - -int main(void) { - char *command; - char *args; - - serial_init(); - cprintf("\r\n[DOSAGENT] Ready on COM1. Press Ctrl+C here to stop.\r\n"); - serial_write("DOSAGENT READY\r\n"); - for (;;) { - if (serial_readline(linebuf, sizeof(linebuf)) < 0) { - cprintf("\r\n[DOSAGENT] Stopped from VGA console.\r\n"); - break; - } - command = linebuf; - args = strchr(command, ' '); - if (args) *args++ = '\0'; else args = command + strlen(command); - - cprintf("[DOSAGENT] %s\r\n", command); - - if (!strcmp(command, "PING")) ok_data((const unsigned char *)"PONG", 4); - else if (!strcmp(command, "READ")) command_read(args); - else if (!strcmp(command, "WRITE")) command_write(args); - else if (!strcmp(command, "LIST")) command_list(args); - else if (!strcmp(command, "EXEC")) command_exec(args); - else if (!strcmp(command, "QUIT")) { ok_data((const unsigned char *)"BYE", 3); break; } - else error_text("Unknown command"); - } - return 0; -} diff --git a/.qemu/tcp-agent-relay.mjs b/.qemu/tcp-agent-relay.mjs new file mode 100644 index 0000000..2768bc4 --- /dev/null +++ b/.qemu/tcp-agent-relay.mjs @@ -0,0 +1,71 @@ +import net from "node:net"; + +const HOST = "127.0.0.1"; +const TOOL_PORT = 5555; +const OBSERVER_PORT = 5557; +const TCP_AGENT_PORT = 5558; + +let tcpAgent = null; +let controller = null; +const observers = new Set(); + +function safeWrite(socket, data) { + if (socket && !socket.destroyed && socket.writable) socket.write(data); +} + +function trace(source, data) { + const text = data.toString("ascii"); + const shortened = text.length > 400 ? `${text.slice(0, 400)}…` : text; + for (const observer of observers) safeWrite(observer, `${source} ${shortened}\r\n`); +} + +const tcpAgentServer = net.createServer((socket) => { + if (tcpAgent && !tcpAgent.destroyed) { + socket.end(); + return; + } + tcpAgent = socket; + socket.setNoDelay(true); + socket.on("data", (data) => { + safeWrite(controller, data); + trace("DOS->Pi", data); + }); + socket.on("close", () => { + if (tcpAgent === socket) tcpAgent = null; + }); + socket.on("error", () => {}); +}); + +const toolServer = net.createServer((socket) => { + if (controller && !controller.destroyed) { + socket.end("ERR 544350204147454E5420434F4E54524F4C4C45522042555359\r\n"); + return; + } + if (!tcpAgent || tcpAgent.destroyed) { + socket.end("ERR 544350204147454E54204E4F5420434F4E4E4543544544\r\n"); + return; + } + controller = socket; + socket.setNoDelay(true); + socket.on("data", (data) => { + safeWrite(tcpAgent, data); + trace("Pi->DOS", data); + }); + socket.on("close", () => { + if (controller === socket) controller = null; + }); + socket.on("error", () => {}); +}); + +const observerServer = net.createServer((socket) => { + observers.add(socket); + socket.setNoDelay(true); + socket.write("DOS TCP agent observer (read-only)\r\n"); + socket.on("data", () => {}); + socket.on("close", () => observers.delete(socket)); + socket.on("error", () => observers.delete(socket)); +}); + +tcpAgentServer.listen(TCP_AGENT_PORT, HOST); +toolServer.listen(TOOL_PORT, HOST); +observerServer.listen(OBSERVER_PORT, HOST); diff --git a/fec/tcp-stage.bat b/fec/tcp-stage.bat deleted file mode 100644 index d4825c5..0000000 --- a/fec/tcp-stage.bat +++ /dev/null @@ -1,19 +0,0 @@ -@echo off -rem Pull a host-built TCP staging bundle into the authoritative C:\FEC tree. -rem QEMU user networking exposes the host as 10.0.2.2. HTGET and UNZIP are -rem supplied by the installed FreeDOS network tools. -if "%1"=="" goto usage -if exist C:\FEC\STAGE.ZIP del C:\FEC\STAGE.ZIP -htget -o C:\FEC\STAGE.ZIP %1 -if errorlevel 1 goto fail -unzip -o C:\FEC\STAGE.ZIP -d C:\FEC -if errorlevel 1 goto fail -if exist C:\FEC\TCPSTG.OK del C:\FEC\TCPSTG.OK -echo OK>C:\FEC\TCPSTG.OK -goto done -:usage -echo usage: TCP-STAGE.BAT http://10.0.2.2:8000/STAGE.ZIP -goto done -:fail -echo FAIL>C:\FEC\TCPSTG.FAIL -:done diff --git a/tools/dos_stage.py b/tools/dos_stage.py index b022d13..1218749 100644 --- a/tools/dos_stage.py +++ b/tools/dos_stage.py @@ -12,7 +12,7 @@ from pathlib import Path HOST = "127.0.0.1" PORT = 5555 -CHUNK_SIZE = 4096 +DEFAULT_CHUNK_SIZE = 4096 def request(line: str) -> str: @@ -32,11 +32,50 @@ def request(line: str) -> str: return text -def stage(local: Path, remote: str) -> None: +def recv_line(sock: socket.socket) -> bytes: + line = bytearray() + while not line.endswith(b"\n"): + part = sock.recv(1) + if not part: + raise RuntimeError("TCP agent closed the connection") + line.extend(part) + return bytes(line).strip() + + +def fnv1a(data: bytes) -> int: + value = 2166136261 + for byte in data: + value = ((value ^ byte) * 16777619) & 0xFFFFFFFF + return value + + +def binary_stage(local: Path, remote: str) -> None: + data = local.read_bytes() + path_hex = remote.encode("ascii").hex().upper() + with socket.create_connection((HOST, PORT), timeout=30) as sock: + sock.sendall(("PUT %s %d\n" % (path_hex, len(data))).encode("ascii")) + sock.sendall(data) + response = recv_line(sock) + if not response.startswith(b"OK"): + raise RuntimeError("PUT failed: " + response.decode("ascii", "replace")) + sock.sendall(("HASH %s\n" % path_hex).encode("ascii")) + header = recv_line(sock).decode("ascii", "strict") + fields = header.split() + if len(fields) != 3 or fields[0] != "STAT": + raise RuntimeError("HASH failed: " + header) + remote_length = int(fields[1]) + remote_hash = int(fields[2], 16) + if remote_length != len(data) or remote_hash != fnv1a(data): + raise RuntimeError("verification mismatch for " + remote) + print("staged %s -> %s (%d bytes, sha256 %s, binary TCP)" % ( + local, remote, len(data), hashlib.sha256(data).hexdigest())) + + +def stage(local: Path, remote: str, chunk_size: int) -> None: data = local.read_bytes() remote_hex = remote.encode("ascii").hex().upper() - for offset in range(0, max(1, len(data)), CHUNK_SIZE): - part = data[offset : offset + CHUNK_SIZE] + for offset in range(0, max(1, len(data)), chunk_size): + part = data[offset : offset + chunk_size] mode = "T" if offset == 0 else "A" request("WRITE %s %s %s" % (remote_hex, mode, part.hex().upper())) remote_data = bytearray() @@ -56,14 +95,26 @@ def stage(local: Path, remote: str) -> None: def main() -> int: - if len(sys.argv) < 2: - print("usage: dos_stage.py LOCAL=DOS_PATH [... ]", file=sys.stderr) + args = sys.argv[1:] + chunk_size = DEFAULT_CHUNK_SIZE + binary = False + if args and args[0] == "--binary": + binary = True + args = args[1:] + if len(args) >= 2 and args[0] == "--chunk-size": + chunk_size = int(args[1]) + args = args[2:] + if not args: + print("usage: dos_stage.py [--binary] [--chunk-size N] LOCAL=DOS_PATH [... ]", file=sys.stderr) return 2 - for spec in sys.argv[1:]: + for spec in args: if "=" not in spec: raise SystemExit("missing '=' in " + spec) local, remote = spec.split("=", 1) - stage(Path(local), remote) + if binary: + binary_stage(Path(local), remote) + else: + stage(Path(local), remote, chunk_size) return 0 diff --git a/tools/dosagent.c b/tools/dosagent.c deleted file mode 100644 index 1c94073..0000000 --- a/tools/dosagent.c +++ /dev/null @@ -1,243 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define LINE_SIZE 9000 -#define CHUNK_SIZE 4096 - -static char linebuf[LINE_SIZE]; -static unsigned char data[CHUNK_SIZE + 1]; - -#define COM1_BASE 0x3F8 -/* The QEMU backend is a TCP socket; use the fastest standard 16550 rate. - 1.8432 MHz / (16 * 1) = 115200 baud. */ -#define COM1_DIVISOR 1 - -static void serial_init(void) { - outp(COM1_BASE + 1, 0x00); - outp(COM1_BASE + 3, 0x80); - outp(COM1_BASE + 0, COM1_DIVISOR); - outp(COM1_BASE + 1, 0); - outp(COM1_BASE + 3, 0x03); - outp(COM1_BASE + 2, 0xC7); - outp(COM1_BASE + 4, 0x0B); -} - -static int console_break_requested(void) { - unsigned short key = _bios_keybrd(_KEYBRD_READY); - if ((key & 0xFF) != 3) return 0; - _bios_keybrd(_KEYBRD_READ); - return 1; -} - -static int serial_getc(void) { - while ((inp(COM1_BASE + 5) & 0x01) == 0) { - if (console_break_requested()) return -1; - } - return inp(COM1_BASE); -} - -static void serial_putc(int c) { - while ((inp(COM1_BASE + 5) & 0x20) == 0) { } - outp(COM1_BASE, c); -} - -static void serial_write(const char *text) { - while (*text) serial_putc((unsigned char)*text++); -} - -static int serial_readline(char *buffer, int cap) { - int used = 0; - int c; - for (;;) { - c = serial_getc(); - if (c < 0) return -1; - if (c == '\r' || c == '\n') { - if (used == 0) continue; - buffer[used] = '\0'; - return used; - } - if (used < cap - 1) buffer[used++] = (char)c; - } -} - -static int hexval(int c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - return -1; -} - -static int decode_hex(const char *src, unsigned char *dst, int cap) { - int n = 0; - int hi, lo; - while (*src && src[1]) { - if (n >= cap) return -1; - hi = hexval((unsigned char)src[0]); - lo = hexval((unsigned char)src[1]); - if (hi < 0 || lo < 0) return -1; - dst[n++] = (unsigned char)((hi << 4) | lo); - src += 2; - } - return *src ? -1 : n; -} - -static void print_hex(const unsigned char *src, unsigned count) { - static const char digits[] = "0123456789ABCDEF"; - unsigned i; - for (i = 0; i < count; ++i) { - serial_putc(digits[src[i] >> 4]); - serial_putc(digits[src[i] & 15]); - } -} - -static void ok_data(const unsigned char *src, unsigned count) { - serial_write("OK "); - print_hex(src, count); - serial_write("\r\n"); -} - -static void error_text(const char *message) { - serial_write("ERR "); - print_hex((const unsigned char *)message, strlen(message)); - serial_write("\r\n"); -} - -static int decode_path(const char *hex, char *path, int cap) { - int n = decode_hex(hex, (unsigned char *)path, cap - 1); - if (n < 0) return 0; - path[n] = '\0'; - return 1; -} - -static void command_read(char *args) { - char path[260]; - char *offset_text = strchr(args, ' '); - FILE *file; - long offset; - size_t count; - int eof; - - if (!offset_text) { error_text("READ requires path and offset"); return; } - *offset_text++ = '\0'; - if (!decode_path(args, path, sizeof(path))) { error_text("Invalid path encoding"); return; } - offset = atol(offset_text); - file = fopen(path, "rb"); - if (!file) { error_text("Cannot open file"); return; } - if (fseek(file, offset, SEEK_SET) != 0) { fclose(file); error_text("Cannot seek file"); return; } - count = fread(data, 1, CHUNK_SIZE, file); - eof = feof(file) ? 1 : 0; - if (count < CHUNK_SIZE) eof = 1; - fclose(file); - sprintf(linebuf, "OK %d ", eof); - serial_write(linebuf); - print_hex(data, count); - serial_write("\r\n"); -} - -static void command_write(char *args) { - char path[260]; - char *mode_text = strchr(args, ' '); - char *payload; - FILE *file; - int count; - - if (!mode_text) { error_text("WRITE requires path, mode and data"); return; } - *mode_text++ = '\0'; - payload = strchr(mode_text, ' '); - if (!payload) { error_text("WRITE requires data"); return; } - *payload++ = '\0'; - if (!decode_path(args, path, sizeof(path))) { error_text("Invalid path encoding"); return; } - count = decode_hex(payload, data, CHUNK_SIZE); - if (count < 0) { error_text("Invalid data encoding"); return; } - file = fopen(path, mode_text[0] == 'A' ? "ab" : "wb"); - if (!file) { error_text("Cannot write file"); return; } - if (count && fwrite(data, 1, count, file) != (size_t)count) { - fclose(file); error_text("Short write"); return; - } - fclose(file); - ok_data((const unsigned char *)"", 0); -} - -static void command_list(char *args) { - char path[260]; - char pattern[300]; - char output[CHUNK_SIZE]; - struct find_t found; - unsigned used = 0; - int rc; - - if (!decode_path(args, path, sizeof(path))) { error_text("Invalid path encoding"); return; } - strcpy(pattern, path); - if (pattern[0] && pattern[strlen(pattern) - 1] != '\\' && pattern[strlen(pattern) - 1] != '/') strcat(pattern, "\\"); - strcat(pattern, "*.*"); - rc = _dos_findfirst(pattern, _A_NORMAL | _A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_SUBDIR | _A_ARCH, &found); - while (rc == 0) { - char entry[100]; - int len; - if (strcmp(found.name, ".") && strcmp(found.name, "..")) { - sprintf(entry, "%s\t%lu\t%s\n", found.name, found.size, (found.attrib & _A_SUBDIR) ? "DIR" : "FILE"); - len = strlen(entry); - if (used + len >= sizeof(output)) break; - memcpy(output + used, entry, len); - used += len; - } - rc = _dos_findnext(&found); - } - ok_data((const unsigned char *)output, used); -} - -static void command_exec(char *args) { - char command[700]; - char shell_command[800]; - const char *temp_path = "C:\\PIEXEC.TMP"; - FILE *file; - int n, exit_code; - size_t count; - - n = decode_hex(args, (unsigned char *)command, sizeof(command) - 1); - if (n < 0) { error_text("Invalid command encoding"); return; } - command[n] = '\0'; - sprintf(shell_command, "COMMAND.COM /C %s > %s", command, temp_path); - exit_code = system(shell_command); - file = fopen(temp_path, "rb"); - count = file ? fread(data, 1, CHUNK_SIZE, file) : 0; - if (file) fclose(file); - remove(temp_path); - sprintf(linebuf, "OK %d ", exit_code); - serial_write(linebuf); - print_hex(data, count); - serial_write("\r\n"); -} - -int main(void) { - char *command; - char *args; - - serial_init(); - cprintf("\r\n[DOSAGENT] Ready on COM1. Press Ctrl+C here to stop.\r\n"); - serial_write("DOSAGENT READY\r\n"); - for (;;) { - if (serial_readline(linebuf, sizeof(linebuf)) < 0) { - cprintf("\r\n[DOSAGENT] Stopped from VGA console.\r\n"); - break; - } - command = linebuf; - args = strchr(command, ' '); - if (args) *args++ = '\0'; else args = command + strlen(command); - - cprintf("[DOSAGENT] %s\r\n", command); - - if (!strcmp(command, "PING")) ok_data((const unsigned char *)"PONG", 4); - else if (!strcmp(command, "READ")) command_read(args); - else if (!strcmp(command, "WRITE")) command_write(args); - else if (!strcmp(command, "LIST")) command_list(args); - else if (!strcmp(command, "EXEC")) command_exec(args); - else if (!strcmp(command, "QUIT")) { ok_data((const unsigned char *)"BYE", 3); break; } - else error_text("Unknown command"); - } - return 0; -} diff --git a/tools/tcp_stage.py b/tools/tcp_stage.py deleted file mode 100644 index f29647d..0000000 --- a/tools/tcp_stage.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 -"""Build and serve a FreeDOS TCP staging bundle. - -The guest pulls STAGE.ZIP with its existing HTGET client from QEMU user-net's -host address (10.0.2.2), then extracts it into C:\\FEC. This avoids changing -the live vvfat exchange disk and keeps serial for bootstrap/recovery only. -""" -import argparse -import http.server -import os -import shutil -import zipfile -from pathlib import Path - -ROOT = Path(__file__).resolve().parent.parent -DEFAULT_STAGE = ROOT / ".tcp-stage" - - -def bundle(stage: Path, files: list[str]) -> Path: - if stage.exists(): - shutil.rmtree(stage) - stage.mkdir(parents=True) - archive = stage / "STAGE.ZIP" - with zipfile.ZipFile(archive, "w", zipfile.ZIP_DEFLATED) as zf: - for item in files: - source = (ROOT / item).resolve() - try: - relative = source.relative_to(ROOT / "fec") - except ValueError as exc: - raise SystemExit("only files below fec/ may be staged: " + item) from exc - if not source.is_file(): - raise SystemExit("not a file: " + item) - zf.write(source, relative.as_posix()) - print("created %s (%d bytes)" % (archive, archive.stat().st_size)) - return archive - - -def serve(stage: Path, port: int) -> None: - if not (stage / "STAGE.ZIP").is_file(): - raise SystemExit("missing STAGE.ZIP; run bundle first") - handler = lambda *args, **kwargs: http.server.SimpleHTTPRequestHandler( - *args, directory=str(stage), **kwargs) - server = http.server.ThreadingHTTPServer(("0.0.0.0", port), handler) - print("serving %s at http://10.0.2.2:%d/STAGE.ZIP" % (stage, port)) - try: - server.serve_forever() - finally: - server.server_close() - - -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("--stage-dir", type=Path, default=DEFAULT_STAGE) - commands = parser.add_subparsers(dest="command", required=True) - make = commands.add_parser("bundle") - make.add_argument("files", nargs="+") - web = commands.add_parser("serve") - web.add_argument("--port", type=int, default=8000) - args = parser.parse_args() - if args.command == "bundle": - bundle(args.stage_dir, args.files) - else: - serve(args.stage_dir, args.port) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tools/tcpagent/BUILD.BAT b/tools/tcpagent/BUILD.BAT new file mode 100644 index 0000000..fa13ec9 --- /dev/null +++ b/tools/tcpagent/BUILD.BAT @@ -0,0 +1,6 @@ +@echo off +set WATCOM=C:\DEVEL\WATCOMC +set PATH=C:\DEVEL\WATCOMC\BINW;C:\DEVEL\WATCOMC\BINP;C:\FREEDOS\BIN +set INCLUDE=C:\DEVEL\WATCOMC\H +set EDPATH= +wmake diff --git a/tools/tcpagent/INSTALL.BAT b/tools/tcpagent/INSTALL.BAT new file mode 100644 index 0000000..5532775 --- /dev/null +++ b/tools/tcpagent/INSTALL.BAT @@ -0,0 +1,15 @@ +@echo off +if not exist TCPAGENT.EXE goto fail +copy TCPAGENT.EXE C:\FREEDOS\BIN\TCPAGENT.EXE > nul +if errorlevel 1 goto fail +find "TCPAGENT.EXE" C:\FDAUTO.BAT > nul +if not errorlevel 1 goto done +echo SET MTCPCFG=C:\FREEDOS\MTCP.CFG>>C:\FDAUTO.BAT +echo IF EXIST C:\FREEDOS\BIN\TCPAGENT.EXE C:\FREEDOS\BIN\TCPAGENT.EXE>>C:\FDAUTO.BAT +:done +echo TCPAGENT installed. +goto end +:fail +echo TCPAGENT install failed. +verify other 2>nul +:end diff --git a/tools/tcpagent/Makefile b/tools/tcpagent/Makefile new file mode 100644 index 0000000..ceb4f0f --- /dev/null +++ b/tools/tcpagent/Makefile @@ -0,0 +1,38 @@ +# Place this directory at MTCP\APPS\TCPAGENT in an mTCP checkout. +tcp_c_dir = ..\..\TCPLIB +memory_model = -mc +compile_options = @WPP.RSP +tcpobjs = packet.obj arp.obj eth.obj ip.obj tcp.obj tcpsockm.obj udp.obj utils.obj timer.obj ipasm.obj trace.obj +all : tcpagent.exe +.cpp.obj : + wpp $[* $(compile_options) +tcpagent.obj: tcpagent.cpp + wpp $(compile_options) tcpagent.cpp +packet.obj: + wpp $(tcp_c_dir)\packet.cpp $(compile_options) +arp.obj: + wpp $(tcp_c_dir)\arp.cpp $(compile_options) +eth.obj: + wpp $(tcp_c_dir)\eth.cpp $(compile_options) +ip.obj: + wpp $(tcp_c_dir)\ip.cpp $(compile_options) +tcp.obj: + wpp $(tcp_c_dir)\tcp.cpp $(compile_options) +tcpsockm.obj: + wpp $(tcp_c_dir)\tcpsockm.cpp $(compile_options) +udp.obj: + wpp $(tcp_c_dir)\udp.cpp $(compile_options) +utils.obj: + wpp $(tcp_c_dir)\utils.cpp $(compile_options) +timer.obj: + wpp $(tcp_c_dir)\timer.cpp $(compile_options) +trace.obj: + wpp $(tcp_c_dir)\trace.cpp $(compile_options) +ipasm.obj: + wasm -0 -mc $(tcp_c_dir)\ipasm.asm +tcpagent.exe: tcpagent.obj $(tcpobjs) + wlink system dos option map option eliminate option stack=16384 name $@ file *.obj +clean : .symbolic + del *.obj + del *.map + del tcpagent.exe diff --git a/tools/tcpagent/README.md b/tools/tcpagent/README.md new file mode 100644 index 0000000..d0d60f0 --- /dev/null +++ b/tools/tcpagent/README.md @@ -0,0 +1,32 @@ +# FreeDOS resident TCP agent + +`TCPAGENT.EXE` is a foreground resident automation process. It uses the mTCP +packet-driver stack and maintains an outbound connection to QEMU's host at +`10.0.2.2:5558`. The host relay keeps the existing Pi tool endpoint on 5555. + +## Build in FreeDOS + +1. Obtain the GPLv3 mTCP source tree (tested with the jhpyle/mTCP 2022 fork). +2. Copy this directory to `MTCP\APPS\TCPAGENT` inside that tree. +3. Set `WATCOM` for Open Watcom and run `BUILD.BAT`. +4. Run `INSTALL.BAT`; it installs the executable and adds startup lines after + the existing packet-driver setup in `C:\FDAUTO.BAT`. + +The build uses mTCP's compact memory model and Open Watcom C++16. The agent is +therefore distributed under GPLv3 when linked with mTCP. + +## Protocol + +Legacy text commands remain for Pi tool compatibility: `PING`, `READ`, +`WRITE`, `LIST`, and `EXEC`. Fast staging uses binary framing: + +- `PUT \n` -> `OK\r\n` +- `GET \n` -> `DATA \r\n` +- `HASH \n` -> `STAT \r\n` + +Use `python tools/dos_stage.py --binary LOCAL=C:\\DOS\\PATH` for verified +staging. Verification reads only the length and FNV-1a hash, avoiding a slow +full-file return transfer. + +Use `.qemu/reset.ps1` to restart QEMU. It waits for an established TCP agent +and a successful PONG rather than sleeping for a fixed boot duration. diff --git a/tools/tcpagent/WPP.RSP b/tools/tcpagent/WPP.RSP new file mode 100644 index 0000000..08000f4 --- /dev/null +++ b/tools/tcpagent/WPP.RSP @@ -0,0 +1,11 @@ +-0 +-mc +-DCFG_H="tcpagent.cfg" +-oh +-os +-s +-zp2 +-zpw +-we +-i=..\..\TCPINC +-i=..\..\INCLUDE diff --git a/tools/tcpagent/tcpagent.cfg b/tools/tcpagent/tcpagent.cfg new file mode 100644 index 0000000..b962291 --- /dev/null +++ b/tools/tcpagent/tcpagent.cfg @@ -0,0 +1,12 @@ +#ifndef CONFIG_H +#define CONFIG_H +#define MTCP_PROGRAM_NAME "tcpagent" +#include "Global.Cfg" +#define COMPILE_ARP +#define IP_FRAGMENTS_ON +#define COMPILE_UDP +#define COMPILE_TCP +#define COMPILE_ICMP +#undef TCP_MAX_SOCKETS +#define TCP_MAX_SOCKETS (1) +#endif diff --git a/tools/tcpagent/tcpagent.cpp b/tools/tcpagent/tcpagent.cpp new file mode 100644 index 0000000..7e5db7e --- /dev/null +++ b/tools/tcpagent/tcpagent.cpp @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "trace.h" +#include "utils.h" +#include "packet.h" +#include "arp.h" +#include "udp.h" +#include "tcp.h" +#include "tcpsockm.h" + +#define LINE_SIZE 9000 +#define CHUNK_SIZE 4096 +#define SERVER_PORT 5558 +#define LOCAL_PORT 2058 +#define RECV_SIZE 12288 + +static char linebuf[LINE_SIZE]; +static unsigned char data[CHUNK_SIZE+1]; +static TcpSocket *socketp; +static volatile uint8_t stop_requested; +static FILE *put_file; +static unsigned long put_remaining; + +void __interrupt __far ctrl_break(void) { stop_requested=1; } +void __interrupt __far ctrl_c(void) { stop_requested=1; } + +static void drive(void) { + PACKET_PROCESS_MULT(5); + Arp::driveArp(); + Tcp::drivePackets(); +} + +static int send_all(const void *buffer, unsigned length) { + const uint8_t *p=(const uint8_t *)buffer; + unsigned sent=0; + int rc; + while(sentsend((uint8_t *)(p+sent),length-sent); + if(rc>0) sent+=(unsigned)rc; + else if(rc<0 || socketp->isRemoteClosed()) return -1; + } + return sent==length ? 0 : -1; +} + +static void write_text(const char *s) { send_all(s,strlen(s)); } +static int hexval(int c) { + if(c>='0'&&c<='9') return c-'0'; + if(c>='A'&&c<='F') return c-'A'+10; + if(c>='a'&&c<='f') return c-'a'+10; + return -1; +} +static int decode_hex(const char *src,unsigned char *dst,int cap) { + int n=0,hi,lo; + while(*src&&src[1]) { + if(n>=cap) return -1; + hi=hexval((unsigned char)src[0]); lo=hexval((unsigned char)src[1]); + if(hi<0||lo<0) return -1; + dst[n++]=(unsigned char)((hi<<4)|lo); src+=2; + } + return *src ? -1 : n; +} +static void write_hex(const unsigned char *src,unsigned count) { + static const char digits[]="0123456789ABCDEF"; + char out[256]; unsigned i,n; + while(count) { + n=count>sizeof(out)/2 ? sizeof(out)/2 : count; + for(i=0;i>4]; out[i*2+1]=digits[src[i]&15]; } + send_all(out,n*2); src+=n; count-=n; + } +} +static void ok_data(const unsigned char *src,unsigned count) { + write_text("OK "); write_hex(src,count); write_text("\r\n"); +} +static void error_text(const char *s) { + write_text("ERR "); write_hex((const unsigned char *)s,strlen(s)); write_text("\r\n"); +} +static int decode_path(const char *hex,char *path,int cap) { + int n=decode_hex(hex,(unsigned char *)path,cap-1); + if(n<0) return 0; path[n]='\0'; return 1; +} +static void command_read(char *args) { + char path[260],*off=strchr(args,' '); FILE *f; long pos; size_t count; int eof; + if(!off){error_text("READ requires path and offset");return;} *off++='\0'; + if(!decode_path(args,path,sizeof(path))){error_text("Invalid path encoding");return;} + pos=atol(off); f=fopen(path,"rb"); if(!f){error_text("Cannot open file");return;} + if(fseek(f,pos,SEEK_SET)){fclose(f);error_text("Cannot seek file");return;} + count=fread(data,1,CHUNK_SIZE,f); eof=count0) if(send_all(data,count)<0)break; + fclose(f); +} +static void command_hash(char *args) { + char path[260]; FILE *f; size_t count; unsigned i; unsigned long length=0,hash=2166136261UL; + if(!decode_path(args,path,sizeof(path))){error_text("Invalid path encoding");return;} + f=fopen(path,"rb"); if(!f){error_text("Cannot open file");return;} + while((count=fread(data,1,CHUNK_SIZE,f))>0){length+=(unsigned long)count;for(i=0;i=sizeof(output)) break; memcpy(output+used,entry,len); used+=(unsigned)len; } + rc=_dos_findnext(&found); + } + ok_data((unsigned char *)output,used); +} +static void command_exec(char *args) { + char command[700],shell[800]; const char *tmp="C:\\PIEXEC.TMP"; FILE *f; int n,code; size_t count; + n=decode_hex(args,(unsigned char *)command,sizeof(command)-1); if(n<0){error_text("Invalid command encoding");return;} command[n]='\0'; + sprintf(shell,"COMMAND.COM /C %s > %s",command,tmp); code=system(shell); + f=fopen(tmp,"rb"); count=f?fread(data,1,CHUNK_SIZE,f):0; if(f)fclose(f); remove(tmp); + sprintf(linebuf,"OK %d ",code); write_text(linebuf); write_hex(data,count); write_text("\r\n"); +} +static void process_line(char *line) { + char *cmd=line,*args=strchr(line,' '); if(args)*args++='\0';else args=cmd+strlen(cmd); + if(!strcmp(cmd,"PING"))ok_data((const unsigned char *)"PONG",4); + else if(!strcmp(cmd,"READ"))command_read(args); + else if(!strcmp(cmd,"WRITE"))command_write(args); + else if(!strcmp(cmd,"PUT"))command_put(args); + else if(!strcmp(cmd,"GET"))command_get(args); + else if(!strcmp(cmd,"HASH"))command_hash(args); + else if(!strcmp(cmd,"LIST"))command_list(args); + else if(!strcmp(cmd,"EXEC"))command_exec(args); + else if(!strcmp(cmd,"QUIT")){ok_data((const unsigned char *)"BYE",3);stop_requested=1;} + else { char message[96]; sprintf(message,"Unknown command: %.70s",cmd); error_text(message); } +} +static int connect_host(void) { + IpAddr_t host={10,0,2,2}; int8_t rc; + socketp=TcpSocketMgr::getSocket(); if(!socketp)return -1; + socketp->setRecvBuffer(RECV_SIZE); + rc=socketp->connect(LOCAL_PORT,host,SERVER_PORT,10000); + while(rc==0&&!socketp->isConnectComplete()&&!socketp->isRemoteClosed()&&!stop_requested)drive(); + if(socketp->isRemoteClosed()){TcpSocketMgr::freeSocket(socketp);socketp=0;return -1;} + return 0; +} +int main(void) { + int used,rc; uint16_t key; + if(Utils::parseEnv()!=0)return 2; + if(Utils::initStack(1,TCP_SOCKET_RING_SIZE,ctrl_break,ctrl_c))return 3; + cprintf("[TCPAGENT] Connecting to 10.0.2.2:%u\r\n",SERVER_PORT); + while(!stop_requested) { + if(connect_host()!=0){unsigned long spins=0;while(spins++<60000UL&&!stop_requested)drive();continue;} + cprintf("[TCPAGENT] Connected. Alt-X stops.\r\n"); write_text("TCPAGENT READY\r\n"); used=0; + while(!stop_requested&&!socketp->isRemoteClosed()) { + drive(); rc=socketp->recv((uint8_t *)data,CHUNK_SIZE); + if(rc<0)break; + for(int i=0;i>8)==45)stop_requested=1;} + } + socketp->close(); TcpSocketMgr::freeSocket(socketp); socketp=0; + } + Utils::endStack(); return 0; +}