Skip to main content

beyond_lib/protocol/
memory.rs

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
18/// The CRC algorithm used for error checking in the BSB's internal memory.
19///
20/// The CRC8 parameters goes as follows:
21/// - Polynomial: 0x07
22/// - Initial: 0xFF
23/// - Reflect input: false
24/// - Reflect output: false
25/// - Xor: 0x00
26pub 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/// All currently known memory items.
38#[derive(Debug, Clone, PartialEq, Eq, FromRepr)]
39#[non_exhaustive]
40#[repr(u8)]
41pub enum MemoryItem {
42    /// String.
43    ///
44    /// OEM set (**Do not modify**).
45    PrimarySerial = 0x1,
46    /// See [`crate::helpers::RgbColor`].
47    LedColor = 0x2,
48    /// Byte from 0 to 100.
49    Fan = 0x3,
50    /// Little-endian u16.
51    ///
52    /// OEM offset from the raw proximity data values.
53    ///
54    /// OEM set (**Do not modify**).
55    ///
56    /// Also see: [`Self::ProximityTrigger`],[`Self::ProximityOffset`].
57    ProximityCalibration = 0x6,
58    /// String.
59    ///
60    /// OEM set (**Do not modify**).
61    SecondarySerial = 0x8,
62    /// Little-endian u16.
63    ///
64    /// Also see: [`crate::helpers::Brightness`].
65    Brightness = 0xA,
66    /// Little-endian u16.
67    ///
68    /// OEM value at which the proximity sensor will trigger.
69    ///
70    /// OEM set (**Do not modify**).
71    ///
72    /// Also see: [`Self::ProximityCalibration`],[`Self::ProximityOffset`].
73    ProximityTrigger = 0xB,
74    /// See [`crate::helpers::DisplayMode`].
75    DisplayMode = 0xD,
76    /// Little-endian i16.
77    ///
78    /// User set offset from the OEM value ([`Self::ProximityTrigger`]) at which
79    /// the proximity sensor will trigger.
80    ///
81    /// Also see: [`Self::ProximityCalibration`],[`Self::ProximityTrigger`].
82    ProximityOffset = 0xE,
83    /// Bool.
84    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        // When the next byte is 0xFF, there is no more data to be decoded
110        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        // TODO: Possibly do CRC validation here
122        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        // TODO: Figure out what else I can do here
132        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    /// Try reading from a byte buffer, and turning it into [`Self`].
144    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        // The smallest entry is 3 bytes ([ID, size (0), CRC]), so lets just
149        // limit the loop just in case
150        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        // Unused data in the memory is set to 0xFF
163        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        // TODO: Check the length
172        buf[0..data.len()].copy_from_slice(data);
173
174        buf
175    }
176
177    /// Returns an array of blocks needed to properly write to the headset.
178    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    /// Low level getter of the raw ID.
187    pub fn get(&self, id: u8) -> Option<&[u8]> {
188        self.data.get(&id).map(std::ops::Deref::deref)
189    }
190
191    /// Low level setter of the raw ID & data.
192    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    /// Low level removes an ID, returning whatever value it had before.
201    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    // ========== Getters ========== //
237    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    // ========== Setters ========== //
278    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    /// Sets the display mode of this [`ConfigurationMemory`].
291    ///
292    /// # Safety
293    ///
294    /// Function is always safe, however:
295    /// If a new display mode is written to the headset, an extra Ack
296    /// ([`crate::protocol::CommandIds::Ack`]) will be returned by the headset.
297    /// Please ensure you handle this correctly.
298    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    /// Tests that the memory gets encoded properly, and that the return value
341    /// is valid
342    #[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    /// Tests that the actions of the setters can be reversed
443    #[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    /// Tests the invalid deserializations
467    #[test]
468    fn single_deserialize_error() {
469        assert_eq!(
470            ConfigurationMemory::item_from_buf(&mut Bytes::copy_from_slice(&[
471                0x1u8, // ID
472                6,     // Size
473                0, 0, 0,    // Data (smaller than size)
474                0x2F  // CRC
475            ])),
476            Err(ParseError::OutOfData),
477        );
478
479        assert_eq!(
480            ConfigurationMemory::item_from_buf(&mut Bytes::copy_from_slice(&[
481                0x1, // ID
482                4,   // Size
483                0, 0, 0, // Data: smaller than size...
484                // But the rest of the message is (technically) large enough if
485                // you account for the CRC
486                0x03 // CRC
487            ])),
488            Err(ParseError::OutOfData),
489        );
490
491        // The end of the packet is just a bunch of 0xFFs
492        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    /// Tests invalid deserializations, only caught when accessing the values
520    #[test]
521    fn type_deserialize_errors() {
522        // Brightness should be 2 bytes long
523        assert_eq!(
524            mem_item(0xA, Box::new([0])).brightness(),
525            Err(ParseError::OutOfData),
526        );
527
528        // Invalid utf8 string
529        assert_eq!(
530            mem_item(0x1, Box::new([0xFF, 0x10])).primary_serial(),
531            Err(ParseError::Parse),
532        );
533
534        // Display mode "3" doesn't exist
535        assert_eq!(
536            mem_item(0xD, Box::new([3])).display_mode(),
537            Err(ParseError::Parse),
538        );
539    }
540}