Fix RFM protocol to match rfm.asm wire format#5
Open
DrPitre wants to merge 34 commits into
Open
Conversation
The Swift server's RFM handlers were designed for an extended protocol that rfm.asm never implemented. Align every handler with what the CoCo client actually sends and reads: - Open/Create: expect path#(1)+mode(1) then CR-terminated pathname; send 1-byte error code - Read/ReadLn: expect path#(1)+maxBytes(2); send count(1) then data; count=0 signals EOF - Seek: expect path#(1)+X(2)+U(2) for 32-bit position; send 1-byte error - Close: expect path#(1); send 1-byte error; close FileHandle and remove from path table - GetStat: expect path#(1)+SS.code(1); send nothing (rfm.asm handlers are stubs that don't read a response) - SetStat: consume nothing (rfm.asm sendit sends only the sub-opcode); send nothing - Write/WritLn: expect path#(1)+count(2)+data; send nothing - MakDir/Delete: consume nothing (rfm.asm sendit sends only sub-opcode) Replace single rfmPathDescriptor with rfmPaths:[Int:RFMPathDescriptor] keyed by path number to support multiple simultaneously open paths. Add rfmRootPath property (defaults to home directory) replacing the hardcoded /Users/boisy prefix in openLocalFile. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Restore processID and pathDescriptorAddress fields in RFMPathDescriptor - Add shouldCreate flag; OPRFMCREATE creates file if missing, OPRFMOPEN returns E$PNNF (216); both share rfmOpen() helper - Strip bit 7 from pathname bytes (OS-9 high-bit terminates last char) - Respect mode byte: open forUpdating when write (0x02) or update (0x08) bits are set - Guard against path traversal: reject resolved paths outside rfmRootPath - Return E$PNNF for empty pathnames - Add --rfm-root CLI option (defaults to home directory) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DriveWireTCPServerDriver listens for an incoming TCP connection using Network framework's NWListener. When MAME is launched with -bitb socket.127.0.0.1:<port> it connects to this server and the DriveWire host handles the session. Usage: drivewire-cli --tcp-port 65400 --rfm-root ~/rfm-test --disk0 image.dsk --port (serial) and --tcp-port are mutually exclusive; one is required. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove dead do/catch blocks (nothing inside can throw) - Remove unused offset parameter; filePosition from the struct is always used - Fix off-by-one in readLineFromFile: <= maximumCount allowed one extra byte - readFromFile now uses a Data slice instead of a byte-by-byte loop - readLineFromFile uses data.append() instead of allocating a new Data per byte Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The watchdog fires on wall-clock time, which can desync the state machine when MAME runs faster than real-time: a legitimate multi-byte transaction can straddle the 500ms window and get reset mid-packet. Invalidate the watchdog as soon as bytes are consumed so it only fires during true idle periods (no bytes arriving at all). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…disks start() was creating a new DriveWireHost, throwing away any virtual disks inserted into d.host before calling start(). Change to just set the delegate on the existing host so disks survive the call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The default init() left processor=nil, causing a fatal unwrap when send() was called on a host created without a delegate. Now both init() paths call setupTransactions() so the state machine is always ready. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Protocol desync can cause sector data bytes ($D6 $02) to be misread as OP_VFM + DW.open, producing a pathname of null bytes that confuses FileManager. Return E$PNNF (216) immediately for null-containing paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
String(bytes:data:encoding:.ascii)! crashes when data contains non-ASCII bytes (null bytes, control chars) from protocol desync during NitrOS-9 boot. Replace with ?? "" fallback in both OP_NAMEOBJ_MOUNT2 instances. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 8 OP_SERxxx handlers were consuming only 1 byte (the opcode) instead of the full packet. This caused protocol framing desync: leftover channel#, stat_code, and data bytes were misinterpreted as new opcodes, eventually producing spurious OP_VFM ($D6) triggers and RFM errors. Correct byte counts: SERINIT/SERTERM/SERREAD/SERREADM: 2 (opcode + channel#) SERWRITE/SERGETSTAT/SERSETSTAT: 3 (opcode + channel# + byte) SERWRITEM: variable 3 + count (opcode + channel# + count + data) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add writeToFile() to RFMPathDescriptor: seeks to filePosition, writes data via FileHandle, keeps fileContents in sync for subsequent reads. OPRFMWRITE: writes raw bytes at current position. OPRFMWRITLN: same but translates CR→LF (OS-9 uses CR, host uses LF). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
try seek(toOffset:) was silently catching exceptions in do/catch, causing writes to fail with no indication. Switch to seek(toFileOffset:) which doesn't throw so failures are not swallowed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OS-9 I$Dup2 maps parent's path# X to child's path# 1 without calling the file manager, so the server never gets an OPEN for path# 1. When shell redirects (mdir>/y0/xx), mdir writes to path# 1 but rfmPaths only has path# X. Fall back to the single open writable descriptor when the given path# isn't found. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OS-9 shell redirect: CREATE → CLOSE (shell releases ref) → WRITELNs. If the path was opened via CREATE, treat the first CLOSE as the shell handing off to the child writer — keep the descriptor alive. Second CLOSE does the real cleanup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OS-9 pads string buffers with $FF bytes after the CR terminator. WriteLn data is fixed-length (80 bytes) with $FF fill after the actual content. Break at the first $FF to avoid writing garbage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The seek+fileContents approach caused zeros to appear between written chunks. FileHandle already tracks position after each write; just call file.write() sequentially without seeking. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Receives same packet format as rfmOpen (path# + processID + mode + pathname + CR). Creates the directory at rfmRootPath + pathname using FileManager.createDirectory(withIntermediateDirectories: true). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mdir packs data like [name$0D][rest_of_info$0D][$00 padding]. The previous break-at-first-CR discarded everything after the first CR. Now: strip trailing $00/$FF padding, convert ALL $0D→$0A, preserving complete multi-part line content. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fers mdir's writeBUF appends CR then resets bufptr and sends all 80 bytes. Everything after the first CR is leftover from previous writeBUF calls. Break at first CR (converting to LF), discard the rest. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OPRFMSEEK sets filePosition in the descriptor but writeToFile was just appending at the FileHandle's current position. Now seeks to filePosition first, so a SEEK to 0 followed by WRITE rewrites from the beginning. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same packet format as rfmOpen (path# + processID + mode + pathname + CR). Uses FileManager.removeItem which handles both files and directories. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In OS-9, del only removes files. deldir clears the DIR bit first then calls I$Delete. Return E$FNA (214) if the target is a directory so the OS-9 del command behaves correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OPRFMCHGDIR was calling OPRFMOPEN which waited for more bytes, consuming the following GETSTT packet as path data and corrupting state. Fixed to reset immediately since chgdir uses sendit (no path data). OPRFMDELETE now allows deleting empty directories (for deldir path) and returns E$DNE (215) for non-empty ones. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cking - Track per-process CWD; resolve relative pathnames using pid then ppid. - Expand OS-9 multi-dot path components (N dots = N-1 parent refs). - Synthesize OS-9 directory entries with stable LSNs for dir opens. - Synthesize FD sectors from host file attributes for SS.FD/SS.FDInf and intercept matching OP_READEX LSNs to serve the same FD bytes. - Handle SS.Size, SS.Pos, SS.EOF in OPRFMGETSTT. - READ/READLN now use 2-byte length (allowing >255-byte transfers). - OPRFMOPEN/CREATE/MAKDIR/CHGDIR/DELETE now receive ppid (4-byte header); CHGDIR actually receives and resolves a path. - Reset RFM state on guest disconnect, OP_INIT, and OP_RESET so a reconnecting client starts clean. - Remove pendingClose/shouldCreate descriptor fields; pass shouldCreate through openLocalFile() instead. - Add drivewire-cli shared scheme. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
hathaway3
added a commit
to hathaway3/DriveWire
that referenced
this pull request
Jun 15, 2026
…re#5) init_drives overwrote self.drives[i] in place, so reloading config discarded any unflushed dirty sectors of the previous drive and leaked its open file handle. Rework init_drives to iterate all slots, keep an unchanged live mount (preserving its dirty buffers), flush+close a drive whose slot is cleared, and route replacements through swap_drive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hathaway3
added a commit
to hathaway3/DriveWire
that referenced
this pull request
Jun 15, 2026
Move DrPitre#1/DrPitre#5/DrPitre#6/DrPitre#7/#8/#9/#10/#11/#12 and F2 to Fixed with commit refs, note DrPitre#2/DrPitre#3 pending on-device verification, and record why DrPitre#4 (server listen mode) and feature gaps F1/F3/F5 are deferred to an on-device pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OS-9 maintains distinct data and execution directories per process. When the EXEC bit (0x04) is set in the mode byte of ChgDir, Open, Create, MakeDir, or Delete, resolve and store the path against the execution directory map (rfmCurrentExecDir) rather than the data directory map (rfmCurrentDir). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Improve legibility of bordered controls on the dark dashboard by tinting the Copy, Disconnect, Serial Port, and Baud Rate controls white, and give Eject a readable tint in both enabled and disabled states. Also restrict the Mount/Replace open panel to .dsk and .img files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Populate the AppIcon asset with all macOS sizes (16/32/128/256/512 at 1x and 2x) generated from the ObjC project's DriveWire Icon.icns. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a formal spec section for OP_RFM ($D6), covering the envelope, path-descriptor model, dual data/exec directory contexts, error codes, and byte-level request/response layouts for all 13 sub-opcodes, plus the synthesized directory listing and file descriptor formats. Derived from the Swift host implementation, currently the only one that implements RFM.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Full RFM (Remote File Manager) implementation — CoCo 3 NitrOS-9 can now read and write files on the Mac host via the DriveWire Becker port in MAME.
What works
list /y0/file— read files from hostPRINT #1in Basic09 — write text to host filesmdir>/y0/output— redirect command output to host files>redirect for any NitrOS-9 commandProtocol fixes
rfm.asmwire format (open, read, readln, seek, close, getstat, setstat, write, writeln)rfmPathsdictionary replaces single descriptor — multiple simultaneous paths workrfmRootPath(--rfm-root) sets the host base directory;/y0/filemaps torfmRootPath/y0/fileWrite correctness
file.write()— no seek/position tracking that caused zero-padding corruption$FFpadding strip for OS-9 string buffersshouldCreatepaths survive their first close (OS-9 shell redirect pattern: CREATE→CLOSE→WRITELNs)Server crash fixes
DriveWireHost.init()callssetupTransactions()soprocessoris never nilstart()no longer discards pre-inserted virtual disksTCP server (
--tcp-port)DriveWireTCPServerDriverfor MAME's Becker port socket connectiondrivewire-cligains--tcp-portand--rfm-rootTest plan
list /y0/filenamereads host filesPRINT #1,"text"in Basic09 writes to host filesmdir>/y0/outputredirects mdir output to host file🤖 Generated with Claude Code