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#[derive(Clone, Debug, Eq, Hash, PartialEq)]
21pub struct Endpoint(PathBuf);
22
23impl Endpoint {
24 pub fn new(path: impl AsRef<Path>) -> Result<Self> {
26 platform::validate_endpoint(path.as_ref())
27 .map_err(|error| KodeBridgeError::configuration(format!("Invalid IPC endpoint: {error}")))?;
28 Ok(Self(path.as_ref().to_path_buf()))
29 }
30
31 pub fn as_path(&self) -> &Path {
33 &self.0
34 }
35}
36
37impl AsRef<Path> for Endpoint {
38 fn as_ref(&self) -> &Path {
39 self.as_path()
40 }
41}
42
43#[derive(Clone, Debug)]
45pub struct ListenerOptions {
46 reclaim_name: bool,
47 try_overwrite: bool,
48 #[cfg(unix)]
49 max_spin_time: Option<Duration>,
50 #[cfg(unix)]
51 mode: Option<libc::mode_t>,
52 #[cfg(windows)]
53 security_descriptor: Option<std::sync::Arc<windows::SecurityDescriptor>>,
54}
55
56impl ListenerOptions {
57 pub const fn new() -> Self {
59 Self {
60 reclaim_name: true,
61 try_overwrite: false,
62 #[cfg(unix)]
63 max_spin_time: None,
64 #[cfg(unix)]
65 mode: None,
66 #[cfg(windows)]
67 security_descriptor: None,
68 }
69 }
70
71 #[must_use]
73 pub const fn reclaim_name(mut self, reclaim_name: bool) -> Self {
74 self.reclaim_name = reclaim_name;
75 self
76 }
77
78 #[must_use]
80 pub const fn try_overwrite(mut self, try_overwrite: bool) -> Self {
81 self.try_overwrite = try_overwrite;
82 self
83 }
84
85 #[must_use]
87 #[cfg_attr(not(unix), allow(unused_mut))]
88 pub const fn max_spin_time(mut self, max_spin_time: Duration) -> Self {
89 #[cfg(unix)]
90 {
91 self.max_spin_time = Some(max_spin_time);
92 }
93 let _ = max_spin_time;
94 self
95 }
96
97 #[cfg(unix)]
99 #[must_use]
100 pub const fn mode(mut self, mode: libc::mode_t) -> Self {
101 self.mode = Some(mode);
102 self
103 }
104
105 #[cfg(windows)]
111 #[must_use]
112 #[allow(clippy::panic)]
113 pub fn security_descriptor(mut self, sddl: &str) -> Self {
114 if sddl.encode_utf16().any(|unit| unit == 0) {
115 panic!("Invalid SDDL string");
116 }
117 let descriptor = match windows::SecurityDescriptor::from_sddl(sddl) {
118 Ok(descriptor) => descriptor,
119 Err(error) => panic!("Failed to parse SDDL: {error}"),
120 };
121 self.security_descriptor = Some(std::sync::Arc::new(descriptor));
122 self
123 }
124}
125
126#[cfg(all(test, windows))]
127mod windows_tests {
128 use super::ListenerOptions;
129
130 #[test]
131 #[should_panic(expected = "Invalid SDDL string")]
132 fn interior_nul_keeps_legacy_panic_classification() {
133 let _options = ListenerOptions::new().security_descriptor("D:\0(A;;GA;;;WD)");
134 }
135
136 #[test]
137 #[should_panic(expected = "Failed to parse SDDL")]
138 fn malformed_sddl_keeps_legacy_panic_classification() {
139 let _options = ListenerOptions::new().security_descriptor("not-valid-sddl");
140 }
141}
142
143impl Default for ListenerOptions {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149#[derive(Debug)]
151pub struct IpcStream(platform::ClientStream);
152
153impl IpcStream {
154 pub(crate) async fn connect(endpoint: &Endpoint) -> std::io::Result<Self> {
155 platform::connect(endpoint.as_path()).await.map(Self)
156 }
157}
158
159impl AsyncRead for IpcStream {
160 fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buffer: &mut ReadBuf<'_>) -> Poll<std::io::Result<()>> {
161 Pin::new(&mut self.get_mut().0).poll_read(cx, buffer)
162 }
163}
164
165impl AsyncWrite for IpcStream {
166 fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buffer: &[u8]) -> Poll<std::io::Result<usize>> {
167 Pin::new(&mut self.get_mut().0).poll_write(cx, buffer)
168 }
169
170 fn poll_write_vectored(
171 self: Pin<&mut Self>,
172 cx: &mut Context<'_>,
173 buffers: &[IoSlice<'_>],
174 ) -> Poll<std::io::Result<usize>> {
175 Pin::new(&mut self.get_mut().0).poll_write_vectored(cx, buffers)
176 }
177
178 fn is_write_vectored(&self) -> bool {
179 self.0.is_write_vectored()
180 }
181
182 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
183 Pin::new(&mut self.get_mut().0).poll_flush(cx)
184 }
185
186 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
187 Pin::new(&mut self.get_mut().0).poll_shutdown(cx)
188 }
189}
190
191#[cfg(feature = "server")]
192pub(crate) type ServerStream = platform::ServerStream;
193
194#[cfg(feature = "server")]
195pub(crate) struct Listener(platform::Listener);
196
197#[cfg(feature = "server")]
198impl Listener {
199 pub(crate) fn bind(endpoint: &Endpoint, options: &ListenerOptions) -> std::io::Result<Self> {
200 platform::Listener::bind(endpoint.as_path(), options).map(Self)
201 }
202
203 pub(crate) async fn accept(&mut self) -> std::io::Result<ServerStream> {
204 self.0.accept().await
205 }
206}