1const AUTHZ_HEADER: &str = "bWlvbjovTXVsdGlfSS9PX05ldHdvcmsv";
16
17mod control;
18mod dump_eeprom;
19mod dump_memory;
20mod errors;
21mod setup;
22mod signal_get;
23mod status;
24mod update;
25
26pub use control::*;
27pub use dump_eeprom::*;
28pub use dump_memory::*;
29pub use errors::*;
30pub use setup::*;
31pub use signal_get::*;
32pub use status::*;
33pub use update::*;
34
35use crate::{
36 errors::{NetworkError, NetworkParseError},
37 mion::proto::cgis::MIONCGIErrors,
38};
39use bytes::Bytes;
40use reqwest::{Body, Client, Method, Response, Version};
41use tracing::{field::valuable, warn};
42
43async fn do_simple_request<BodyTy>(
59 client: &Client,
60 method: Method,
61 url: String,
62 body: Option<BodyTy>,
63) -> Result<String, NetworkError>
64where
65 BodyTy: Into<Body>,
66{
67 let mut req = client
68 .request(method, url)
69 .version(Version::HTTP_11)
70 .header("authorization", format!("Basic {AUTHZ_HEADER}"))
71 .header("content-type", "application/x-www-form-urlencoded")
72 .header("user-agent", concat!("cat-dev/", env!("CARGO_PKG_VERSION")));
73 if let Some(body) = body {
74 req = req.body(body);
75 }
76 let response_body =
77 assert_status_and_read_body(200, req.send().await.map_err(NetworkError::HTTP)?).await?;
78
79 Ok(String::from_utf8(response_body.into()).map_err(NetworkParseError::Utf8Expected)?)
80}
81
82async fn assert_status_and_read_body(
88 needed_status: u16,
89 response: Response,
90) -> Result<Bytes, NetworkError> {
91 let status = response.status().as_u16();
92 let body_result = response.bytes().await.map_err(NetworkError::HTTP);
93 if status != needed_status {
94 if let Ok(body) = body_result {
95 return Err(MIONCGIErrors::UnexpectedStatusCode(status, body).into());
96 }
97
98 return Err(MIONCGIErrors::UnexpectedStatusCodeNoBody(status).into());
99 }
100
101 body_result
102}
103
104fn parse_result_from_body(body: &str, operation_name: &str) -> Result<bool, MIONCGIErrors> {
109 let start_tag_location = body
110 .find("<body>")
111 .map(|num| num + 6)
112 .or(
113 body.find("<HTML>").map(|num| num + 6),
116 )
117 .ok_or_else(|| MIONCGIErrors::HtmlResponseMissingBody(body.to_owned()))?;
118 let body_without_start_tag = body.split_at(start_tag_location).1;
119 let end_tag_location = body_without_start_tag
120 .find("</body>")
121 .ok_or_else(|| MIONCGIErrors::HtmlResponseMissingBody(body.to_owned()))?;
122 let just_inner_body = body_without_start_tag.split_at(end_tag_location).0;
123 let without_newlines_or_extra_tags = just_inner_body
124 .replace('\n', "")
125 .replace("<CENTER>", "")
126 .replace("</CENTER>", "");
127
128 let mut was_successful = false;
129 let mut returned_result_code = "";
130 let mut log_lines = Vec::with_capacity(0);
131 let mut extra_lines = Vec::with_capacity(0);
132 for line in without_newlines_or_extra_tags
133 .split("<br>")
134 .fold(Vec::new(), |mut accum, item| {
135 accum.extend(item.split("<br/>"));
136 accum
137 }) {
138 let trimmed_line = line.trim();
139 if trimmed_line.is_empty() {
140 continue;
141 }
142
143 if let Some(result_code) = trimmed_line.strip_prefix("RESULT:") {
144 returned_result_code = result_code;
145 if result_code == "OK" {
146 was_successful = true;
147 }
148 } else if trimmed_line.starts_with("INFO:")
149 || trimmed_line.starts_with("ERROR:")
150 || trimmed_line.starts_with("WARN:")
151 {
152 log_lines.push(trimmed_line);
153 } else {
154 extra_lines.push(trimmed_line);
155 }
156 }
157
158 if !was_successful {
159 warn!(
160 log_lines = valuable(&log_lines),
161 extra_lines = valuable(&extra_lines),
162 %operation_name,
163 result_code = %returned_result_code,
164 "got an error from a result status page",
165 );
166 }
167
168 Ok(was_successful)
169}
170
171#[cfg(test)]
172mod unit_tests {
173 use super::*;
174
175 #[test]
176 pub fn can_parse_response_from_power_on_v2_on_14_80() {
177 assert!(
183 parse_result_from_body(
184 "<HTML>\r\nINFO: Enabling CATDEV mode...<br>\nINFO: no change in parameters<br>\nINFO: Enabling HSATA keep alive<br/>\nRESULT:OK<br>cafe powered on successfully, nAtt=0<br>\n</body>\n</HTML>",
185 "power_on_v2"
186 ).expect("Failed to parse result from body!"),
187 "Expected successful responsee."
188 );
189 }
190}