io_indexed.py (5744B)
1 #!/usr/bin/env python3 2 """Independent framed/authenticated indexed-read and bounded-transfer controls.""" 3 import argparse 4 import hashlib 5 import json 6 from pathlib import Path 7 import struct 8 from source_order import Runner 9 from process_rss import run_guarded 10 from native_indexed_store import indexed_bytes, digest 11 from executable_object import INDEXED, indexed_manifest 12 from http_headers import number, result, tree_list 13 14 ROOT = Path(__file__).resolve().parent.parent 15 16 def text(s): 17 return tree_list([number(ord(c)) for c in s]) 18 19 def problem(category, detail, progress): 20 return result(False, tree_list([text(category), text('indexed-tree/read'), text(detail), text(progress)])) 21 22 def padded(size): 23 # Valid noncritical opaque section grows TRANSPORT, not the decoded DAG. 24 # This is not a large-DAG memory or compiler-capacity benchmark. 25 manifest = indexed_manifest(0) 26 nodes = struct.pack('>QIB', 1, 1, 0) 27 def section(kind, flags, offset, count): 28 return struct.pack('>IHHIQQI', kind, 1, flags, 0, offset, count, 0) 29 offset = 128 + len(manifest) + len(nodes) 30 return (b'ARBORICX' + struct.pack('>HHIQQ', 1, 0, 3, 0, 32) 31 + section(1, 1, 128, len(manifest)) 32 + section(2, 1, 128 + len(manifest), len(nodes)) 33 + section(99, 0, offset, size) + manifest + nodes + b'x' * size) 34 35 def main(): 36 parser = argparse.ArgumentParser(description=__doc__) 37 parser.add_argument('tricu', type=Path) 38 parser.add_argument('--directory', required=True, type=Path) 39 args = parser.parse_args() 40 directory = args.directory.resolve() 41 directory.mkdir(parents=True, exist_ok=False) 42 scratch = directory/'spool-λ' 43 scratch.mkdir() 44 runner = Runner(args.tricu, directory) 45 source = ROOT/'demos/io/indexed-read.tri' 46 bundle = directory/'indexed-read.arboricx' 47 runner.run('compile', ['compile', '--file', source, '--store', runner.store, '--output', bundle]) 48 pin = runner.imported(bundle, 'import') 49 checks = [] 50 def check(label, payload, size, limit, expected_digest, expected_reply, position): 51 path = directory/(label+'.bin') 52 path.write_bytes(payload) 53 values = [text(str(path)), text(str(scratch)), number(limit), number(size), text(expected_digest)] 54 argv = ['--io'] 55 for i, value in enumerate(values): 56 argument = directory/f'{label}-{i}.tree' 57 argument.write_bytes(value) 58 argv += ['--arg-tree-file', argument] 59 expected = tree_list([expected_reply, number(position)]) 60 assert runner.invoke(label, pin, 'probe', argv) == expected, label 61 assert path.read_bytes() == payload, label 62 assert list(scratch.iterdir()) == [], label 63 checks.append(dict(label=label, input_bytes=len(payload), frame_bytes=size)) 64 for i, tree in enumerate([b'\0', b'\1\0', b'\2\1\0\0', number(42)]): 65 payload = indexed_bytes(tree) 66 check(f'tree-{i}', payload+b'next frame', len(payload), len(payload), digest(INDEXED, payload), result(True, tree), len(payload)) 67 payload = indexed_bytes(number(7)) 68 check('wrong-digest', payload, len(payload), len(payload), '0'*64, 69 problem('integrity-failed', 'CAS digest mismatch', 'unknown'), len(payload)) 70 check('truncated', payload[:-1], len(payload), len(payload), digest(INDEXED, payload), 71 problem('invalid-data', 'incomplete indexed frame', 'unknown'), len(payload)-1) 72 check('limit', payload, len(payload), len(payload)-1, digest(INDEXED, payload), 73 problem('invalid-bound', 'invalid snapshot size limit', 'none'), 0) 74 check('bad-digest', payload, len(payload), len(payload), 'A'*64, 75 problem('invalid-argument', '', 'none'), 0) 76 check('bad-bound-wire', payload, 2**63, 2**63, digest(INDEXED, payload), 77 problem('invalid-argument', '', 'none'), 0) 78 malformed = b'not a bundle' 79 check('malformed-authenticated', malformed, len(malformed), len(malformed), digest(INDEXED, malformed), 80 problem('invalid-data', 'section extends beyond bundle end', 'unknown'), len(malformed)) 81 check('malformed-unauthenticated', malformed, len(malformed), len(malformed), '0'*64, 82 problem('integrity-failed', 'CAS digest mismatch', 'unknown'), len(malformed)) 83 for size in [4096, 1048576, 16777216, 67108864]: 84 payload = padded(size) 85 check(f'transport-{size}', payload, len(payload), len(payload), digest(INDEXED, payload), result(True, b'\0'), len(payload)) 86 observations = [s for s in runner.records if s['label'].startswith('transport-')] 87 for sample in observations: 88 assert sample['peak_kib'] <= 48*1024, sample 89 assert sample['rts_process']['maximum_live_bytes'] <= 16*1024*1024, sample 90 files = ['lib/io.tri', 'demos/io/indexed-read.tri', 'test/io_indexed.py', 91 'test/source_order.py', 'test/rts_metrics.py', 'test/native_indexed_store.py', 92 'test/executable_object.py', 'test/stable_bundle.py', 'src/ContentStore/Snapshot.hs', 93 'src/ContentStore/Indexed.hs', 'src/Wire.hs', 'src/IO/Codec.hs', 94 'src/IO/Resources.hs', 'src/IO/Driver.hs', 'src/IO/Protocol.hs', 95 'bootstrap/build-stable.arboricx'] 96 (directory/'report.json').write_text(json.dumps(dict(checks=checks, observations=observations, 97 98 measurement_notes='Opaque extension grows transport, not DAG. RSS is sampled: tiny runs can exit before representative samples; use the 64-MiB control for the transfer RSS guard. No concurrency or large-DAG capacity claim.', 99 source_hashes={p:hashlib.sha256((ROOT/p).read_bytes()).hexdigest() for p in files}), indent=2)+'\n') 100 print(f'{len(checks)} indexed-read controls passed') 101 102 if __name__ == '__main__': 103 main()