Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ jobs:
build-command: |
mkdir -p /tmp/space-boot
command nasm -f bin boot/multiboot.asm -o /tmp/space-boot/trampoline.bin
command in compile --path kernel/kernel-root.in --entry kernel_entry --emit boot \
command in compile --path kernel/kernel-root.in --entry kernel-entry --emit boot \
--trampoline /tmp/space-boot/trampoline.bin \
--target native --target-triple x86_64-unknown-none --linkage static-lib \
--out /tmp/space-boot/kernel.bin
test-command: |
echo "Boot image: $(wc -c < /tmp/space-boot/kernel.bin) bytes"
bash scripts/check-spdp-protocol.sh
bash scripts/check-sci-contract.sh

website:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ bash scripts/check-terminal-editor.sh # serial editor save test
bash scripts/check-desktop-visual.sh # desktop / display visual path
bash scripts/check-desktop-damage.sh # moving-window compositor bench
bash scripts/check-spdp-composite.sh # SPDP surface-pipeline proof
bash scripts/check-spdp-protocol.sh # SPDP wire-format / parser contract
bash scripts/check-audit-fixes.sh # kernel-audit hardening assertions
bash scripts/check-linux-elf.sh # Linux ELF personality
bash scripts/check-volume-deep-soak.sh # NVMe volume deep soak
Expand Down
3 changes: 1 addition & 2 deletions components/display.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package space.display
import "../protocol/display.in"
import "font.in"

const SPDP-OBJ-SURFACE = 5

interface DisplayDevice {
fn map(phys: Int, size: Int) -> void
fn present() -> void
Expand Down Expand Up @@ -2334,6 +2332,7 @@ fn dsp-init() -> void {
dsp-surfaces = alloc(DSP-MAX-SURFACES * dsp-surface-size())
dsp-pools = alloc(DSP-MAX-POOLS * 8) // array of pool addresses
dsp-pool-sizes = alloc(DSP-MAX-POOLS * 8) // array of pool sizes (bytes)
dsp-msg = alloc(SPDP-MSG-SIZE)
let i = 0
while i < DSP-MAX-POOLS {
store64(dsp-pools + i * 8, 0)
Expand Down
15 changes: 8 additions & 7 deletions protocol/display.in
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
// Space Display Protocol — Wayland-inspired compositor wire format.
// Objects communicate over channels using a fixed-size message format:
// [obj_id(8)][opcode(8)][argc(8)][args...]
// Each message is exactly 32 bytes (header + up to 3 x 8-byte args).
// [obj_id(8)][opcode(8)][arg0(8)][arg1(8)]
// Each message is exactly 32 bytes (16-byte header + two 8-byte args).

const SPDP-MSG-SIZE = 32
const SPDP-HEADER-SIZE = 24 // obj_id + opcode + argc
const SPDP-HEADER-SIZE = 16 // obj_id + opcode

// Object IDs (well-known)
const SPDP-OBJ-DISPLAY = 1 // global display object
const SPDP-OBJ-COMPOSITOR = 2 // compositor interface
const SPDP-OBJ-SHM = 3 // shared memory interface
const SPDP-OBJ-SEAT = 4 // input seat
const SPDP-OBJ-SURFACE = 5 // surface object

// Display object opcodes (client → server)
const SPDP-GET-REGISTRY = 0 // no args
Expand All @@ -22,8 +23,8 @@ const SPDP-REGISTRY-GLOBAL = 1 // arg0: obj_id, arg1: interface_addr

// Compositor opcodes
const SPDP-COMPOSITOR-CREATE-SURFACE = 0 // returns surface_id
const SPDP-SURFACE-ATTACH = 1 // arg0: surface_id, arg1: buffer_id
const SPDP-SURFACE-DAMAGE = 2 // arg0: surface_id, arg1: x/w packed
const SPDP-SURFACE-ATTACH = 1 // arg0: surface_id, arg1: pool_id<<32 | offset
const SPDP-SURFACE-DAMAGE = 2 // arg0: surface_id, arg1: x<<48|y<<32|w<<16|h

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fix geometry field order in compositor records

For any surface where x != y or w != h, this newly specified packing is decoded by dsp-surface-geometry, but that function stores (w << 16) | h and (x << 16) | y, while dsp-composite reads the low halves as width and x. The resulting surface is rendered at (y, x) with dimensions (h, w). The added check misses the mismatch because it uses equal coordinates and square dimensions and only round-trips Python helpers; align the record storage/read order with this wire contract and cover asymmetric values.

Useful? React with 👍 / 👎.

const SPDP-SURFACE-COMMIT = 3 // arg0: surface_id
const SPDP-SURFACE-DESTROY = 4 // arg0: surface_id

Expand All @@ -45,8 +46,8 @@ const SPDP-SEAT-KEYBOARD-KEY = 4 // arg0: time, arg1: key/state packed
// Surface life cycle:
// 1. Client sends COMPOSITOR_CREATE_SURFACE → receives surface_id
// 2. Client creates an SHM pool via SHM_CREATE_POOL
// 3. Client sends SURFACE_ATTACH(surface_id, buffer_id) to associate buffer
// 4. Client sends SURFACE_DAMAGE(surface_id, x, y, w, h) to mark dirty region
// 3. Client sends SURFACE_ATTACH(surface_id, pool_id<<32 | offset)
// 4. Client sends SURFACE_DAMAGE(surface_id, x<<48|y<<32|w<<16|h) to mark dirty region
// 5. Client sends SURFACE_COMMIT(surface_id) to present
// 6. Compositor renders the surface, sends frame callback
//
Expand Down
138 changes: 138 additions & 0 deletions scripts/check-spdp-protocol.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"exec" "python3" "$0" "$@"
# check-spdp-protocol.sh — Host-side SPDP wire-format + compositor parse checks.
# No QEMU / compiler. Run: bash scripts/check-spdp-protocol.sh
"""Validate protocol/display.in against the compositor's 32-byte SPDP frames.

The kernel parser in components/display.in (dsp-run / dsp-handle-msg) is the
source of truth: four little-endian u64 words, no argc field.
"""
import os, re, struct, sys

passed, failed = 0, 0

def check(label, ok):
global passed, failed
if ok:
print(f" ok: {label}")
passed += 1
else:
print(f" FAIL: {label}")
failed += 1

def parse_consts(text):
found = {}
for name, val in re.findall(r"^const (SPDP-[A-Z0-9-]+) = (\d+)", text, re.M):
found[name] = int(val)
return found

def encode(obj_id, opcode, arg0, arg1):
return struct.pack("<QQQQ", obj_id, opcode, arg0, arg1)

def decode(buf):
if len(buf) != 32:
raise ValueError("SPDP frame must be 32 bytes")
return struct.unpack("<QQQQ", buf)

def pack_attach(pool_id, offset):
return ((pool_id & 0xFFFFFFFF) << 32) | (offset & 0xFFFFFFFF)

def pack_geometry(x, y, w, h):
return ((x & 0xFFFF) << 48) | ((y & 0xFFFF) << 32) | ((w & 0xFFFF) << 16) | (h & 0xFFFF)

def unpack_attach(packed):
return (packed >> 32) & 0xFFFFFFFF, packed & 0xFFFFFFFF

def unpack_geometry(packed):
return (
(packed >> 48) & 0xFFFF,
(packed >> 32) & 0xFFFF,
(packed >> 16) & 0xFFFF,
packed & 0xFFFF,
)

def main():
here = os.path.dirname(os.path.abspath(__file__))
space = os.path.abspath(os.path.join(here, ".."))
proto_path = os.path.join(space, "protocol", "display.in")
display_path = os.path.join(space, "components", "display.in")
ci_path = os.path.join(space, ".github", "workflows", "ci.yml")

proto = open(proto_path).read()
display = open(display_path).read()
ci = open(ci_path).read()
consts = parse_consts(proto)

print("[1/4] Protocol constants and frame layout...")
check("SPDP-MSG-SIZE == 32", consts.get("SPDP-MSG-SIZE") == 32)
check("SPDP-HEADER-SIZE == 16 (obj_id + opcode, no argc)",
consts.get("SPDP-HEADER-SIZE") == 16)
check("header + two args == message size",
consts.get("SPDP-HEADER-SIZE", 0) + 16 == consts.get("SPDP-MSG-SIZE", -1))
check("protocol does not reserve an argc word",
"argc" not in proto.lower())

for name, val in [
("SPDP-OBJ-DISPLAY", 1),
("SPDP-OBJ-COMPOSITOR", 2),
("SPDP-OBJ-SHM", 3),
("SPDP-OBJ-SEAT", 4),
("SPDP-OBJ-SURFACE", 5),
("SPDP-COMPOSITOR-CREATE-SURFACE", 0),
("SPDP-SURFACE-ATTACH", 1),
("SPDP-SURFACE-DAMAGE", 2),
("SPDP-SURFACE-COMMIT", 3),
("SPDP-SHM-CREATE-POOL", 0),
]:
check(f"{name} == {val}", consts.get(name) == val)

print("[2/4] Encode/decode packed arguments...")
surface = consts.get("SPDP-OBJ-SURFACE", 5)
attach_op = consts.get("SPDP-SURFACE-ATTACH", 1)
frame = encode(surface, attach_op, 7, pack_attach(3, 0x1000))
check("encoded frame is 32 bytes", len(frame) == 32)
obj_id, opcode, arg0, arg1 = decode(frame)
check("decode obj_id/opcode/arg0", obj_id == surface and opcode == attach_op and arg0 == 7)
pool, off = unpack_attach(arg1)
check("SURFACE-ATTACH packs pool<<32 | offset", pool == 3 and off == 0x1000)

gx, gy, gw, gh = unpack_geometry(pack_geometry(100, 100, 64, 64))
check("SURFACE-DAMAGE packs x<<48|y<<32|w<<16|h",
(gx, gy, gw, gh) == (100, 100, 64, 64))

# A client that followed the old argc-at-offset-16 layout would put argc in
# the word the compositor reads as arg0. That must not be the spec.
wrong = struct.pack("<QQQQ", 5, 1, 2, pack_attach(3, 0x1000)) # argc=2 as arg0
w_obj, w_op, w_a0, w_a1 = decode(wrong)
check("argc-shaped frame is distinguishable from attach(sid=7)",
not (w_a0 == 7 and unpack_attach(w_a1) == (3, 0x1000)))
check("argc-shaped frame's arg0 is the bogus argc, not surface_id", w_a0 == 2)

print("[3/4] Compositor parser matches the four-word layout...")
check("dsp-run stores word 0 at dsp-msg+0", "store64(dsp-msg + 0, chan-recv(ch))" in display)
check("dsp-run stores word 1 at dsp-msg+8", "store64(dsp-msg + 8, chan-recv(ch))" in display)
check("dsp-run stores word 2 at dsp-msg+16", "store64(dsp-msg + 16, chan-recv(ch))" in display)
check("dsp-run stores word 3 at dsp-msg+24", "store64(dsp-msg + 24, chan-recv(ch))" in display)
check("dsp-run loads obj-id from +0", "let obj-id = load64(dsp-msg + 0)" in display)
check("dsp-run loads opcode from +8", "let opcode = load64(dsp-msg + 8)" in display)
check("dsp-run loads arg0 from +16", "let arg0 = load64(dsp-msg + 16)" in display)
check("dsp-run loads arg1 from +24", "let arg1 = load64(dsp-msg + 24)" in display)
check("dsp-init allocates the hoisted 32-byte message buffer",
"dsp-msg = alloc(SPDP-MSG-SIZE)" in display)
check("dsp-handle-msg dispatches SURFACE on SPDP-OBJ-SURFACE",
"obj-id == SPDP-OBJ-SURFACE" in display)
check("display.in does not re-declare SPDP-OBJ-SURFACE",
"const SPDP-OBJ-SURFACE" not in display)

print("[4/4] CI compile entry matches kernel-entry...")
check("ci.yml uses --entry kernel-entry", "--entry kernel-entry" in ci)
check("ci.yml does not use C-style kernel_entry", "--entry kernel_entry" not in ci)
check("ci.yml runs check-spdp-protocol.sh", "scripts/check-spdp-protocol.sh" in ci)

print(f"\n=== Results: {passed} passed, {failed} failed ===")
if failed:
print("FAIL: SPDP protocol/parser contract mismatch.")
sys.exit(1)
print("PASS: SPDP 32-byte frames match the compositor parser.")

if __name__ == "__main__":
main()
Loading