fsp-server: implement GET_PRO (0x47), which Swiss asks for first
Swiss sent CC_GET_PRO 'swiss/patches' and the server replied "unsupported".
Reply shape, from fsplib's fsp_getpro:
* pos MUST equal FSP_PRO_BYTES (1), or fsplib fails with ENOMSG
* the flags byte is at buf[len] -- the first byte of the EXTRA region,
after the echoed name, NOT inside the data field
* a missing directory must be CC_ERR so fsplib reports ENOENT, because
Swiss probes "swiss/patches", then "swiss", then "" and relies on that
fallback chain
Those bits are not cosmetic. deviceHandler-FSP.c does:
if ((pro & (MKDIR|ADD|DEL|GET)) == (MKDIR|ADD|DEL) || (pro & OWNER)) {
features |= FEAT_PATCHES; emulable |= EMU_ETHERNET;
} else { ... clears both ... }
so a plain read-only answer makes Swiss DISABLE patch support on this device.
Default is therefore the honest GET|LIST (0x50) with patches disabled, and
--claim-owner reports OWNER to turn them back on -- flagged in the help as
unsafe for now, because this server has no UP_LOAD or MAKE_DIR, so Swiss
believing it can write here would make a patch write fail rather than degrade.
Try without it first.
T9 checks pos == 1 and that the flags land at buf[len] with GET|LIST and
without OWNER; T10 checks a missing directory gives CC_ERR so the fallback
chain works.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,15 @@ import time
|
||||
CC_VERSION, CC_INFO = 0x10, 0x11
|
||||
CC_ERR, CC_GET_DIR, CC_GET_FILE = 0x40, 0x41, 0x42
|
||||
CC_BYE, CC_STAT = 0x4A, 0x4D
|
||||
CC_GET_PRO = 0x47
|
||||
# Directory protection bits (fsplib.h). Swiss uses these to decide whether
|
||||
# the device supports patches:
|
||||
# if ((pro & (MKDIR|ADD|DEL|GET)) == (MKDIR|ADD|DEL) || (pro & OWNER))
|
||||
# features |= FEAT_PATCHES; emulable |= EMU_ETHERNET;
|
||||
# so a plain read-only answer makes Swiss DISABLE patch support here.
|
||||
PRO_OWNER, PRO_DEL, PRO_ADD, PRO_MKDIR = 0x01, 0x02, 0x04, 0x08
|
||||
PRO_GET, PRO_README, PRO_LIST, PRO_RENAME = 0x10, 0x20, 0x40, 0x80
|
||||
PRO_BYTES = 1
|
||||
RDTYPE_END, RDTYPE_FILE, RDTYPE_DIR, RDTYPE_SKIP = 0x00, 0x01, 0x02, 0x2A
|
||||
HSIZE, FSP_SPACE = 12, 1024
|
||||
SERVER_KEY = 0x1234
|
||||
@@ -128,6 +137,13 @@ def main():
|
||||
ap.add_argument("--port", type=int, default=21)
|
||||
ap.add_argument("--quiet-reads", action="store_true",
|
||||
help="only log the rate, not every read")
|
||||
ap.add_argument("--claim-owner", action="store_true",
|
||||
help="report FSP_DIR_OWNER on GET_PRO, which makes Swiss "
|
||||
"enable FEAT_PATCHES and EMU_ETHERNET on this device. "
|
||||
"⚠️ This server is READ-ONLY -- it has no UP_LOAD or "
|
||||
"MAKE_DIR -- so Swiss believing it can write here "
|
||||
"will make any patch write fail. Try without it "
|
||||
"first; use it only if a boot demands patches.")
|
||||
a = ap.parse_args()
|
||||
root = os.path.realpath(a.root)
|
||||
|
||||
@@ -192,6 +208,25 @@ def main():
|
||||
s.sendto(pack(CC_STAT, SERVER_KEY, seq, pos, body), peer)
|
||||
continue
|
||||
|
||||
if cmd == CC_GET_PRO:
|
||||
# Reply shape (fsplib fsp_getpro): pos MUST equal FSP_PRO_BYTES,
|
||||
# and the flags byte sits at buf[len] -- i.e. the first byte of
|
||||
# the EXTRA region, after the echoed name, not inside data.
|
||||
# A non-existent directory must be CC_ERR so fsplib reports
|
||||
# ENOENT: Swiss probes "swiss/patches", then "swiss", then "".
|
||||
if not os.path.isdir(real):
|
||||
s.sendto(pack(CC_ERR, SERVER_KEY, seq, pos, b"no such dir\0"), peer)
|
||||
print(f"[{peer[0]}] GET_PRO {path!r} -> ENOENT (expected; "
|
||||
f"Swiss falls back)")
|
||||
continue
|
||||
pro = PRO_GET | PRO_LIST | (PRO_OWNER if a.claim_owner else 0)
|
||||
s.sendto(pack(CC_GET_PRO, SERVER_KEY, seq, PRO_BYTES,
|
||||
path.encode("latin-1", "replace"), bytes([pro])), peer)
|
||||
print(f"[{peer[0]}] GET_PRO {path!r} -> {pro:#04x}"
|
||||
+ (" (OWNER: Swiss will enable patches)" if a.claim_owner
|
||||
else " (read-only: Swiss disables patches here)"))
|
||||
continue
|
||||
|
||||
if cmd == CC_GET_DIR:
|
||||
if not os.path.isdir(real):
|
||||
s.sendto(pack(CC_ERR, SERVER_KEY, seq, pos, b"not a dir\0"), peer)
|
||||
|
||||
Reference in New Issue
Block a user