diff --git a/firmware/acid-firmware/src/db/mod.rs b/firmware/acid-firmware/src/db/mod.rs index 47d88d6..7b433c9 100644 --- a/firmware/acid-firmware/src/db/mod.rs +++ b/firmware/acid-firmware/src/db/mod.rs @@ -146,31 +146,34 @@ type DbPathSegment<'a> = Cow<'a, str>; type DbPathBuf<'a> = Vec>; type DbPath<'a> = [DbPathSegment<'a>]; -struct DbKey(Vec); +struct DbKey { + /// Segments separated by `0x00`, with the whole thing suffixed with `[0x00, 0xFF]`. + bytes: Vec, +} impl Deref for DbKey { type Target = [u8]; fn deref(&self) -> &Self::Target { - &self.0[0..(self.0.len() - 2)] + &self.bytes[0..(self.bytes.len() - 2)] } } impl DbKey { fn from_raw(mut key: Vec) -> Self { - key.extend_from_slice(&[0, 1]); - Self(key) + key.extend_from_slice(&[0x00, 0xFF]); + Self { bytes: key } } fn new<'a>(path: impl IntoIterator>) -> Self { // Null bytes are not allowed within path segments, and will cause a panic. - // The segments are separated by `[0, 0]`. - // Two null-bytes are used to allow for easy range lookups by suffixing the key with: - // `[0]..[0, 1]` - // To avoid reallocations, we always suffix the key with `[0, 1]`. - // Then, a specific key can be looked up using by omitting the last two bytes. - // By omitting one byte, you get the start of the range of all paths within this path. - // By not omitting any bytes, you get the end of that range. + // Bytes of `0xFF` cannot appear in valid UTF-8. + // The byte vector stored in a `DbKey` is formed by interspersing the segments with `0x00`, and suffixing the key with `[0x00, 0xFF]`. + // This lets us represent three significant keys with a single allocation: + // * The key for querying the path itself: `bytes[..bytes.len() - 2]`, e.g. `b"first\x00second"` + // * The keys for range-querying the path's children: + // * Start (inclusive): `bytes[..bytes.len() - 1]`, e.g. `b"first\x00second\x00"` + // * End (exclusive): `bytes[..]`, e.g. `b"first\x00second\x00\xFF"` let mut bytes = Vec::new(); @@ -179,22 +182,23 @@ impl DbKey { !segment.as_bytes().contains(&0x00), "A path segment must not contain null bytes." ); + // No need to check for `0xFF` bytes in UTF-8 strings. bytes.extend_from_slice(segment.as_bytes()); - bytes.extend_from_slice(&[0, 0]); + bytes.push(0x00); } if let Some(last_byte) = bytes.last_mut() { - *last_byte = 1; + bytes.push(0xFF); } else { panic!("An empty path is not a valid path."); } - DbKey(bytes) + DbKey { bytes } } fn range_of_children(&self) -> Range<&[u8]> { - (&self.0[0..(self.0.len() - 1)])..(&self.0[..]) + (&self.bytes[0..(self.bytes.len() - 1)])..(&self.bytes[..]) } fn segments(&self) -> impl Iterator> { @@ -209,7 +213,7 @@ impl DbKey { if let Some(end_index) = self.rest.iter().position(|byte| *byte == 0) { let segment = &self.rest[..end_index]; let segment = str::from_utf8(segment).unwrap(); - self.rest = &self.rest[end_index + 2..]; + self.rest = &self.rest[end_index + 1..]; Some(Cow::Borrowed(segment)) } else { @@ -219,7 +223,7 @@ impl DbKey { } SegmentIterator { - rest: self.0.as_slice(), + rest: self.bytes.as_slice(), } } }