1use bytes::{Buf, BufMut, Bytes, BytesMut};
2use indexmap::IndexMap;
3use strum_macros::FromRepr;
4
5use crate::{
6 ParseError,
7 helpers::{Brightness, DisplayMode, RgbColor},
8};
9
10pub const MEMORY_SIZE: usize = 512;
11pub const BANK_CAPACITY: u8 = 32;
12#[allow(
13 clippy::cast_possible_truncation,
14 reason = "This is a compile time constant that we are sure can't be truncated."
15)]
16pub const BANK_COUNT: u8 = (MEMORY_SIZE / BANK_CAPACITY as usize) as u8;
17
18pub const BSB_CRC: crc::Algorithm<u8> = crc::Algorithm {
27 width: 8,
28 poly: 0x07,
29 init: 0xff,
30 refin: false,
31 refout: false,
32 xorout: 0x00,
33 check: 0xFB,
34 residue: 0x00,
35};
36
37#[derive(Debug, Clone, PartialEq, Eq, FromRepr)]
39#[non_exhaustive]
40#[repr(u8)]
41pub enum MemoryItem {
42 PrimarySerial = 0x1,
46 LedColor = 0x2,
48 Fan = 0x3,
50 ProximityCalibration = 0x6,
58 SecondarySerial = 0x8,
62 Brightness = 0xA,
66 ProximityTrigger = 0xB,
74 DisplayMode = 0xD,
76 ProximityOffset = 0xE,
83 AutoShutoff = 0xF,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ConfigurationMemory {
89 data: IndexMap<u8, Box<[u8]>>,
90}
91
92impl TryFrom<[u8; MEMORY_SIZE]> for ConfigurationMemory {
93 type Error = ParseError;
94
95 fn try_from(value: [u8; MEMORY_SIZE]) -> Result<Self, Self::Error> {
96 Self::try_from_bytes(&value)
97 }
98}
99
100impl From<ConfigurationMemory> for [u8; MEMORY_SIZE] {
101 fn from(value: ConfigurationMemory) -> Self {
102 value.to_bytes()
103 }
104}
105
106impl ConfigurationMemory {
107 fn item_from_buf(buf: &mut dyn Buf) -> Result<Option<(u8, Box<[u8]>)>, ParseError> {
108 let id = buf.try_get_u8()?;
109 if id == 0xFF {
111 return Ok(None);
112 }
113
114 let len = buf.try_get_u8()?;
115
116 if buf.remaining() < len as usize {
117 return Err(ParseError::OutOfData);
118 }
119 let data = buf.copy_to_bytes(len as usize);
120
121 let _crc = buf.try_get_u8()?;
123
124 Ok(Some((id, data[..].into())))
125 }
126
127 fn item_as_bytes(id: u8, data: &[u8]) -> Bytes {
128 let mut buf = BytesMut::with_capacity(64);
129
130 buf.put_u8(id);
131 buf.put_u8(u8::try_from(data.len()).expect("Size of data is larger than expected."));
133 buf.put_slice(data);
134
135 let crc = crc::Crc::<u8>::new(&BSB_CRC);
136 let mut digest = crc.digest();
137 digest.update(&buf);
138 buf.put_u8(digest.finalize());
139
140 buf.freeze()
141 }
142
143 pub fn try_from_bytes(buf: &[u8; MEMORY_SIZE]) -> Result<Self, ParseError> {
145 let mut memory = &buf[..];
146
147 let mut data = IndexMap::new();
148 for _ in 0..=(MEMORY_SIZE / 3) {
151 let Some((id, buf)) = Self::item_from_buf(&mut memory)? else {
152 break;
153 };
154
155 data.insert(id, buf);
156 }
157
158 Ok(Self { data })
159 }
160
161 pub fn to_bytes(&self) -> [u8; MEMORY_SIZE] {
162 let mut buf = [0xFFu8; MEMORY_SIZE];
164
165 let data = &self
166 .data
167 .iter()
168 .flat_map(|(id, buf)| Self::item_as_bytes(*id, buf))
169 .collect::<Box<[u8]>>();
170
171 buf[0..data.len()].copy_from_slice(data);
173
174 buf
175 }
176
177 pub fn to_blocks(&self) -> [[u8; BANK_CAPACITY as usize]; BANK_COUNT as usize] {
179 let buf = self.to_bytes();
180 let (chunks, &[]) = buf.as_chunks() else {
181 unreachable!("Size of bytes is known, so no remainders should be made");
182 };
183 chunks.try_into().unwrap_or_else(|_| unreachable!())
184 }
185
186 pub fn get(&self, id: u8) -> Option<&[u8]> {
188 self.data.get(&id).map(std::ops::Deref::deref)
189 }
190
191 pub fn set(&mut self, id: u8, data: Box<[u8]>) {
193 if let Some(opt) = self.data.get_mut(&id) {
194 *opt = data;
195 } else {
196 self.data.insert(id, data);
197 }
198 }
199
200 pub fn remove(&mut self, id: u8) -> Option<Box<[u8]>> {
202 self.data.shift_remove(&id)
203 }
204}
205
206type MemResult<E> = Result<E, ParseError>;
207impl ConfigurationMemory {
208 #[inline]
209 fn get_from<T, F>(&self, id: MemoryItem, f: F) -> MemResult<T>
210 where
211 T: Default,
212 F: FnOnce(&[u8]) -> MemResult<T>,
213 {
214 self.get(id as u8).map_or_else(|| Ok(Default::default()), f)
215 }
216 #[inline]
217 fn get_from_string(&self, id: MemoryItem) -> MemResult<String> {
218 self.get_from(id, |v| {
219 String::from_utf8(v.to_vec()).map_err(|_| ParseError::Parse)
220 })
221 }
222 #[inline]
223 fn get_from_bytesfn<T, F, const S: usize>(&self, id: MemoryItem, f: F) -> MemResult<T>
224 where
225 T: Default,
226 F: Fn([u8; S]) -> T,
227 {
228 self.get_from(id, |v| {
229 v.get(0..S)
230 .and_then(|v| v.try_into().ok())
231 .map(f)
232 .ok_or(ParseError::OutOfData)
233 })
234 }
235
236 pub fn primary_serial(&self) -> MemResult<String> {
238 self.get_from_string(MemoryItem::PrimarySerial)
239 }
240 pub fn secondary_serial(&self) -> MemResult<String> {
241 self.get_from_string(MemoryItem::SecondarySerial)
242 }
243 pub fn led_color(&self) -> MemResult<RgbColor> {
244 self.get_from(MemoryItem::LedColor, RgbColor::from_buf)
245 }
246 pub fn fan(&self) -> MemResult<u8> {
247 self.get_from(MemoryItem::Fan, |v| {
248 v.first().ok_or(ParseError::OutOfData).copied()
249 })
250 }
251 pub fn brightness(&self) -> MemResult<Brightness> {
252 self.get_from_bytesfn(MemoryItem::Brightness, u16::from_le_bytes)
253 .map(From::from)
254 }
255 pub fn display_mode(&self) -> MemResult<DisplayMode> {
256 self.get_from(MemoryItem::DisplayMode, |v| match v.first() {
257 Some(v) => DisplayMode::from_id(*v).ok_or(ParseError::Parse),
258 None => Err(ParseError::OutOfData),
259 })
260 }
261 pub fn proximity_offset(&self) -> MemResult<i16> {
262 self.get_from_bytesfn(MemoryItem::ProximityOffset, i16::from_le_bytes)
263 }
264 pub fn proximity_calibration(&self) -> MemResult<u16> {
265 self.get_from_bytesfn(MemoryItem::ProximityCalibration, u16::from_le_bytes)
266 }
267 pub fn proximity_trigger(&self) -> MemResult<u16> {
268 self.get_from_bytesfn(MemoryItem::ProximityTrigger, u16::from_le_bytes)
269 }
270 pub fn auto_shutoff(&self) -> MemResult<bool> {
271 self.get_from(MemoryItem::AutoShutoff, |v| match v.first() {
272 Some(&v) => Ok(v != 0x00),
273 None => Err(ParseError::OutOfData),
274 })
275 }
276
277 pub fn set_led_color(&mut self, color: &RgbColor) {
279 self.set(MemoryItem::LedColor as u8, Box::new(color.as_bytes()));
280 }
281 pub fn set_fan(&mut self, v: u8) {
282 self.set(MemoryItem::Fan as u8, Box::new([v]));
283 }
284 pub fn set_brightness(&mut self, brightness: &Brightness) {
285 self.set(
286 MemoryItem::Brightness as u8,
287 Box::new(brightness.0.to_le_bytes()),
288 );
289 }
290 pub unsafe fn set_display_mode(&mut self, v: DisplayMode) {
299 self.set(MemoryItem::DisplayMode as u8, Box::new([v.id()]));
300 }
301 pub fn set_proximity_offset(&mut self, v: i16) {
302 self.set(MemoryItem::ProximityOffset as u8, Box::new(v.to_le_bytes()));
303 }
304 pub fn set_auto_shutoff(&mut self, v: bool) {
305 self.set(MemoryItem::AutoShutoff as u8, Box::new([u8::from(v)]));
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use indexmap::indexmap;
312
313 use super::*;
314
315 #[inline]
316 fn empty_mem() -> ConfigurationMemory {
317 ConfigurationMemory {
318 data: IndexMap::new(),
319 }
320 }
321 #[inline]
322 fn get_mem(id: MemoryItem, data: Box<[u8]>) -> ConfigurationMemory {
323 ConfigurationMemory {
324 data: indexmap::indexmap! {
325 id as u8 => data
326 },
327 }
328 }
329 fn check_data(id: MemoryItem, data: Box<[u8]>, result: &[u8]) -> ConfigurationMemory {
330 let mut buf = [0xFFu8; MEMORY_SIZE];
331 buf[0..result.len()].copy_from_slice(result);
332
333 let dat = get_mem(id, data);
334
335 assert_eq!(dat.to_bytes(), buf);
336
337 dat
338 }
339
340 #[test]
343 fn getters_and_memory() {
344 assert_eq!(
345 check_data(
346 MemoryItem::PrimarySerial,
347 Box::new(*b"FooBar"),
348 &[0x1, 6, 0x46, 0x6F, 0x6F, 0x42, 0x61, 0x72, 0x76],
349 )
350 .primary_serial(),
351 Ok("FooBar".into())
352 );
353
354 assert_eq!(
355 check_data(
356 MemoryItem::SecondarySerial,
357 Box::new(*b"BS164foobar"),
358 &[
359 0x8, 11, 0x42, 0x53, 0x31, 0x36, 0x34, 0x66, 0x6F, 0x6F, 0x62, 0x61, 0x72, 0x27
360 ]
361 )
362 .secondary_serial(),
363 Ok("BS164foobar".into())
364 );
365
366 assert_eq!(
367 check_data(
368 MemoryItem::LedColor,
369 Box::new(RgbColor::new(10, 20, 30).as_bytes()),
370 &[0x2, 3, 10, 20, 30, 0x19]
371 )
372 .led_color(),
373 Ok(RgbColor::new(10, 20, 30))
374 );
375
376 assert_eq!(
377 check_data(MemoryItem::Fan, Box::new([50]), &[0x3, 1, 50, 0x1D]).fan(),
378 Ok(50)
379 );
380
381 assert_eq!(
382 check_data(
383 MemoryItem::Brightness,
384 Box::new(300u16.to_le_bytes()),
385 &[0xA, 2, 0x2C, 0x01, 0xCE]
386 )
387 .brightness(),
388 Ok(Brightness(300))
389 );
390
391 assert_eq!(
392 check_data(
393 MemoryItem::DisplayMode,
394 Box::new([DisplayMode::Hz75.id()]),
395 &[0xD, 1, 0, 0xAF]
396 )
397 .display_mode(),
398 Ok(DisplayMode::Hz75)
399 );
400
401 assert_eq!(
402 check_data(
403 MemoryItem::ProximityOffset,
404 Box::new((-30i16).to_le_bytes()),
405 &[0xE, 2, 0xE2, 0xFF, 0x59]
406 )
407 .proximity_offset(),
408 Ok(-30)
409 );
410
411 assert_eq!(
412 check_data(
413 MemoryItem::ProximityCalibration,
414 Box::new((2000u16).to_le_bytes()),
415 &[0x6, 2, 0xD0, 0x07, 0xDC]
416 )
417 .proximity_calibration(),
418 Ok(2000)
419 );
420
421 assert_eq!(
422 check_data(
423 MemoryItem::ProximityTrigger,
424 Box::new((1000u16).to_le_bytes()),
425 &[0xB, 2, 0xE8, 0x03, 0x6F]
426 )
427 .proximity_trigger(),
428 Ok(1000)
429 );
430
431 assert_eq!(
432 check_data(
433 MemoryItem::AutoShutoff,
434 Box::new([false as u8]),
435 &[0xF, 1, 0, 0x79]
436 )
437 .auto_shutoff(),
438 Ok(false)
439 );
440 }
441
442 #[test]
444 fn setters() {
445 let mut mem = empty_mem();
446 mem.set_led_color(&RgbColor::new(50, 60, 70));
447 assert_eq!(mem.led_color(), Ok(RgbColor::new(50, 60, 70)));
448
449 let mut mem = empty_mem();
450 mem.set_brightness(&Brightness(500));
451 assert_eq!(mem.brightness(), Ok(Brightness(500)));
452
453 let mut mem = empty_mem();
454 unsafe { mem.set_display_mode(DisplayMode::Hz90) };
455 assert_eq!(mem.display_mode(), Ok(DisplayMode::Hz90));
456
457 let mut mem = empty_mem();
458 mem.set_proximity_offset(420);
459 assert_eq!(mem.proximity_offset(), Ok(420));
460
461 let mut mem = empty_mem();
462 mem.set_auto_shutoff(true);
463 assert_eq!(mem.auto_shutoff(), Ok(true));
464 }
465
466 #[test]
468 fn single_deserialize_error() {
469 assert_eq!(
470 ConfigurationMemory::item_from_buf(&mut Bytes::copy_from_slice(&[
471 0x1u8, 6, 0, 0, 0, 0x2F ])),
476 Err(ParseError::OutOfData),
477 );
478
479 assert_eq!(
480 ConfigurationMemory::item_from_buf(&mut Bytes::copy_from_slice(&[
481 0x1, 4, 0, 0, 0, 0x03 ])),
488 Err(ParseError::OutOfData),
489 );
490
491 assert_eq!(
493 ConfigurationMemory::item_from_buf(&mut Bytes::copy_from_slice(&[0xFF; 1])),
494 Ok(None),
495 );
496 assert_eq!(
497 ConfigurationMemory::item_from_buf(&mut Bytes::copy_from_slice(&[0xFF; 5])),
498 Ok(None),
499 );
500 assert_eq!(
501 ConfigurationMemory::item_from_buf(&mut Bytes::copy_from_slice(&[0xFF; 200])),
502 Ok(None),
503 );
504 assert_eq!(
505 ConfigurationMemory::item_from_buf(&mut Bytes::copy_from_slice(&[0xFF; 400])),
506 Ok(None),
507 );
508 }
509
510 #[inline]
511 fn mem_item(id: u8, data: Box<[u8]>) -> ConfigurationMemory {
512 ConfigurationMemory {
513 data: indexmap! {
514 id => data,
515 },
516 }
517 }
518
519 #[test]
521 fn type_deserialize_errors() {
522 assert_eq!(
524 mem_item(0xA, Box::new([0])).brightness(),
525 Err(ParseError::OutOfData),
526 );
527
528 assert_eq!(
530 mem_item(0x1, Box::new([0xFF, 0x10])).primary_serial(),
531 Err(ParseError::Parse),
532 );
533
534 assert_eq!(
536 mem_item(0xD, Box::new([3])).display_mode(),
537 Err(ParseError::Parse),
538 );
539 }
540}