Skip to content

MacTCP driver implementation - #66

Open
mishan wants to merge 8 commits into
autc04:masterfrom
mishan:mactcp
Open

MacTCP driver implementation#66
mishan wants to merge 8 commits into
autc04:masterfrom
mishan:mactcp

Conversation

@mishan

@mishan mishan commented Aug 2, 2026

Copy link
Copy Markdown

(Has commits from PR #64 so it would build for me)

  • Move callcomp out of serial.cpp so other drivers can use it
  • Add a native MacTCP (.IPP) driver
  • Add runtime tests for the MacTCP driver
  • Add a dual-mode MacTCP ABI conformance test
  • Pin the MacTCP ABI to Apple's actual layouts
  • Basic HTTP fetch test

@mishan mishan changed the title Mactcp MacTCP driver implementation Aug 2, 2026
mishan added 7 commits August 3, 2026 15:39
Both loops walk LM(UTableBase)[0..UnitNtryCnt) and dereference each
entry without checking it.  The unit table is sparse -- native drivers
install themselves at fixed unit numbers, leaving the slots between
them null -- so any empty slot below the loop bound is a null deref.

This is latent rather than live: LM(UnitNtryCnt) is set to 0 in
init.cpp and never updated by anything, so neither loop body has ever
executed and no driver has ever been sent accRun.  How UnitNtryCnt
ought to be maintained is a separate Device Manager question; guard
the dereference first so that raising it is safe.
callcomp re-enters emulated code to run a driver call's completion
routine (A0 = param block, A1 = the routine, D0 = result).  It was
defined in serial.cpp at global scope, where no other translation unit
could reach it, even though it implements a Device Manager convention
rather than anything serial-specific.

Move the definition to device.cpp and declare it in rsys/device.h.
No behaviour change; serial.cpp picks it up via using namespace
Executor.
MacTCP is reached by applications through PBOpen("\p.IPP") plus
PBControl with a csCode, so it needs no trap work at all: the whole
feature is a device driver plus the type definitions that just landed
in multiversal.

Phase 1, modelled on serial.cpp: driver registration at .IPP / refnum
-48, csCode dispatch, an opaque stream-cookie table, and synchronous
TCP client operations over non-blocking POSIX sockets -- ipctlGetAddr,
TCPCreate, TCPActiveOpen, TCPSend (writev over the WDS gather list),
TCPRcv, TCPClose (half-close), TCPAbort (RST via SO_LINGER 0),
TCPStatus and TCPRelease.  Guest memory is big-endian, so htonl/htons
at the sockaddr boundary is both necessary and sufficient.

Operations execute synchronously and then complete, the same idiom
serial.cpp uses.  Phase 2 replaces the bodies with a pending-op queue
drained from an accRun pump -- which will first require sorting out
LM(UnitNtryCnt), since accRun is currently never delivered to anyone.
Passive open, NoCopyRcv, UDP, the ASR and the DNR are all stubbed with
notes rather than half-implemented.

The documented parameter block offsets are asserted at build time, so
a disagreement between the generated headers and the MacTCP ABI fails
the build instead of corrupting guest memory at run time.

Also bumps the multiversal submodule, which carries the MacTCP
definitions along with the two generator fixes on that branch.
Until now the driver had only been verified statically -- it compiled,
linked and registered, but no part of it had ever executed.

main_executor.cpp already stands up memory, the 68k emulator, traps
and low memory in a native gtest binary, which is enough to drive the
driver through the real OpenDriver/PBControl path with no 68k
application involved.  These tests use that: name lookup and the unit
slot, ipctlGetAddr, rejection of undersized receive buffers, unknown
csCodes and unknown streams, and a full TCPCreate / TCPActiveOpen /
TCPSend / TCPRcv / TCPStatus / TCPClose / TCPRelease round trip.

The round trip is hermetic: the test process itself listens on a
loopback socket and the driver connects back to it, so nothing depends
on the host having a network, a route or a resolver.  The send goes
through a multi-entry WDS so the gather path is covered rather than
just the single-buffer case.  ipctlGetAddr skips rather than fails on
a host with no non-loopback IPv4 interface, which is the normal
situation in a container.

Native-only for now.  The dual-mode offsetof/sizeof comparison against
Apple's Universal Interfaces belongs in TEST_SOURCES once the Retro68
side can generate MacTCP.h, and is what will settle the VERIFY markers
still in MacTCP.yaml.
Checks the size and every byte offset of the parameter blocks an
application shares with the .IPP driver, both at compile time and at
runtime, plus the result codes, csCodes and connection states.

The two build modes answer two different questions.  Natively it
checks that multiversal's generated header really lays out the way
defs/MacTCP.yaml says: the YAML's size: assertions only pin totals, so
a mac68k alignment mistake in the middle of a struct that preserves
the total would otherwise go unnoticed.  Under Retro68, compiled
against Apple's Universal Interfaces, it checks the numbers taken from
the MacTCP Programmer's Guide against Apple's own -- which is the
ground truth that settles the VERIFY markers still in the YAML.

The expected values are written out here rather than derived from the
YAML, so a transcription error in the YAML surfaces as a failure
instead of being mirrored.

DumpLayout prints the whole layout in a diffable form for comparing
the two sides directly, since an assertion tells you something is
wrong but not what the other side thinks it should be.

The file compiles to nothing where there is no MacTCP.h, so it does
not break Retro68 builds without Apple's interfaces installed.
Picks up the multiversal correction to TCPReceivePB and TCPStatusPB,
and tightens the assertions on both sides to match.

The driver's guard rails now cover the fields that were wrong rather
than only the ones that happened to be right: TCPReceivePB's rcvBuff,
markFlag and urgentFlag, plus the sizes of TCPStatusPB and TCPiopb,
which changed from 66 and 98 to 70 and 102.

Worth noting for anyone reading the earlier test results: the loopback
round trip passed both before and after this fix.  It writes the
parameter block through the same header the driver reads it through,
so a wrong offset is invisible to it -- both sides are wrong in the
same place.  Only a comparison against an independent definition of
the ABI could catch this, which is what the dual-mode test is for.
A real HTTP/1.0 request goes out through TCPSend and a real response
comes back: status line, headers, and a body larger than the
application's receive buffer, so it takes a dozen TCPRcv calls to
drain and the server's hangup is what ends the entity.  That last part
matters -- connectionClosing is the normal end of an HTTP/1.0
response, not an error, and an application has to treat it that way.

The server is inside the test process rather than out on the internet.
MacTCP has no resolver yet, so a real host would mean a hardcoded IP
address that rots; a test needing egress cannot run in CI or offline;
and the driver cannot tell the difference, since routing and DNS are
not parts of it.  Setting EXECUTOR_MACTCP_TEST_ADDR points the same
exchange at a real server for anyone who wants to watch it work.
@mishan
mishan force-pushed the mactcp branch 3 times, most recently from fceaac8 to 159a2e3 Compare August 6, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant