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, Immutable, 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, Immutable)]
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 /// Return true when any reserved field is non-zero.
114 ///
115 /// Reserved fields must be zero on the wire; servers should reject
116 /// such handshakes so that future protocol upgrades cannot smuggle
117 /// new semantics past a validator that ignores them.
118 pub fn has_reserved_bits_set(&self) -> bool {
119 self._reserved != 0 || self.flags != 0
120 }
121}
122
123/// Server reply to a handshake.
124///
125/// Wire size: 16 bytes.
126///
127/// # Fields
128///
129/// - `magic`: echo of [`IPC_HANDSHAKE_MAGIC`]
130/// - `protocol_version`: server's IPC protocol version
131/// - `status`: result code (`IPC_HANDSHAKE_OK`, `_VERSION_MISMATCH`, or `_REJECTED`)
132/// - `server_abi_major/minor`: server's ABI version
133/// - `flags`: reserved for future use
134#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
135#[repr(C)]
136pub struct IpcHandshakeReply {
137 /// Echo of the handshake magic.
138 pub magic: u32,
139 /// Server's IPC protocol version.
140 pub protocol_version: u16,
141 /// Handshake status code.
142 pub status: u16,
143 /// Server ABI major version.
144 pub server_abi_major: u16,
145 /// Server ABI minor version.
146 pub server_abi_minor: u16,
147 /// Reserved flags.
148 pub flags: u32,
149}
150
151/// Handshake succeeded.
152pub const IPC_HANDSHAKE_OK: u16 = 0;
153
154/// Protocol version mismatch between client and server.
155pub const IPC_HANDSHAKE_VERSION_MISMATCH: u16 = 1;
156
157/// Connection rejected by the server (permissions, capacity, etc.).
158pub const IPC_HANDSHAKE_REJECTED: u16 = 2;
159impl IpcHandshakeReply {
160 /// Build a successful handshake reply for the current ABI version.
161 pub const fn ok() -> Self {
162 Self {
163 magic: IPC_HANDSHAKE_MAGIC,
164 protocol_version: IPC_PROTOCOL_VERSION,
165 status: IPC_HANDSHAKE_OK,
166 server_abi_major: crate::ABI_VERSION_MAJOR,
167 server_abi_minor: crate::ABI_VERSION_MINOR,
168 flags: 0,
169 }
170 }
171
172 /// Build a rejected handshake reply with an explicit status code.
173 pub const fn reject(status: u16) -> Self {
174 Self {
175 magic: IPC_HANDSHAKE_MAGIC,
176 protocol_version: IPC_PROTOCOL_VERSION,
177 status,
178 server_abi_major: crate::ABI_VERSION_MAJOR,
179 server_abi_minor: crate::ABI_VERSION_MINOR,
180 flags: 0,
181 }
182 }
183
184 /// Return true when any reserved field is non-zero
185 /// (see [`IpcHandshake::has_reserved_bits_set`]).
186 pub fn has_reserved_bits_set(&self) -> bool {
187 self.flags != 0
188 }
189}
190
191static_assertions::assert_eq_size!(IpcHandshake, [u8; 20]);
192static_assertions::assert_eq_size!(IpcHandshakeReply, [u8; 16]);