lib/choir/src/composition/abi/frame.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const contract = @import("contract.zig");
3 const Context = @import("context.zig").Context;
4
5 const Access = contract.Access;
6 const Status = contract.Status;
7
8 /// One invocation-scoped byte resource with explicit access.
9 /// The pointed-to bytes remain owned by the invocation caller.
10 pub const Resource = extern struct {
11 context_cookie: u64,
12 invocation_id: u64,
13 handle: u64,
14 byte_size: u64,
15 access: u32,
16 reserved: u32 = 0,
17
18 pub fn init(
19 context_cookie: u64,
20 invocation_id: u64,
21 buffer: []u8,
22 access_value: Access,
23 ) Resource {
24 return .{
25 .context_cookie = context_cookie,
26 .invocation_id = invocation_id,
27 .handle = @intFromPtr(buffer.ptr),
28 .byte_size = buffer.len,
29 .access = @backingInt(access_value),
30 };
31 }
32
33 pub fn validate(self: Resource, frame: *const Frame) Status {
34 if (self.context_cookie != frame.context_cookie or self.invocation_id != frame.invocation_id) return .invalid_resource;
35 if (self.handle == 0 or self.byte_size == 0) return .invalid_resource;
36 if (self.accessValue() == null) return .invalid_resource;
37 return .ok;
38 }
39
40 pub fn accessValue(self: Resource) ?Access {
41 return contract.decodeAccess(self.access);
42 }
43
44 pub fn bytes(self: Resource) StatusOrBytes {
45 const length = std.math.cast(usize, self.byte_size) orelse return .{ .status = .invalid_resource };
46 if (self.handle == 0 or length == 0) return .{ .status = .invalid_resource };
47 const pointer: [*]u8 = @ptrFromInt(self.handle);
48 return .{ .value = pointer[0..length] };
49 }
50 };
51
52 /// Versioned invocation header and borrowed resource table.
53 /// Callers retain frame storage until synchronous return.
54 pub const Frame = extern struct {
55 version: u32,
56 byte_size: u32,
57 invocation_id: u64,
58 context_cookie: u64,
59 resources_address: u64,
60 resource_count: u32,
61 status: u32,
62
63 pub fn init(
64 context_cookie: u64,
65 invocation_id: u64,
66 resource_values: []Resource,
67 ) Frame {
68 std.debug.assert(resource_values.len <= contract.max_resources);
69 return .{
70 .version = contract.version,
71 .byte_size = @sizeOf(Frame),
72 .invocation_id = invocation_id,
73 .context_cookie = context_cookie,
74 .resources_address = if (resource_values.len == 0) 0 else @intFromPtr(resource_values.ptr),
75 .resource_count = @intCast(resource_values.len),
76 .status = @backingInt(Status.ok),
77 };
78 }
79
80 pub fn validate(self: *const Frame, context: *const Context) Status {
81 if (self.version != contract.version or self.byte_size < @sizeOf(Frame)) return .incompatible_frame;
82 if (self.context_cookie != context.owner_cookie or self.invocation_id == 0) return .incompatible_frame;
83 if (self.resource_count > contract.max_resources) return .incompatible_frame;
84 if (self.resource_count != 0 and self.resources_address == 0) return .incompatible_frame;
85 return .ok;
86 }
87
88 pub fn resources(self: *const Frame, context: *const Context) StatusOrResources {
89 const frame_status = self.validate(context);
90 if (frame_status != .ok) return .{ .status = frame_status };
91 if (self.resource_count == 0) return .{ .value = &.{} };
92 const count = std.math.cast(usize, self.resource_count) orelse return .{ .status = .incompatible_frame };
93 const pointer: [*]Resource = @ptrFromInt(self.resources_address);
94 const values = pointer[0..count];
95 for (values) |resource| {
96 const resource_status = resource.validate(self);
97 if (resource_status != .ok) return .{ .status = resource_status };
98 }
99 return .{ .value = values };
100 }
101
102 pub fn setStatus(self: *Frame, value: Status) void {
103 self.status = @backingInt(value);
104 }
105
106 pub fn setStatusIfCompatible(self: *Frame, value: Status) void {
107 if (self.version == contract.version and self.byte_size >= @sizeOf(Frame)) self.setStatus(value);
108 }
109
110 pub fn statusValue(self: Frame) Status {
111 return contract.decodeStatus(self.status);
112 }
113 };
114
115 pub const StatusOrResources = union(enum) {
116 status: Status,
117 value: []Resource,
118 };
119
120 pub const StatusOrBytes = union(enum) {
121 status: Status,
122 value: []u8,
123 };