cat_dev/mion/proto/parameter/
well_known.rs

1//! Parameters that are well known, and can be referred to by their name
2//! rather than just their index.
3
4use crate::mion::proto::{
5	control::MionBootType, images::MionDiscImageType, parameter::MionParameterAPIError,
6};
7use bytes::Bytes;
8use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
9
10/// Ways to specify what parameter to update.
11#[derive(Clone, Debug, Hash, PartialEq, Eq)]
12pub enum ParameterLocationSpecification {
13	/// Either a name, or an index encoded as a string.
14	NameLike(String),
15	/// An actual index between 0, and 511 inclusive.
16	Index(u16),
17}
18
19impl TryFrom<&str> for ParameterLocationSpecification {
20	type Error = MionParameterAPIError;
21
22	fn try_from(value: &str) -> Result<Self, Self::Error> {
23		if index_from_parameter_name(value).is_some() {
24			Ok(Self::NameLike(value.to_owned()))
25		} else {
26			Err(MionParameterAPIError::NameNotKnown(value.to_owned()))
27		}
28	}
29}
30impl TryFrom<&String> for ParameterLocationSpecification {
31	type Error = MionParameterAPIError;
32
33	fn try_from(value: &String) -> Result<Self, Self::Error> {
34		Self::try_from(value.as_str())
35	}
36}
37impl TryFrom<String> for ParameterLocationSpecification {
38	type Error = MionParameterAPIError;
39
40	fn try_from(value: String) -> Result<Self, Self::Error> {
41		Self::try_from(value.as_str())
42	}
43}
44
45impl TryFrom<u16> for ParameterLocationSpecification {
46	type Error = MionParameterAPIError;
47
48	fn try_from(value: u16) -> Result<Self, Self::Error> {
49		if value < 512 {
50			Ok(Self::Index(value))
51		} else {
52			Err(MionParameterAPIError::NotInRange(usize::from(value)))
53		}
54	}
55}
56
57/// Attempt to get the index of a marater based on a name
58#[must_use]
59pub fn index_from_parameter_name(name: &str) -> Option<usize> {
60	if let Ok(number) = name.parse::<usize>()
61		&& number < 512
62	{
63		return Some(number);
64	}
65
66	match name {
67		"nand-mode" | "nand_mode" | "nandmode" | "nand mode" => Some(2),
68		"sdk-major" | "sdk_major" | "sdk major" | "sdk-major-version" | "sdk_major_version"
69		| "sdk major version" | "major-version" | "major_version" | "major version" | "major" => Some(3),
70		"sdk-minor" | "sdk_minor" | "sdk minor" | "sdk-minor-version" | "sdk_minor_version"
71		| "sdk minor version" | "minor-version" | "minor_version" | "minor version" | "minor" => Some(4),
72		"sdk-misc" | "sdk_misc" | "sdk misc" | "sdk-misc-version" | "sdk_misc_version"
73		| "sdk misc version" | "misc-version" | "misc_version" | "misc version" | "misc" => Some(5),
74		"bank-0" | "bank_0" | "bank 0" => Some(100),
75		"bank-1" | "bank_1" | "bank 1" => Some(101),
76		"bank-2" | "bank_2" | "bank 2" => Some(102),
77		"bank-3" | "bank_3" | "bank 3" => Some(103),
78		"bank-4" | "bank_4" | "bank 4" => Some(104),
79		"bank-5" | "bank_5" | "bank 5" => Some(105),
80		"bank-6" | "bank_6" | "bank 6" => Some(106),
81		"bank-7" | "bank_7" | "bank 7" => Some(107),
82		"bank-8" | "bank_8" | "bank 8" => Some(108),
83		"bank-9" | "bank_9" | "bank 9" => Some(109),
84		"bank-10" | "bank_10" | "bank 10" => Some(110),
85		_ => None,
86	}
87}
88
89/// Validate the valuue at a particular index against a well known type.
90#[must_use]
91pub fn validate_value_at_index(
92	specification: &ParameterLocationSpecification,
93	byte_value: u8,
94) -> bool {
95	let index = match specification {
96		ParameterLocationSpecification::Index(idx) => usize::from(*idx),
97		ParameterLocationSpecification::NameLike(name) => {
98			if let Some(idx) = index_from_parameter_name(name) {
99				idx
100			} else {
101				return false;
102			}
103		}
104	};
105
106	match index {
107		2 => match MionBootType::from(byte_value) {
108			MionBootType::NAND | MionBootType::PCFS | MionBootType::DUAL => true,
109			MionBootType::Unk(_) => false,
110		},
111		// Bank types
112		//
113		//  255 == WUM/WUD/Unset
114		//  254 == WUMAD
115		100..=110 => byte_value >= 254,
116		// Has no specific validation rule we know of.
117		_ => true,
118	}
119}
120
121const PARAMETER_DUMP_FIELDS: &[NamedField<'static>] = &[
122	NamedField::new("NandMode"),
123	NamedField::new("SdkMajor"),
124	NamedField::new("SdkMinor"),
125	NamedField::new("SdkMisc"),
126	NamedField::new("Bank0"),
127	NamedField::new("Bank1"),
128	NamedField::new("Bank2"),
129	NamedField::new("Bank3"),
130	NamedField::new("Bank4"),
131	NamedField::new("Bank5"),
132	NamedField::new("Bank6"),
133	NamedField::new("Bank7"),
134	NamedField::new("Bank8"),
135	NamedField::new("Bank9"),
136	NamedField::new("Bank10"),
137	NamedField::new("UnknownParameters"),
138];
139const KNOWN_INDEXES: &[usize] = &[
140	2_usize, 3_usize, 4_usize, 5_usize, 100_usize, 101_usize, 102_usize, 103_usize, 104_usize,
141	105_usize, 106_usize, 107_usize, 108_usize, 109_usize, 110_usize,
142];
143pub struct ValuableParameterDump<'value>(pub &'value Bytes);
144impl Structable for ValuableParameterDump<'_> {
145	fn definition(&self) -> StructDef<'_> {
146		StructDef::new_static(
147			"ValuableParameterDump",
148			Fields::Named(PARAMETER_DUMP_FIELDS),
149		)
150	}
151}
152impl Valuable for ValuableParameterDump<'_> {
153	fn as_value(&self) -> valuable::Value<'_> {
154		Value::Structable(self)
155	}
156
157	fn visit(&self, visitor: &mut dyn Visit) {
158		let mut unknown_params = Vec::with_capacity(self.0.len() - KNOWN_INDEXES.len());
159		for (idx, byte) in self.0.iter().enumerate() {
160			if KNOWN_INDEXES.contains(&idx) {
161				continue;
162			}
163			unknown_params.push((idx, *byte));
164		}
165
166		visitor.visit_named_fields(&NamedValues::new(
167			PARAMETER_DUMP_FIELDS,
168			&[
169				Valuable::as_value(&MionBootType::from(self.0[KNOWN_INDEXES[0]])),
170				Valuable::as_value(&self.0[KNOWN_INDEXES[1]]),
171				Valuable::as_value(&self.0[KNOWN_INDEXES[2]]),
172				Valuable::as_value(&self.0[KNOWN_INDEXES[3]]),
173				Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[4]])),
174				Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[5]])),
175				Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[6]])),
176				Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[7]])),
177				Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[8]])),
178				Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[9]])),
179				Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[10]])),
180				Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[11]])),
181				Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[12]])),
182				Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[13]])),
183				Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[14]])),
184				Valuable::as_value(&unknown_params),
185			],
186		));
187	}
188}
189
190#[cfg(test)]
191mod unit_tests {
192	use super::*;
193	use bytes::BytesMut;
194	use valuable::Visit;
195
196	#[test]
197	pub fn can_map_parameter_name_to_index() {
198		for (name, expected_index) in vec![
199			("nand-mode", Some(2)),
200			("nand_mode", Some(2)),
201			("nandmode", Some(2)),
202			("nand mode", Some(2)),
203			("sdk-major", Some(3)),
204			("sdk_major", Some(3)),
205			("sdk major", Some(3)),
206			("sdk-major-version", Some(3)),
207			("sdk_major_version", Some(3)),
208			("sdk major version", Some(3)),
209			("major-version", Some(3)),
210			("major_version", Some(3)),
211			("major version", Some(3)),
212			("major", Some(3)),
213			("sdk-minor", Some(4)),
214			("sdk_minor", Some(4)),
215			("sdk minor", Some(4)),
216			("sdk-minor-version", Some(4)),
217			("sdk_minor_version", Some(4)),
218			("sdk minor version", Some(4)),
219			("minor-version", Some(4)),
220			("minor_version", Some(4)),
221			("minor version", Some(4)),
222			("minor", Some(4)),
223			("sdk-misc", Some(5)),
224			("sdk_misc", Some(5)),
225			("sdk misc", Some(5)),
226			("sdk-misc-version", Some(5)),
227			("sdk_misc_version", Some(5)),
228			("sdk misc version", Some(5)),
229			("misc-version", Some(5)),
230			("misc_version", Some(5)),
231			("misc version", Some(5)),
232			("misc", Some(5)),
233			("trust me babes", None),
234			("mics", None),
235		] {
236			assert_eq!(
237				index_from_parameter_name(name),
238				expected_index,
239				"Parameter name: {name} expected to map to index: {expected_index:?}, but did not get that!",
240			);
241		}
242
243		for num in 0..512 {
244			let displayed = format!("{num}");
245			assert_eq!(
246				index_from_parameter_name(&displayed),
247				Some(num),
248				"Parameter name from index as string should be mapped to self!",
249			);
250		}
251
252		for num in 512..1024 {
253			let displayed = format!("{num}");
254			assert_eq!(
255				index_from_parameter_name(&displayed),
256				None,
257				"Invalid Parameter index number should not map to any value!",
258			);
259		}
260	}
261
262	#[test]
263	pub fn properly_parses_name_fields() {
264		struct AssertableVisitor;
265		impl Visit for AssertableVisitor {
266			fn visit_named_fields(&mut self, named_values: &NamedValues<'_>) {
267				for name in PARAMETER_DUMP_FIELDS {
268					assert!(
269						named_values.get_by_name(name.name()).is_some(),
270						"Parameter visitor did not pass a visit that had a required named field!"
271					);
272				}
273			}
274
275			fn visit_value(&mut self, value: Value<'_>) {
276				match value {
277					Value::Structable(v) => {
278						v.visit(self);
279					}
280					Value::Enumerable(v) => {
281						v.visit(self);
282					}
283					Value::Listable(v) => {
284						v.visit(self);
285					}
286					Value::Mappable(v) => {
287						v.visit(self);
288					}
289					_ => {}
290				}
291			}
292		}
293
294		let empty_param_set = BytesMut::zeroed(512).freeze();
295		let dumpee = ValuableParameterDump(&empty_param_set);
296		let mut visitor = AssertableVisitor;
297		dumpee.visit(&mut visitor);
298	}
299}