feat: replace serial automation with resident TCP agent
This commit is contained in:
@@ -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.'
|
||||
+4
-11
@@ -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
|
||||
|
||||
@@ -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);
|
||||
@@ -1,240 +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
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user