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 };