# lib9p/idl/2002-9P2000.9p - Definitions of 9P2000 messages
#
# Copyright (C) 2024-2025  Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later

# "9P2000" base protocol
# https://ericvh.github.io/9p-rfc/rfc9p2000.html
# https://github.com/ericvh/9p-rfc/blob/master/9p2000.xml
#
# But due to incompleteness of the draft RFC, the Plan 9 manual
# section-5 and the Plan 9 headers (particularly fcall.h) are often
# better references.  The s{32,64}_max limitations are not documented
# in the draft RFC or the manual pages, but have been enforced by
# lib9p/srv.c in every release of Plan 9 4e.
#
# https://github.com/plan9foundation/plan9/tree/main/sys/man/5
# https://man.cat-v.org/plan_9/5/
# https://github.com/plan9foundation/plan9/blob/main/sys/include/fcall.h
# https://github.com/plan9foundation/plan9/blob/main/sys/include/libc.h
# https://github.com/plan9foundation/plan9/blob/main/sys/src/lib9p/srv.c
version "9P2000"

# tag - identify a request/response pair
num tag = 2
    "NOTAG = ~0"

# file identifier - like a UNIX file-descriptor
num fid = 4
    "NOFID = ~0"

# string - u16le `n`, then `n` bytes of UTF-8, without any nul-bytes
struct s = "len[2] len*(utf8[1])"

# "D"ir-entry "M"ode - file permissions and attributes
bitfield dm = 4
	"31=DIR"
	"30=APPEND"
	"29=EXCL"
	# DMMOUNT has been around in Plan 9 forever (CHMOUNT in <4e),
	# but is undocumented, and is explicitly excluded from the
	# 9P2000 draft RFC.  As I understand it, DMMOUNT indicates
	# that the file is mounted by the kernel as a 9P transport;
	# that the kernel has a lock on doing I/O on it, so userspace
	# can't do I/O on it.
	"28=_PLAN9_MOUNT"
	"27=AUTH"
	"26=TMP"
	#...
	"8=OWNER_R"
	"7=OWNER_W"
	"6=OWNER_X"
	"5=GROUP_R"
	"4=GROUP_W"
	"3=GROUP_X"
	"2=OTHER_R"
	"1=OTHER_W"
	"0=OTHER_X"

	"PERM_MASK=0777" # {OWNER,GROUP,OTHER}_{R,W,X}

# QID Type - see `struct qid` below
bitfield qt = 1
	"7=DIR"
	"6=APPEND"
	"5=EXCL"
	"4=_PLAN9_MOUNT" # See "_PLAN9_MOUNT" in "dm" above.
	"3=AUTH"
	# Fun historical fact: QTTMP was a relatively late addition to
	# Plan 9, in 2003-12.
	"2=TMP"
	#"1=unused"

	# "The name QTFILE, defined to be zero, identifies the value
	# of the type for a plain file."
	"FILE=0"

# uni"Q"ue "ID"entification - "two files on the same server hierarchy
# are the same if and only if their qids are the same"
#
#  - "path" is a unique uint64_t that does most of the work in the
#    above statement about files being the same if their QIDs are the
#    same; " If a file is deleted and recreated with the same name in
#    the same directory, the old and new path components of the qids
#    should be different"
#
#  - "vers" "is a version number for a file; typically, it is
#    incremented every time the file is modified.
#
#  - "type" indicates "whether the file is a directory, append-only
#    file, etc."; is an instance of the qt bitfield.
struct qid = "type[qt] vers[4] path[8]"

# stat - file status
struct stat = "stat_size[2,val=end-&kern_type]"
              "kern_type[2]"
              "kern_dev[4]"
              "file_qid[qid]"
              "file_mode[dm]"
              "file_atime[4]"
              "file_mtime[4]"
              "file_size[8]"
              "file_name[s]"
              "file_owner_uid[s]"
              "file_owner_gid[s]"
              "file_last_modified_uid[s]"

# "O"pen flags (flags to pass to Topen and Tcreate)
# Unused bits *must* be 0.
bitfield o = 1
	"0=mode_0" # low bit of the 2-bit READ/WRITE/RDWR/EXEC enum
	"1=mode_1" # high bit of the 2-bit READ/WRITE/RDWR/EXEC enum
	#"2=unused"
	#"3=unused"
	"4=TRUNC"
	#"5=_reserved_CEXEC" # close-on-exec
	"6=RCLOSE" # remove-on-close
	#"7=unused"

	"READ  = 0" # make available for this FID: Tread()
	"WRITE = 1" # make available for this FID: Twrite()
	"RDWR  = 2" # make available for this FID: Tread() and Twrite()
	"EXEC  = 3" # make available for this FID: Tread()

	"MODE_MASK = 0b00000011"
	"FLAG_MASK = 0b11111100"

# A 9P2000 session goes:
#
#    version()
#    [auth_fid=auth]
#    attach([auth_fid])
#    ...

msg Tversion = "size[4,val=end-&size] typ[1,val=100] tag[tag] max_msg_size[4] version[s]"
msg Rversion = "size[4,val=end-&size] typ[1,val=101] tag[tag] max_msg_size[4] version[s]"
msg Tauth    = "size[4,val=end-&size] typ[1,val=102] tag[tag] afid[fid] uname[s] aname[s]"
msg Rauth    = "size[4,val=end-&size] typ[1,val=103] tag[tag] aqid[qid]"
msg Tattach  = "size[4,val=end-&size] typ[1,val=104] tag[tag] fid[fid] afid[fid] uname[s] aname[s]"
msg Rattach  = "size[4,val=end-&size] typ[1,val=105] tag[tag] qid[qid]"
#msg Terror   = "size[4,val=end-&size] typ[1,val=106] tag[tag] illegal"
msg Rerror   = "size[4,val=end-&size] typ[1,val=107] tag[tag] ename[s]"
msg Tflush   = "size[4,val=end-&size] typ[1,val=108] tag[tag] oldtag[2]"
msg Rflush   = "size[4,val=end-&size] typ[1,val=109] tag[tag]"
msg Twalk    = "size[4,val=end-&size] typ[1,val=110] tag[tag] fid[fid] newfid[fid] nwname[2,max=16] nwname*(wname[s])"
msg Rwalk    = "size[4,val=end-&size] typ[1,val=111] tag[tag] nwqid[2,max=16] nwqid*(wqid[qid])"
msg Topen    = "size[4,val=end-&size] typ[1,val=112] tag[tag] fid[fid] mode[o]"
msg Ropen    = "size[4,val=end-&size] typ[1,val=113] tag[tag] qid[qid] iounit[4]"
msg Tcreate  = "size[4,val=end-&size] typ[1,val=114] tag[tag] fid[fid] name[s] perm[dm] mode[o]"
msg Rcreate  = "size[4,val=end-&size] typ[1,val=115] tag[tag] qid[qid] iounit[4]"
msg Tread    = "size[4,val=end-&size] typ[1,val=116] tag[tag] fid[fid] offset[8,max=s64_max] count[4,max=s32_max]" # See 4e `sys/src/lib9p/srv.c:sread()` for `offset:max` and `count:max`.
msg Rread    = "size[4,val=end-&size] typ[1,val=117] tag[tag] count[4,max=s32_max] count*(data[1])" # `max` is inherited from Tread, for directories `data` is the sequence "cnt*(entries[stat])".
msg Twrite   = "size[4,val=end-&size] typ[1,val=118] tag[tag] fid[fid] offset[8,max=s64_max] count[4,max=s32_max] count*(data[1])" # See 4e `sys/src/lib9p/srv.c:swrite()` for `offset:max` and `count:max`.
msg Rwrite   = "size[4,val=end-&size] typ[1,val=119] tag[tag] count[4,max=s32_max]" # `max` is inherited from Twrite.
msg Tclunk   = "size[4,val=end-&size] typ[1,val=120] tag[tag] fid[fid]"
msg Rclunk   = "size[4,val=end-&size] typ[1,val=121] tag[tag]"
msg Tremove  = "size[4,val=end-&size] typ[1,val=122] tag[tag] fid[fid]"
msg Rremove  = "size[4,val=end-&size] typ[1,val=123] tag[tag]"
msg Tstat    = "size[4,val=end-&size] typ[1,val=124] tag[tag] fid[fid]"
msg Rstat    = "size[4,val=end-&size] typ[1,val=125] tag[tag] nstat[2,val=end-&stat] stat[stat]"          # See the "BUG" note in the RFC for the nstat field
msg Twstat   = "size[4,val=end-&size] typ[1,val=126] tag[tag] fid[fid] nstat[2,val=end-&stat] stat[stat]" # See the "BUG" note in the RFC for the nstat field
msg Rwstat   = "size[4,val=end-&size] typ[1,val=127] tag[tag]"