cat_dev/mion/cgis/
update.rs1use crate::{
12 errors::NetworkError,
13 mion::{
14 cgis::do_simple_request,
15 proto::cgis::{MionCGIErrors, MionFirmwareVersions},
16 },
17};
18use reqwest::{Body, Client, Method};
19use std::net::Ipv4Addr;
20
21const FORM_PREFIX: &str = r#"<form method="POST" enctype="multipart/form-data""#;
23const FORM_SUFFIX: &str = "</form>";
25const VERISON_PREFIX: &str = "Version : ";
28
29pub async fn get_versions(mion_ip: Ipv4Addr) -> Result<MionFirmwareVersions, NetworkError> {
39 get_versions_with_raw_client(&Client::default(), mion_ip).await
40}
41
42pub async fn get_versions_with_raw_client(
53 client: &Client,
54 mion_ip: Ipv4Addr,
55) -> Result<MionFirmwareVersions, NetworkError> {
56 let body_as_string = do_simple_request::<Body>(
57 client,
58 Method::GET,
59 format!("http://{mion_ip}/update.cgi"),
60 None,
61 None,
62 )
63 .await?;
64
65 let (fw_version, fpga_version) = parse_versions_from_update_html(&body_as_string)?;
66 Ok(MionFirmwareVersions::from_versions(
67 fw_version,
68 fpga_version,
69 ))
70}
71
72fn parse_versions_from_update_html(body_as_string: &str) -> Result<([u8; 3], u32), MionCGIErrors> {
73 let start_tag_location = body_as_string
76 .find("<body")
77 .map(|num| num + 5)
78 .ok_or_else(|| MionCGIErrors::HtmlResponseMissingBody(body_as_string.to_owned()))?;
79 let body_without_start_tag = body_as_string.split_at(start_tag_location).1;
80 let end_tag_location = body_without_start_tag
81 .find("</body>")
82 .ok_or_else(|| MionCGIErrors::HtmlResponseMissingBody(body_as_string.to_owned()))?;
83 let body_contents = body_without_start_tag
84 .split_at(end_tag_location)
85 .0
86 .to_owned();
87 let versions = get_version_elements(&body_contents)?;
88 if versions.len() != 2 {
91 return Err(MionCGIErrors::HtmlResponseMissingVersions(versions));
92 }
93
94 let fpga_version = u32::from_str_radix(&versions[0], 16)
95 .map_err(MionCGIErrors::HtmlResponseNumberExpectedButNotThere)?;
96 let mut fw_version = [0_u8; 3];
97 for (idx, item) in versions[1].splitn(3, '.').enumerate() {
98 fw_version[idx] = item
99 .parse::<u8>()
100 .map_err(MionCGIErrors::HtmlResponseNumberExpectedButNotThere)?;
101 }
102
103 Ok((fw_version, fpga_version))
104}
105
106fn get_version_elements(mut html_response: &str) -> Result<Vec<String>, MionCGIErrors> {
108 let mut results = Vec::new();
109
110 while let Some(idx) = html_response.find(FORM_PREFIX) {
111 let missing_form_start = html_response.split_at(idx + FORM_PREFIX.len()).1;
118 let Some(form_close_at) = missing_form_start.find(FORM_SUFFIX) else {
119 return Err(MionCGIErrors::HtmlResponseMissingClosingTag(
120 FORM_SUFFIX.to_owned(),
121 missing_form_start.to_owned(),
122 ));
123 };
124 let (form_insides, form_outsides) = missing_form_start.split_at(form_close_at);
125
126 html_response = &form_outsides[FORM_SUFFIX.len()..];
128
129 let version_data = if let Some(loc) = form_insides.find("<input") {
132 form_insides.split_at(loc).0.trim().replace("<br>", "")
133 } else {
134 form_insides.trim().replace("<br>", "")
135 };
136 let Some(version_prefix_location) = version_data.find(VERISON_PREFIX) else {
137 return Err(MionCGIErrors::HtmlResponseMissingVersionPart(
138 VERISON_PREFIX.to_owned(),
139 version_data,
140 ));
141 };
142
143 let version_with_extra_data = version_data
144 .split_at(version_prefix_location + VERISON_PREFIX.len())
145 .1;
146 let Some(version_suffix_location) = version_with_extra_data.find(')') else {
147 return Err(MionCGIErrors::HtmlResponseMissingVersionPart(
148 ")".to_owned(),
149 version_with_extra_data.to_owned(),
150 ));
151 };
152 results.push(
153 version_with_extra_data
154 .split_at(version_suffix_location)
155 .0
156 .trim()
157 .to_owned(),
158 );
159 }
160
161 Ok(results)
162}
163
164#[cfg(test)]
165mod unit_tests {
166 use super::*;
167
168 const REAL_LIFE_UPDATE_PAGE: &str = r#"<HTML>
171<head>
172<meta http-equiv="Content-Type" content="text/html; charset=ASCII">
173<meta http-equiv="Pragma" content="no-cache"><meta http-equiv="Cache-Control" content="no-cache"><meta http-equiv="Expires" content="0"><title>Program Update</title>
174<script type="text/javascript" src="pwiss.js"></script>
175</head>
176<body bgcolor="FFFFFF" text="000000">
177<div align="center">
178<h1>Program Update</h1><br><br>
179<div id="disp1">
180<table border=0 cellspacing=0 cellpadding=0>
181<tr>
182<td>
183<form method="POST" enctype="multipart/form-data" action="update_fpga.cgi">
184FPGA Data (Present FPGA Version : 13052071)<br>
185<input type="hidden" name="func" value="fpga_upd">
186<input type="file" name="filename" size=50>
187<input type="submit" value="Upload" OnClick="disp_change();">
188<br>
189</form>
190<form method="POST" enctype="multipart/form-data" action="update_fw.cgi">
191Firmware (Present Firmware Version : 00.14.70)<br>
192<input type="hidden" name="func" value="fw_upd">
193<input type="file" name="filename" size=50>
194<input type="submit" value="Upload" OnClick="disp_change();">
195<br>
196</form>
197</td>
198</tr>
199</table>
200</div>
201<div id="disp2" style="display:none;">
202 File Uploading . . .<br>
203<br>
204</div>
205</div>
206<hr>
207<a href="./">Homepage</a>
208</body>
209</HTML>"#;
210
211 #[test]
212 pub fn can_parse_update_html() {
213 let (fw_version, fpga_verison) = parse_versions_from_update_html(REAL_LIFE_UPDATE_PAGE)
214 .expect("Failed to parse versions out of a 0.0.14.70 update.cgi page!");
215 assert_eq!(
216 fw_version,
217 [0_u8, 14_u8, 70_u8],
218 "Firmware Version did not match expected!"
219 );
220 assert_eq!(
221 fpga_verison, 0x13052071,
222 "FPGA version did not match expected!",
223 );
224 }
225}