async_resol_vbus/
device_information.rs1use std::{net::SocketAddr, time::Duration};
2
3use async_std::{
4 net::{Shutdown, TcpStream},
5 prelude::*,
6};
7
8use crate::error::Result;
9
10#[derive(Debug, Clone)]
12pub struct DeviceInformation {
13 pub address: SocketAddr,
15
16 pub vendor: Option<String>,
18
19 pub product: Option<String>,
21
22 pub serial: Option<String>,
24
25 pub version: Option<String>,
27
28 pub build: Option<String>,
30
31 pub name: Option<String>,
33
34 pub features: Option<String>,
36}
37
38impl DeviceInformation {
39 pub async fn fetch(addr: SocketAddr, timeout: Duration) -> Result<DeviceInformation> {
60 let (buf, len) = async_std::io::timeout(timeout, async {
61 let mut stream = TcpStream::connect(addr).await?;
62
63 stream
64 .write_all(b"GET /cgi-bin/get_resol_device_information HTTP/1.0\r\n\r\n")
65 .await?;
66
67 stream.shutdown(Shutdown::Write)?;
68
69 let mut buf = Vec::with_capacity(1024);
70 let len = stream.read_to_end(&mut buf).await?;
71
72 Ok((buf, len))
73 })
74 .await?;
75
76 let buf = &buf[0..len];
77
78 let body_idx = {
79 let mut body_idx = None;
80
81 let mut idx = 0;
82 while idx < len - 4 {
83 if buf[idx] != 13 {
84 } else if buf[idx + 1] != 10 {
86 } else if buf[idx + 2] != 13 {
88 } else if buf[idx + 3] != 10 {
90 } else {
92 body_idx = Some(idx + 4);
93 break;
94 }
95
96 idx += 1;
97 }
98
99 match body_idx {
100 Some(idx) => idx,
101 None => return Err("No HTTP header separator found".into()),
102 }
103 };
104
105 let body_bytes = &buf[body_idx..];
106 let body = std::str::from_utf8(body_bytes)?;
107
108 DeviceInformation::parse(addr, body)
109 }
110
111 pub fn parse(address: SocketAddr, s: &str) -> Result<DeviceInformation> {
130 #[derive(PartialEq)]
131 enum Phase {
132 InKey,
133 WaitingForEquals,
134 WaitingForValueStartQuote,
135 InValue,
136 AfterValueEndQuote,
137 Malformed,
138 }
139
140 let mut vendor = None;
141 let mut product = None;
142 let mut serial = None;
143 let mut version = None;
144 let mut build = None;
145 let mut name = None;
146 let mut features = None;
147
148 for line in s.lines() {
149 let key_start_idx = 0;
150 let mut key_end_idx = 0;
151 let mut value_start_idx = 0;
152 let mut value_end_idx = 0;
153 let mut phase = Phase::InKey;
154
155 for (idx, c) in line.char_indices() {
156 let is_word_char = match c {
157 '0'..='9' | 'A'..='Z' | 'a'..='z' | '_' => true,
158 _ => false,
159 };
160
161 match phase {
162 Phase::InKey => {
163 if !is_word_char {
164 key_end_idx = idx;
165 phase = if c == '=' {
166 Phase::WaitingForValueStartQuote
167 } else {
168 Phase::WaitingForEquals
169 };
170 }
171 }
172 Phase::WaitingForEquals => {
173 if c == '=' {
174 phase = Phase::WaitingForValueStartQuote;
175 } else {
176 phase = Phase::Malformed;
177 }
178 }
179 Phase::WaitingForValueStartQuote => {
180 if c == '"' {
181 value_start_idx = idx + 1;
182 phase = Phase::InValue;
183 } else if is_word_char {
184 phase = Phase::Malformed;
185 }
186 }
187 Phase::InValue => {
188 if c == '"' {
189 value_end_idx = idx;
190 phase = Phase::AfterValueEndQuote;
191 }
192 }
193 Phase::AfterValueEndQuote => phase = Phase::Malformed,
194 Phase::Malformed => {}
195 }
196 }
197
198 if phase == Phase::AfterValueEndQuote {
199 let key = &line[key_start_idx..key_end_idx];
200 let value = &line[value_start_idx..value_end_idx];
201
202 if key.eq_ignore_ascii_case("vendor") {
203 vendor = Some(value.into());
204 } else if key.eq_ignore_ascii_case("product") {
205 product = Some(value.into());
206 } else if key.eq_ignore_ascii_case("serial") {
207 serial = Some(value.into());
208 } else if key.eq_ignore_ascii_case("version") {
209 version = Some(value.into());
210 } else if key.eq_ignore_ascii_case("build") {
211 build = Some(value.into());
212 } else if key.eq_ignore_ascii_case("name") {
213 name = Some(value.into());
214 } else if key.eq_ignore_ascii_case("features") {
215 features = Some(value.into());
216 } else {
217 }
219 }
220 }
221
222 Ok(DeviceInformation {
223 address,
224 vendor,
225 product,
226 serial,
227 version,
228 build,
229 name,
230 features,
231 })
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use async_std::net::{SocketAddr, TcpListener};
238
239 use super::*;
240
241 #[test]
242 fn test() -> Result<()> {
243 async_std::task::block_on(async {
244 let web_addr = "127.0.0.1:0".parse::<SocketAddr>()?;
245 let web_socket = TcpListener::bind(web_addr).await?;
246 let web_addr = web_socket.local_addr()?;
247
248 let web_future = async_std::task::spawn::<_, Result<()>>(async move {
249 loop {
250 let (mut stream, _) = web_socket.accept().await?;
251
252 let mut buf = Vec::new();
253 stream.read_to_end(&mut buf).await?;
254
255 let response = b"HTTP/1.0 200 OK\r\n\r\nvendor = \"RESOL\"\r\nproduct = \"DL2\"\r\nserial = \"001E66xxxxxx\"\r\nversion = \"2.2.0\"\r\nbuild = \"rc1\"\r\nname = \"DL2-001E66xxxxxx\"\r\nfeatures = \"vbus,dl2\"\r\n";
256 stream.write_all(response).await?;
257 drop(stream);
258 }
259 });
260
261 let fetch_future = async_std::task::spawn::<_, Result<()>>(async move {
262 let device = DeviceInformation::fetch(web_addr, Duration::from_millis(100)).await?;
263
264 assert_eq!(Some("RESOL"), device.vendor.as_ref().map(|s| s.as_str()));
265 assert_eq!(Some("DL2"), device.product.as_ref().map(|s| s.as_str()));
266 assert_eq!(
267 Some("001E66xxxxxx"),
268 device.serial.as_ref().map(|s| s.as_str())
269 );
270 assert_eq!(Some("2.2.0"), device.version.as_ref().map(|s| s.as_str()));
271 assert_eq!(Some("rc1"), device.build.as_ref().map(|s| s.as_str()));
272 assert_eq!(
273 Some("DL2-001E66xxxxxx"),
274 device.name.as_ref().map(|s| s.as_str())
275 );
276 assert_eq!(
277 Some("vbus,dl2"),
278 device.features.as_ref().map(|s| s.as_str())
279 );
280
281 Ok(())
282 });
283
284 fetch_future.await?;
285 drop(web_future);
286
287 Ok(())
288 })
289 }
290}