diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-19 23:28:22 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-19 23:28:22 -0300 |
| commit | ba996acfcad1698adbf4a1834fe50e73b1c6cab9 (patch) | |
| tree | 282ba00ce5b10d7416aecb9f2f0f0a439340a57d /9player/test/adv_bridge_hostile.py | |
| parent | b05abcba3ea09ea106ad28364c6e40a3ec31b890 (diff) | |
| download | cloud9-ba996acfcad1698adbf4a1834fe50e73b1c6cab9.tar.gz cloud9-ba996acfcad1698adbf4a1834fe50e73b1c6cab9.zip | |
Rename programs: 9player -> 9ns, introspect -> 9proc, app -> web (9web)
Directories, binaries, build options (-D9ns, -D9proc), step names, module
name (9proc), thread and fs names, env var NINEPLAYER_MOUNT -> NINE_MOUNT,
docs and test scripts. Browser assets move to web/static.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to '9player/test/adv_bridge_hostile.py')
| -rwxr-xr-x | 9player/test/adv_bridge_hostile.py | 487 |
1 files changed, 0 insertions, 487 deletions
diff --git a/9player/test/adv_bridge_hostile.py b/9player/test/adv_bridge_hostile.py deleted file mode 100755 index b842a1a..0000000 --- a/9player/test/adv_bridge_hostile.py +++ /dev/null @@ -1,487 +0,0 @@ -#!/usr/bin/env python3 -"""A scriptable, hostile 9P2000 server on a Unix socket (stdlib only). - -Usage: adv_bridge_hostile.py SOCKET MODE - -Serves a tiny in-memory tree: - /f "hello world\\n" - /d/g "in d\\n" - /fids reading it returns the number of fids currently bound - /big 1 MiB of pseudo-random bytes -plus create/write/remove/wstat so the scratch battery can run in `ok` mode. - -MODE selects one misbehaviour (see MODES below). Everything not covered by -the mode behaves normally, so 9player gets through version/attach/stat(root). -""" -import os -import random -import socket -import struct -import sys -import time - -NOTAG = 0xFFFF -NOFID = 0xFFFFFFFF -QTDIR = 0x80 -DMDIR = 0x80000000 - -Tversion, Rversion = 100, 101 -Tauth, Rauth = 102, 103 -Tattach, Rattach = 104, 105 -Rerror = 107 -Tflush, Rflush = 108, 109 -Twalk, Rwalk = 110, 111 -Topen, Ropen = 112, 113 -Tcreate, Rcreate = 114, 115 -Tread, Rread = 116, 117 -Twrite, Rwrite = 118, 119 -Tclunk, Rclunk = 120, 121 -Tremove, Rremove = 122, 123 -Tstat, Rstat = 124, 125 -Twstat, Rwstat = 126, 127 - -MODES = """ -ok behave (qid paths are recycled LIFO after remove, like many servers) -trunc Rread on /f: send half the frame, then close -short_frame Rread on /f: frame whose size field is 3 -huge_frame Rread on /f: frame whose size field is msize+1 -wrong_tag Rread on /f: reply carries tag+1 -wrong_type Tstat on /f: answer with an Rwalk -rread_big Rread on /f: count = requested+1 -rwalk_many Twalk to f: nwqid = nwname+1 -rwalk_zero Twalk to nope: Rwalk nwqid=0 instead of Rerror -rstat_garbage Tstat on /f: random bytes as the stat -rstat_overlong Tstat on /f: inner stat size disagrees with outer -dir_split Tread on /: a stat record split across two Rreads -dir_forever Tread on /: ignore offset, always return the same records -qid_collide every file and dir shares qid.path 7 (root keeps its own) -qid_zero every qid.path is 0, including the root -name_slash / has an entry "a/b" -name_empty / has an entry "" -name_huge / has an entry with a 60000-byte name -name_dots / lists "." and ".." too -rerror_big Twalk to nope: Rerror with 65535 bytes of text -extra_reply Rread on /f: an unsolicited Rclunk (tag 9) precedes the real reply -never Tread on /f: never reply (hang) -close_mid Tread on /f: close the socket without replying -renegotiate Tread on /f: an unsolicited Rversion precedes the real reply -length_max Tstat on /f: length = 2**64-1 -iounit_one Ropen: iounit = 1 -rwrite_big Rwrite: count = requested+1 -msize_tiny Rversion msize = 64 -version_unknown Rversion "unknown" -rename_fail Twstat with a new name always fails "file already exists" -slow every reply delayed 20 ms (for interrupt tests) -""" - - -def s8(x): return struct.pack('<B', x) -def s16(x): return struct.pack('<H', x) -def s32(x): return struct.pack('<I', x) -def s64(x): return struct.pack('<Q', x) -def sstr(b): - if isinstance(b, str): - b = b.encode() - return s16(len(b)) + b - - -class Node: - def __init__(self, name, isdir, path, content=b''): - self.name = name - self.isdir = isdir - self.path = path - self.content = bytearray(content) - self.children = {} - self.mode = 0o755 if isdir else 0o644 - self.mtime = int(time.time()) - self.removed = False - - def qid(self, srv): - path = self.path - if srv.mode == 'qid_zero': - path = 0 - elif srv.mode == 'qid_collide' and self is not srv.root: - path = 7 - return s8(QTDIR if self.isdir else 0) + s32(1) + s64(path) - - def stat_bytes(self, srv, name=None, length=None): - if name is None: - name = self.name - if length is None: - length = 0 if self.isdir else len(self.content) - body = (s16(0) + s32(0) + self.qid(srv) + s32((DMDIR if self.isdir else 0) | self.mode) - + s32(self.mtime) + s32(self.mtime) + s64(length) - + sstr(name) + sstr('u') + sstr('g') + sstr('u')) - return s16(len(body)) + body - - -class Server: - def __init__(self, mode): - self.mode = mode - self.next_path = 100 - self.free_paths = [] - self.root = Node('', True, 1) - f = self.mk(self.root, 'f', False, b'hello world\n') - d = self.mk(self.root, 'd', True) - self.mk(d, 'g', False, b'in d\n') - self.mk(self.root, 'fids', False) - rnd = random.Random(4) - self.mk(self.root, 'big', False, bytes(rnd.getrandbits(8) for _ in range(1 << 20))) - self.fids = {} - self.msize = 8192 - self.ops = 0 - - def alloc_path(self): - if self.free_paths: - return self.free_paths.pop() - self.next_path += 1 - return self.next_path - - def mk(self, parent, name, isdir, content=b''): - n = Node(name, isdir, self.alloc_path(), content) - parent.children[name] = n - return n - - # -- framing --------------------------------------------------------- - def frame(self, typ, tag, body): - return s32(7 + len(body)) + s8(typ) + s16(tag) + body - - def err(self, tag, text): - return self.frame(Rerror, tag, sstr(text)) - - def serve(self, conn): - buf = b'' - while True: - if len(buf) >= 4: - n = struct.unpack('<I', buf[:4])[0] - if len(buf) >= n: - msg, buf = buf[:n], buf[n:] - out = self.handle(msg) - if out is None: - return # hang up / hang - if self.mode == 'slow': - time.sleep(0.02) - conn.sendall(out) - continue - data = conn.recv(65536) - if not data: - return - buf += data - - def handle(self, msg): - typ = msg[4] - tag = struct.unpack('<H', msg[5:7])[0] - b = msg[7:] - self.ops += 1 - r = Reader(b) - if typ == Tversion: - msize = r.u32() - ver = r.str() - self.msize = min(msize, 1 << 20) - self.fids = {} - if self.mode == 'msize_tiny': - self.msize = 64 - if self.mode == 'version_unknown': - return self.frame(Rversion, NOTAG, s32(self.msize) + sstr('unknown')) - return self.frame(Rversion, NOTAG, s32(self.msize) + sstr('9P2000')) - if typ == Tauth: - return self.err(tag, 'authentication not required') - if typ == Tattach: - fid = r.u32() - r.u32() - r.str() - r.str() - if fid in self.fids: - return self.err(tag, 'fid in use') - self.fids[fid] = [self.root, False] - return self.frame(Rattach, tag, self.root.qid(self)) - if typ == Tflush: - return self.frame(Rflush, tag, b'') - if typ == Twalk: - fid, newfid, nw = r.u32(), r.u32(), r.u16() - names = [r.str() for _ in range(nw)] - if fid not in self.fids: - return self.err(tag, 'unknown fid') - if newfid != fid and newfid in self.fids: - return self.err(tag, 'fid in use') - node = self.fids[fid][0] - qids = b'' - n = 0 - for name in names: - if not node.isdir: - break - if name == '..': - nxt = node # root's parent is itself; good enough - elif name in node.children: - nxt = node.children[name] - else: - break - node = nxt - qids += node.qid(self) - n += 1 - if n < nw and n == 0: - if self.mode == 'rwalk_zero' and names == ['nope']: - return self.frame(Rwalk, tag, s16(0)) - if self.mode == 'rerror_big' and names == ['nope']: - return self.err(tag, 'x' * 65535) - return self.err(tag, 'file does not exist') - if n == nw: - self.fids[newfid] = [node, False] - if self.mode == 'rwalk_many' and names == ['f']: - return self.frame(Rwalk, tag, s16(n + 1) + qids + node.qid(self)) - return self.frame(Rwalk, tag, s16(n) + qids) - if typ == Topen: - fid, mode = r.u32(), r.u8() - if fid not in self.fids: - return self.err(tag, 'unknown fid') - node = self.fids[fid][0] - if node.isdir and (mode & 3) != 0: - return self.err(tag, 'is a directory') - if mode & 0x10 and not node.isdir: - node.content = bytearray() - self.fids[fid][1] = True - iounit = 1 if self.mode == 'iounit_one' else 0 - return self.frame(Ropen, tag, node.qid(self) + s32(iounit)) - if typ == Tcreate: - fid = r.u32() - name = r.str() - perm = r.u32() - mode = r.u8() - if fid not in self.fids: - return self.err(tag, 'unknown fid') - parent = self.fids[fid][0] - if not parent.isdir: - return self.err(tag, 'not a directory') - if name in parent.children: - return self.err(tag, 'file already exists') - node = self.mk(parent, name, bool(perm & DMDIR)) - node.mode = perm & 0o777 - self.fids[fid] = [node, True] - return self.frame(Rcreate, tag, node.qid(self) + s32(0)) - if typ == Tread: - fid, off, count = r.u32(), r.u64(), r.u32() - if fid not in self.fids: - return self.err(tag, 'unknown fid') - node, opened = self.fids[fid] - if not opened: - return self.err(tag, 'fid not open') - if node.isdir: - return self.readdir(tag, node, off, count) - if node.name == 'fids': - data = ('%d\n' % len(self.fids)).encode() - data = data[off:off + count] - return self.frame(Rread, tag, s32(len(data)) + data) - data = bytes(node.content[off:off + count]) - if node is self.root.children.get('f'): - m = self.mode - if m == 'trunc': - fr = self.frame(Rread, tag, s32(len(data)) + data) - self.conn.sendall(fr[:len(fr) // 2]) - return None - if m == 'short_frame': - return s32(3) + s8(Rread) + s16(tag) - if m == 'huge_frame': - return s32(self.msize + 1) + s8(Rread) + s16(tag) + s32(len(data)) + data - if m == 'wrong_tag': - return self.frame(Rread, (tag + 1) & 0xFFFF, s32(len(data)) + data) - if m == 'rread_big': - data = b'x' * (count + 1) - return self.frame(Rread, tag, s32(len(data)) + data) - if m == 'extra_reply': - return self.frame(Rclunk, 9, b'') + self.frame(Rread, tag, s32(len(data)) + data) - if m == 'never': - time.sleep(3600) - return None - if m == 'close_mid': - return None - if m == 'renegotiate': - return self.frame(Rversion, NOTAG, s32(self.msize) + sstr('9P2000')) + self.frame(Rread, tag, s32(len(data)) + data) - return self.frame(Rread, tag, s32(len(data)) + data) - if typ == Twrite: - fid, off = r.u32(), r.u64() - data = r.data() - if fid not in self.fids: - return self.err(tag, 'unknown fid') - node, opened = self.fids[fid] - if not opened or node.isdir: - return self.err(tag, 'fid not open for write') - if node.name == 'fids': - return self.err(tag, 'permission denied') - if off > len(node.content): - node.content.extend(b'\0' * (off - len(node.content))) - node.content[off:off + len(data)] = data - node.mtime = int(time.time()) - n = len(data) + 1 if self.mode == 'rwrite_big' else len(data) - return self.frame(Rwrite, tag, s32(n)) - if typ == Tclunk: - fid = r.u32() - if fid not in self.fids: - return self.err(tag, 'unknown fid') - del self.fids[fid] - return self.frame(Rclunk, tag, b'') - if typ == Tremove: - fid = r.u32() - if fid not in self.fids: - return self.err(tag, 'unknown fid') - node = self.fids[fid][0] - del self.fids[fid] - if node is self.root: - return self.err(tag, 'cannot remove root') - if node.isdir and node.children: - return self.err(tag, 'directory not empty') - parent = self.find_parent(self.root, node) - if parent is not None: - del parent.children[node.name] - self.free_paths.append(node.path) - node.removed = True - return self.frame(Rremove, tag, b'') - if typ == Tstat: - fid = r.u32() - if fid not in self.fids: - return self.err(tag, 'unknown fid') - node = self.fids[fid][0] - if node is self.root.children.get('f'): - m = self.mode - if m == 'wrong_type': - return self.frame(Rwalk, tag, s16(0)) - if m == 'rstat_garbage': - junk = bytes([0xAB] * 60) - return self.frame(Rstat, tag, s16(len(junk)) + junk) - if m == 'rstat_overlong': - st = node.stat_bytes(self) - inner = st[2:] - return self.frame(Rstat, tag, s16(len(inner) + 5) + inner) - if m == 'length_max': - st = node.stat_bytes(self, length=2 ** 64 - 1) - return self.frame(Rstat, tag, s16(len(st)) + st) - st = node.stat_bytes(self) - return self.frame(Rstat, tag, s16(len(st)) + st) - if typ == Twstat: - fid = r.u32() - r.u16() - st = r.bytes(r.u16()) - if fid not in self.fids: - return self.err(tag, 'unknown fid') - node = self.fids[fid][0] - sr = Reader(st) - sr.u16(); sr.u32(); sr.bytes(13) - mode = sr.u32(); sr.u32(); mtime = sr.u32(); length = sr.u64() - name = sr.str() - if name and name != node.name: - if self.mode == 'rename_fail': - return self.err(tag, 'file already exists') - parent = self.find_parent(self.root, node) - if name in parent.children: - return self.err(tag, 'file already exists') - del parent.children[node.name] - node.name = name - parent.children[name] = node - if mode != 0xFFFFFFFF: - node.mode = mode & 0o777 - if mtime != 0xFFFFFFFF: - node.mtime = mtime - if length != 0xFFFFFFFFFFFFFFFF and not node.isdir: - if length < len(node.content): - del node.content[length:] - else: - node.content.extend(b'\0' * (length - len(node.content))) - return self.frame(Rwstat, tag, b'') - return self.err(tag, 'unsupported message') - - def find_parent(self, cur, node): - for c in cur.children.values(): - if c is node: - return cur - if c.isdir: - p = self.find_parent(c, node) - if p is not None: - return p - return None - - def readdir(self, tag, node, off, count): - recs = [] - if node is self.root: - m = self.mode - if m == 'name_slash': - recs.append(node.stat_bytes(self, name='a/b')) - if m == 'name_empty': - recs.append(node.stat_bytes(self, name='')) - if m == 'name_huge': - recs.append(node.stat_bytes(self, name='h' * 60000)) - if m == 'name_dots': - recs.append(node.stat_bytes(self, name='.')) - recs.append(node.stat_bytes(self, name='..')) - for c in node.children.values(): - recs.append(c.stat_bytes(self)) - blob = b''.join(recs) - if node is self.root and self.mode == 'dir_forever': - return self.frame(Rread, tag, s32(len(blob)) + blob) - if node is self.root and self.mode == 'dir_split': - # first read: up to the middle of the second record; second read: the rest - cut = len(recs[0]) + len(recs[1]) // 2 - if off == 0: - data = blob[:cut] - elif off == cut: - data = blob[cut:] - else: - data = b'' - return self.frame(Rread, tag, s32(len(data)) + data) - # 9P rule: offset 0 or previous offset+count; never split a record. - out = b'' - pos = 0 - for rec in recs: - if pos >= off and len(out) + len(rec) <= count: - out += rec - elif pos >= off: - break - pos += len(rec) - return self.frame(Rread, tag, s32(len(out)) + out) - - -class Reader: - def __init__(self, b): - self.b = b - self.i = 0 - - def bytes(self, n): - v = self.b[self.i:self.i + n] - self.i += n - return v - - def u8(self): return struct.unpack('<B', self.bytes(1))[0] - def u16(self): return struct.unpack('<H', self.bytes(2))[0] - def u32(self): return struct.unpack('<I', self.bytes(4))[0] - def u64(self): return struct.unpack('<Q', self.bytes(8))[0] - def str(self): return self.bytes(self.u16()).decode('utf-8', 'surrogateescape') - def data(self): return self.bytes(self.u32()) - - -def main(): - if len(sys.argv) != 3: - print(__doc__ + MODES) - sys.exit(2) - path, mode = sys.argv[1], sys.argv[2] - if mode not in [l.split()[0] for l in MODES.strip().splitlines()]: - print('unknown mode', mode) - sys.exit(2) - try: - os.unlink(path) - except FileNotFoundError: - pass - ls = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - ls.bind(path) - ls.listen(8) - while True: - conn, _ = ls.accept() - srv = Server(mode) - srv.conn = conn - try: - srv.serve(conn) - except (BrokenPipeError, ConnectionResetError): - pass - finally: - conn.close() - - -if __name__ == '__main__': - main() |
