Eric Bower
·
2025-11-27
ipc.zig
1const std = @import("std");
2const posix = std.posix;
3
4pub const Tag = enum(u8) {
5 Input = 0,
6 Output = 1,
7 Resize = 2,
8 Detach = 3,
9 DetachAll = 4,
10 Kill = 5,
11 Info = 6,
12 Init = 7,
13};
14
15pub const Header = packed struct {
16 tag: Tag,
17 len: u32,
18};
19
20pub const Resize = packed struct {
21 rows: u16,
22 cols: u16,
23};
24
25pub const Info = packed struct {
26 clients_len: usize,
27 pid: i32,
28};
29
30pub fn expectedLength(data: []const u8) ?usize {
31 if (data.len < @sizeOf(Header)) return null;
32 const header = std.mem.bytesToValue(Header, data[0..@sizeOf(Header)]);
33 return @sizeOf(Header) + header.len;
34}
35
36pub fn send(fd: i32, tag: Tag, data: []const u8) !void {
37 const header = Header{
38 .tag = tag,
39 .len = @intCast(data.len),
40 };
41 const header_bytes = std.mem.asBytes(&header);
42 try writeAll(fd, header_bytes);
43 if (data.len > 0) {
44 try writeAll(fd, data);
45 }
46}
47
48pub fn appendMessage(alloc: std.mem.Allocator, list: *std.ArrayList(u8), tag: Tag, data: []const u8) !void {
49 const header = Header{
50 .tag = tag,
51 .len = @intCast(data.len),
52 };
53 try list.appendSlice(alloc, std.mem.asBytes(&header));
54 if (data.len > 0) {
55 try list.appendSlice(alloc, data);
56 }
57}
58
59fn writeAll(fd: i32, data: []const u8) !void {
60 var index: usize = 0;
61 while (index < data.len) {
62 const n = try posix.write(fd, data[index..]);
63 if (n == 0) return error.DiskQuota;
64 index += n;
65 }
66}
67
68pub const Message = struct {
69 tag: Tag,
70 data: []u8,
71
72 pub fn deinit(self: Message, alloc: std.mem.Allocator) void {
73 if (self.data.len > 0) {
74 alloc.free(self.data);
75 }
76 }
77};
78
79pub const SocketMsg = struct {
80 header: Header,
81 payload: []const u8,
82};
83
84pub const SocketBuffer = struct {
85 buf: std.ArrayList(u8),
86 alloc: std.mem.Allocator,
87 head: usize,
88
89 pub fn init(alloc: std.mem.Allocator) !SocketBuffer {
90 return .{
91 .buf = try std.ArrayList(u8).initCapacity(alloc, 4096),
92 .alloc = alloc,
93 .head = 0,
94 };
95 }
96
97 pub fn deinit(self: *SocketBuffer) void {
98 self.buf.deinit(self.alloc);
99 }
100
101 /// Reads from fd into buffer.
102 /// Returns number of bytes read.
103 /// Propagates error.WouldBlock and other errors to caller.
104 /// Returns 0 on EOF.
105 pub fn read(self: *SocketBuffer, fd: i32) !usize {
106 if (self.head > 0) {
107 const remaining = self.buf.items.len - self.head;
108 if (remaining > 0) {
109 std.mem.copyForwards(u8, self.buf.items[0..remaining], self.buf.items[self.head..]);
110 self.buf.items.len = remaining;
111 } else {
112 self.buf.clearRetainingCapacity();
113 }
114 self.head = 0;
115 }
116
117 var tmp: [4096]u8 = undefined;
118 const n = try posix.read(fd, &tmp);
119 if (n > 0) {
120 try self.buf.appendSlice(self.alloc, tmp[0..n]);
121 }
122 return n;
123 }
124
125 /// Returns the next complete message or `null` when none available.
126 /// `buf` is advanced automatically; caller keeps the returned slices
127 /// valid until the following `next()` (or `deinit`).
128 pub fn next(self: *SocketBuffer) ?SocketMsg {
129 const available = self.buf.items[self.head..];
130 const total = expectedLength(available) orelse return null;
131 if (available.len < total) return null;
132
133 const hdr = std.mem.bytesToValue(Header, available[0..@sizeOf(Header)]);
134 const pay = available[@sizeOf(Header)..total];
135
136 self.head += total;
137 return .{ .header = hdr, .payload = pay };
138 }
139};