cat_dev/fsemul/sdio/
client.rs

1//! "Client" implementations for SDIO protocols for handling PCFS for cat-dev.
2//!
3//! Reminder that SDIO Client actually acts like a server, and binds on a port
4//! on the machine it's running on. Then the server _connects to us_. This is
5//! still exposed at the exact same tcp-client class that you know and
6//! hopefully love.
7
8use crate::{
9	errors::{CatBridgeError, NetworkError},
10	fsemul::sdio::{
11		SDIO_DATA_STREAMS,
12		data_stream::DataStream,
13		errors::SdioNetworkError,
14		proto::{
15			SDIO_BLOCK_SIZE,
16			message::{
17				SdioControlMessageRequest, SdioControlTelnetChannel, SdioControlTelnetMessage,
18			},
19			read::SdioControlReadRequest,
20		},
21	},
22	net::client::{TCPClient, models::RequestStreamEvent},
23};
24use bytes::Bytes;
25use std::{net::SocketAddr, sync::Arc, time::Duration};
26use tokio::{net::TcpListener, sync::RwLock};
27use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
28
29/// The default time to wait for successfully sending a packet.
30const DEFAULT_SEND_TIMEOUT: Duration = Duration::from_secs(5 * 60);
31
32/// A TCP Client capable of interacting with an SDIO server.
33#[derive(Debug)]
34pub struct SdioClient {
35	/// A TCP listener for accepting new TCP Data stream connections.
36	data_listener: Arc<RwLock<TcpListener>>,
37	/// How long to wait for a response.
38	response_timeout: Duration,
39	/// The actual TCP Client we use to stream bytes to and from.
40	underlying_client: TCPClient,
41}
42
43impl SdioClient {
44	/// Create a new SDIO TCP Client.
45	///
46	/// This will bind listeners, but will not necissarily ensure a client is
47	/// connected. Most internal methods will ensure a client is connected, but
48	/// you can
49	///
50	/// ## Errors
51	///
52	/// If we cannot bind to any of the ports we need to bind too.
53	pub async fn new(
54		bind_address_control: SocketAddr,
55		bind_address_data: SocketAddr,
56		trace_during_debug: bool,
57	) -> Result<Self, CatBridgeError> {
58		let mut client = TCPClient::new("sdio", 512_usize, (None, None), trace_during_debug);
59		let data = Arc::new(RwLock::new(
60			TcpListener::bind(bind_address_data)
61				.await
62				.map_err(NetworkError::IO)?,
63		));
64		client.bind(bind_address_control).await?;
65
66		let cloned_data_start = data.clone();
67		#[cfg(debug_assertions)]
68		let copied_trace: bool = trace_during_debug;
69		client.set_on_stream_begin(move |event: RequestStreamEvent<()>| async move {
70			let sid = event.stream_id();
71			let (connection, server_location) = {
72				let guard = cloned_data_start.write().await;
73				guard.accept().await
74			}
75			.map_err(NetworkError::IO)?;
76			connection.set_nodelay(true).map_err(NetworkError::IO)?;
77			let client_location = connection.local_addr().map_err(NetworkError::IO)?;
78
79			_ = SDIO_DATA_STREAMS
80				.insert_async(
81					sid,
82					DataStream::from_stream(
83						client_location,
84						server_location,
85						connection,
86						#[cfg(debug_assertions)]
87						copied_trace,
88					)?,
89				)
90				.await;
91
92			Ok::<bool, CatBridgeError>(true)
93		})?;
94
95		client.set_on_stream_end(|event: RequestStreamEvent<()>| async move {
96			SDIO_DATA_STREAMS.remove_async(&event.stream_id()).await;
97			Ok(())
98		})?;
99
100		Ok(Self {
101			data_listener: data,
102			response_timeout: DEFAULT_SEND_TIMEOUT,
103			underlying_client: client,
104		})
105	}
106
107	#[must_use]
108	pub const fn get_response_timeout(&self) -> Duration {
109		self.response_timeout
110	}
111
112	/// Send a telnet message over `CafeOS`.
113	///
114	/// This will chunk the message into 499 byte chunks, as that is the maximum
115	/// amount SDIO protocol can support at once. This may split char boundaries
116	/// which is probably fine.
117	///
118	/// ## Errors
119	///
120	/// If we cannot send out all of the messages to chunk out on the stream.
121	/// NOTE that some _may_ still be sent, as it will only be the first error
122	/// that gets bubbled up.
123	///
124	/// This may mean prior messages may have been sent out first.
125	pub async fn send_telnet_cafe_os(&self, message: String) -> Result<(), CatBridgeError> {
126		for char_chunk in message.bytes().collect::<Vec<u8>>().chunks(499) {
127			let msg_chunk = unsafe { String::from_utf8_unchecked(char_chunk.to_vec()) };
128			self.underlying_client
129				.send(
130					SdioControlMessageRequest::new(vec![SdioControlTelnetMessage::new(
131						msg_chunk,
132						SdioControlTelnetChannel::CafeOS,
133					)?]),
134					None,
135				)
136				.await?;
137		}
138
139		Ok(())
140	}
141
142	/// Send a telnet message over devkitmsg.
143	///
144	/// This will chunk the message into 499 byte chunks, as that is the maximum
145	/// amount SDIO protocol can support at once. This may split char boundaries
146	/// which is probably fine.
147	///
148	/// ## Errors
149	///
150	/// If we cannot send out all of the messages to chunk out on the stream.
151	/// NOTE that some _may_ still be sent, as it will only be the first error
152	/// that gets bubbled up.
153	///
154	/// This may mean prior messages may have been sent out first.
155	pub async fn send_telnet_dkm(&self, message: String) -> Result<(), CatBridgeError> {
156		for char_chunk in message.bytes().collect::<Vec<u8>>().chunks(499) {
157			let msg_chunk = unsafe { String::from_utf8_unchecked(char_chunk.to_vec()) };
158			self.underlying_client
159				.send(
160					SdioControlMessageRequest::new(vec![SdioControlTelnetMessage::new(
161						msg_chunk,
162						SdioControlTelnetChannel::DevkitMsg,
163					)?]),
164					None,
165				)
166				.await?;
167		}
168
169		Ok(())
170	}
171
172	/// Send a telnet message over devkitmsg.
173	///
174	/// This will chunk the message into 499 byte chunks, as that is the maximum
175	/// amount SDIO protocol can support at once. This may split char boundaries
176	/// which is probably fine.
177	///
178	/// ## Errors
179	///
180	/// If we cannot send out all of the messages to chunk out on the stream.
181	/// NOTE that some _may_ still be sent, as it will only be the first error
182	/// that gets bubbled up.
183	///
184	/// This may mean prior messages may have been sent out first.
185	pub async fn send_telnet_sysconfigtool(&self, message: String) -> Result<(), CatBridgeError> {
186		for char_chunk in message.bytes().collect::<Vec<u8>>().chunks(499) {
187			let msg_chunk = unsafe { String::from_utf8_unchecked(char_chunk.to_vec()) };
188			self.underlying_client
189				.send(
190					SdioControlMessageRequest::new(vec![SdioControlTelnetMessage::new(
191						msg_chunk,
192						SdioControlTelnetChannel::SysConfigTool,
193					)?]),
194					None,
195				)
196				.await?;
197		}
198
199		Ok(())
200	}
201
202	/// Send a series of telnet messages across the telnet channel of this SDIO
203	/// client.
204	///
205	/// In general you should probably prefer [`Self::send_telnet_cafe_os`],
206	/// [`Self::send_telnet_dkm`], [`Self::send_telnet_sysconfigtool`] where we
207	/// just send strings over.
208	///
209	/// ## Errors
210	///
211	/// If we cannot send out all of the messages to chunk out on the stream.
212	/// NOTE that some _may_ still be sent, as it will only be the first error
213	/// that gets bubbled up.
214	///
215	/// This may mean prior messages may have been sent out first.
216	pub async fn send_raw_telnet_message(
217		&self,
218		messages: Vec<SdioControlTelnetMessage>,
219	) -> Result<(), CatBridgeError> {
220		for message in messages {
221			self.underlying_client
222				.send(SdioControlMessageRequest::new(vec![message]), None)
223				.await?;
224		}
225
226		Ok(())
227	}
228
229	/// Perform a read request against the remote upstream.
230	///
231	/// This will read from an address a certain amount of blocks. In real
232	/// uses the channel is always the same low number, but you may want to
233	/// perform multi channel reads.
234	///
235	/// ## Errors
236	///
237	/// If we cannot send a request over the Control stream, or if we cannot read
238	/// a response on the data stream.
239	pub async fn read(&self, lba: u32, blocks: u32, channel: u32) -> Result<Bytes, CatBridgeError> {
240		// Send a message then we'll access the primary streams data stream.
241		let (primary_stream_id, _req_id, _) = self
242			.underlying_client
243			.send(SdioControlReadRequest::new(lba, blocks, channel)?, None)
244			.await?;
245		let Some(data_stream) = SDIO_DATA_STREAMS.get_async(&primary_stream_id).await else {
246			return Err(SdioNetworkError::DataStreamMissing(primary_stream_id).into());
247		};
248
249		Ok(data_stream
250			.recv(usize::try_from(blocks).unwrap_or(usize::MAX) * SDIO_BLOCK_SIZE)
251			.await?)
252	}
253}
254
255const SDIO_CLIENT_FIELDS: &[NamedField<'static>] = &[
256	NamedField::new("data_listener"),
257	NamedField::new("underlying_client"),
258];
259
260impl Structable for SdioClient {
261	fn definition(&self) -> StructDef<'_> {
262		StructDef::new_static("SdioClient", Fields::Named(SDIO_CLIENT_FIELDS))
263	}
264}
265
266impl Valuable for SdioClient {
267	fn as_value(&self) -> Value<'_> {
268		Value::Structable(self)
269	}
270
271	fn visit(&self, visitor: &mut dyn Visit) {
272		visitor.visit_named_fields(&NamedValues::new(
273			SDIO_CLIENT_FIELDS,
274			&[
275				Valuable::as_value(&format!("{:?}", self.data_listener)),
276				Valuable::as_value(&self.underlying_client),
277			],
278		));
279	}
280}