running_process_platform_internal/platform/
ipc.rs1#[cfg(feature = "ipc")]
8pub use crate::{
9 ipc_current_user_id as current_user_id, IpcEndpoint as Endpoint,
10 IpcInheritedListener as InheritedListener, IpcListener as Listener,
11 IpcListenerNonblockingMode as ListenerNonblockingMode, IpcPeerIdentity as PeerIdentity,
12 IpcPeerIdentitySource as PeerIdentitySource, IpcStream as Stream,
13};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct HandoffAttachment {
23 protocol_value: u64,
24 backend_may_adopt_before_offer: bool,
25}
26
27#[derive(Clone, Debug, Default, PartialEq, Eq)]
33#[cfg(feature = "ipc")]
34pub struct EndpointAddressCandidates {
35 kernel_namespace: Option<String>,
36 filesystem: Option<std::path::PathBuf>,
37}
38
39#[cfg(feature = "ipc")]
40impl EndpointAddressCandidates {
41 pub fn new(kernel_namespace: Option<String>, filesystem: Option<std::path::PathBuf>) -> Self {
42 Self {
43 kernel_namespace,
44 filesystem,
45 }
46 }
47
48 pub fn select(self) -> Option<String> {
50 crate::ipc_select_endpoint_address(self.kernel_namespace, self.filesystem)
51 }
52}
53
54impl HandoffAttachment {
55 pub(crate) fn new(protocol_value: u64, backend_may_adopt_before_offer: bool) -> Self {
56 Self {
57 protocol_value,
58 backend_may_adopt_before_offer,
59 }
60 }
61
62 pub fn append_unsigned_varint(self, output: &mut Vec<u8>) {
67 let mut value = self.protocol_value;
68 while value >= 0x80 {
69 output.push((value as u8 & 0x7f) | 0x80);
70 value >>= 7;
71 }
72 output.push(value as u8);
73 }
74
75 pub fn backend_may_adopt_before_offer(self) -> bool {
82 self.backend_may_adopt_before_offer
83 }
84}
85
86#[cfg(feature = "ipc")]
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum OwnerPrivateDirectoryOutcome {
90 AlreadyPrivate,
92 Hardened,
94}
95
96#[cfg(feature = "ipc")]
98pub fn ensure_owner_private_directory(
99 path: &std::path::Path,
100) -> std::io::Result<OwnerPrivateDirectoryOutcome> {
101 crate::ipc_ensure_owner_private_directory(path)
102}
103
104#[cfg(feature = "ipc")]
106pub fn owner_private_directory(path: &std::path::Path) -> std::io::Result<bool> {
107 crate::ipc_owner_private_directory(path)
108}
109
110#[cfg(feature = "ipc")]
113pub fn nonblocking_zero_read_is_pending() -> bool {
114 crate::ipc_nonblocking_zero_read_is_pending()
115}
116
117#[cfg(feature = "ipc")]
119pub fn endpoint_is_filesystem_backed() -> bool {
120 crate::ipc_endpoint_is_filesystem_backed()
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125pub enum HandoffTransferErrorKind {
126 Unsupported,
127 PermissionDenied,
128 BackendUnavailable,
129 WouldBlock,
130 Failed,
131}
132
133#[derive(Clone, Debug, PartialEq, Eq)]
135pub struct HandoffTransferError {
136 kind: HandoffTransferErrorKind,
137 may_have_reached_backend: bool,
138 detail: String,
139}
140
141impl HandoffTransferError {
142 pub(crate) fn new(
143 kind: HandoffTransferErrorKind,
144 may_have_reached_backend: bool,
145 detail: impl Into<String>,
146 ) -> Self {
147 Self {
148 kind,
149 may_have_reached_backend,
150 detail: detail.into(),
151 }
152 }
153
154 pub fn kind(&self) -> HandoffTransferErrorKind {
156 self.kind
157 }
158
159 pub fn may_have_reached_backend(&self) -> bool {
161 self.may_have_reached_backend
162 }
163}
164
165impl std::fmt::Display for HandoffTransferError {
166 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 formatter.write_str(&self.detail)
168 }
169}
170
171impl std::error::Error for HandoffTransferError {}
172
173#[cfg(feature = "ipc")]
175pub fn broker_endpoint_name(bare_name: &str, path_scoped: bool) -> std::io::Result<String> {
176 crate::IpcBrokerEndpointName(bare_name, path_scoped)
177}
178
179#[derive(Clone, Copy, Debug, PartialEq, Eq)]
186pub struct EndpointNameLimit {
187 pub max_bytes: usize,
189 pub label: &'static str,
191}
192
193#[cfg(feature = "ipc")]
195pub fn endpoint_name_limit() -> EndpointNameLimit {
196 crate::ipc_endpoint_name_limit()
197}
198
199#[derive(Clone, Copy, Debug, PartialEq, Eq)]
201pub struct EndpointNameTooLong {
202 pub len: usize,
204 pub max: usize,
206 pub limit_label: &'static str,
208}
209
210#[cfg(feature = "ipc")]
217pub(crate) fn per_user_runtime_fallback() -> std::path::PathBuf {
218 dirs::cache_dir()
219 .or_else(dirs::data_local_dir)
220 .or_else(dirs::home_dir)
221 .unwrap_or_else(std::env::temp_dir)
222 .join("running-process")
223 .join("broker-v2")
224}
225
226#[cfg(feature = "ipc")]
234pub fn endpoint_scope_bytes(path: &std::path::Path) -> Vec<u8> {
235 crate::ipc_endpoint_scope_bytes(path)
236}
237
238#[cfg(feature = "ipc")]
249pub fn broker_v2_runtime_dir() -> std::path::PathBuf {
250 crate::ipc_broker_v2_runtime_dir()
251}
252
253#[cfg(feature = "ipc")]
261pub fn broker_v1_endpoint_path(bare_name: &str) -> Result<String, EndpointNameTooLong> {
262 crate::ipc_broker_v1_endpoint_path(bare_name)
263}
264
265#[cfg(feature = "ipc-async")]
266pub use crate::{
267 IpcAsyncListener as AsyncListener, IpcAsyncStream as AsyncStream,
268 IpcIntoAsyncListener as IntoAsyncListener, IpcIntoAsyncStream as IntoAsyncStream,
269};
270
271#[cfg(all(test, feature = "ipc"))]
272mod tests {
273 use std::io::{Read, Write};
274
275 use super::{
276 current_user_id, ensure_owner_private_directory, owner_private_directory, Endpoint,
277 HandoffAttachment, Listener, Stream,
278 };
279
280 #[test]
281 fn ensure_private_dir_passes_private_check() {
282 let temporary = tempfile::tempdir().expect("temporary directory");
283 let path = temporary.path().join("private");
284 ensure_owner_private_directory(&path).expect("harden directory");
285 assert!(owner_private_directory(&path).expect("inspect directory"));
286 }
287
288 #[test]
289 fn handoff_attachment_can_be_encoded_without_exposing_its_value() {
290 let mut encoded = Vec::new();
291 HandoffAttachment::new(300, false).append_unsigned_varint(&mut encoded);
292 assert_eq!(encoded, [0xac, 0x02]);
293 }
294
295 #[test]
296 fn handoff_attachment_reports_pre_offer_adoption_semantics() {
297 assert!(HandoffAttachment::new(0, true).backend_may_adopt_before_offer());
298 assert!(!HandoffAttachment::new(0, false).backend_may_adopt_before_offer());
299 }
300
301 #[test]
302 fn endpoint_lifecycle_mechanics_are_facade_owned() {
303 let endpoint = Endpoint::test("lifecycle").expect("test endpoint");
304 endpoint.retire().expect("retire absent endpoint");
305
306 let listener = Listener::bind(&endpoint).expect("bind endpoint");
307
308 drop(listener);
309 endpoint.retire().expect("retire endpoint");
310 }
311
312 #[test]
313 fn sync_bind_accept_connect_and_peer_identity_round_trip() {
314 let endpoint = Endpoint::test("sync-roundtrip").expect("test endpoint");
315 let listener = Listener::bind(&endpoint).expect("bind");
316 let expected_user = current_user_id().expect("current user identity");
317 let server = std::thread::spawn(move || {
318 let mut stream = listener.accept().expect("accept");
319 let peer = stream.peer_identity().expect("peer identity");
320 assert_eq!(peer.user_id, expected_user);
321 let mut request = [0_u8; 4];
322 stream.read_exact(&mut request).expect("read request");
323 assert_eq!(&request, b"ping");
324 stream.write_all(b"pong").expect("write response");
325 });
326
327 let mut client = Stream::connect(&endpoint).expect("connect");
328 client.write_all(b"ping").expect("write request");
329 let mut response = [0_u8; 4];
330 client.read_exact(&mut response).expect("read response");
331 assert_eq!(&response, b"pong");
332 server.join().expect("server thread");
333 }
334
335 #[cfg(feature = "ipc-async")]
336 #[tokio::test]
337 async fn async_bind_accept_connect_and_peer_identity_round_trip() {
338 use super::{AsyncListener, AsyncStream};
339 use tokio::io::{AsyncReadExt, AsyncWriteExt};
340
341 let endpoint = Endpoint::test("async-roundtrip").expect("test endpoint");
342 let listener = AsyncListener::bind(&endpoint).expect("bind");
343 let expected_user = current_user_id().expect("current user identity");
344 let server = tokio::spawn(async move {
345 let mut stream = listener.accept().await.expect("accept");
346 let peer = stream.peer_identity().expect("peer identity");
347 assert_eq!(peer.user_id, expected_user);
348 let mut request = [0_u8; 4];
349 stream.read_exact(&mut request).await.expect("read request");
350 assert_eq!(&request, b"ping");
351 stream.write_all(b"pong").await.expect("write response");
352 });
353
354 let mut client = AsyncStream::connect(&endpoint).await.expect("connect");
355 client.write_all(b"ping").await.expect("write request");
356 let mut response = [0_u8; 4];
357 client
358 .read_exact(&mut response)
359 .await
360 .expect("read response");
361 assert_eq!(&response, b"pong");
362 server.await.expect("server task");
363 }
364}