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