Skip to main content

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]);