strat9_abi/ipc.rs
1//! IPC handshake protocol for connection negotiation.
2//!
3//! When a client connects to a server via IPC, it first sends an
4//! [`IpcHandshake`] message. The server validates the magic number and
5//! protocol version, then replies with an [`IpcHandshakeReply`].
6//!
7//! # Handshake flow
8//!
9//! ```text
10//! Client Server
11//! │ │
12//! │── IpcHandshake ──────────────▶│
13//! │ (magic, version, nonce) │
14//! │ │
15//! │◀── IpcHandshakeReply ─────────│
16//! │ (magic, version, status) │
17//! │ │
18//! │── normal IPC messages ───────▶│
19//! ```
20//!
21//! # Example
22//!
23//! ```ignore
24//! use strat9_abi::ipc::{IpcHandshake, IpcHandshakeReply};
25//!
26//! // Client builds a handshake
27//! let handshake = IpcHandshake::new_with_nonce(0xDEAD_BEEF);
28//! assert!(handshake.is_valid());
29//! assert!(handshake.is_compatible());
30//!
31//! // Server validates and replies
32//! let reply = if handshake.is_compatible() {
33//! IpcHandshakeReply::ok()
34//! } else {
35//! IpcHandshakeReply::reject(1) // VERSION_MISMATCH
36//! };
37//! ```
38
39use zerocopy::{FromBytes, IntoBytes};
40
41/// Magic number for IPC handshake (`"IPC9"` in ASCII).
42///
43/// Both client and server must agree on this value. If the magic doesn't
44/// match, the connection is rejected immediately.
45pub const IPC_HANDSHAKE_MAGIC: u32 = 0x4950_4339; // "IPC9"
46
47/// Current IPC protocol version.
48///
49/// Increment when the handshake format or IPC wire protocol changes.
50/// A version mismatch causes the server to reject the connection.
51pub const IPC_PROTOCOL_VERSION: u16 = 1;
52
53/// First message a client sends after `ipc_connect` to negotiate protocol.
54///
55/// Wire size: 20 bytes.
56///
57/// # Fields
58///
59/// - `magic`: must be [`IPC_HANDSHAKE_MAGIC`] (`0x4950_4339`)
60/// - `protocol_version`: client's IPC protocol version
61/// - `client_abi_major/minor`: client's ABI version
62/// - `nonce`: random value for connection identification (optional)
63/// - `flags`: reserved for future use (must be 0)
64#[derive(Debug, Clone, Copy, FromBytes, IntoBytes)]
65#[repr(C)]
66pub struct IpcHandshake {
67 /// Magic number (`"IPC9"`).
68 pub magic: u32,
69 /// IPC protocol version.
70 pub protocol_version: u16,
71 pub _reserved: u16,
72 /// Client ABI major version.
73 pub client_abi_major: u16,
74 /// Client ABI minor version.
75 pub client_abi_minor: u16,
76 /// Random nonce for connection identification.
77 pub nonce: u32,
78 /// Reserved flags (must be 0).
79 pub flags: u32,
80}
81
82impl IpcHandshake {
83 /// Build a default handshake with a zero nonce.
84 pub const fn new() -> Self {
85 Self::new_with_nonce(0)
86 }
87
88 /// Build a handshake with a caller-provided nonce.
89 ///
90 /// The nonce is used by the server to uniquely identify this connection.
91 pub const fn new_with_nonce(nonce: u32) -> Self {
92 Self {
93 magic: IPC_HANDSHAKE_MAGIC,
94 protocol_version: IPC_PROTOCOL_VERSION,
95 _reserved: 0,
96 client_abi_major: crate::ABI_VERSION_MAJOR,
97 client_abi_minor: crate::ABI_VERSION_MINOR,
98 nonce,
99 flags: 0,
100 }
101 }
102
103 /// Return true when the message carries the expected handshake magic.
104 pub fn is_valid(&self) -> bool {
105 self.magic == IPC_HANDSHAKE_MAGIC
106 }
107
108 /// Return true when magic and protocol version match this ABI.
109 pub fn is_compatible(&self) -> bool {
110 self.is_valid() && self.protocol_version == IPC_PROTOCOL_VERSION
111 }
112}
113
114/// Server reply to a handshake.
115///
116/// Wire size: 16 bytes.
117///
118/// # Fields
119///
120/// - `magic`: echo of [`IPC_HANDSHAKE_MAGIC`]
121/// - `protocol_version`: server's IPC protocol version
122/// - `status`: result code (`IPC_HANDSHAKE_OK`, `_VERSION_MISMATCH`, or `_REJECTED`)
123/// - `server_abi_major/minor`: server's ABI version
124/// - `flags`: reserved for future use
125#[derive(Debug, Clone, Copy, FromBytes, IntoBytes)]
126#[repr(C)]
127pub struct IpcHandshakeReply {
128 /// Echo of the handshake magic.
129 pub magic: u32,
130 /// Server's IPC protocol version.
131 pub protocol_version: u16,
132 /// Handshake status code.
133 pub status: u16,
134 /// Server ABI major version.
135 pub server_abi_major: u16,
136 /// Server ABI minor version.
137 pub server_abi_minor: u16,
138 /// Reserved flags.
139 pub flags: u32,
140}
141
142/// Handshake succeeded.
143pub const IPC_HANDSHAKE_OK: u16 = 0;
144
145/// Protocol version mismatch between client and server.
146pub const IPC_HANDSHAKE_VERSION_MISMATCH: u16 = 1;
147
148/// Connection rejected by the server (permissions, capacity, etc.).
149pub const IPC_HANDSHAKE_REJECTED: u16 = 2;
150
151impl IpcHandshakeReply {
152 /// Build a successful handshake reply for the current ABI version.
153 pub const fn ok() -> Self {
154 Self {
155 magic: IPC_HANDSHAKE_MAGIC,
156 protocol_version: IPC_PROTOCOL_VERSION,
157 status: IPC_HANDSHAKE_OK,
158 server_abi_major: crate::ABI_VERSION_MAJOR,
159 server_abi_minor: crate::ABI_VERSION_MINOR,
160 flags: 0,
161 }
162 }
163
164 /// Build a rejected handshake reply with an explicit status code.
165 pub const fn reject(status: u16) -> Self {
166 Self {
167 magic: IPC_HANDSHAKE_MAGIC,
168 protocol_version: IPC_PROTOCOL_VERSION,
169 status,
170 server_abi_major: crate::ABI_VERSION_MAJOR,
171 server_abi_minor: crate::ABI_VERSION_MINOR,
172 flags: 0,
173 }
174 }
175}
176
177static_assertions::assert_eq_size!(IpcHandshake, [u8; 20]);
178static_assertions::assert_eq_size!(IpcHandshakeReply, [u8; 16]);