Skip to main content

cat_dev/fsemul/sdio/proto/
message.rs

1//! An SDIO "message", which is effectively a wrapper around multiple telnet
2//! streams.
3
4use crate::{
5	errors::NetworkParseError,
6	fsemul::sdio::errors::{SdioApiError, SdioProtocolError},
7};
8use bytes::{Buf, BufMut, Bytes, BytesMut};
9use std::fmt::Write;
10use valuable::{
11	EnumDef, Enumerable, Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value,
12	Variant, VariantDef, Visit,
13};
14
15/// Telnet 'channels' or streams. As SDIO actually jumbles multiple telnet
16/// channels.
17#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Valuable)]
18pub enum SdioControlTelnetChannel {
19	/// The SCT or sysconfigtool telnet channel.
20	SysConfigTool,
21	/// The `CafeOS` or 'COS' telnet channel.
22	CafeOS,
23	/// Devkit Message channel.
24	DevkitMsg,
25	/// An arbitrary channel that can represent anything.
26	Arbitrary(u16),
27}
28
29impl From<SdioControlTelnetChannel> for u16 {
30	fn from(value: SdioControlTelnetChannel) -> Self {
31		match value {
32			SdioControlTelnetChannel::SysConfigTool => 4,
33			SdioControlTelnetChannel::DevkitMsg => 8,
34			SdioControlTelnetChannel::CafeOS => 9,
35			SdioControlTelnetChannel::Arbitrary(val) => val,
36		}
37	}
38}
39
40impl TryFrom<u16> for SdioControlTelnetChannel {
41	type Error = SdioProtocolError;
42
43	fn try_from(value: u16) -> Result<Self, Self::Error> {
44		match value {
45			4 => Ok(Self::SysConfigTool),
46			8 => Ok(Self::DevkitMsg),
47			9 => Ok(Self::CafeOS),
48			_ => Ok(SdioControlTelnetChannel::Arbitrary(value)),
49		}
50	}
51}
52
53/// Sdio Telnet data that came over the channel.
54///
55/// NOTE: We do strip things like IAC sequences (except for backspace, and clear
56/// line)! The only thing left is raw string data.
57#[derive(Clone, Debug, PartialEq, Eq, Hash)]
58pub enum SdioControlTelnetData {
59	/// What should be _string like_ data.
60	StringData(Bytes),
61	/// Backspace and remove the last character from a string.
62	Backspace,
63	/// Clear the entire telnet data so far.
64	ClearLine,
65}
66
67impl From<&SdioControlTelnetData> for Bytes {
68	fn from(value: &SdioControlTelnetData) -> Self {
69		match value {
70			SdioControlTelnetData::Backspace => Bytes::from(vec![0xFF, 0xF7]),
71			SdioControlTelnetData::ClearLine => Bytes::from(vec![0xFF, 0xF8]),
72			SdioControlTelnetData::StringData(by) => by.clone(),
73		}
74	}
75}
76
77impl From<SdioControlTelnetData> for Bytes {
78	fn from(value: SdioControlTelnetData) -> Self {
79		Self::from(&value)
80	}
81}
82
83static SDIO_CONTROL_TELNET_DATA_VARIANTS: &[VariantDef<'static>] = &[
84	VariantDef::new("Backspace", Fields::Unnamed(0)),
85	VariantDef::new("ClearLine", Fields::Unnamed(0)),
86	VariantDef::new("StringData", Fields::Unnamed(1)),
87];
88
89impl Enumerable for SdioControlTelnetData {
90	fn definition(&self) -> EnumDef<'_> {
91		EnumDef::new_static("SdioControlTelnetData", SDIO_CONTROL_TELNET_DATA_VARIANTS)
92	}
93
94	fn variant(&self) -> Variant<'_> {
95		match self {
96			Self::Backspace => Variant::Static(&SDIO_CONTROL_TELNET_DATA_VARIANTS[0]),
97			Self::ClearLine => Variant::Static(&SDIO_CONTROL_TELNET_DATA_VARIANTS[1]),
98			Self::StringData(_) => Variant::Static(&SDIO_CONTROL_TELNET_DATA_VARIANTS[2]),
99		}
100	}
101}
102
103impl Valuable for SdioControlTelnetData {
104	fn as_value(&self) -> Value<'_> {
105		Value::Enumerable(self)
106	}
107
108	fn visit(&self, visitor: &mut dyn Visit) {
109		match self {
110			Self::StringData(line) => {
111				let mut data = String::with_capacity(line.len() * 2);
112				for byte in line {
113					_ = write!(&mut data, "{byte:02x}");
114				}
115				visitor.visit_unnamed_fields(&[Valuable::as_value(&data)]);
116			}
117			Self::Backspace | Self::ClearLine => visitor.visit_unnamed_fields(&[]),
118		}
119	}
120}
121
122/// A telnet message that has come in.
123#[derive(Clone, Debug, PartialEq, Eq, Valuable)]
124pub struct SdioControlTelnetMessage {
125	/// The buffer we've parsed out of the telnet message, and aim to send.
126	///
127	/// This will just cover a series of string data, and other telnet escapes
128	/// that we care about (backspace, and clear line).
129	buff: Vec<SdioControlTelnetData>,
130	/// INTERNAL: actual read amount for actually calculating total chars used as
131	/// we remove on deserialize.
132	read_amount: usize,
133	/// The underlying channel this telnet message is for.
134	telnet_channel: SdioControlTelnetChannel,
135}
136
137impl SdioControlTelnetMessage {
138	/// Construct a new sdio control telnet message.
139	///
140	/// ## Errors
141	///
142	/// If the serialized size is >499 bytes long.
143	pub fn new(
144		buff: String,
145		telnet_channel: SdioControlTelnetChannel,
146	) -> Result<Self, SdioApiError> {
147		let this = Self {
148			buff: vec![SdioControlTelnetData::StringData(Bytes::from(
149				buff.into_bytes(),
150			))],
151			read_amount: 0,
152			telnet_channel,
153		};
154		if this.calculate_size() > 499 {
155			return Err(SdioApiError::TelnetMessageToLong(this.serialize_all()));
156		}
157		Ok(this)
158	}
159
160	/// Construct a new SDIO control telnet message from a series of raw bytes.
161	///
162	/// ## Errors
163	///
164	/// If the serialized size is >499 bytes long.
165	pub fn new_raw(
166		buff: Vec<SdioControlTelnetData>,
167		telnet_channel: SdioControlTelnetChannel,
168	) -> Result<Self, SdioApiError> {
169		let this = Self {
170			buff,
171			read_amount: 0,
172			telnet_channel,
173		};
174		if this.calculate_size() > 499 {
175			return Err(SdioApiError::TelnetMessageToLong(this.serialize_all()));
176		}
177		Ok(this)
178	}
179
180	/// Test helper used to creeate a telnet message with a read amount.
181	///
182	/// ## Errors
183	///
184	/// If the serialized size is >499 bytes long.
185	#[cfg(test)]
186	#[must_use]
187	pub fn new_with_read_amount(
188		buff: String,
189		read_amount: usize,
190		telnet_channel: SdioControlTelnetChannel,
191	) -> Result<Self, SdioApiError> {
192		let this = Self {
193			buff: vec![SdioControlTelnetData::StringData(Bytes::from(
194				buff.into_bytes(),
195			))],
196			read_amount,
197			telnet_channel,
198		};
199		if this.calculate_size() > 499 {
200			return Err(SdioApiError::TelnetMessageToLong(this.serialize_all()));
201		}
202		Ok(this)
203	}
204
205	/// Get the underlying buffer of input.
206	#[must_use]
207	pub fn buffer(&self) -> &Vec<SdioControlTelnetData> {
208		&self.buff
209	}
210
211	/// Get the buffer as a complete string, you should only call this when you
212	/// are sure this data isn't split over multiple messages!
213	#[must_use]
214	pub fn buffer_as_complete_string(self) -> String {
215		let mut result = String::new();
216		for item in self.buff {
217			match item {
218				SdioControlTelnetData::Backspace => {
219					_ = result.pop();
220				}
221				SdioControlTelnetData::ClearLine => {
222					result = String::new();
223				}
224				SdioControlTelnetData::StringData(data) => {
225					result.push_str(&String::from_utf8_lossy(&data));
226				}
227			}
228		}
229		result
230	}
231
232	/// Set the buffer to a new message.
233	///
234	/// ## Errors
235	///
236	/// If the buffer is too long to fit inside a single message.
237	pub fn set_buffer(&mut self, new: String) -> Result<(), SdioApiError> {
238		if new.len() > 499 {
239			return Err(SdioApiError::TelnetMessageToLong(Bytes::from(
240				new.into_bytes(),
241			)));
242		}
243
244		self.buff = vec![SdioControlTelnetData::StringData(Bytes::from(
245			new.into_bytes(),
246		))];
247		Ok(())
248	}
249
250	/// Set the buffer to a new message of raw bytes.
251	///
252	/// ## Errors
253	///
254	/// If the buffer is too long to fit inside a single message.
255	pub fn set_buffer_raw(
256		&mut self,
257		mut new: Vec<SdioControlTelnetData>,
258	) -> Result<(), SdioApiError> {
259		std::mem::swap(&mut new, &mut self.buff);
260		if self.calculate_size() > 499 {
261			let serialized = self.serialize_all();
262			std::mem::swap(&mut new, &mut self.buff);
263			return Err(SdioApiError::TelnetMessageToLong(serialized));
264		}
265
266		Ok(())
267	}
268
269	/// Get the telnet channel this message is for.
270	#[must_use]
271	pub const fn channel(&self) -> SdioControlTelnetChannel {
272		self.telnet_channel
273	}
274
275	pub const fn set_channel(&mut self, new: SdioControlTelnetChannel) {
276		self.telnet_channel = new;
277	}
278
279	/// Calculate the total size of the buffer when serialized.
280	#[must_use]
281	fn calculate_size(&self) -> usize {
282		let mut result = 0_usize;
283		for item in &self.buff {
284			result += Bytes::from(item).len();
285		}
286		result
287	}
288
289	fn serialize_all(&self) -> Bytes {
290		let mut final_result = BytesMut::new();
291		for item in &self.buff {
292			final_result.extend(Bytes::from(item));
293		}
294		final_result.freeze()
295	}
296}
297
298impl From<&SdioControlTelnetMessage> for Bytes {
299	fn from(value: &SdioControlTelnetMessage) -> Self {
300		let mut bytes = BytesMut::with_capacity(8 + value.buff.len());
301		bytes.put_u16_le(u16::from(value.telnet_channel));
302		bytes.extend_from_slice(&[0x0C, 0x00, 0xFF, 0xFF]);
303		let serialized = value.serialize_all();
304		bytes.put_u16_le(u16::try_from(serialized.len() + 12).unwrap_or(u16::MAX));
305		bytes.extend(serialized);
306		bytes.put_u8(0x00);
307		bytes.freeze()
308	}
309}
310
311impl From<SdioControlTelnetMessage> for Bytes {
312	fn from(value: SdioControlTelnetMessage) -> Self {
313		Self::from(&value)
314	}
315}
316
317impl TryFrom<Bytes> for SdioControlTelnetMessage {
318	type Error = NetworkParseError;
319
320	fn try_from(value: Bytes) -> Result<Self, Self::Error> {
321		if value.len() < 8 {
322			return Err(NetworkParseError::NotEnoughData(
323				"SdioControlTelnetMessage",
324				8,
325				value.len(),
326				value,
327			));
328		}
329
330		let telnet_channel =
331			SdioControlTelnetChannel::try_from(u16::from_le_bytes([value[0], value[1]]))?;
332		debug_assert_eq!(
333			[value[2], value[3], value[4], value[5]],
334			[0x0C, 0x00, 0xFF, 0xFF],
335			"Seems to be constant across all telnet messages? MYTHRA to validate",
336		);
337		// Remove 4 as character len includes packet header which we want to ignore...
338		let character_len = std::cmp::min(
339			std::cmp::min(
340				u16::from_le_bytes([value[6], value[7]]).saturating_sub(4),
341				500,
342			),
343			u16::try_from(value.len().saturating_sub(8)).unwrap_or_default(),
344		);
345		let read_bytes = 8 + character_len;
346
347		// Create final event buff.
348		let mut final_buffer = Vec::new();
349		let mut current_string_data = Vec::new();
350
351		let mut my_iter = value
352			.iter()
353			.skip(8)
354			.take(usize::from(character_len))
355			.peekable();
356		while let Some(byte) = my_iter.next() {
357			// Things to watch out for:
358			// - 0x00: We are a CString so 0x00 means that's our end.
359			// - 0xFF: Start of a telnet command.
360			if *byte == 0xFF {
361				let next_byte = my_iter.peek().map(|b| **b).unwrap_or_default();
362				// Is a three byte command.
363				if (251..=254).contains(&next_byte) {
364					_ = my_iter.next();
365					_ = my_iter.next();
366				} else if next_byte == 247 {
367					if !current_string_data.is_empty() {
368						final_buffer.push(SdioControlTelnetData::StringData(Bytes::from(
369							std::mem::take(&mut current_string_data),
370						)));
371					}
372					final_buffer.push(SdioControlTelnetData::Backspace);
373				} else if next_byte == 248 {
374					if !current_string_data.is_empty() {
375						final_buffer.push(SdioControlTelnetData::StringData(Bytes::from(
376							std::mem::take(&mut current_string_data),
377						)));
378					}
379					final_buffer.push(SdioControlTelnetData::ClearLine);
380				} else {
381					_ = my_iter.next();
382				}
383
384				continue;
385			} else if *byte == 0x00 {
386				break;
387			}
388			current_string_data.push(*byte);
389		}
390
391		if !current_string_data.is_empty() {
392			final_buffer.push(SdioControlTelnetData::StringData(Bytes::from(
393				std::mem::take(&mut current_string_data),
394			)));
395		}
396
397		Ok(Self {
398			buff: final_buffer,
399			read_amount: usize::from(read_bytes),
400			telnet_channel,
401		})
402	}
403}
404
405/// Handle a Write Request coming over the SDIO Control port.
406#[derive(Debug, PartialEq, Eq)]
407pub struct SdioControlMessageRequest {
408	messages: Vec<SdioControlTelnetMessage>,
409}
410
411impl SdioControlMessageRequest {
412	#[must_use]
413	pub const fn new(messages: Vec<SdioControlTelnetMessage>) -> Self {
414		Self { messages }
415	}
416
417	#[must_use]
418	pub const fn messages(&self) -> &Vec<SdioControlTelnetMessage> {
419		&self.messages
420	}
421
422	#[must_use]
423	pub fn messages_owned(self) -> Vec<SdioControlTelnetMessage> {
424		self.messages
425	}
426}
427
428impl TryFrom<&SdioControlMessageRequest> for Bytes {
429	type Error = NetworkParseError;
430
431	#[allow(
432		// We will actually loop in the future.
433		clippy::never_loop,
434	)]
435	fn try_from(value: &SdioControlMessageRequest) -> Result<Self, Self::Error> {
436		let mut inner_size = 3_usize;
437		let mut request_body = BytesMut::with_capacity(500);
438
439		for msg in value.messages() {
440			let value = Bytes::from(msg);
441			inner_size += value.len();
442			request_body.extend(value);
443		}
444		let pad_amount = 500_usize.saturating_sub(request_body.len());
445		request_body.extend(BytesMut::zeroed(pad_amount));
446		if request_body.len() > 500 {
447			return Err(NetworkParseError::UnexpectedTrailer(
448				"SdioControlMessageRequest",
449				request_body.freeze().slice(500..),
450			));
451		}
452
453		let mut final_buff = BytesMut::with_capacity(512);
454		final_buff.put_u16_le(8);
455		final_buff.put_u16_le(u16::try_from(inner_size).unwrap_or(u16::MAX));
456		final_buff.extend(request_body);
457		final_buff.extend(BytesMut::zeroed(512_usize.saturating_sub(final_buff.len())));
458		Ok(final_buff.freeze())
459	}
460}
461
462impl TryFrom<SdioControlMessageRequest> for Bytes {
463	type Error = NetworkParseError;
464
465	fn try_from(value: SdioControlMessageRequest) -> Result<Self, Self::Error> {
466		Self::try_from(&value)
467	}
468}
469
470impl TryFrom<Bytes> for SdioControlMessageRequest {
471	type Error = NetworkParseError;
472
473	fn try_from(mut value: Bytes) -> Result<Self, Self::Error> {
474		if value.len() != 512 {
475			return Err(SdioProtocolError::PrintfInvalidSize(value.len()).into());
476		}
477		let packet_type = value.get_u16_le();
478		if packet_type != 8 {
479			return Err(SdioProtocolError::UnknownPrintfPacketType(packet_type).into());
480		}
481
482		let total_character_length = usize::from(value.get_u16_le());
483		let mut messages = Vec::with_capacity(1);
484		let mut read_amount = 4_usize;
485		while read_amount < total_character_length {
486			let actual_msg = SdioControlTelnetMessage::try_from(value.clone())?;
487			read_amount += actual_msg.read_amount;
488			value.advance(actual_msg.read_amount);
489			messages.push(actual_msg);
490		}
491
492		Ok(Self { messages })
493	}
494}
495
496const MESSAGE_REQUEST_FIELDS: &[NamedField<'static>] = &[NamedField::new("messages")];
497
498impl Structable for SdioControlMessageRequest {
499	fn definition(&self) -> StructDef<'_> {
500		StructDef::new_static(
501			"SdioControlMessageRequest",
502			Fields::Named(MESSAGE_REQUEST_FIELDS),
503		)
504	}
505}
506
507impl Valuable for SdioControlMessageRequest {
508	fn as_value(&self) -> Value<'_> {
509		Value::Structable(self)
510	}
511
512	fn visit(&self, visitor: &mut dyn Visit) {
513		visitor.visit_named_fields(&NamedValues::new(
514			MESSAGE_REQUEST_FIELDS,
515			&[Valuable::as_value(&self.messages)],
516		));
517	}
518}
519
520#[cfg(test)]
521mod unit_tests {
522	use super::*;
523
524	#[test]
525	pub fn serdeser_real_life_message_request() {
526		{
527			let message_request = SdioControlMessageRequest::try_from(Bytes::from(vec![
528				0x08, 0x00, 0x3a, 0x00, 0x04, 0x00, 0x0c, 0x00, 0xff, 0xff, 0x3a, 0x00, 0x42, 0x4f,
529				0x4f, 0x54, 0x31, 0x3a, 0x20, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x44,
530				0x55, 0x41, 0x4c, 0x20, 0x62, 0x6f, 0x6f, 0x74, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72,
531				0x20, 0x61, 0x74, 0x20, 0x30, 0x78, 0x30, 0x38, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
532				0x2e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
533				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
534				0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
535				0x08, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04,
536				0x18, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00,
537				0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
538				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
539				0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
540				0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
541				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
542				0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
543				0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
544				0x00, 0x00, 0x00, 0x10, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,
545				0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x00, 0x40, 0x00, 0x04,
546				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
547				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
548				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
549				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
550				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
551				0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
552				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
553				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x10, 0x82,
554				0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
555				0x00, 0x00, 0x40, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
556				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
557				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
558				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
559				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
560				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
561				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
562				0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x80, 0x00, 0x01, 0x08, 0x00, 0x00, 0x20, 0x00,
563				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0xc0,
564				0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
565			]))
566			.expect("Failed to parse real life SDIO Control Message Request");
567
568			assert_eq!(
569				message_request.messages(),
570				&vec![
571					SdioControlTelnetMessage::new_with_read_amount(
572						"BOOT1: Running DUAL bootloader at 0x08000000.\n".to_owned(),
573						0x3E,
574						SdioControlTelnetChannel::SysConfigTool,
575					)
576					.expect("Failed to create telnet message to match against!")
577				],
578			);
579
580			assert_eq!(
581				Bytes::try_from(&message_request)
582					.expect("Failed to serialize real message request!"),
583				// Same body as above, but without random cache bytes that don't matter.
584				Bytes::from(vec![
585					0x08, 0x00, 0x3a, 0x00, 0x04, 0x00, 0x0c, 0x00, 0xff, 0xff, 0x3a, 0x00, 0x42,
586					0x4f, 0x4f, 0x54, 0x31, 0x3a, 0x20, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67,
587					0x20, 0x44, 0x55, 0x41, 0x4c, 0x20, 0x62, 0x6f, 0x6f, 0x74, 0x6c, 0x6f, 0x61,
588					0x64, 0x65, 0x72, 0x20, 0x61, 0x74, 0x20, 0x30, 0x78, 0x30, 0x38, 0x30, 0x30,
589					0x30, 0x30, 0x30, 0x30, 0x2e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
590					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
591					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
592					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
593					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
594					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
595					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
596					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
597					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
598					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
599					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
600					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
601					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
602					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
603					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
604					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
605					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
606					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
607					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
608					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
609					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
610					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
611					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
612					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
613					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
614					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
615					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
616					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
617					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
618					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
619					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
620					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
621					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
622					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
623					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
624					0x00, 0x00, 0x00, 0x00, 0x00,
625				]),
626			);
627		}
628	}
629
630	#[test]
631	pub fn roundtrip_telnet_channel_type() {
632		for channel_ty in vec![
633			SdioControlTelnetChannel::SysConfigTool,
634			SdioControlTelnetChannel::DevkitMsg,
635			SdioControlTelnetChannel::CafeOS,
636		] {
637			assert_eq!(
638				Ok(channel_ty),
639				SdioControlTelnetChannel::try_from(u16::from(channel_ty)),
640				"Round-tripped telnet channel type was not the same?"
641			);
642		}
643	}
644}