Add developer package: sources, build/test tools, HANDOFF
Browse files- README.md +4 -0
- dev/HANDOFF.md +128 -0
- dev/dosmqtt-dev.zip +3 -0
README.md
CHANGED
|
@@ -41,3 +41,7 @@ So the DOS side is genuine TCP/IP; only the last hop (browser → broker) is a W
|
|
| 41 |
## Sources and licenses
|
| 42 |
|
| 43 |
See `LICENSE`, `NOTICE.md` and `src/` (DOS program sources, build scripts, boot-disk scripts). GPL-3.0-or-later.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
## Sources and licenses
|
| 42 |
|
| 43 |
See `LICENSE`, `NOTICE.md` and `src/` (DOS program sources, build scripts, boot-disk scripts). GPL-3.0-or-later.
|
| 44 |
+
|
| 45 |
+
## Developer package
|
| 46 |
+
|
| 47 |
+
`dev/dosmqtt-dev.zip` holds the sources, build scripts, tests and the developer handover (`dev/HANDOFF.md`). It also contains the mTCP source archive that A.EXE / B.EXE are linked against. Rebuild everything with `tools/build_all.sh`.
|
dev/HANDOFF.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HANDOFF — DOS MQTT LAB
|
| 2 |
+
|
| 3 |
+
Read this first. Everything below was checked against the real running page, not assumed.
|
| 4 |
+
Project owner language: Traditional Chinese. Replies: short, factual, no emojis, say limitations honestly.
|
| 5 |
+
|
| 6 |
+
## 1. What it is (and what it is not)
|
| 7 |
+
|
| 8 |
+
* Space: `HelloSun/dosmqtt` (static, public). Live: https://hellosun-dosmqtt.static.hf.space/
|
| 9 |
+
* Two **FreeDOS 1.4** machines ("A" listener, "B" sender) run side by side in the page on the **v86** x86 emulator.
|
| 10 |
+
* Inside each DOS: Crynwr **NE2000 packet driver** -> mTCP **DHCP.EXE** (gets 192.168.86.100) ->
|
| 11 |
+
**A.EXE / B.EXE**, real DOS programs (Open Watcom + mTCP library) doing DNS, TCP and **MQTT 3.1.1** (QoS 0) themselves.
|
| 12 |
+
* `A.EXE`: connect, subscribe, print `[hh:mm:ss] A rx: <msg>`, 1 s later print `[hh:mm:ss] A echo: <msg>`.
|
| 13 |
+
* `B.EXE`: run once: connect, publish `Hello from DOS B at hh:mm:ss`, disconnect, exit. The page types `B` every 5 s
|
| 14 |
+
(DOS has no scheduler). The page types `A` once.
|
| 15 |
+
* **Honest limits**: only the last hop is not TCP: the browser cannot open raw TCP, and a static Space has no server,
|
| 16 |
+
so `site/bridge.js` relays the guest's TCP streams on **port 1883** to a public broker's **MQTT-over-WSS** endpoint.
|
| 17 |
+
MQTT is identical on both transports, so the bytes DOS writes are exactly what the broker receives (verified with an
|
| 18 |
+
independent raw-TCP subscriber, see section 5). "Background" A is a second VM because DOS is single-tasking.
|
| 19 |
+
js-dos 6.22 (an earlier version of this Space) has no TCP/IP at all, which is why v86 is used now.
|
| 20 |
+
|
| 21 |
+
## 2. Data path
|
| 22 |
+
|
| 23 |
+
```
|
| 24 |
+
A.EXE/B.EXE (mTCP TCP/IP stack, DOS)
|
| 25 |
+
-> packet driver NE2000.COM (INT 0x60, I/O 0xC000, IRQ 10)
|
| 26 |
+
-> v86 emulated NE2000 (PCI) DHCP + DNS answered by v86 itself
|
| 27 |
+
-> v86 "wisp" network adapter (terminates guest TCP) DNS = DoH to cloudflare-dns.com (needs internet)
|
| 28 |
+
-> WebSocket('wisp://mqttbridge.invalid/?m=A|B') <- monkeypatched in bridge.js (Proxy on window.WebSocket)
|
| 29 |
+
-> FakeWispServer (bridge.js): per TCP stream opens
|
| 30 |
+
new WebSocket('wss://<broker>', 'mqtt') (port must be 1883, else stream refused)
|
| 31 |
+
-> public broker
|
| 32 |
+
```
|
| 33 |
+
* WISP frames used: 1 CONNECT, 2 DATA, 3 CONTINUE (flow-control credit, we hand out 128 on stream 0 and per DATA), 4 CLOSE.
|
| 34 |
+
* Data the guest sends before the WSS is open is queued; broker->guest data is cut into 1 KB frames.
|
| 35 |
+
* Both VMs use the same IP; they are isolated (wisp backend isolates guests).
|
| 36 |
+
|
| 37 |
+
## 3. Layout and how to build
|
| 38 |
+
|
| 39 |
+
See `README.md` for the tree. Build chain (`tools/build_all.sh`, a few seconds once deps are cached):
|
| 40 |
+
|
| 41 |
+
1. `tools/fetch_deps.sh` downloads v86 0.5.472 (npm), SeaBIOS/VGABIOS, FreeDOS 1.4 floppy, Crynwr NE2000.COM,
|
| 42 |
+
mTCP 2026-10-10 (bin + src), Open Watcom **1.9** into `deps/`, and checks pinned SHA-256s (edit the hashes on purpose when upgrading).
|
| 43 |
+
2. `src/build.sh` -> `build/A.EXE`, `build/B.EXE` (16-bit DOS compact model, `-0 -mc -oh -os -s -zp2 -zpw -we`).
|
| 44 |
+
mTCP sources are mirrored in lowercase first (Linux is case sensitive, mTCP is not).
|
| 45 |
+
3. `src/make_image.sh` -> `build/dosmqtt.img` (FreeDOS boot floppy, strips installer files, adds boot files + NE2000.COM + DHCP.EXE + A/B.EXE; ~1 MB stays free).
|
| 46 |
+
4. `src/build_assets.py` -> `dist/assets.b64.js` (v86.wasm, BIOSes, floppy as **gzip+base64 text**) and `dist/libv86.js`.
|
| 47 |
+
Reason: HF static Spaces answer binary files with a CORS-less redirect; text files are served directly.
|
| 48 |
+
5. `build_all.sh` also copies `site/*` and `src/` into `dist/`. **`dist/` is exactly what gets uploaded to the Space.**
|
| 49 |
+
|
| 50 |
+
Rebuilt EXEs equal the deployed ones except 1 byte (mTCP embeds the build date `Oct 24 2026` vs `Oct 25 2026`).
|
| 51 |
+
|
| 52 |
+
`site/app.js` flow: unpack assets (`DecompressionStream`) -> start two `V86` instances -> poll the DOS text screens until
|
| 53 |
+
`TCP/IP is up` appears (printed by `FDAUTO.BAT`) -> enable the start button -> on start: type `SET MQTT_HOST/PORT/TOPIC`
|
| 54 |
+
in both machines, type `A` in machine A, wait for `subscribed to` on A's screen, then every 5 s type `B` in machine B
|
| 55 |
+
(skipped and logged if B's prompt `X:\>` is not the last screen line). Results are read **from the DOS screens**
|
| 56 |
+
(`emu.screen_adapter.get_text_screen()`), regex on `A rx:` / `A echo:` / `B sent:`; the log also shows bridge events.
|
| 57 |
+
A and B read host/port/topic from the DOS environment (`MQTT_HOST`, `MQTT_PORT` default 1883, `MQTT_TOPIC`) or from the command line `A <broker> <port> <topic>`.
|
| 58 |
+
|
| 59 |
+
## 4. Hard-won facts (do not rediscover these)
|
| 60 |
+
|
| 61 |
+
* **NE2000 is NOT at 0x300.** SeaBIOS re-maps the PCI card: I/O **0xC000, IRQ 10**. Load with `NE2000 0x60 10 0xC000`.
|
| 62 |
+
(Read from `emu.v86.cpu.devices.pci.device_spaces`: BAR0 `c001`, dword 0x3c `10a`.) Packet interrupt 0x60 must match `PACKETINT 0x60` in `MTCP.CFG`.
|
| 63 |
+
* DHCP.EXE rewrites `A:\MTCP.CFG` on the floppy (writable). `MTCPCFG=A:\MTCP.CFG` is set in `FDAUTO.BAT`.
|
| 64 |
+
* **Use Open Watcom 1.9, not 2.0 beta.** EXEs from 2.0 beta end with `End: heap is corrupted!` (mTCP's `_heapchk`) even for an empty program
|
| 65 |
+
(`_heapchk` returned 3). With 1.9 it returns 0. `-we` (warnings are errors) bites on struct padding (W880): order fields so no padding is needed.
|
| 66 |
+
* mTCP needs `#include "global.cfg"` lowercase in `a.cfg/b.cfg` on Linux; the configs are copies of mTCP's `SAMPLE.CFG` with `TCP_MAX_SOCKETS 2`.
|
| 67 |
+
* **Typing into DOS:** `emu.keyboard_send_text(text, 25)` (25 ms per char). Without the delay FreeCOM drops characters
|
| 68 |
+
(`SET PATH=...` came out as `FRESET MTCPCFG=...`). Give each command >= 250 ms before the next.
|
| 69 |
+
* **Screen scaling:** in text mode v86 draws a DOM text layer (80x25, 14 px monospace), not the canvas. `screen_set_scale()` and
|
| 70 |
+
`use_graphical_text` turned the screen black/cropped. `fitScreen()` in `app.js` scales whichever layer is visible with a CSS `transform`.
|
| 71 |
+
* B uses a pseudo-random local TCP port per run; DNS answers rotate between several A records (that is normal).
|
| 72 |
+
* DOS clock is **UTC**; mTCP timer granularity is the 18.2 Hz BIOS tick (55 ms), so "1 s" echo reads 0.8-1.2 s from the host.
|
| 73 |
+
* **Broker quality differs a lot** (CONNECT -> CONNACK over WSS from headless Chromium in this sandbox; 12 attempts per broker, HiveMQ also 30):
|
| 74 |
+
`broker.emqx.io:8084` 12/12 fast (~560 ms); `test.mosquitto.org:8081` 12/12 fast (~390 ms);
|
| 75 |
+
`broker.hivemq.com:8884` only 7/12 (and 19/30 in another run) fast, the rest took ~10 s or never answered (an idle WS without CONNECT is closed at ~10 s);
|
| 76 |
+
`mqtt.eclipseprojects.io/mqtt` 0/12. Default is EMQX. B's 10 s CONNACK timeout then shows `MQTT: CONNECT refused or timed out`.
|
| 77 |
+
* A sends PINGREQ every 30 s (keepalive 60 s) and keeps <= 8 pending echoes. A has no reconnect: restart the VMs ("停止並重新開機").
|
| 78 |
+
* The "stop" button can't send Ctrl-Break; it reboots both VMs (`vm.restart()`), then waits for `TCP/IP is up` again.
|
| 79 |
+
|
| 80 |
+
## 5. Testing (Playwright + independent MQTT peer)
|
| 81 |
+
|
| 82 |
+
`pip install playwright && playwright install chromium`; Chromium flags `--no-sandbox --disable-gpu --disable-dev-shm-usage`.
|
| 83 |
+
* `tests/serve_harness.sh`: bare v86 page on :8801 (verbose bridge logging) for manual DOS poking via `drive2.py`
|
| 84 |
+
(`session([(cmd, wait_s), ...])` prints the DOS screen + bridge log).
|
| 85 |
+
* `python3 -m http.server 8802 --directory dist`, then
|
| 86 |
+
`python3 tests/full.py http://127.0.0.1:8802/ <topic> <seconds> <shot.png>` runs the whole page (also works on the live URL).
|
| 87 |
+
* **Always cross-check with something that is not the page**: `python3 tests/sub.py <topic> <secs> broker.emqx.io` (raw TCP 1883 subscriber)
|
| 88 |
+
must print every `Hello from DOS B at ...`; `python3 tests/pub.py <topic> <delay> <n>` publishes to test A on its own.
|
| 89 |
+
Use the same topic in the page (`full.py` fills it) and the same broker host (default EMQX).
|
| 90 |
+
* Results when this was written: local 8/8 matched, 8 messages seen by the independent subscriber; **live URL** 9 B runs, 9 sent, A received 9 and echoed 8
|
| 91 |
+
(the 9th echo was still within its 1 s delay when sampled); the independent subscriber saw all 9.
|
| 92 |
+
* Use `kill <pid>`; `pkill -f <name>` kills your own shell.
|
| 93 |
+
|
| 94 |
+
## 6. Deploy rules (the owner was angry about breakage before)
|
| 95 |
+
|
| 96 |
+
1. **Only write to `HelloSun/dosmqtt`.** Never touch `HelloSun/c_dos`, `flare-gamet2` or any other Space (reading `c_dos` for reference is fine).
|
| 97 |
+
2. **Never delete remote files.** Upload with `HfApi.create_commit` + `CommitOperationAdd` only; compare `list_repo_files` before/after and report `deleted: []`.
|
| 98 |
+
3. Account name is `HelloSun` (case matters: `hellosun/...` gave 403 "no rights to create a space"). Auth: read the token from
|
| 99 |
+
`/root/.cache/huggingface/token`. Env `HF_TOKEN` is invalid. A token was once pasted into chat; it is exposed and
|
| 100 |
+
lacks create rights: advise revoking it. **Never write a token into any file.** `hf` CLI is broken here (typer/click mismatch): use the Python API.
|
| 101 |
+
4. After upload HF needs ~1 min to publish; verify with `curl` for a new symbol and then run `tests/full.py` on the live URL.
|
| 102 |
+
|
| 103 |
+
```python
|
| 104 |
+
from huggingface_hub import HfApi, CommitOperationAdd
|
| 105 |
+
from pathlib import Path
|
| 106 |
+
api = HfApi(token=Path("/root/.cache/huggingface/token").read_text().strip()); repo = "HelloSun/dosmqtt"
|
| 107 |
+
before = set(api.list_repo_files(repo, repo_type="space"))
|
| 108 |
+
ops = [CommitOperationAdd(path_in_repo=str(p.relative_to(root)), path_or_fileobj=str(p)) for p in files]
|
| 109 |
+
api.create_commit(repo_id=repo, repo_type="space", operations=ops, commit_message="...")
|
| 110 |
+
assert not (before - set(api.list_repo_files(repo, repo_type="space")))
|
| 111 |
+
```
|
| 112 |
+
Space file set: web files + `assets.b64.js` + `libv86.js` + `src/` + licenses, and `dev/` (this package, `dev/dosmqtt-dev.zip`, `dev/HANDOFF.md`).
|
| 113 |
+
Unused leftovers from the old js-dos version, **deliberately not deleted**: `js-dos.js`, `emu.b64.js`, `dos.b64.js`, `LICENSE-js-dos.txt`.
|
| 114 |
+
|
| 115 |
+
## 7. Licensing
|
| 116 |
+
|
| 117 |
+
A.EXE/B.EXE link mTCP (GPL-3.0-or-later), so our DOS sources, scripts and web code are GPL-3.0-or-later and the mTCP source archive
|
| 118 |
+
is shipped in `third_party/`. FreeDOS (GPL-2.0), v86 (BSD-2-Clause), SeaBIOS (LGPL-3.0) and the Crynwr packet driver
|
| 119 |
+
(source must stay available: Crynwr 11.x sources are at https://archive.org/details/PKTD11A_ZIP) are listed in `site/NOTICE.md`.
|
| 120 |
+
Open Watcom is only a build tool and is not redistributed.
|
| 121 |
+
|
| 122 |
+
## 8. Known limits / ideas
|
| 123 |
+
|
| 124 |
+
* QoS 0 only, no TLS and no auth in the DOS clients (TLS is done by the browser's WSS). Topic/payload are small (payload <= 95 chars in A).
|
| 125 |
+
* B is started by the page typing `B`; if the previous run is still going the tick is skipped (log shows it). A real in-DOS loop would need a `B.EXE -loop` mode.
|
| 126 |
+
* Only port 1883 is bridged. Other ports are refused on purpose.
|
| 127 |
+
* The page needs `DecompressionStream`, WebAssembly and internet (DoH + broker). No offline mode.
|
| 128 |
+
* Ideas: reconnect in A, in-DOS 5 s loop for B, MQTT QoS 1, mobile on-screen keyboard, a selectable second bridge (own WISP server) for brokers without WSS.
|
dev/dosmqtt-dev.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:16f49d5ad27b3cf0304a1375ae38ec7d0cb4def1f1cd1b9d22c7dc95c3ca02ab
|
| 3 |
+
size 893923
|