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