use crate::{
errors::NetworkError,
mion::{
cgis::do_simple_request,
proto::cgis::{MionCGIErrors, MionFirmwareVersions},
},
};
use reqwest::{Body, Client, Method};
use std::net::Ipv4Addr;
const FORM_PREFIX: &str = r#"<form method="POST" enctype="multipart/form-data""#;
const FORM_SUFFIX: &str = "</form>";
const VERISON_PREFIX: &str = "Version : ";
pub async fn get_versions(mion_ip: Ipv4Addr) -> Result<MionFirmwareVersions, NetworkError> {
get_versions_with_raw_client(&Client::default(), mion_ip).await
}
pub async fn get_versions_with_raw_client(
client: &Client,
mion_ip: Ipv4Addr,
) -> Result<MionFirmwareVersions, NetworkError> {
let body_as_string = do_simple_request::<Body>(
client,
Method::GET,
format!("http://{mion_ip}/update.cgi"),
None,
None,
)
.await?;
let (fw_version, fpga_version) = parse_versions_from_update_html(&body_as_string)?;
Ok(MionFirmwareVersions::from_versions(
fw_version,
fpga_version,
))
}
fn parse_versions_from_update_html(body_as_string: &str) -> Result<([u8; 3], u32), MionCGIErrors> {
let start_tag_location = body_as_string
.find("<body")
.map(|num| num + 5)
.ok_or_else(|| MionCGIErrors::HtmlResponseMissingBody(body_as_string.to_owned()))?;
let body_without_start_tag = body_as_string.split_at(start_tag_location).1;
let end_tag_location = body_without_start_tag
.find("</body>")
.ok_or_else(|| MionCGIErrors::HtmlResponseMissingBody(body_as_string.to_owned()))?;
let body_contents = body_without_start_tag
.split_at(end_tag_location)
.0
.to_owned();
let versions = get_version_elements(&body_contents)?;
if versions.len() != 2 {
return Err(MionCGIErrors::HtmlResponseMissingVersions(versions));
}
let fpga_version = u32::from_str_radix(&versions[0], 16)
.map_err(MionCGIErrors::HtmlResponseNumberExpectedButNotThere)?;
let mut fw_version = [0_u8; 3];
for (idx, item) in versions[1].splitn(3, '.').enumerate() {
fw_version[idx] = item
.parse::<u8>()
.map_err(MionCGIErrors::HtmlResponseNumberExpectedButNotThere)?;
}
Ok((fw_version, fpga_version))
}
fn get_version_elements(mut html_response: &str) -> Result<Vec<String>, MionCGIErrors> {
let mut results = Vec::new();
while let Some(idx) = html_response.find(FORM_PREFIX) {
let missing_form_start = html_response.split_at(idx + FORM_PREFIX.len()).1;
let Some(form_close_at) = missing_form_start.find(FORM_SUFFIX) else {
return Err(MionCGIErrors::HtmlResponseMissingClosingTag(
FORM_SUFFIX.to_owned(),
missing_form_start.to_owned(),
));
};
let (form_insides, form_outsides) = missing_form_start.split_at(form_close_at);
html_response = &form_outsides[FORM_SUFFIX.len()..];
let version_data = if let Some(loc) = form_insides.find("<input") {
form_insides.split_at(loc).0.trim().replace("<br>", "")
} else {
form_insides.trim().replace("<br>", "")
};
let Some(version_prefix_location) = version_data.find(VERISON_PREFIX) else {
return Err(MionCGIErrors::HtmlResponseMissingVersionPart(
VERISON_PREFIX.to_owned(),
version_data,
));
};
let version_with_extra_data = version_data
.split_at(version_prefix_location + VERISON_PREFIX.len())
.1;
let Some(version_suffix_location) = version_with_extra_data.find(')') else {
return Err(MionCGIErrors::HtmlResponseMissingVersionPart(
")".to_owned(),
version_with_extra_data.to_owned(),
));
};
results.push(
version_with_extra_data
.split_at(version_suffix_location)
.0
.trim()
.to_owned(),
);
}
Ok(results)
}
#[cfg(test)]
mod unit_tests {
use super::*;
const REAL_LIFE_UPDATE_PAGE: &str = r#"<HTML>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ASCII">
<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>
<script type="text/javascript" src="pwiss.js"></script>
</head>
<body bgcolor="FFFFFF" text="000000">
<div align="center">
<h1>Program Update</h1><br><br>
<div id="disp1">
<table border=0 cellspacing=0 cellpadding=0>
<tr>
<td>
<form method="POST" enctype="multipart/form-data" action="update_fpga.cgi">
FPGA Data (Present FPGA Version : 13052071)<br>
<input type="hidden" name="func" value="fpga_upd">
<input type="file" name="filename" size=50>
<input type="submit" value="Upload" OnClick="disp_change();">
<br>
</form>
<form method="POST" enctype="multipart/form-data" action="update_fw.cgi">
Firmware (Present Firmware Version : 00.14.70)<br>
<input type="hidden" name="func" value="fw_upd">
<input type="file" name="filename" size=50>
<input type="submit" value="Upload" OnClick="disp_change();">
<br>
</form>
</td>
</tr>
</table>
</div>
<div id="disp2" style="display:none;">
File Uploading . . .<br>
<br>
</div>
</div>
<hr>
<a href="./">Homepage</a>
</body>
</HTML>"#;
#[test]
pub fn can_parse_update_html() {
let (fw_version, fpga_verison) = parse_versions_from_update_html(REAL_LIFE_UPDATE_PAGE)
.expect("Failed to parse versions out of a 0.0.14.70 update.cgi page!");
assert_eq!(
fw_version,
[0_u8, 14_u8, 70_u8],
"Firmware Version did not match expected!"
);
assert_eq!(
fpga_verison, 0x13052071,
"FPGA version did not match expected!",
);
}
}