Skip to main content

tightbeam/
matrix.rs

1use crate::Asn1Matrix;
2
3#[cfg(feature = "derive")]
4use crate::Errorizable;
5
6pub type MatrixResult<T> = core::result::Result<T, MatrixError>;
7
8#[cfg_attr(feature = "derive", derive(Errorizable))]
9#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
10pub enum MatrixError {
11	#[cfg_attr(feature = "derive", error("Asn1Matrix: n MUST be in 1..=255 (got {0})"))]
12	InvalidN(u8),
13	#[cfg_attr(
14		feature = "derive",
15		error("Asn1Matrix: data length MUST equal n*n (n={n}, len={len})")
16	)]
17	LengthMismatch { n: u8, len: usize },
18}
19
20crate::impl_error_display!(MatrixError {
21	InvalidN(n) => "Asn1Matrix: n MUST be in 1..=255 (got {n})",
22	LengthMismatch { n, len } => "Asn1Matrix: data length MUST equal n*n (n={n}, len={len})",
23});
24
25/// A common interface for NxN flag matrices (u8 cells), row-major.
26pub trait MatrixLike {
27	/// Dimension N (matrix is N×N).
28	fn n(&self) -> u8;
29
30	/// Get cell (row r, col c). Out-of-bounds returns 0.
31	fn get(&self, r: u8, c: u8) -> u8;
32
33	/// Set cell (row r, col c) to value. Out-of-bounds is a no-op.
34	fn set(&mut self, r: u8, c: u8, value: u8);
35
36	/// Fill all cells with value.
37	fn fill(&mut self, value: u8);
38
39	/// Clear all cells to zero.
40	fn clear(&mut self) {
41		self.fill(0);
42	}
43}
44
45/// Trait for converting matrix types to MatrixDyn.
46/// This avoids the Infallible error type when MatrixDyn is converted to itself.
47pub trait IntoMatrixDyn {
48	fn into_matrix_dyn(self) -> Result<MatrixDyn, MatrixError>;
49}
50
51// Identity conversion for MatrixDyn (no error possible)
52impl IntoMatrixDyn for MatrixDyn {
53	fn into_matrix_dyn(self) -> Result<MatrixDyn, MatrixError> {
54		Ok(self)
55	}
56}
57
58// Conversion for other MatrixLike types via TryFrom
59impl<M> IntoMatrixDyn for M
60where
61	M: MatrixLike,
62	MatrixDyn: TryFrom<M, Error = MatrixError>,
63{
64	fn into_matrix_dyn(self) -> Result<MatrixDyn, MatrixError> {
65		MatrixDyn::try_from(self)
66	}
67}
68
69/// Runtime-sized N×N matrix of u8 flags (row-major).
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct MatrixDyn {
72	n: u8,
73	data: Vec<u8>,
74}
75
76impl Default for MatrixDyn {
77	fn default() -> Self {
78		Self { n: 1, data: Vec::new() }
79	}
80}
81
82impl MatrixDyn {
83	/// Construct from row-major n-bytes. Length MUST be n*n.
84	pub fn from_row_major(n: u8, bytes: Vec<u8>) -> Option<Self> {
85		if n == 0 {
86			return None;
87		}
88
89		let n_usize = n as usize;
90		if bytes.len() == n_usize * n_usize {
91			Some(Self { n, data: bytes })
92		} else {
93			None
94		}
95	}
96
97	#[inline]
98	fn idx(&self, r: u8, c: u8) -> Option<usize> {
99		let n = self.n as usize;
100		let (ru, cu) = (r as usize, c as usize);
101		if ru < n && cu < n {
102			Some(ru * n + cu)
103		} else {
104			None
105		}
106	}
107
108	/// Borrow the underlying row-major bytes.
109	pub fn as_bytes(&self) -> &[u8] {
110		&self.data
111	}
112
113	/// Mutable borrow of row-major bytes.
114	pub fn as_bytes_mut(&mut self) -> &mut [u8] {
115		&mut self.data
116	}
117
118	/// Borrow a single row as a slice.
119	pub fn row(&self, r: u8) -> Option<&[u8]> {
120		let n = self.n;
121		if r >= n {
122			return None;
123		}
124		let start = r as usize * n as usize;
125		Some(&self.data[start..start + n as usize])
126	}
127}
128
129impl MatrixLike for MatrixDyn {
130	fn n(&self) -> u8 {
131		self.n
132	}
133
134	fn get(&self, r: u8, c: u8) -> u8 {
135		self.idx(r, c).map(|i| self.data[i]).unwrap_or(0)
136	}
137
138	fn set(&mut self, r: u8, c: u8, value: u8) {
139		if let Some(i) = self.idx(r, c) {
140			self.data[i] = value;
141		}
142	}
143
144	fn fill(&mut self, value: u8) {
145		for b in &mut self.data {
146			*b = value;
147		}
148	}
149}
150
151/// Compile-time N×N matrix of u8 flags (row-major).
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub struct Matrix<const N: usize> {
154	data: [[u8; N]; N],
155}
156
157impl<const N: usize> Default for Matrix<N> {
158	fn default() -> Self {
159		Self { data: [[0u8; N]; N] }
160	}
161}
162
163impl<const N: usize> Matrix<N> {
164	/// Create a zero-initialized matrix.
165	pub fn new() -> Self {
166		Self::default()
167	}
168
169	/// Construct from row-major bytes; extra bytes are ignored, missing are
170	/// zeroed.
171	pub fn from_row_major(bytes: &[u8]) -> Self {
172		let mut m = Self::default();
173		let mut i = 0usize;
174		for r in 0..N {
175			for c in 0..N {
176				if i < bytes.len() {
177					m.data[r][c] = bytes[i];
178				}
179				i += 1;
180			}
181		}
182		m
183	}
184
185	/// Borrow a row by index.
186	pub fn row(&self, r: u8) -> Option<&[u8; N]> {
187		if (r as usize) < N {
188			Some(&self.data[r as usize])
189		} else {
190			None
191		}
192	}
193}
194
195impl<const N: usize> MatrixLike for Matrix<N> {
196	fn n(&self) -> u8 {
197		N as u8
198	}
199
200	fn get(&self, r: u8, c: u8) -> u8 {
201		if (r as usize) < N && (c as usize) < N {
202			self.data[r as usize][c as usize]
203		} else {
204			0
205		}
206	}
207
208	fn set(&mut self, r: u8, c: u8, value: u8) {
209		if (r as usize) < N && (c as usize) < N {
210			self.data[r as usize][c as usize] = value;
211		}
212	}
213
214	fn fill(&mut self, value: u8) {
215		for r in 0..N {
216			for c in 0..N {
217				self.data[r][c] = value;
218			}
219		}
220	}
221}
222
223macro_rules! validate_n {
224	($n:expr) => {
225		if $n == 0 {
226			return Err(MatrixError::InvalidN($n));
227		}
228	};
229}
230
231impl TryFrom<u8> for MatrixDyn {
232	type Error = MatrixError;
233	fn try_from(n: u8) -> Result<Self, Self::Error> {
234		validate_n!(n);
235
236		let n_usize = n as usize;
237		let data = vec![0u8; n_usize * n_usize];
238		Ok(Self { n, data })
239	}
240}
241
242impl TryFrom<MatrixDyn> for Asn1Matrix {
243	type Error = crate::matrix::MatrixError;
244
245	fn try_from(mut matrix: MatrixDyn) -> Result<Self, Self::Error> {
246		let n = matrix.n();
247		validate_n!(n);
248
249		// MatrixDyn stores row-major n*n bytes, same as Asn1Matrix
250		let expected_len = (n as usize) * (n as usize);
251		if matrix.data.len() != expected_len {
252			return Err(crate::matrix::MatrixError::LengthMismatch { n, len: matrix.data.len() });
253		}
254
255		let data = std::mem::take(&mut matrix.data);
256		Ok(Self { n, data })
257	}
258}
259
260macro_rules! asn1_to_matrix_dyn_impl {
261	($matrix:expr) => {{
262		let n = $matrix.n;
263		validate_n!(n);
264
265		let n_u8 = n as u8;
266		let n2 = n_u8 as usize * n_u8 as usize;
267		if $matrix.data.len() != n2 {
268			return Err(MatrixError::LengthMismatch { n, len: $matrix.data.len() });
269		}
270	}};
271}
272
273impl TryFrom<Asn1Matrix> for MatrixDyn {
274	type Error = MatrixError;
275	fn try_from(m: Asn1Matrix) -> Result<Self, Self::Error> {
276		asn1_to_matrix_dyn_impl!(m);
277
278		let mut m = m;
279		let data = core::mem::take(&mut m.data);
280		let len = data.len();
281
282		MatrixDyn::from_row_major(m.n, data).ok_or(MatrixError::LengthMismatch { n: m.n, len })
283	}
284}
285
286impl TryFrom<&Asn1Matrix> for MatrixDyn {
287	type Error = MatrixError;
288	fn try_from(m: &Asn1Matrix) -> Result<Self, Self::Error> {
289		asn1_to_matrix_dyn_impl!(m);
290		let n_u8 = m.n;
291
292		let mut md = MatrixDyn::try_from(n_u8)?;
293		let mut i = 0usize;
294		for r in 0..n_u8 {
295			for c in 0..n_u8 {
296				md.set(r, c, m.data[i]);
297				i += 1;
298			}
299		}
300
301		Ok(md)
302	}
303}
304
305impl TryFrom<Option<Asn1Matrix>> for MatrixDyn {
306	type Error = MatrixError;
307	fn try_from(m: Option<Asn1Matrix>) -> Result<Self, Self::Error> {
308		match m {
309			Some(m) => Self::try_from(m),
310			None => Ok(Default::default()),
311		}
312	}
313}
314
315#[cfg(test)]
316mod tests {
317	use super::*;
318
319	#[test]
320	#[cfg(feature = "std")]
321	fn test_matrix_like_reality_end_to_end() -> crate::error::Result<()> {
322		// Build a 3×3 from row-major bytes 0..9
323		let bytes: Vec<u8> = (0u8..9u8).collect();
324		let mut dyn_m = MatrixDyn::from_row_major(3, bytes.clone()).expect("n*n bytes");
325		let mut stat_m: Matrix<3> = Matrix::<3>::from_row_major(&bytes);
326
327		// Paint identity for any MatrixLike without recursion.
328		fn paint_diag<M: MatrixLike>(m: &mut M) {
329			let n = m.n();
330			// Ensure non-diagonal cells are zeroed
331			m.clear();
332			for i in 0..n {
333				m.set(i, i, 1);
334			}
335		}
336
337		// Dimensions
338		assert_eq!(dyn_m.n(), 3);
339		assert_eq!(stat_m.n(), 3);
340
341		// Row views match source bytes
342		assert_eq!(dyn_m.row(1).unwrap(), &[3, 4, 5]);
343		assert_eq!(stat_m.row(1).unwrap().as_slice(), &[3, 4, 5]);
344
345		// Indexing
346		assert_eq!(dyn_m.get(2, 2), 8);
347		assert_eq!(stat_m.get(0, 2), 2);
348
349		// Paint identity on both
350		paint_diag(&mut dyn_m);
351		paint_diag(&mut stat_m);
352
353		// Validate diagonal = 1, others = 0
354		for r in 0..3 {
355			for c in 0..3 {
356				let dv = dyn_m.get(r, c);
357				let sv = stat_m.get(r, c);
358				if r == c {
359					assert_eq!(dv, 1);
360					assert_eq!(sv, 1);
361				} else {
362					assert_eq!(dv, 0);
363					assert_eq!(sv, 0);
364				}
365			}
366		}
367
368		// Fill and clear
369		dyn_m.fill(7);
370		for r in 0..3 {
371			for c in 0..3 {
372				assert_eq!(dyn_m.get(r, c), 7);
373			}
374		}
375		dyn_m.clear();
376		for r in 0..3 {
377			for c in 0..3 {
378				assert_eq!(dyn_m.get(r, c), 0);
379			}
380		}
381
382		// Byte view length and static reconstruction
383		assert_eq!(dyn_m.as_bytes().len(), 9);
384		let stat_bytes = Matrix::<3>::from_row_major(&bytes);
385		assert_eq!(stat_bytes.row(0).unwrap(), &[0, 1, 2]);
386
387		// Invalid constructor length rejected
388		assert!(MatrixDyn::from_row_major(3, vec![0u8; 8]).is_none());
389
390		Ok(())
391	}
392
393	#[test]
394	#[cfg(feature = "std")]
395	fn test_matrix_specification_compliance() -> crate::error::Result<()> {
396		// Test data for various matrix sizes
397		let test_matrices = vec![
398			(1u8, vec![42u8]),
399			(2u8, vec![1, 2, 3, 4]),
400			(3u8, (0u8..9u8).collect()),
401			(255u8, vec![0u8; 255 * 255]),
402		];
403
404		// Test 1: Wire format compliance (n ∈ [1, 255], data.len == n*n)
405		for (n, data) in &test_matrices {
406			// Valid construction
407			let asn1_matrix = Asn1Matrix { n: *n, data: data.clone() };
408
409			// Validate n bounds
410			assert!(*n >= 1);
411
412			// Validate data length equals n*n
413			let expected_len = (*n as usize) * (*n as usize);
414			assert_eq!(data.len(), expected_len);
415
416			// Test conversion to MatrixDyn preserves row-major ordering
417			let matrix_dyn = MatrixDyn::try_from(&asn1_matrix)?;
418			assert_eq!(matrix_dyn.n(), *n);
419			assert_eq!(matrix_dyn.as_bytes(), data.as_slice());
420		}
421
422		// Test 2: Error handling - invalid n (n == 0)
423		let invalid_n = MatrixDyn::try_from(0u8);
424		assert!(invalid_n.is_err());
425
426		// Test 3: Error handling - length mismatch
427		let invalid_asn1 = Asn1Matrix { n: 2, data: vec![1, 2, 3] }; // 3 != 2*2
428		let invalid_conversion = MatrixDyn::try_from(invalid_asn1);
429		assert!(invalid_conversion.is_err());
430
431		// Test 4: Row-major ordering preservation
432		// Fill with values: row 0 = [10, 11, 12], row 1 = [20, 21, 22], row 2 = [30, 31, 32]
433		let matrix_3x3 = MatrixDyn::try_from(3u8)?;
434		let mut test_matrix = matrix_3x3;
435		for r in 0..3 {
436			for c in 0..3 {
437				let value = (r + 1) * 10 + c;
438				test_matrix.set(r, c, value);
439			}
440		}
441
442		// Verify row-major layout
443		let expected_bytes = vec![10, 11, 12, 20, 21, 22, 30, 31, 32];
444		assert_eq!(test_matrix.as_bytes(), expected_bytes.as_slice());
445
446		// Test 5: MatrixLike trait compliance
447		let mut matrix = MatrixDyn::try_from(2u8)?;
448
449		// Test get/set operations
450		matrix.set(0, 1, 99);
451		assert_eq!(matrix.get(0, 1), 99);
452
453		// Test out-of-bounds behavior (returns 0, no-op for set)
454		assert_eq!(matrix.get(5, 5), 0);
455		matrix.set(5, 5, 123); // Should be no-op
456
457		// Test fill operation
458		matrix.fill(77);
459		for r in 0..2 {
460			for c in 0..2 {
461				assert_eq!(matrix.get(r, c), 77);
462			}
463		}
464
465		// Test clear operation
466		matrix.clear();
467		for r in 0..2 {
468			for c in 0..2 {
469				assert_eq!(matrix.get(r, c), 0);
470			}
471		}
472
473		// Test 6: Conversion consistency between Matrix<N> and MatrixDyn
474		let static_matrix: Matrix<3> = Matrix::from_row_major(&[1, 2, 3, 4, 5, 6, 7, 8, 9]);
475		let dynamic_matrix = MatrixDyn::from_row_major(3, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap();
476		for r in 0..3 {
477			for c in 0..3 {
478				assert_eq!(static_matrix.get(r, c), dynamic_matrix.get(r, c));
479			}
480		}
481
482		Ok(())
483	}
484}