|
| 1 | +// TSI Named Pipes Proxy for Windows |
| 2 | +// Handles Windows Named Pipe connections for vsock communication |
| 3 | + |
| 4 | +use super::stream_proxy::ProxyError; |
| 5 | +use std::io::{self, Read, Write}; |
| 6 | +use std::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle}; |
| 7 | +use std::ptr; |
| 8 | +use windows_sys::Win32::Foundation::{CloseHandle, ERROR_IO_PENDING, ERROR_PIPE_BUSY, HANDLE, INVALID_HANDLE_VALUE}; |
| 9 | +use windows_sys::Win32::Storage::FileSystem::{ |
| 10 | + CreateFileW, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, |
| 11 | +}; |
| 12 | +use windows_sys::Win32::System::Pipes::{ |
| 13 | + ConnectNamedPipe, CreateNamedPipeW, DisconnectNamedPipe, PIPE_ACCESS_DUPLEX, |
| 14 | + PIPE_READMODE_BYTE, PIPE_TYPE_BYTE, PIPE_UNLIMITED_INSTANCES, PIPE_WAIT, |
| 15 | +}; |
| 16 | +use windows_sys::Win32::System::IO::{GetOverlappedResult, OVERLAPPED}; |
| 17 | + |
| 18 | +/// Named Pipe proxy status |
| 19 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 20 | +pub enum PipeStatus { |
| 21 | + Init, |
| 22 | + Listening, |
| 23 | + Connected, |
| 24 | + Closed, |
| 25 | +} |
| 26 | + |
| 27 | +/// TSI Named Pipe Proxy for Windows |
| 28 | +pub struct TsiPipeProxyWindows { |
| 29 | + pipe_handle: HANDLE, |
| 30 | + status: PipeStatus, |
| 31 | + pipe_name: String, |
| 32 | +} |
| 33 | + |
| 34 | +impl TsiPipeProxyWindows { |
| 35 | + /// Create a new pipe proxy |
| 36 | + pub fn new() -> Self { |
| 37 | + Self { |
| 38 | + pipe_handle: INVALID_HANDLE_VALUE, |
| 39 | + status: PipeStatus::Init, |
| 40 | + pipe_name: String::new(), |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + /// Create and listen on a named pipe |
| 45 | + pub fn listen(&mut self, pipe_name: &str) -> Result<(), ProxyError> { |
| 46 | + if self.status != PipeStatus::Init { |
| 47 | + return Err(ProxyError::InvalidState); |
| 48 | + } |
| 49 | + |
| 50 | + // Convert pipe name to Windows format: \\.\pipe\name |
| 51 | + let full_name = if pipe_name.starts_with("\\\\.\\pipe\\") { |
| 52 | + pipe_name.to_string() |
| 53 | + } else { |
| 54 | + format!("\\\\.\\pipe\\{}", pipe_name) |
| 55 | + }; |
| 56 | + |
| 57 | + // Convert to wide string |
| 58 | + let wide_name: Vec<u16> = full_name.encode_utf16().chain(std::iter::once(0)).collect(); |
| 59 | + |
| 60 | + // Create named pipe |
| 61 | + let handle = unsafe { |
| 62 | + CreateNamedPipeW( |
| 63 | + wide_name.as_ptr(), |
| 64 | + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, |
| 65 | + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, |
| 66 | + PIPE_UNLIMITED_INSTANCES, |
| 67 | + 4096, // out buffer size |
| 68 | + 4096, // in buffer size |
| 69 | + 0, // default timeout |
| 70 | + ptr::null_mut(), |
| 71 | + ) |
| 72 | + }; |
| 73 | + |
| 74 | + if handle == INVALID_HANDLE_VALUE { |
| 75 | + return Err(ProxyError::IoError(io::Error::last_os_error())); |
| 76 | + } |
| 77 | + |
| 78 | + self.pipe_handle = handle; |
| 79 | + self.pipe_name = full_name; |
| 80 | + self.status = PipeStatus::Listening; |
| 81 | + Ok(()) |
| 82 | + } |
| 83 | + |
| 84 | + /// Accept a connection (blocking) |
| 85 | + pub fn accept(&mut self) -> Result<(), ProxyError> { |
| 86 | + if self.status != PipeStatus::Listening { |
| 87 | + return Err(ProxyError::InvalidState); |
| 88 | + } |
| 89 | + |
| 90 | + let result = unsafe { ConnectNamedPipe(self.pipe_handle, ptr::null_mut()) }; |
| 91 | + |
| 92 | + if result == 0 { |
| 93 | + let err = io::Error::last_os_error(); |
| 94 | + if err.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) { |
| 95 | + return Err(ProxyError::WouldBlock); |
| 96 | + } |
| 97 | + return Err(ProxyError::IoError(err)); |
| 98 | + } |
| 99 | + |
| 100 | + self.status = PipeStatus::Connected; |
| 101 | + Ok(()) |
| 102 | + } |
| 103 | + |
| 104 | + /// Connect to an existing named pipe (client mode) |
| 105 | + pub fn connect(&mut self, pipe_name: &str) -> Result<(), ProxyError> { |
| 106 | + if self.status != PipeStatus::Init { |
| 107 | + return Err(ProxyError::InvalidState); |
| 108 | + } |
| 109 | + |
| 110 | + // Convert pipe name to Windows format |
| 111 | + let full_name = if pipe_name.starts_with("\\\\.\\pipe\\") { |
| 112 | + pipe_name.to_string() |
| 113 | + } else { |
| 114 | + format!("\\\\.\\pipe\\{}", pipe_name) |
| 115 | + }; |
| 116 | + |
| 117 | + let wide_name: Vec<u16> = full_name.encode_utf16().chain(std::iter::once(0)).collect(); |
| 118 | + |
| 119 | + // Open existing pipe |
| 120 | + let handle = unsafe { |
| 121 | + CreateFileW( |
| 122 | + wide_name.as_ptr(), |
| 123 | + 0x80000000 | 0x40000000, // GENERIC_READ | GENERIC_WRITE |
| 124 | + FILE_SHARE_READ | FILE_SHARE_WRITE, |
| 125 | + ptr::null_mut(), |
| 126 | + OPEN_EXISTING, |
| 127 | + FILE_FLAG_OVERLAPPED, |
| 128 | + 0, |
| 129 | + ) |
| 130 | + }; |
| 131 | + |
| 132 | + if handle == INVALID_HANDLE_VALUE { |
| 133 | + return Err(ProxyError::IoError(io::Error::last_os_error())); |
| 134 | + } |
| 135 | + |
| 136 | + self.pipe_handle = handle; |
| 137 | + self.pipe_name = full_name; |
| 138 | + self.status = PipeStatus::Connected; |
| 139 | + Ok(()) |
| 140 | + } |
| 141 | + |
| 142 | + /// Send data through the pipe |
| 143 | + pub fn send_data(&mut self, data: &[u8]) -> Result<usize, ProxyError> { |
| 144 | + if self.status != PipeStatus::Connected { |
| 145 | + return Err(ProxyError::InvalidState); |
| 146 | + } |
| 147 | + |
| 148 | + // Use std::fs::File wrapper for Write trait |
| 149 | + let mut file = unsafe { std::fs::File::from_raw_handle(self.pipe_handle as RawHandle) }; |
| 150 | + let result = file.write(data); |
| 151 | + std::mem::forget(file); // Don't close the handle |
| 152 | + |
| 153 | + result.map_err(|e| { |
| 154 | + if e.kind() == io::ErrorKind::WouldBlock { |
| 155 | + ProxyError::WouldBlock |
| 156 | + } else { |
| 157 | + ProxyError::IoError(e) |
| 158 | + } |
| 159 | + }) |
| 160 | + } |
| 161 | + |
| 162 | + /// Receive data from the pipe |
| 163 | + pub fn recv_data(&mut self, buf: &mut [u8]) -> Result<usize, ProxyError> { |
| 164 | + if self.status != PipeStatus::Connected { |
| 165 | + return Err(ProxyError::InvalidState); |
| 166 | + } |
| 167 | + |
| 168 | + let mut file = unsafe { std::fs::File::from_raw_handle(self.pipe_handle as RawHandle) }; |
| 169 | + let result = file.read(buf); |
| 170 | + std::mem::forget(file); |
| 171 | + |
| 172 | + result.map_err(|e| { |
| 173 | + if e.kind() == io::ErrorKind::WouldBlock { |
| 174 | + ProxyError::WouldBlock |
| 175 | + } else { |
| 176 | + ProxyError::IoError(e) |
| 177 | + } |
| 178 | + }) |
| 179 | + } |
| 180 | + |
| 181 | + /// Disconnect the pipe |
| 182 | + pub fn disconnect(&mut self) -> Result<(), ProxyError> { |
| 183 | + if self.status == PipeStatus::Connected && self.pipe_handle != INVALID_HANDLE_VALUE { |
| 184 | + unsafe { |
| 185 | + DisconnectNamedPipe(self.pipe_handle); |
| 186 | + } |
| 187 | + self.status = PipeStatus::Listening; |
| 188 | + } |
| 189 | + Ok(()) |
| 190 | + } |
| 191 | + |
| 192 | + /// Get current status |
| 193 | + pub fn status(&self) -> PipeStatus { |
| 194 | + self.status |
| 195 | + } |
| 196 | + |
| 197 | + /// Get pipe name |
| 198 | + pub fn pipe_name(&self) -> &str { |
| 199 | + &self.pipe_name |
| 200 | + } |
| 201 | +} |
| 202 | + |
| 203 | +impl Drop for TsiPipeProxyWindows { |
| 204 | + fn drop(&mut self) { |
| 205 | + if self.pipe_handle != INVALID_HANDLE_VALUE { |
| 206 | + unsafe { |
| 207 | + CloseHandle(self.pipe_handle); |
| 208 | + } |
| 209 | + } |
| 210 | + } |
| 211 | +} |
| 212 | + |
| 213 | +#[cfg(test)] |
| 214 | +mod tests { |
| 215 | + use super::*; |
| 216 | + |
| 217 | + #[test] |
| 218 | + fn test_pipe_proxy_creation() { |
| 219 | + let proxy = TsiPipeProxyWindows::new(); |
| 220 | + assert_eq!(proxy.status(), PipeStatus::Init); |
| 221 | + } |
| 222 | + |
| 223 | + #[test] |
| 224 | + #[ignore] // Requires Windows Named Pipe support |
| 225 | + fn test_pipe_listen() { |
| 226 | + let mut proxy = TsiPipeProxyWindows::new(); |
| 227 | + let result = proxy.listen("test_pipe_listen"); |
| 228 | + assert!(result.is_ok()); |
| 229 | + assert_eq!(proxy.status(), PipeStatus::Listening); |
| 230 | + } |
| 231 | +} |
0 commit comments