strat9_abi/ipc_payload.rs
1//! Typed `repr(C)` payload structs for fixed-size IPC messages.
2//!
3//! Each struct derives zerocopy's `FromBytes + IntoBytes + Immutable`,
4//! enabling safe zero-cost cast to/from [`IpcMessage`] payloads via
5//! [`encode_fixed`] / [`decode_fixed`].
6//!
7//! # Conventions
8//!
9//! - All sizes are ≤ 48 bytes (the payload capacity).
10//! - Variable-length path/data fields use an [`InlineBlobHeader`] prefix.
11//! - `status == 0` means success; non-zero is an errno-compatible error code.
12//! - Padding fields are named `_pad` or `_reserved` and must be zero.
13//!
14//! # Wire format
15//!
16//! Messages are sent through IPC ports or channels as raw bytes.
17//! The `msg_type` field in the `IpcMessage` header identifies the operation.
18//! The `payload` field contains the struct-specific data.
19//!
20//! # Example
21//!
22//! ```ignore
23//! use strat9_abi::ipc_codec::{encode_fixed, decode_fixed, put_str, InlineBlobHeader};
24//! use strat9_abi::ipc_payload::{OpenRequest, OpenReply};
25//!
26//! // Encode an open request with path "/tmp/test"
27//! let mut msg = encode_fixed(0x01, &OpenRequest {
28//! flags: 0x02, // WRITE
29//! path_hdr: InlineBlobHeader { len: 10, kind: 0 },
30//! });
31//! put_str(&mut msg.payload, 8, "/tmp/test").unwrap();
32//!
33//! // Decode the reply
34//! let reply: &OpenReply = decode_fixed(&reply_msg).unwrap();
35//! assert_eq!(reply.status, 0); // success
36//! ```
37
38use zerocopy::{FromBytes, Immutable, IntoBytes};
39
40use crate::ipc_codec::InlineBlobHeader;
41
42// ===========================================================================
43// Helpers for compile-time size checks
44// ===========================================================================
45
46macro_rules! assert_payload_size {
47 ($ty:ty) => {
48 static_assertions::const_assert!(core::mem::size_of::<$ty>() <= 48);
49 };
50}
51
52// ===========================================================================
53// Generic status-only reply (used by every scheme)
54// ===========================================================================
55
56/// Minimal reply carrying only a status code.
57///
58/// Used by scheme handlers that don't need to return additional data.
59///
60/// Wire layout: `status @ 0..4`.
61///
62/// # Example
63///
64/// ```ignore
65/// let reply = StatusReply { status: 0 }; // success
66/// let err = StatusReply { status: 13 }; // EACCES
67/// ```
68#[repr(C)]
69#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
70pub struct StatusReply {
71 /// Status code: 0 = success, non-zero = errno.
72 pub status: u32,
73}
74assert_payload_size!(StatusReply);
75
76// ===========================================================================
77// VFS / file-system scheme payloads
78// ===========================================================================
79
80/// Open request.
81///
82/// Wire layout:
83/// `flags @ 0..4`, `path_hdr @ 4..8` (InlineBlobHeader), `path_data @ 8..`.
84///
85/// The path is variable-length and follows the header at offset 8.
86#[repr(C)]
87#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
88pub struct OpenRequest {
89 /// Open flags (`O_RDONLY`, `O_WRONLY`, `O_RDWR`, `O_CREAT`, etc.).
90 pub flags: u32,
91 /// InlineBlobHeader describing the path that follows.
92 /// `kind` should be `0` for a plain path.
93 pub path_hdr: InlineBlobHeader,
94}
95assert_payload_size!(OpenRequest);
96
97/// Open reply.
98///
99/// Wire layout:
100/// `status @ 0..4`, `_pad0 @ 4..8`, `ino @ 8..16`, `size @ 16..24`,
101/// `file_flags @ 24..28`, `_pad1 @ 28..32`.
102///
103/// # Example
104///
105/// ```ignore
106/// let reply = OpenReply {
107/// status: 0,
108/// _pad0: 0,
109/// ino: 42,
110/// size: 1024,
111/// file_flags: 0x01, // readable
112/// _pad1: 0,
113/// };
114/// ```
115#[repr(C)]
116#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
117pub struct OpenReply {
118 /// Status code: 0 = success.
119 pub status: u32,
120 pub _pad0: u32,
121 /// Inode number of the opened file.
122 pub ino: u64,
123 /// File size in bytes.
124 pub size: u64,
125 /// File flags (device, pipe, append, etc.).
126 pub file_flags: u32,
127 pub _pad1: u32,
128}
129assert_payload_size!(OpenReply);
130static_assertions::assert_eq_size!(OpenReply, [u8; 32]);
131
132/// Read request.
133///
134/// Wire layout:
135/// `ino @ 0..8`, `offset @ 8..16`, `count @ 16..20`, `_pad @ 20..24`.
136#[repr(C)]
137#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
138pub struct ReadRequest {
139 /// Inode number of the file to read.
140 pub ino: u64,
141 /// Byte offset in the file to start reading from.
142 pub offset: u64,
143 /// Maximum number of bytes to read.
144 pub count: u32,
145 pub _pad: u32,
146}
147assert_payload_size!(ReadRequest);
148static_assertions::assert_eq_size!(ReadRequest, [u8; 24]);
149
150/// Read reply prefix (variable-length data follows at offset 8).
151///
152/// Wire layout:
153/// `status @ 0..4`, `count @ 4..8`, `data @ 8..`.
154///
155/// The `data` field contains `count` bytes of file content.
156#[repr(C)]
157#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
158pub struct ReadReply {
159 /// Status code: 0 = success.
160 pub status: u32,
161 /// Number of data bytes written starting at offset 8.
162 pub count: u32,
163}
164assert_payload_size!(ReadReply);
165
166/// Write request with variable-length inline data.
167///
168/// Wire layout:
169/// `ino @ 0..8`, `offset @ 8..16`, `data_hdr @ 16..20`, `_pad @ 20..24`,
170/// `data @ 24..`.
171///
172/// The data is variable-length and follows the header at offset 24.
173#[repr(C)]
174#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
175pub struct WriteRequest {
176 /// Inode number of the file to write.
177 pub ino: u64,
178 /// Byte offset in the file to start writing.
179 pub offset: u64,
180 /// InlineBlobHeader describing the data that follows.
181 pub data_hdr: InlineBlobHeader,
182 pub _pad: u32,
183}
184assert_payload_size!(WriteRequest);
185static_assertions::assert_eq_size!(WriteRequest, [u8; 24]);
186
187/// Write reply.
188///
189/// Wire layout: `status @ 0..4`, `written @ 4..8`.
190#[repr(C)]
191#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
192pub struct WriteReply {
193 /// Status code: 0 = success.
194 pub status: u32,
195 /// Number of bytes actually written.
196 pub written: u32,
197}
198assert_payload_size!(WriteReply);
199
200/// Create request (file or directory).
201///
202/// Wire layout:
203/// `mode @ 0..4`, `path_hdr @ 4..8`, `path_data @ 8..`.
204///
205/// The `mode` field specifies permissions (e.g. `0o755` for directories).
206/// The path is variable-length and follows the header at offset 8.
207#[repr(C)]
208#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
209pub struct CreateRequest {
210 /// Permission mode (e.g. `0o644` for files, `0o755` for directories).
211 pub mode: u32,
212 /// InlineBlobHeader describing the path that follows.
213 pub path_hdr: InlineBlobHeader,
214}
215assert_payload_size!(CreateRequest);
216
217/// Create reply.
218///
219/// Wire layout:
220/// `status @ 0..4`, `_pad @ 4..8`, `ino @ 8..16`.
221#[repr(C)]
222#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
223pub struct CreateReply {
224 /// Status code: 0 = success.
225 pub status: u32,
226 pub _pad: u32,
227 /// Inode number of the newly created file/directory.
228 pub ino: u64,
229}
230assert_payload_size!(CreateReply);
231static_assertions::assert_eq_size!(CreateReply, [u8; 16]);
232
233/// Close request.
234///
235/// Wire layout: `ino @ 0..8`.
236#[repr(C)]
237#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
238pub struct CloseRequest {
239 /// Inode number of the file to close.
240 pub ino: u64,
241}
242assert_payload_size!(CloseRequest);
243
244/// Stat request.
245///
246/// Wire layout: `ino @ 0..8`.
247#[repr(C)]
248#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
249pub struct StatRequest {
250 /// Inode number to query.
251 pub ino: u64,
252}
253assert_payload_size!(StatRequest);
254
255/// Lseek request.
256///
257/// Wire layout: `ino @ 0..8`, `offset @ 8..16`, `whence @ 16..20`, `_pad @ 20..24`.
258#[repr(C)]
259#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
260pub struct LseekRequest {
261 /// Inode number of the file.
262 pub ino: u64,
263 /// Offset relative to `whence` (can be negative).
264 pub offset: i64,
265 /// Whence: `SEEK_SET` (0), `SEEK_CUR` (1), or `SEEK_END` (2).
266 pub whence: u32,
267 pub _pad: u32,
268}
269assert_payload_size!(LseekRequest);
270static_assertions::assert_eq_size!(LseekRequest, [u8; 24]);
271
272/// Lseek reply.
273///
274/// Wire layout:
275/// `status @ 0..4`, `_pad @ 4..8`, `offset @ 8..16`.
276#[repr(C)]
277#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
278pub struct LseekReply {
279 /// Status code: 0 = success.
280 pub status: u32,
281 pub _pad: u32,
282 /// New file position (absolute offset from beginning of file).
283 pub offset: i64,
284}
285assert_payload_size!(LseekReply);
286static_assertions::assert_eq_size!(LseekReply, [u8; 16]);
287
288// ===========================================================================
289// Network scheme payloads
290// ===========================================================================
291
292/// TCP connect request.
293///
294/// Wire layout:
295/// `port @ 0..4`, `addr_hdr @ 4..8`, `addr_data @ 8..`.
296///
297/// The address is variable-length and follows the header at offset 8.
298/// For IPv4: 4 bytes of network-order octets.
299/// For IPv6: 16 bytes of network-order octets.
300#[repr(C)]
301#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
302pub struct TcpConnectRequest {
303 /// Target port number (host byte order).
304 pub port: u16,
305 pub _padding: [u8; 2],
306 /// InlineBlobHeader describing the address that follows.
307 pub addr_hdr: InlineBlobHeader,
308}
309assert_payload_size!(TcpConnectRequest);
310
311/// TCP connect reply.
312///
313/// Wire layout:
314/// `status @ 0..4`, `_pad @ 4..8`, `conn_id @ 8..16`.
315#[repr(C)]
316#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
317pub struct TcpConnectReply {
318 /// Status code: 0 = success.
319 pub status: u32,
320 pub _pad: u32,
321 /// Connection identifier for subsequent read/write operations.
322 pub conn_id: u64,
323}
324assert_payload_size!(TcpConnectReply);
325static_assertions::assert_eq_size!(TcpConnectReply, [u8; 16]);