1use crate::error::{ProblemJson, ViiperError};
4use crate::types::*;
5use std::io::{Read, Write};
6use std::net::TcpStream;
7
8pub struct ViiperClient {
10 addr: String,
11}
12
13impl ViiperClient {
14 pub fn new(host: impl Into<String>, port: u16) -> Self {
16 Self {
17 addr: format!("{}:{}", host.into(), port),
18 }
19 }
20
21 fn do_request<T: for<'de> serde::Deserialize<'de>>(
22 &self,
23 path: &str,
24 payload: Option<&str>,
25 ) -> Result<T, ViiperError> {
26 let mut stream = TcpStream::connect(&self.addr)?;
27
28 stream.write_all(path.as_bytes())?;
29 if let Some(p) = payload {
30 stream.write_all(b" ")?;
31 stream.write_all(p.as_bytes())?;
32 }
33 stream.write_all(b"\0")?;
34
35 let mut buf = Vec::new();
36 stream.read_to_end(&mut buf)?;
37
38 let response = String::from_utf8(buf)
39 .map_err(|_| ViiperError::UnexpectedResponse("invalid UTF-8".into()))?
40 .trim_end_matches('\n')
41 .to_string();
42
43 if response.starts_with("{\"status\":") {
44 let problem: ProblemJson = serde_json::from_str(&response)?;
45 return Err(ViiperError::Protocol(problem));
46 }
47
48 serde_json::from_str(&response).map_err(Into::into)
49 }
50
51 pub fn bus_list(&self) -> Result<BusListResponse, ViiperError> {
53 let path = "bus/list";
54 let payload: Option<String> = None;
55 self.do_request(&path, payload.as_deref())
56 }
57
58 pub fn bus_create(&self, uint32: Option<u32>) -> Result<BusCreateResponse, ViiperError> {
60 let path = "bus/create";
61 let payload = uint32.map(|v| v.to_string());
62 self.do_request(&path, payload.as_deref())
63 }
64
65 pub fn bus_remove(&self, uint32: Option<u32>) -> Result<BusRemoveResponse, ViiperError> {
67 let path = "bus/remove";
68 let payload = uint32.map(|v| v.to_string());
69 self.do_request(&path, payload.as_deref())
70 }
71
72 pub fn bus_devices_list(&self, id: u32) -> Result<DevicesListResponse, ViiperError> {
74 let path = format!("bus/{}/list", id);
75 let payload: Option<String> = None;
76 self.do_request(&path, payload.as_deref())
77 }
78
79 pub fn bus_device_add(&self, id: u32, device_create_request: &DeviceCreateRequest) -> Result<Device, ViiperError> {
81 let path = format!("bus/{}/add", id);
82 let payload = Some(serde_json::to_string(&device_create_request)?);
83 self.do_request(&path, payload.as_deref())
84 }
85
86 pub fn bus_device_remove(&self, id: u32, string: Option<&str>) -> Result<DeviceRemoveResponse, ViiperError> {
88 let path = format!("bus/{}/remove", id);
89 let payload = string.map(|s| s.to_string());
90 self.do_request(&path, payload.as_deref())
91 }
92
93 pub fn connect_device(&self, bus_id: u32, dev_id: &str) -> Result<DeviceStream, ViiperError> {
95 DeviceStream::connect(&self.addr, bus_id, dev_id)
96 }
97}
98
99pub struct DeviceStream {
101 stream: TcpStream,
102 output_thread: Option<std::thread::JoinHandle<()>>,
103}
104
105impl DeviceStream {
106 pub fn connect(addr: &str, bus_id: u32, dev_id: &str) -> Result<Self, ViiperError> {
107 let mut stream = TcpStream::connect(addr)?;
108 let handshake = format!("bus/{}/{}\0", bus_id, dev_id);
109 stream.write_all(handshake.as_bytes())?;
110 Ok(Self {
111 stream,
112 output_thread: None,
113 })
114 }
115
116 pub fn send<T: crate::wire::DeviceInput>(&mut self, input: &T) -> Result<(), ViiperError> {
118 let bytes = input.to_bytes();
119 self.stream.write_all(&bytes)?;
120 Ok(())
121 }
122
123 pub fn on_output<F>(&mut self, mut callback: F) -> Result<(), ViiperError>
128 where
129 F: FnMut(&mut dyn std::io::BufRead) -> std::io::Result<()> + Send + 'static,
130 {
131 if self.output_thread.is_some() {
132 return Err(ViiperError::UnexpectedResponse("Output callback already registered".into()));
133 }
134
135 let stream = self.stream.try_clone()?;
136 let handle = std::thread::spawn(move || {
137 let mut reader = std::io::BufReader::new(stream);
138 loop {
139 match callback(&mut reader) {
140 Ok(()) => continue,
141 Err(_) => break,
142 }
143 }
144 });
145 self.output_thread = Some(handle);
146 Ok(())
147 }
148
149 pub fn send_raw(&mut self, data: &[u8]) -> Result<(), ViiperError> {
151 self.stream.write_all(data)?;
152 Ok(())
153 }
154
155 pub fn read_raw(&mut self, buf: &mut [u8]) -> Result<usize, ViiperError> {
157 self.stream.read(buf).map_err(Into::into)
158 }
159
160 pub fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), ViiperError> {
162 self.stream.read_exact(buf).map_err(Into::into)
163 }
164}
165
166impl Drop for DeviceStream {
167 fn drop(&mut self) {
168 let _ = self.stream.shutdown(std::net::Shutdown::Both);
169 if let Some(handle) = self.output_thread.take() {
170 let _ = handle.join();
171 }
172 }
173}