FujiNet Network Protocol Handbook - FujiNetWIFI/fujinet-firmware GitHub Wiki
Every protocol the FujiNet N: device speaks β file servers, web
services, sockets, shells, mail, and calendars β with the complete
devicespec grammar, every value and error code, and worked examples for
each. With chapters for Atari BASIC, Applesoft BASIC, ADAM SmartBASIC,
C with fujinet-lib, IntyBASIC, and FujiNet NOS.
This is the GitHub-wiki edition of the print handbook (
fujinet-network-protocol-handbook.pdf). The two are kept in sync by hand. Every scheme string, aux value, command byte, constant, and error code is transcribed from the live sources:fujinet-firmwarelib/network-protocol(commitsc026d9a12+07e53c764, Sep 2026),fujinet-libv4.11.2,fujinet-nhandler,smartbasic-1.x, and the programmer's guides infujinet-manuals.
- Part I β The Network Device Model: Introduction Β· The devicespec Β· Open modes and translation Β· Channel modes Β· Status and errors Β· Special commands
- Part II β Filesystem Protocols: The filesystem model Β· TNFS Β· SD Β· SMB Β· NFS Β· FTP Β· SFTP Β· HTTP and HTTPS Β· S3 Β· GDRIVE Β· ONEDRIVE
- Part III β Stream Protocols: TCP Β· UDP Β· TELNET Β· SSH Β· WS and WSS
- Part IV β Session Utilities: SSH.KEYGEN Β· SSH.COPYID Β· CLIPBOARD Β· CPM Β· TEST
- Part V β Mail and Calendar: The mailbox model Β· GMAIL Β· IMAPS Β· The calendar model Β· GCAL Β· ICAL / WEBCAL / ICALH
- Part VI β From Your Machine: Atari BASIC Β· Applesoft BASIC Β· ADAM SmartBASIC 1.x Β· C with fujinet-lib Β· IntyBASIC Β· NOS
- Appendices: Capability matrix Β· Scheme quick reference Β· Implementation notes Β· The programmer's guides
To your computer, the FujiNet's network device is one more peripheral: open it with a name, read and write like a file, close it. The name is a devicespec:
N:HTTPS://fujinet.online/hello.txt
Everything before the first colon addresses a device on your machine's bus; everything after is a URL whose scheme selects a protocol adapter in the firmware β 25 adapters answering to 28 scheme strings.
Every protocol is driven by the same four beats:
- Open β devicespec + access mode (aux1) + translation mode (aux2).
- Status β bytes waiting, connection state, error code.
- Read / write β move bytes.
- Close β flush, tear down, free.
The adapter lives in the FujiNet, not your computer: the ESP32 (or fujinet-pc) holds the buffers, speaks TLS, parses JSON, walks OAuth. An 8 KB machine reads Gmail because the hard part happens across the bus.
| Scheme | Class | Reaches |
|---|---|---|
TNFS |
NetworkProtocolTNFS | TNFS file servers |
SD |
NetworkProtocolSD | the FujiNet's own SD card |
SMB |
NetworkProtocolSMB | Windows / Samba shares |
NFS |
NetworkProtocolNFS | UNIX NFSv3 exports |
FTP |
NetworkProtocolFTP | FTP servers (anonymous) |
SFTP |
NetworkProtocolSFTP | files over SSH |
HTTP HTTPS
|
NetworkProtocolHTTP | web servers, REST APIs, WebDAV |
S3 |
NetworkProtocolS3 | Amazon S3 and compatibles |
GDRIVE |
NetworkProtocolGDRIVE | Google Drive |
ONEDRIVE |
NetworkProtocolONEDRIVE | Microsoft OneDrive |
TCP |
NetworkProtocolTCP | raw sockets, client or server |
UDP |
NetworkProtocolUDP | datagrams |
TELNET |
NetworkProtocolTELNET | negotiated Telnet |
SSH |
NetworkProtocolSSH | an interactive SSH shell |
WS WSS
|
NetworkProtocolWS/WSS | WebSockets, plain or TLS |
SSH.KEYGEN |
NetworkProtocolSSHKeygen | generate the SSH key pair |
SSH.COPYID |
NetworkProtocolSSHCopyId | install it on a server |
CLIPBOARD |
NetworkProtocolClipboard | the FujiNet clipboard + history |
CPM |
NetworkProtocolCPM | a CP/M 2.2 machine inside the FujiNet |
GMAIL |
NetworkProtocolGMAIL | a Gmail mailbox β read, compose, reply |
IMAPS |
NetworkProtocolIMAPS | any IMAP mailbox over TLS |
GCAL |
NetworkProtocolGCAL | Google Calendar β read, compose, edit |
ICAL WEBCAL ICALH
|
NetworkProtocolICAL | published iCalendar feeds |
TEST |
NetworkProtocolTest | a fixed test pattern / adapter skeleton |
Schemes are uppercased before matching. An unknown scheme fails the open with error 144; an unparseable URL with 165.
N[unit]:SCHEME://user:password@host:port/path?query
-
N1:βN8:select independent channels (bareN:=N1:); how many you get is a bus property (Atari 8, Apple/ADAM extensions expose 1β15, Intellivision devices$71β$78). - Credentials reach a protocol three ways: in the URL; via the username
(
$FD) / password ($FE) specials before the open; or from stored config (OAuth tokens, S3 keys) so nothing secret crosses the vintage bus. - Pitfall: the firmware only hands stashed credentials over when the username is non-empty. A password alone never arrives.
aux1 (access mode): 4 read Β· 6 directory (7 alt) Β· 8 write Β· 9 append Β· 12 read/write Β· 5/13/14 HTTP-specific (DELETE / POST / PUT-with-headers).
aux2 (translation): what a line ending looks like on the network side; the FujiNet folds it to/from your machine's native EOL.
| aux2 | Network EOL |
|---|---|
| 0 | none β binary, untouched |
| 1 | CR ($0D) |
| 2 | LF ($0A) |
| 3 | CR/LF |
| 4 | PETSCII character translation (on top of the stream) |
Use mode 0 for anything binary. The Atari also maps BEL/BS/TAB to
ATASCII equivalents. A sticky translation ($54 special) is OR-ed into
later opens β except directory opens, where aux2 means a format or
width. Generated text (directories, indexes, status reports) arrives
with the platform line ending already applied and translation off.
The $FC special sets the channel mode: 0 protocol (default), 1 JSON,
2 SGML. The JSON three-beat, identical on every platform:
- Open the URL, set channel mode JSON (
$FCaux2=1). -
Parse (
$50'P') β the FujiNet builds the document tree. -
Query (
$51'Q') β send a path like/results/0/name; read the value back.
$FB tunes result translation (aux1=0) and the result line-ending byte
(aux1=1). JSON/SGML live in lib/fnjson / lib/fnsgml, driven by the
bus device β which is why any protocol can carry them. Do not confuse
$FC (channel mode) with $4D (HTTP body/header channel, HTTP only).
Status returns 4 bytes on every bus: avail (u16 LE, capped 65534),
connected, error. The idiomatic loop: status until avail > 0 (or
connected drops), read, repeat until error 136. Some adapters do their
first real work at the first status (HTTP fires its request there). A
failed open destroys the adapter, but the error is latched for the next
status. On buses with an interrupt line, a timer polls channels and
asserts it when data waits or the connection drops ($5A sets the
rate).
| Code | Meaning | Code | Meaning | |
|---|---|---|---|---|
| 1 | success | 202 | socket timeout | |
| 131 | write only | 203 | network down | |
| 132 | invalid command | 204 | connection reset | |
| 135 | read only | 205 | connect in progress | |
| 136 | end of file | 206 | address in use | |
| 138 | timeout | 207 | not connected | |
| 144 | general fatal | 208 | server not running | |
| 146 | not implemented | 209 | no connection waiting | |
| 151 | file exists | 210 | service unavailable | |
| 162 | no space | 211 | connection aborted (never raised) | |
| 165 | invalid devicespec | 212 | bad username/password | |
| 166 | invalid point | 213 | could not parse JSON | |
| 167 | access denied | 214 | HTTP 4xx client error | |
| 170 | file not found | 215 | HTTP 5xx server error | |
| 200 | connection refused | 255 | out of buffers | |
| 201 | network unreachable |
Printable command bytes match their mnemonic; on the Atari the XIO number is the command byte.
| Hex | Dec | Char | Command |
|---|---|---|---|
$20 |
32 | rename (old,new in one devicespec) |
|
$21 |
33 | ! |
delete |
$23/$24
|
35/36 |
# $
|
lock / unlock |
$25/$26
|
37/38 |
% &
|
seek (POINT) / tell (NOTE) |
$2A/$2B
|
42/43 |
* +
|
mkdir / rmdir |
$2C/$30
|
44/48 |
, 0
|
chdir / getcwd |
$41/$63
|
65/99 |
A c
|
TCP accept / close client |
$44/$72
|
68/114 |
D r
|
UDP set destination / get remote |
$4C |
76 | L |
set native EOL |
$4D |
77 | M |
HTTP channel mode |
$50/$51
|
80/81 |
P Q
|
JSON/SGML parse / query |
$54 |
84 | T |
sticky translation |
$5A |
90 | Z |
interrupt rate |
$FB |
251 | JSON parameters | |
$FC |
252 | channel mode (protocol/JSON/SGML) | |
$FD/$FE
|
253/254 | username / password | |
$FF |
255 | data-direction inquiry (how NDEV relays unknowns) |
The filesystem specials are one-shot: a fresh adapter is built from the devicespec in the payload, does the op, and is destroyed β they work with no open channel, and need a complete devicespec every time.
All ten adapters derive from NetworkProtocolFS. File opens resolve the
path (with crunched-8.3 recovery for long filenames), reads count
fileSize down to error 136, seek/tell work where the backend allows.
Directory opens (aux1=6) use aux2 as a format selector:
| aux2 | Format |
|---|---|
| < 128 | classic 8.3 short entries (sizes in FSSectorSize sectors) |
| 128 | long filename, platform width (37 / ADAM 30 / CoCo 31) |
| 129 | Apple II 80-column |
| 130 | long + provider file ID (GDRIVE) |
| 131 | RAW β filename only, / marks directories; for programs |
| 132 | ProDOS 40-col CAT (real dates only from TNFS) |
| 133 | ProDOS 80-col CATALOG |
Wildcards * ? filter listings (*.*, **, - normalize to *);
Atari builds append 999+FREE SECTORS except in RAW. Rename carries
both names comma-separated: N:TNFS://host/path/OLD,NEW (no comma β
165). Some adapters advertise but stub operations β accepted,
"success," nothing happens; see the capability matrix.
N:TNFS://host[:port]/path β port 16384, anonymous, plain UDP. The most
complete citizen: all modes, all specials, real seek, and the only
adapter returning real modified and created timestamps. Errors: not
found β 170, read-only/denied β 167, no space β 162, EOF β 136, exists β
151, mount timeout β 138, else 144. Treat as LAN/public-archive
transport β nothing is encrypted.
N:SD:/path β the FujiNet's own card; no host, no network. All modes;
rename/delete/mkdir/rmdir real; lock/unlock honestly report 146; seek
real. No card mounted β 200 on everything. Errno map: ENOENT β 170,
EEXIST β 151, EACCES β 167, ENOSPC β 162, ENOMEM β 255.
N:SMB://[user:pass@]server/share/path β SMB2 via libsmb2, signing
enabled. First path component is the share. Credentials from URL, else
the $FD/$FE specials. A username/password with no lowercase at all
is lowercased (friendly to all-caps machines; hostile to all-caps
passwords). mkdir/rmdir/seek real; rename/delete/lock/unlock are
silent stubs. Every library failure reports 144 β check server, share,
credentials in that order.
N:NFS://host[:port]/export/path β NFSv3 via libnfs; a URL port sets
both mount and NFS ports. Credentials are numeric: username special =
UID, password = GID. Auto-negotiates the export boundary (tries the
path's dir, falls back to the root export). mkdir/rmdir/seek real;
rename/delete/lock/unlock stubs; errors collapse to 144.
N:FTP://host[:port]/path β always anonymous
(anonymous/[email protected]); URL credentials ignored; no TLS.
Read = RETR, write = STOR; append/read-write β 146. All six
filesystem specials are silent stubs. Reply map: 226 β 136 (EOF),
421 β 210, 430 β 212, 45x β 167, 550 β 170, other 4xx/5xx β 144.
N:SFTP://user[:pass]@host[:port]/path β port 22. Password in URL =
password auth; none = key auth with /.ssh/id_ed25519 from the SD
card (see SSH.KEYGEN / SSH.COPYID). Full operation set, all real: seek,
rename, delete, mkdir 755, rmdir, lock chmod 444 / unlock 644. EOF
zero-pads reads. Errors: EOF β 136, no file/path β 170,
permission/write-protect β 167, exists β 151, missing user/empty pass β
212, else 144. Host keys are not verified β safe from eavesdroppers,
not from an active MITM.
N:HTTPS://[user:pass@]host[:port]/path?query β the biggest adapter:
REST client, form poster, WebDAV filesystem.
The lazy transaction: open does not send the request β the first status does. Between them you may stage request headers and POST data. HTTP errors surface in that first status.
aux1 = method: 4 GET (path encoded) Β· 12 GET verbatim+headers Β· 8 PUT Β· 13 POST Β· 14 PUT-with-headers (sent as POST) Β· 5 DELETE Β· 9 DELETE+headers Β· 6 WebDAV PROPFIND directory.
$4D HTTP channel modes: 0 body Β· 1 collect headers (write names
before the transaction; GET modes only) Β· 2 get headers (read values
back) Β· 3 set headers (write Name: value) Β· 4 set POST data.
WebDAV: directory listings via depth-1 PROPFIND (falling back to plain GET on 405/408); rename = MOVE, delete = DELETE, mkdir = MKCOL, rmdir = delete.
Seek: on a GET body channel, $25 re-issues with Range: bytes=n-;
a 200 (Range ignored) is handled by skipping.
Status map: 2xx β 1 Β· 401/402/403/407 β 212 Β· 404/410 β 170 Β· 405 β 146 Β· 408 β 138 Β· 423/451 β 167 Β· other 4xx β 214 Β· 5xx β 215 Β· connect failure β 207.
S3://[KEY:SECRET@]endpoint[:port]/bucket/key?region=..&tls=0|1 β
AWS Signature V4 signed inside the FujiNet; works with MinIO, Wasabi,
Ceph. Credentials/region/TLS fall back to web-UI config. Reads stream
GET; writes buffer and go out as one PUT at close (512 KB cap β
162); append downloads first; directories via ListObjectsV2 (paginated);
mkdir = zero-byte folder marker; rename = copy+delete. No seek. Map:
2xx β 1, 403 β 167, 404 β 170, 409 β 151, no response β 207, else 144.
GDRIVE:///path/to/file β OAuth once in the web UI (shared grant with
GMAIL/GCAL; token refresh via the FujiNet relay, client secret never on
device). Paths resolve component-by-component to file IDs; shortcuts
followed; trash invisible. Writes buffer (64 KB) and upload multipart at
close; append pre-downloads. Directory format 130 appends Drive file
IDs. Delete/mkdir real; rmdir = delete (removes non-empty folders!);
no rename. Mount failure (207) means re-authorize.
ONEDRIVE:///path/to/file β Microsoft Graph, by path (no ID walk).
Same buffered-upload model (64 KB PUT at close). Delete, mkdir
(conflict β 151), rmdir = delete, and β unlike GDRIVE β real rename
via PATCH. OAuth via the same relay pattern, [OneDrive] config.
| Devicespec | Meaning |
|---|---|
N:TCP://host:port/ |
connect |
N:TCP://host/ |
connect, port 23 |
N:TCP://:port/ |
listen |
N:TCP:// |
empty channel |
Server mode: status connected=1 means a client is waiting; accept with
$41; drop with $63 and it listens again (one client at a time).
Errors: refused β 200, short read/write β 202, reset β 204, write with
no client β 207, accept with no server β 208, nobody waiting β 209.
Quirk: $63 reports an error even on success β trust the next status.
N:UDP://host:port/ (talk) or N:UDP://:port/ (listen) β the port is
mandatory. connected is always 1. $44 retargets writes
(N:host:port payload); $72 reports the last sender (fujinet-pc
only); and the destination auto-learns the last sender, so
read-then-write answers whoever spoke. Write with no destination β 207.
Multicast join is not implemented.
N:TELNET://host[:port]/?term=ansi&cols=40&rows=24 β TCP plus a real
Telnet state machine; negotiations answered invisibly (refuses ECHO,
offers TTYPE, declines COMPRESS2/MSSP). term defaults dumb; NAWS is
mentioned only if both cols and rows are given. Client only.
N:SSH://user[:pass]@host[:port]/?term=vanilla&cols=80&rows=24 β an
interactive shell with a real PTY. Password in URL = password auth; none
= key auth (/.ssh/id_ed25519). Missing username β 212 immediately.
Status 136 when the remote shell exits. Auth rejected β 167. Host key
logged, not verified. Password rides in the devicespec β prefer key
auth.
N:WSS://host[:port]/path?frame=text β WebSocket client; WSS is WS over
TLS. ?frame=text sends TEXT frames (default binary). Handshake gets
10 s β 200 on failure. One write = one frame. Peer close β 136;
stalled transfer β 202; write after drop β 207.
N:SSH.KEYGEN://ed25519/ (add ?overwrite=1 β exact literal β to
replace). Generates /.ssh/id_ed25519 + .pub on the SD card; refuses
to touch an existing key otherwise; overwrite writes tmp files and
renames. Read the report:
OK SSH.KEYGEN / TYPE ed25519 / OVERWRITE 0 / PRIVATE ... / PUBLIC ...
Failures are one-line SSH.KEYGEN error: ... reports with status 144.
N:SSH.COPYID://user:pass@host[:port]/ β password required (that's the
point). Appends the public key to the server's authorized_keys
(creating dir/file with right permissions, skipping if present). Report:
OK SSH.COPYID / HOST / USER / KEY / STATUS installed|already-present.
After keygen + copyid, password-less SSH://user@host and
SFTP://user@host just work.
N:CLIPBOARD:///[0-9] β index 0 is the clipboard (shared with the web
UI), 1β9 the history, newest first. Only 0 writable (else 167); unfilled
slot β 170; 64 KB cap β 162; ?binary=1 stores verbatim forever. Text
is stored LF-terminated; translation applies at close (commit).
Pitfall: a write-mode open commits at close even if nothing was
written β mode 8 + close empties the clipboard. Short reads NUL-pad and
report 136.
N:CPM:// β boots RunCPM (CP/M 2.2 + CCP) inside the FujiNet; the
channel is its console. Drives live on the SD card. Warm boots handled
invisibly; session end β 136; empty console poll β 202; close halts the
machine.
N:TEST://anything/ β returns one fixed line (This is test dataβ¦)
terminated per your aux2; writes are hex-dumped to the debug log before
and after translation. The loopback for plumbing tests, the microscope
for translation bugs, and the official skeleton (Test.h/.cpp) for
writing a new adapter.
Shared by GMAIL and IMAPS. Append/read-write β 135; write (mode 8) works only on providers that can send β GMAIL yes, IMAPS no (135).
| Path | aux1 | Returns |
|---|---|---|
/FOLDER |
4 | message count |
/FOLDER |
6 | message index |
/FOLDER/N |
4 | message N's body |
/FOLDER/N |
6 | attachment index |
/FOLDER/N/A |
4 | attachment data (0 = body) |
/ |
8 | compose a new message (write, then close) |
/FOLDER/N |
8 | reply to message N |
?range=START-END (0-based, default first 20, max 200) and
?newest=0|1 page the index. For index opens aux2 = 255 yields
packed records; any other value is a human listing at width = aux2 & 127
(0 β platform default: Atari 38, Apple 40, ADAM/CoCo 32, RS-232 80).
Human index: two lines per message, sized to wrap perfectly at 40
columns. Packed records (little-endian, NUL-padded):
MailIndexItem = msgNum u32, displayName[32], emailAddress[48],
subject[128], timestamp u64 (220 bytes);
MailAttachmentItem = attachmentNum u8, displayName[128], fileName[128],
mimeType[48], length u64 (313 bytes). Everything is staged at open;
reads drain to 136.
Writing a message: open mode 8 starts a draft; write RFC822-style
header lines β KEY: value, colon required, keys case-insensitive β
then a blank line, then the body verbatim; close sends:
TO: [email protected]
SUBJECT: Hello from an Atari
Sent from my 130XE over FujiNet.
Only TO, CC, BCC, SUBJECT (no FROM β the sender is the
authenticated account). TO/CC/BCC accumulate on repeat; SUBJECT is
last-wins. Any EOL works (0x9B/CR/LF/CRLF, mixed); aux2 translation is
never applied to a write channel. Replies (mode 8 on /FOLDER/N)
default an omitted TO to the original's Reply-To/From and an omitted
SUBJECT to Re: (never doubled); the shortest legal reply is a blank
line and a sentence; the target is pinned at open (bad N β 170).
Verdict in the post-close status: 1 sent, 132 rejected draft (reason in
debug log only), 162 oversized (16 KB, nothing sent); writing nothing =
clean abort. Sent mail is text/plain UTF-8, no attachments; reading a
write channel β 131. Sending is the only write β nothing is marked
read, moved, or deleted. (The post-close verdict is latched on
SIO/AdamNet/DriveWire/RS-232; other buses don't latch it yet.)
GMAIL:///Inbox[/N[/A]] β folders are Gmail labels
(case-insensitive). Message 1 = oldest; newest = the count β so "latest"
is read /Inbox, then open /Inbox/<count>. Bodies prefer text/plain.
Uses the shared Google grant (scopes gmail.readonly + gmail.send).
Map: 401 β 212 (re-authorize), 403 β 167 (scope missing), 404 β 170,
silent/5xx β 210.
Sending (newest firmware): mode 8 on / composes, on /Inbox/N
replies β the mailbox draft grammar above. Gmail stamps From/date from
the account and files a copy under Sent; replies thread via the
original's threadId + Message-ID/References (override the Re: subject
and Gmail may display the reply outside the conversation). Needs the
gmail.send scope β an older grant keeps reading but fails its first
send with 167; re-authorize in the web UI. Reading still marks, moves,
and deletes nothing.
IMAPS://user:pass@host[:993]/FOLDER[/N[/A]] β any IMAP server,
implicit TLS (no STARTTLS). N = IMAP sequence number (1 = oldest). The
most fine-grained login diagnostics in the book: no host β 165, no user
β 212, TLS fail β 200, silent server β 202, odd greeting β 210, LOGIN
rejected β 212, missing folder/message β 170, parse/fetch β 144. No
sending: a mode-8 open is refused with 135 (compose is GMAIL-only).
Shared by GCAL and ICAL:
SCHEME://selector/VIEW[/DATE[/N]]
VIEW = DAY | WEEK | MONTH | AGENDA (scanned from the path's end);
DATE = YYYY-MM-DD (or YYYY-MM for MONTH), default today; N = 1-based
event in the view. aux1: 6 index Β· 4 count (or detail with N) Β· 8
compose (on /sel) or edit (on /sel/VIEW/DATE/N), providers
permitting. Params: ?category= filter, ?count= (AGENDA cap, 20),
?days= (horizon, 365), ?wkst=MO, ?tz=, plus ?view=/date=/n=
escape hatches. Windows are DST-correct; recurring events expand into
occurrences; numbering is deterministic (sorted by start) and a 2-minute
cache makes index-then-detail one fetch. aux2 = width/255-raw for
indexes (as Mailbox), ignored for details. Packed record CalEventItem
= eventNum u32, start u64, end u64 (exclusive, UTC), flags u8 (1
all-day, 2 recurring), summary[96], location[64], category[32], uid[64]
β 277 bytes; CalListItem = name[64], category[32], id[128].
Writing: open mode 8, write field lines (KEY: value β the colon is
required), close commits:
SUMMARY: Dentist
START: 2026-09-03 14:30
END: 2026-09-03 15:15
LOCATION: 12 Main St.
DESCRIPTION: bring the x-rays
CATEGORY: health
Six keys only: SUMMARY, START, END, LOCATION, DESCRIPTION (repeats to
build paragraphs), CATEGORY. Blank lines are skipped (no header/body
divide, unlike mail); any EOL works; aux2 translation never applies to
a write channel. Times: YYYY-MM-DD (date-only = all-day) or
YYYY-MM-DD HH:MM[:SS] β a T may replace the space, compact
YYYYMMDD[THHMMSS] works, and a trailing Z/Β±HH:MM makes it
absolute; otherwise it resolves in ?tz= (default the FujiNet's zone).
Compose needs SUMMARY + START (missing END = +1 hour, or +1 day
all-day; all-day END is inclusive). Edits change only sent fields:
neither START nor END = times untouched; lone START keeps duration
(unless it switches all-dayβtimed, then compose defaults apply); lone
END must match the event's form. Rejections all report 132 (unknown
key, missing colon, bad time, missing required field, END β€ START,
mixed date forms β reason in debug log only). Verdict in the post-close
status: 1 ok, 132 rejected, 162 oversized (16 KB), 170 target gone
(resolved at open); writing nothing = clean abort. No delete. A
successful commit drops the 2-minute listing cache, so re-list before
editing again. (Verdict latched on SIO/AdamNet/DriveWire/RS-232 only.)
GCAL:///[sel]/VIEW[/DATE[/N]] β selector = calendar name β id β
verbatim; empty = your "shown" calendars merged; * = everything (max
8). Categories: explicit (extended property) β Google color name β
calendar name. Same Google grant; needs calendar.readonly
(+calendar.events to write) β a grant never gains scopes
retroactively; re-authorize in the web UI (scope problems β 167).
Compose to / = primary; * refused (165). A calendar literally
named Day/Week/Month/Agenda can't be compose-targeted by name
(the view scan claims that segment) β use its id. Written CATEGORY
round-trips via private extended properties. Edits go up as a PATCH
and translate between Google's two date forms, so sending START in the
other form switches all-dayβtimed. Indexes expand recurrences, so
editing N touches that occurrence only, never the series.
Unparseable JSON β 213.
ICAL://feed-host/feed-path.ics/VIEW[/DATE[/N]] β any published .ics
feed; WEBCAL = same over https (paste subscription links unchanged);
ICALH = plain http. The feed URL is resent verbatim (secret URLs
survive). Streaming parse: window-sized memory, recurrence expansion
in-window, RECURRENCE-ID overrides patched at end of feed. No calendar
list (/ β 165); read-only (135); detail is a second pass with the
window widened a day each side. Compressed responses are refused (146).
TZID-named times resolve in your configured zone β ?tz= overrides.
Load the NDEV handler (AUTORUN.SYS/NDEV.COM). Then:
10 OPEN #1,4,0,"N:HTTPS://FUJINET.ONLINE/HELLO.TXT"
20 STATUS #1,A:BW=PEEK(746)+PEEK(747)*256
30 IF BW=0 AND PEEK(748)=1 THEN 20
40 TRAP 80:FOR I=1 TO BW:GET #1,C:? CHR$(C);:NEXT I:GOTO 20
80 CLOSE #1DVSTAT: bytes waiting PEEK(746)+PEEK(747)*256, connection PEEK(748),
error PEEK(749). The XIO number is the command byte β the specials
table above doubles as the XIO reference. XIO 15 flushes locally. JSON:
30 XIO 252,#1,0,1,"N:" : XIO 80,#1,0,0,"N:"
50 XIO 81,#1,0,0,"N:/status/message" : INPUT #1,V$Edges: reads cap at 127 bytes/call; the DOS 2 menu unloads the handler;
NDEV cannot reach the Fuji device; $4D reports 146.
β FujiNet Programming Guide for the Atari (atari/programmers_guide/).
BRUN FUJINET hooks BASIC.SYSTEM's external commands (deliberately not
& β the tokenizer mangles N-words). Eighteen commands, typed at ] or
via PRINT CHR$(4);"β¦": NOPEN/NCLOSE/NSTATUS/NREAD/NWRITE, NJSONPARSE/
NJSONQUERY, NCD/NCAT/NCATALOG/NMKDIR/NRMDIR/NDEL/NTYPE, NLOAD/NSAVE,
NTRANS/NACCEPT/NLOGIN/NHTTPMODE. Channels 1β15; translation 4 =
PETSCII; errors via ONERR + PEEK(222).
10 D$ = CHR$(4): Q$ = CHR$(34)
30 PRINT D$;"NOPEN 1,";Q$;"N:HTTPS://ICANHAZIP.COM/";Q$;",4,0"
40 PRINT D$;"NSTATUS 1,BW,CN,ER"
50 IF BW = 0 AND CN = 1 THEN 40
70 PRINT D$;"NREAD 1,A$,BW": PRINT "MY IP IS ";A$
90 PRINT D$;"NCLOSE 1"β FujiNet BASIC Extension for the Apple II
(apple2/basic_extension/) and the Apple II programmer's guide.
Seventeen native statements patched into the interpreter itself:
same vocabulary as the Apple II (with NDIR), channels 1β15 β EOS
devices 9+. Network failures raise BASIC error 37 (?Network Error n),
ONERR-catchable; codes 1/136 never raise; no FujiNet β ?ILLEGAL QUANTITY ERROR fast.
220 ONERR GOTO 900
300 NOPEN 1,"N:HTTP://api.open-notify.org/iss-now.json",4,0
310 NJSONPARSE 1
320 NJSONQUERY 1,"/iss_position/latitude",lat$
340 NCLOSE 1
900 NSTATUS 1,bw,conn,err : PRINT "Network Error: ";errAdamNet moves β€ 1024 bytes per bus transaction. β the smartbasic-1.x
repository's appendix, and adam/programmers_guide/ for EOS/Z80.
One portable API (cc65/z88dk/CMOCβ¦): network_init/open/close,
network_read/_read_nb (β bytes or βerror; EOF = β136),
network_write, network_status, network_json_parse/query, the
network_http_* family, network_fs_* one-shots, network_ioctl for
everything else. Constants exist for modes 4/8/12, HTTP modes, and
translations; pass 6 (directory) and 9 (append) as literals.
network_open(url, OPEN_MODE_HTTP_GET, OPEN_TRANS_NONE);
network_json_parse(url);
network_json_query(url, "/number", buffer);
network_close(url);β Writing Cross-Platform Client Apps Using fujinet-lib and
fujinet-lib-examples/network/.
A shared-RAM mailbox at $9C00: stage device/command/params/payload,
submit by writing SEQ = ACKSEQ + 1 last, poll ACKSEQ, verify the
reply. The fujinet.bas library wraps it (fn_transact, fn_param,
fn_putstr, net_open/status/read/write/close, api_call). Edges:
derive SEQ from the published ACKSEQ; HTTP fires at the first STATUS
and the library polls until two STATUS reads agree; declare
ASM MEMATTR $8000, $9BFF, "+RWN"; parenthesize (PEEK(x) AND 255).
β FujiNet Programming Guide for the Intellivision
(intv/fujinet-programmers-guide/).
The network as a DOS β no program required:
NCD N1:TNFS://fujinet.online/ DIR LOAD GAMES/JUMPMAN.XEX
USER MOLLY PASS SECRET
NCD N2:SMB://DEN-PC/ATARI/ NCOPY N1:DOCS/README,N2:BACKUP/README
TYPE N3:HTTPS://fujinet.online/ NTRANS N1: 0
Mounts live in the FujiNet and are shared with whatever you run next;
D1:βD8: filespecs forward to N1:βN8: so 1982 programs save to
servers unknowingly. Keep mounts off N4: (NOS uses it for NCOPY and
HELP). Streams (TCP/SSH/Telnet) are the programmer's guide's department.
β NOS: An Introduction (atari/nos_manual/).
Y implemented Β· S accepted but does nothing (reports success) Β· β refused/absent Β· blank = treated as nearest neighbor.
| Protocol | R | W | A | RW | DIR | SEEK | REN | DEL | MKD | RMD | LCK | UNL | Port / notes |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| TNFS | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | 16384 Β· anonymous |
| SD | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | β | β | local card |
| SMB | Y | Y | Y | Y | Y | Y | S | S | Y | Y | S | S | 445 Β· URL or specials |
| NFS | Y | Y | Y | Y | Y | Y | S | S | Y | Y | S | S | 2049 Β· UID/GID |
| FTP | Y | Y | β | β | Y | β | S | S | S | S | S | S | 21 Β· anonymous only |
| SFTP | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | 22 Β· pass or key |
| HTTP/S | Y | Y | β | Y | Y | Y | Y | Y | Y | Y | S | S | 80/443 Β· WebDAV ops |
| S3 | Y | Y | Y | Y | Y | β | Y | Y | Y | Y | S | S | 443 Β· keys or config |
| GDRIVE | Y | Y | Y | Y | Y | β | S | Y | Y | Y | S | S | OAuth (web UI) |
| ONEDRIVE | Y | Y | Y | Y | Y | β | Y | Y | Y | Y | S | S | OAuth (web UI) |
| TCP | Y | Y | Y | β | β | β | β | β | β | β | β | 23 dflt Β· listen + accept | |
| UDP | Y | Y | Y | β | β | β | β | β | β | β | β | port required | |
| TELNET | Y | Y | Y | β | β | β | β | β | β | β | β | 23 Β· negotiated | |
| SSH | Y | Y | Y | β | β | β | β | β | β | β | β | 22 Β· pass or key | |
| WS/WSS | Y | Y | Y | β | β | β | β | β | β | β | β | 80/443 Β· frames | |
| SSH.KEYGEN | Y | β | β | β | β | β | β | β | β | β | report only | ||
| SSH.COPYID | Y | β | β | β | β | β | β | β | β | β | pass required | ||
| CLIPBOARD | Y | Y | Y | Y | β | β | β | β | β | β | β | β | index 0 writable |
| CPM | Y | Y | Y | β | β | β | β | β | β | β | β | console channel | |
| GMAIL | Y | Y | β | β | Y | β | β | β | β | β | β | β | OAuth Β· W = compose/reply |
| IMAPS | Y | β | β | β | Y | β | β | β | β | β | β | β | 993 Β· read-only |
| GCAL | Y | Y | β | Y | Y | β | β | β | β | β | β | β | OAuth Β· W = compose/edit |
| ICAL | Y | β | β | Y | Y | β | β | β | β | β | β | β | feeds Β· read-only |
| TEST | Y | Y | Y | Y | β | β | β | β | β | β | β | β | loopback |
| Scheme | Canonical form Β· query parameters |
|---|---|
TNFS |
N:TNFS://host[:port]/path |
SD |
N:SD:/path |
SMB |
N:SMB://[user:pass@]server/share/path |
NFS |
N:NFS://host[:port]/export/path |
FTP |
N:FTP://host[:port]/path |
SFTP |
N:SFTP://user[:pass]@host[:port]/path |
HTTP HTTPS
|
N:HTTPS://[user:pass@]host[:port]/path[?query] |
S3 |
S3://[key:secret@]endpoint[:port]/bucket/key Β· ?region= ?tls=0|1
|
GDRIVE |
GDRIVE:///path |
ONEDRIVE |
ONEDRIVE:///path |
TCP |
N:TCP://host[:port]/ Β· N:TCP://:port/ (listen) |
UDP |
N:UDP://[host]:port/ |
TELNET |
N:TELNET://host[:port]/ Β· ?term= ?cols= ?rows=
|
SSH |
N:SSH://user[:pass]@host[:port]/ Β· ?term= ?cols= ?rows=
|
WS WSS
|
N:WSS://host[:port]/path Β· ?frame=text
|
SSH.KEYGEN |
N:SSH.KEYGEN://ed25519/ Β· ?overwrite=1
|
SSH.COPYID |
N:SSH.COPYID://user:pass@host[:port]/ |
CLIPBOARD |
N:CLIPBOARD:///[0-9] Β· ?binary=1
|
CPM |
N:CPM:// |
TEST |
N:TEST://anything/ |
GMAIL |
GMAIL:///Folder[/N[/A]] Β· ?range=a-b ?newest=0|1 Β· mode 8: / compose, /Folder/N reply |
IMAPS |
IMAPS://user:pass@host[:port]/FOLDER[/N[/A]] Β· ?range= ?newest= (read-only) |
GCAL |
GCAL:///[sel]/VIEW[/DATE[/N]] Β· ?category= ?count= ?days= ?wkst= ?tz= Β· mode 8: /[sel] compose, /β¦/N edit |
ICAL WEBCAL ICALH
|
ICAL://feed-host/feed-path/VIEW[/DATE[/N]] Β· same as GCAL |
Rough edges as of the colophon's commit, stated plainly:
-
Windows would-block misreport β fujinet-pc Win32 builds report
206 for a would-block socket (fall-through in
Protocol.cpp). -
TCP close-client reports failure on success β ignore
$63's transaction status; trust the next status call. -
Silent stubs β FTP's six filesystem specials; SMB/NFS
rename/delete/lock/unlock; base-class lock/unlock inherited by HTTP,
S3, GDRIVE, ONEDRIVE. All marked
Sin the matrix. -
HTTP
stat()short-circuits before its HEAD logic. - HTTP header reads lack a length clamp β give them 256-byte buffers.
- One Telnet state machine (file-scope static) β one TELNET channel at a time.
- SSH status logic uses an inverted variable name and an unchecked channel pointer; correct for successfully opened channels.
-
is_lockedconventions differ β TNFS sets it for writable files, SFTP for read-only ones. - Shadowed capability flags in TNFS/FTP/SMB/NFS β harmless (nothing consults them), but a trap for future code.
-
Error 211 (
CONNECTION_ABORTED) is defined but never raised. - UDP multicast detection exists but is unwired; no group joins.
- The commit verdict is bus-dependent β the mail/calendar commit-on-close verdict is latched into the next status by the SIO, AdamNet, DriveWire, and RS-232 layers; IEC, IWM, ComLynx, S100, and RC2014 do not yet latch it, so a failed send or commit may not surface there.
- Draft rejections are one number β every mail or calendar draft error (bad key, missing colon, bad time, missing required field, end before start, mixed date forms) reports 132; the cause is named only in the debug log.
-
No delete, no attachments out β the mail adapters never delete,
move, or mark messages and send
text/plainonly; the calendar adapters compose and edit but cannot remove an event.
| Guide | Where |
|---|---|
| FujiNet Programming Guide for the Atari | fujinet-manuals/atari/programmers_guide/ |
| FujiNet BASIC Extension for the Apple II | fujinet-manuals/apple2/basic_extension/ |
| FujiNet Programming Guide for the Apple II | fujinet-manuals/apple2/programmers_guide/ |
| FujiNet Programming Guide for the Coleco ADAM | fujinet-manuals/adam/programmers_guide/ |
| SmartBASIC 1.x FujiNet appendix | the smartbasic-1.x repository |
| FujiNet Programming Guide for the Intellivision | fujinet-manuals/intv/fujinet-programmers-guide/ |
| Writing Cross-Platform Client Apps Using fujinet-lib | fujinet-manuals/writing-cross-platform-client-apps-using-fujinet-lib/ |
| NOS: An Introduction | fujinet-manuals/atari/nos_manual/ |
| FujiNet Programmer's Reference (MS-DOS) | fujinet-manuals/msdos/programmers_reference/ |
The guides differ in spelling; the protocols never do.