Skip to main content

windows_erg/pipes/
client.rs

1use std::io;
2use std::time::Duration;
3
4use windows::Win32::Foundation::GetLastError;
5use windows::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE};
6use windows::Win32::Storage::FileSystem::{
7    CreateFileW, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_MODE, OPEN_EXISTING, ReadFile, WriteFile,
8};
9use windows::Win32::System::Pipes::WaitNamedPipeW;
10use windows::core::PCWSTR;
11
12use crate::error::InvalidParameterError;
13use crate::utils::to_utf16_nul;
14use crate::{Error, Result};
15
16use super::error_map::map_pipe_windows_error;
17use super::security_attrs::NativePipeSecurityAttributes;
18use super::types::{NamedPipeOpenMode, PipeClientEndpoint, PipeName, PipeSecurityOptions};
19
20/// Builder for named pipe client configuration.
21#[derive(Debug, Clone)]
22pub struct NamedPipeClientBuilder {
23    pipe_name: Option<PipeName>,
24    open_mode: NamedPipeOpenMode,
25    connect_timeout: Duration,
26    security: PipeSecurityOptions,
27}
28
29impl NamedPipeClientBuilder {
30    /// Create a new named pipe client builder.
31    pub fn new() -> Self {
32        Self {
33            pipe_name: None,
34            open_mode: NamedPipeOpenMode::Duplex,
35            connect_timeout: Duration::from_secs(5),
36            security: PipeSecurityOptions::default(),
37        }
38    }
39
40    /// Set the named pipe path.
41    pub fn pipe_name(mut self, pipe_name: PipeName) -> Self {
42        self.pipe_name = Some(pipe_name);
43        self
44    }
45
46    /// Set open direction.
47    pub fn open_mode(mut self, open_mode: NamedPipeOpenMode) -> Self {
48        self.open_mode = open_mode;
49        self
50    }
51
52    /// Set connect timeout.
53    pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self {
54        self.connect_timeout = connect_timeout;
55        self
56    }
57
58    /// Set raw security options.
59    pub fn security(mut self, security: PipeSecurityOptions) -> Self {
60        self.security = security;
61        self
62    }
63
64    /// Build a named pipe client configuration.
65    pub fn build(self) -> Result<NamedPipeClientConfig> {
66        let pipe_name = self.pipe_name.ok_or_else(|| {
67            Error::InvalidParameter(InvalidParameterError::new(
68                "pipe_name",
69                "Pipe name must be specified",
70            ))
71        })?;
72
73        Ok(NamedPipeClientConfig {
74            pipe_name,
75            open_mode: self.open_mode,
76            connect_timeout: self.connect_timeout,
77            security: self.security,
78        })
79    }
80}
81
82impl Default for NamedPipeClientBuilder {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88/// Named pipe client runtime configuration.
89#[derive(Debug)]
90pub struct NamedPipeClientConfig {
91    pipe_name: PipeName,
92    open_mode: NamedPipeOpenMode,
93    connect_timeout: Duration,
94    security: PipeSecurityOptions,
95}
96
97impl NamedPipeClientConfig {
98    /// Create a new builder.
99    pub fn builder() -> NamedPipeClientBuilder {
100        NamedPipeClientBuilder::new()
101    }
102
103    /// Connect to the target named pipe endpoint.
104    pub fn connect(&self) -> Result<NamedPipeClient> {
105        let pipe_name_wide = to_utf16_nul(self.pipe_name.as_str());
106        let timeout_ms = self.connect_timeout.as_millis().min(u32::MAX as u128) as u32;
107
108        let waited = unsafe { WaitNamedPipeW(PCWSTR(pipe_name_wide.as_ptr()), timeout_ms) };
109        if !waited.as_bool() {
110            let code = unsafe { GetLastError().0 as i32 };
111            return Err(map_pipe_windows_error(
112                "connect",
113                Some(&self.pipe_name),
114                code,
115            ));
116        }
117
118        let security_attributes =
119            NativePipeSecurityAttributes::from_options(&self.security, self.pipe_name.as_str())?;
120
121        let desired_access = to_client_access(self.open_mode);
122        let raw_handle = unsafe {
123            CreateFileW(
124                PCWSTR(pipe_name_wide.as_ptr()),
125                desired_access,
126                FILE_SHARE_MODE(0),
127                security_attributes.as_option_ptr(),
128                OPEN_EXISTING,
129                FILE_FLAGS_AND_ATTRIBUTES(0),
130                None,
131            )
132        }
133        .map_err(|e| map_pipe_windows_error("connect", Some(&self.pipe_name), e.code().0))?;
134
135        Ok(NamedPipeClient {
136            endpoint: PipeClientEndpoint::from_raw(
137                raw_handle,
138                true,
139                self.pipe_name.clone(),
140                self.open_mode,
141            ),
142        })
143    }
144
145    /// Return named pipe path.
146    pub fn pipe_name(&self) -> &PipeName {
147        &self.pipe_name
148    }
149
150    /// Return open mode.
151    pub fn open_mode(&self) -> NamedPipeOpenMode {
152        self.open_mode
153    }
154
155    /// Return configured connect timeout.
156    pub fn connect_timeout(&self) -> Duration {
157        self.connect_timeout
158    }
159
160    /// Return security options.
161    pub fn security(&self) -> PipeSecurityOptions {
162        self.security.clone()
163    }
164}
165
166/// A connected named pipe client handle.
167#[derive(Debug)]
168pub struct NamedPipeClient {
169    endpoint: PipeClientEndpoint,
170}
171
172impl NamedPipeClient {
173    /// Return the underlying endpoint.
174    pub fn endpoint(&self) -> &PipeClientEndpoint {
175        &self.endpoint
176    }
177}
178
179impl io::Read for NamedPipeClient {
180    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
181        let mut read = 0u32;
182        unsafe { ReadFile(self.endpoint.raw_handle(), Some(buf), Some(&mut read), None) }
183            .map_err(|e| io::Error::from_raw_os_error(e.code().0))?;
184        Ok(read as usize)
185    }
186}
187
188impl io::Write for NamedPipeClient {
189    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
190        let mut written = 0u32;
191        unsafe {
192            WriteFile(
193                self.endpoint.raw_handle(),
194                Some(buf),
195                Some(&mut written),
196                None,
197            )
198        }
199        .map_err(|e| io::Error::from_raw_os_error(e.code().0))?;
200        Ok(written as usize)
201    }
202
203    fn flush(&mut self) -> io::Result<()> {
204        Ok(())
205    }
206}
207
208fn to_client_access(open_mode: NamedPipeOpenMode) -> u32 {
209    match open_mode {
210        NamedPipeOpenMode::Inbound => GENERIC_READ.0,
211        NamedPipeOpenMode::Outbound => GENERIC_WRITE.0,
212        NamedPipeOpenMode::Duplex => GENERIC_READ.0 | GENERIC_WRITE.0,
213    }
214}