cat_dev/mion/cgis/
dump_eeprom.rs

1//! API's for interacting with `/dbg/eeprom_dump.cgi`, a page for live
2//! accessing the memory of the EEPROM on the MION devices.
3
4use crate::{
5	errors::NetworkError,
6	mion::{
7		cgis::{do_simple_request, encode_url_parameters},
8		proto::cgis::MionCGIErrors,
9	},
10};
11use bytes::{BufMut, Bytes, BytesMut};
12use reqwest::{Client, Method};
13use std::{fmt::Display, net::Ipv4Addr, ops::Deref, time::Duration};
14use tracing::debug;
15
16const EEPROM_MAX_ADDRESS: usize = 0x1E00;
17const TABLE_START_SIGIL: &str = "<table border=0 cellspacing=3 cellpadding=3>";
18const TABLE_END_SIGIL: &str = "</table>";
19
20/// Dump the existing EEPROM for a MION.
21///
22/// ## Errors
23///
24/// - If we cannot encode the parameters as a form url encoded.
25/// - If we cannot make the HTTP request.
26/// - If the server does not respond with a 200.
27/// - If we cannot read the body from HTTP.
28/// - If we cannot parse the HTML response.
29pub async fn dump_eeprom(mion_ip: Ipv4Addr) -> Result<Bytes, NetworkError> {
30	dump_eeprom_with_raw_client(&Client::default(), mion_ip).await
31}
32
33/// Perform an EEPROM DUMP request, but with an already existing HTTP client.
34///
35/// ## Errors
36///
37/// - If we cannot encode the parameters as a form url encoded.
38/// - If we cannot make the HTTP request.
39/// - If the server does not respond with a 200.
40/// - If we cannot read the body from HTTP.
41/// - If we cannot parse the HTML response.
42pub async fn dump_eeprom_with_raw_client(
43	client: &Client,
44	mion_ip: Ipv4Addr,
45) -> Result<Bytes, NetworkError> {
46	let mut memory_buffer = BytesMut::with_capacity(0x2000);
47
48	while memory_buffer.len() <= EEPROM_MAX_ADDRESS {
49		debug!(
50		  bridge.ip = %mion_ip,
51		  address = %format!("{:04X}", memory_buffer.len()),
52		  "Dumping eeprom memory area",
53		);
54
55		let body_as_string = do_raw_eeprom_request(
56			client,
57			mion_ip,
58			&[("start_addr", format!("{:04X}", memory_buffer.len()))],
59		)
60		.await?;
61
62		let table = extract_memory_table_body(&body_as_string)?;
63		for table_row in table.split("<tr>").skip(3) {
64			for table_column in table_row
65				.trim()
66				.trim_end_matches("</tbody>")
67				.trim_end()
68				.trim_end_matches("</tr>")
69				.trim_end()
70				.replace("</td>", "")
71				.split("<td>")
72				.skip(3)
73			{
74				if table_column.trim().len() != 2 {
75					return Err(MionCGIErrors::HtmlResponseBadByte(table_column.to_owned()).into());
76				}
77				memory_buffer
78					.put_u8(u8::from_str_radix(table_column.trim(), 16).map_err(|_| {
79						MionCGIErrors::HtmlResponseBadByte(table_column.to_owned())
80					})?);
81			}
82		}
83	}
84
85	Ok(memory_buffer.freeze())
86}
87
88fn extract_memory_table_body(body: &str) -> Result<String, MionCGIErrors> {
89	let start = body
90		.find(TABLE_START_SIGIL)
91		.ok_or_else(|| MionCGIErrors::HtmlResponseMissingMemoryDumpSigil(body.to_owned()))?;
92	let body_minus_start = &body[start + TABLE_START_SIGIL.len()..];
93	let end = body_minus_start
94		.find(TABLE_END_SIGIL)
95		.ok_or_else(|| MionCGIErrors::HtmlResponseMissingMemoryDumpSigil(body.to_owned()))?;
96
97	Ok(body_minus_start[..end].to_owned())
98}
99
100/// Perform a raw request on the MION board's `eeprom_dump.cgi` page.
101///
102/// *note: you probably want to call one of the actual methods, as this is
103/// basically just a thin wrapper around an HTTP Post Request. Not doing much
104/// else more. A lot of it requires that you set things up correctly.*
105///
106/// ## Errors
107///
108/// - If we cannot make an HTTP request to the MION Request.
109/// - If we fail to encode your parameters into a request body.
110pub async fn do_raw_eeprom_request(
111	client: &Client,
112	mion_ip: Ipv4Addr,
113	url_parameters: &[(impl Deref<Target = str>, impl Display)],
114) -> Result<String, NetworkError> {
115	do_simple_request::<String>(
116		client,
117		Method::POST,
118		format!("http://{mion_ip}/dbg/eeprom_dump.cgi"),
119		Some(encode_url_parameters(url_parameters)),
120		// Dump operations are sometimes slow...
121		Some(Duration::from_secs(60)),
122	)
123	.await
124}