feat: replace serial automation with resident TCP agent
This commit is contained in:
+59
-8
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dos.h>
|
||||
#include <conio.h>
|
||||
#include <bios.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 <hex DOS path> <byte length>\n<raw bytes>` -> `OK\r\n`
|
||||
- `GET <hex DOS path>\n` -> `DATA <length>\r\n<raw bytes>`
|
||||
- `HASH <hex DOS path>\n` -> `STAT <length> <FNV1A32>\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.
|
||||
@@ -0,0 +1,11 @@
|
||||
-0
|
||||
-mc
|
||||
-DCFG_H="tcpagent.cfg"
|
||||
-oh
|
||||
-os
|
||||
-s
|
||||
-zp2
|
||||
-zpw
|
||||
-we
|
||||
-i=..\..\TCPINC
|
||||
-i=..\..\INCLUDE
|
||||
@@ -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
|
||||
@@ -0,0 +1,205 @@
|
||||
#include <bios.h>
|
||||
#include <conio.h>
|
||||
#include <dos.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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(sent<length && !stop_requested) {
|
||||
drive();
|
||||
rc=socketp->send((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<n;++i) { out[i*2]=digits[src[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=count<CHUNK_SIZE; fclose(f);
|
||||
sprintf(linebuf,"OK %d ",eof); write_text(linebuf); write_hex(data,count); write_text("\r\n");
|
||||
}
|
||||
static void command_write(char *args) {
|
||||
char path[260],*mode=strchr(args,' '),*payload; FILE *f; int count;
|
||||
if(!mode){error_text("WRITE requires path, mode and data");return;} *mode++='\0';
|
||||
payload=strchr(mode,' '); 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;}
|
||||
f=fopen(path,mode[0]=='A'?"ab":"wb"); if(!f){error_text("Cannot write file");return;}
|
||||
if(count&&fwrite(data,1,count,f)!=(size_t)count){fclose(f);error_text("Short write");return;}
|
||||
fclose(f); ok_data((const unsigned char *)"",0);
|
||||
}
|
||||
static void command_put(char *args) {
|
||||
char path[260],*length_text=strchr(args,' ');
|
||||
if(!length_text){error_text("PUT requires path and length");return;}
|
||||
*length_text++='\0';
|
||||
if(!decode_path(args,path,sizeof(path))){error_text("Invalid path encoding");return;}
|
||||
put_remaining=strtoul(length_text,0,10);
|
||||
put_file=fopen(path,"wb");
|
||||
if(!put_file){put_remaining=0;error_text("Cannot write file");return;}
|
||||
if(!put_remaining){fclose(put_file);put_file=0;ok_data((const unsigned char *)"",0);}
|
||||
}
|
||||
static void command_get(char *args) {
|
||||
char path[260]; FILE *f; long length; size_t count;
|
||||
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;}
|
||||
fseek(f,0,SEEK_END); length=ftell(f); fseek(f,0,SEEK_SET);
|
||||
sprintf(linebuf,"DATA %ld\r\n",length); write_text(linebuf);
|
||||
while((count=fread(data,1,CHUNK_SIZE,f))>0) 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<count;++i){hash^=data[i];hash*=16777619UL;}}
|
||||
fclose(f); sprintf(linebuf,"STAT %lu %08lX\r\n",length,hash); write_text(linebuf);
|
||||
}
|
||||
static void command_list(char *args) {
|
||||
char path[260],pattern[300],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]!='\\') 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+(unsigned)len>=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<rc;++i) {
|
||||
int c=data[i];
|
||||
if(put_remaining) {
|
||||
unsigned available=(unsigned)(rc-i);
|
||||
unsigned take=put_remaining<available ? (unsigned)put_remaining : available;
|
||||
if(fwrite(data+i,1,take,put_file)!=(size_t)take){fclose(put_file);put_file=0;put_remaining=0;error_text("Short write");}
|
||||
else {
|
||||
put_remaining-=take; i+=(int)take-1;
|
||||
if(!put_remaining){fclose(put_file);put_file=0;ok_data((const unsigned char *)"",0);}
|
||||
}
|
||||
} else if(c=='\r'||c=='\n') {
|
||||
if(used){linebuf[used]='\0';process_line(linebuf);used=0;}
|
||||
} else if(used<LINE_SIZE-1) linebuf[used++]=(char)c;
|
||||
}
|
||||
if(_bios_keybrd(1)){key=_bios_keybrd(0);if((key&0xff)==3||(key>>8)==45)stop_requested=1;}
|
||||
}
|
||||
socketp->close(); TcpSocketMgr::freeSocket(socketp); socketp=0;
|
||||
}
|
||||
Utils::endStack(); return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user