-
Notifications
You must be signed in to change notification settings - Fork 94
FujiNet Network Protocol Handbook
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(commit8b61bde69, Aug 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-only |
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/fujinet@fujinet.online); 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. Read-only (writes → 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) |
?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.
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 (scope gmail.readonly). Map: 401 → 212
(re-authorize), 403 → 167 (scope missing), 404 → 170, silent/5xx → 210.
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.
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, 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
Compose needs SUMMARY + START (date-only = all-day; missing END = +1 hour or +1 day; all-day END is inclusive). DESCRIPTION repeats; unknown keys reject the draft (132). Edits change only sent fields (lone START keeps duration). Verdict in the post-close status: 1 ok, 132 rejected, 162 oversized (16 KB), 170 target gone; writing nothing = clean abort.
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. Written CATEGORY round-trips via
private extended properties. 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 | – | – | – | – | – | – | – | OAuth · read-only |
| 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
|
IMAPS |
IMAPS://user:pass@host[:port]/FOLDER[/N[/A]] |
GCAL |
GCAL:///[sel]/VIEW[/DATE[/N]] · ?category= ?count= ?days= ?wkst= ?tz=
|
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.
| 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.
Copyright 2026 Contributors to the FujiNetWIFI project.
Join us on Discord: https://discord.gg/7MfFTvD
- Home
- What is FujiNet?
- The Definition of Done
- Board bring up for FujiNet Platform.IO code
- The Complete Linux CLI Guide
- The Complete macOS CLI Guide
- Development Env for Apps
- FujiNet-Development-Guidelines
- System Quickstarts
- FujiNet Flasher
- Setting up a TNFS Server
- FujiNet Configuration File: fnconfig.ini
- AppKey Registry - AppKey-Registry-Page
- CP-M Support
- BBS
- Official Hardware Versions
- Prototype Board Revisions
- FujiNet Development Guidelines
- The Network Protocol Handbook
- Atari Programming
- Apple Programming
- C64 Programming
- ADAM Programming
- Intellivision Programming
- Testing Plan
- Hacker List
- FujiNet VirtualMachine