kode_bridge/transport/
mod.rs1use crate::errors::{KodeBridgeError, Result};
2use std::io::IoSlice;
3use std::path::{Path, PathBuf};
4use std::pin::Pin;
5use std::task::{Context, Poll};
6use std::time::Duration;
7use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
8
9#[cfg(unix)]
10mod unix;
11#[cfg(windows)]
12mod windows;
13
14#[cfg(unix)]
15use unix as platform;
16#[cfg(windows)]
17use windows as platform;
18
19#[cfg(windows)]
20pub(crate) type WindowsServerPidVerifier = fn(u32) -> std::io::Result<()>;
21
22#[cfg(windows)]
23#[derive(Clone, Copy, Debug, Default)]
24pub(crate) struct WindowsServerVerification {
25 pub(crate) require_system: bool,
26 pub(crate) verifier: Option<WindowsServerPidVerifier>,
27}
28
29#[cfg(windows)]
30impl WindowsServerVerification {
31 pub(crate) const fn new(require_system: bool, verifier: Option<WindowsServerPidVerifier>) -> Self {
32 Self {
33 require_system,
34 verifier,
35 }
36 }
37}
38
39#[derive(Clone, Debug, Eq, Hash, PartialEq)]
41pub struct Endpoint(PathBuf);
42
43impl Endpoint {
44 pub fn new(path: impl AsRef<Path>) -> Result<Self> {
46 platform::validate_endpoint(path.as_ref())
47 .map_err(|error| KodeBridgeError::configuration(format!("Invalid IPC endpoint: {error}")))?;
48 Ok(Self(path.as_ref().to_path_buf()))
49 }
50
51 pub fn as_path(&self) -> &Path {
53 &self.0
54 }
55}
56
57impl AsRef<Path> for Endpoint {
58 fn as_ref(&self) -> &Path {
59 self.as_path()
60 }
61}
62
63#[derive(Clone, Debug)]
65pub struct ListenerOptions {
66 reclaim_name: bool,
67 try_overwrite: bool,
68 #[cfg(unix)]
69 max_spin_time: Option<Duration>,
70 #[cfg(unix)]
71 mode: Option<libc::mode_t>,
72 #[cfg(windows)]
73 security_descriptor: Option<std::sync::Arc<windows::SecurityDescriptor>>,
74}
75
76impl ListenerOptions {
77 pub const fn new() -> Self {
79 Self {
80 reclaim_name: true,
81 try_overwrite: false,
82 #[cfg(unix)]
83 max_spin_time: None,
84 #[cfg(unix)]
85 mode: None,
86 #[cfg(windows)]
87 security_descriptor: None,
88 }
89 }
90
91 #[must_use]
93 pub const fn reclaim_name(mut self, reclaim_name: bool) -> Self {
94 self.reclaim_name = reclaim_name;
95 self
96 }
97
98 #[must_use]
100 pub const fn try_overwrite(mut self, try_overwrite: bool) -> Self {
101 self.try_overwrite = try_overwrite;
102 self
103 }
104
105 #[must_use]
107 #[cfg_attr(not(unix), allow(unused_mut))]
108 pub const fn max_spin_time(mut self, max_spin_time: Duration) -> Self {
109 #[cfg(unix)]
110 {
111 self.max_spin_time = Some(max_spin_time);
112 }
113 let _ = max_spin_time;
114 self
115 }
116
117 #[cfg(unix)]
119 #[must_use]
120 pub const fn mode(mut self, mode: libc::mode_t) -> Self {
121 self.mode = Some(mode);
122 self
123 }
124
125 #[cfg(windows)]
131 #[must_use]
132 #[allow(clippy::panic)]
133 pub fn security_descriptor(mut self, sddl: &str) -> Self {
134 if sddl.encode_utf16().any(|unit| unit == 0) {
135 panic!("Invalid SDDL string");
136 }
137 let descriptor = match windows::SecurityDescriptor::from_sddl(sddl) {
138 Ok(descriptor) => descriptor,
139 Err(error) => panic!("Failed to parse SDDL: {error}"),
140 };
141 self.security_descriptor = Some(std::sync::Arc::new(descriptor));
142 self
143 }
144}
145
146#[cfg(all(test, windows))]
147mod windows_tests {
148 use super::ListenerOptions;
149
150 #[test]
151 #[should_panic(expected = "Invalid SDDL string")]
152 fn interior_nul_keeps_legacy_panic_classification() {
153 let _options = ListenerOptions::new().security_descriptor("D:\0(A;;GA;;;WD)");
154 }
155
156 #[test]
157 #[should_panic(expected = "Failed to parse SDDL")]
158 fn malformed_sddl_keeps_legacy_panic_classification() {
159 let _options = ListenerOptions::new().security_descriptor("not-valid-sddl");
160 }
161}
162
163impl Default for ListenerOptions {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169#[derive(Debug)]
171pub struct IpcStream(platform::ClientStream);
172
173impl IpcStream {
174 pub(crate) async fn connect(endpoint: &Endpoint) -> std::io::Result<Self> {
175 platform::connect(endpoint.as_path()).await.map(Self)
176 }
177
178 #[cfg(windows)]
179 pub(crate) async fn connect_with_windows_server_verification(
180 endpoint: &Endpoint,
181 verification: WindowsServerVerification,
182 ) -> std::io::Result<Self> {
183 platform::connect_with_server_verification(endpoint.as_path(), verification)
184 .await
185 .map(Self)
186 }
187}
188
189impl AsyncRead for IpcStream {
190 fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buffer: &mut ReadBuf<'_>) -> Poll<std::io::Result<()>> {
191 Pin::new(&mut self.get_mut().0).poll_read(cx, buffer)
192 }
193}
194
195impl AsyncWrite for IpcStream {
196 fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buffer: &[u8]) -> Poll<std::io::Result<usize>> {
197 Pin::new(&mut self.get_mut().0).poll_write(cx, buffer)
198 }
199
200 fn poll_write_vectored(
201 self: Pin<&mut Self>,
202 cx: &mut Context<'_>,
203 buffers: &[IoSlice<'_>],
204 ) -> Poll<std::io::Result<usize>> {
205 Pin::new(&mut self.get_mut().0).poll_write_vectored(cx, buffers)
206 }
207
208 fn is_write_vectored(&self) -> bool {
209 self.0.is_write_vectored()
210 }
211
212 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
213 Pin::new(&mut self.get_mut().0).poll_flush(cx)
214 }
215
216 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
217 Pin::new(&mut self.get_mut().0).poll_shutdown(cx)
218 }
219}
220
221#[cfg(feature = "server")]
222pub(crate) type ServerStream = platform::ServerStream;
223
224#[cfg(feature = "server")]
225pub(crate) struct Listener(platform::Listener);
226
227#[cfg(feature = "server")]
228impl Listener {
229 pub(crate) fn bind(endpoint: &Endpoint, options: &ListenerOptions) -> std::io::Result<Self> {
230 platform::Listener::bind(endpoint.as_path(), options).map(Self)
231 }
232
233 pub(crate) async fn accept(&mut self) -> std::io::Result<ServerStream> {
234 self.0.accept().await
235 }
236}
237
238#[cfg(all(feature = "server", unix))]
239pub(crate) fn peer_credentials(stream: &ServerStream) -> std::io::Result<(u32, u32)> {
240 platform::peer_credentials(stream)
241}