Skip to main content

rs_matter/
persist.rs

1/*
2 *
3 *    Copyright (c) 2023-2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18//! This module provides the key-value BLOB store traits used throughout `rs-matter` for persistence, as well as some implementations for those.
19
20use cfg_if::cfg_if;
21
22use crate::error::Error;
23use crate::tlv::{TLVTag, ToTLV};
24use crate::utils::cell::RefCell;
25use crate::utils::storage::WriteBuf;
26use crate::utils::sync::blocking::Mutex;
27
28#[cfg(feature = "std")]
29pub use fileio::*;
30
31cfg_if! {
32    if #[cfg(feature = "kv-blob-store-65536")] {
33        /// The size (in bytes) of the scratch buffer used by the key-value
34        /// persistence machinery for (de)serializing BLOBs. This is the buffer
35        /// owned by [`Matter`](crate::Matter) and recombined with the user's
36        /// raw [`KvBlobStore`] by [`Matter::kv`](crate::Matter::kv) into a full
37        /// [`KvBlobStoreAccess`].
38        pub const KV_BUF_SIZE: usize = 65536;
39    } else if #[cfg(feature = "kv-blob-store-32768")] {
40        /// The size (in bytes) of the scratch buffer used by the key-value
41        /// persistence machinery for (de)serializing BLOBs.
42        pub const KV_BUF_SIZE: usize = 32768;
43    } else if #[cfg(feature = "kv-blob-store-16384")] {
44        /// The size (in bytes) of the scratch buffer used by the key-value
45        /// persistence machinery for (de)serializing BLOBs.
46        pub const KV_BUF_SIZE: usize = 16384;
47    } else if #[cfg(feature = "kv-blob-store-8192")] {
48        /// The size (in bytes) of the scratch buffer used by the key-value
49        /// persistence machinery for (de)serializing BLOBs.
50        pub const KV_BUF_SIZE: usize = 8192;
51    } else if #[cfg(feature = "kv-blob-store-2048")] {
52        /// The size (in bytes) of the scratch buffer used by the key-value
53        /// persistence machinery for (de)serializing BLOBs.
54        pub const KV_BUF_SIZE: usize = 2048;
55    } else if #[cfg(feature = "kv-blob-store-1024")] {
56        /// The size (in bytes) of the scratch buffer used by the key-value
57        /// persistence machinery for (de)serializing BLOBs.
58        pub const KV_BUF_SIZE: usize = 1024;
59    } else { // Default (`kv-blob-store-4096`)
60        /// The size (in bytes) of the scratch buffer used by the key-value
61        /// persistence machinery for (de)serializing BLOBs.
62        pub const KV_BUF_SIZE: usize = 4096;
63    }
64}
65
66/// The first key available for the vendor-specific data.
67pub const VENDOR_KEYS_START: u16 = 0x1000;
68
69/// The key range reserved for fabrics (256 keys).
70pub const FABRIC_KEYS_START: u16 = 0;
71
72/// The key used for storing the basic info settings.
73pub const BASIC_INFO_KEY: u16 = FABRIC_KEYS_START + 256;
74
75/// The key used for storing the events epoch number.
76pub const EVENT_EPOCH_KEY: u16 = BASIC_INFO_KEY + 1;
77
78/// The key used for storing the wireless networks state.
79pub const NETWORKS_KEY: u16 = EVENT_EPOCH_KEY + 1;
80
81/// The key used for storing all UserLabel `LabelList` data across every
82/// endpoint that hosts the UserLabel cluster.
83pub const USER_LABELS_KEY: u16 = NETWORKS_KEY + 1;
84
85/// The key used for storing all Binding entries across every
86/// endpoint+fabric pair that hosts the Binding cluster.
87pub const BINDINGS_KEY: u16 = USER_LABELS_KEY + 1;
88
89/// The key used for storing the Last-Known-Good UTC Time value
90/// (Matter Core spec). A single u64 Matter-epoch microseconds
91/// payload, updated synchronously from
92/// [`crate::Matter::set_utc_time`].
93pub const LKG_UTC_KEY: u16 = BINDINGS_KEY + 1;
94
95/// The key used for storing the Trusted Time Source configured by
96/// the `SetTrustedTimeSource` command (Matter Core spec).
97/// A single 11-byte payload: `[fab_idx:1 | node_id:8 (LE) | endpoint:2 (LE)]`,
98/// updated synchronously from [`crate::Matter::set_trusted_time_source`].
99/// The key is absent on disk when no trusted source is configured.
100pub const TRUSTED_TIME_SOURCE_KEY: u16 = LKG_UTC_KEY + 1;
101
102/// The key used for storing the entire Scenes Management cluster
103/// state (scene table + per-fabric `CurrentScene`) as a single TLV
104/// blob. Re-persisted on every successful mutation.
105pub const SCENES_KEY: u16 = TRUSTED_TIME_SOURCE_KEY + 1;
106
107/// The key used for storing the OTA Requestor's `DefaultOTAProviders` list
108/// (at most one entry per fabric) as a single TLV blob. Re-persisted on every
109/// successful write. Providers learned transiently via `AnnounceOTAProvider`
110/// are **not** persisted.
111pub const OTA_PROVIDERS_KEY: u16 = SCENES_KEY + 1;
112
113/// The key used for storing the ICD Management cluster's `RegisteredClients`
114/// list (across all fabrics) as a single TLV blob. Re-persisted on every
115/// successful registration change.
116pub const ICD_REGISTERED_CLIENTS_KEY: u16 = OTA_PROVIDERS_KEY + 1;
117
118/// The key used for storing the ICD Check-In counter's epoch boundary (a 4-byte
119/// little-endian value). Written only when a new epoch is crossed, so a restart
120/// resumes past every counter value the previous run may have used.
121pub const ICD_CHECK_IN_COUNTER_KEY: u16 = ICD_REGISTERED_CLIENTS_KEY + 1;
122
123/// The key used for storing the CASE session resumption cache — a
124/// single TLV blob holding up to
125/// [`MAX_RESUMPTION_RECORDS`](crate::sc::case::MAX_RESUMPTION_RECORDS)
126/// entries. Re-persisted by the background snapshot task whenever the
127/// in-memory cache diverges from what was last written.
128pub const CASE_RESUMPTION_KEY: u16 = ICD_CHECK_IN_COUNTER_KEY + 1;
129
130/// The key used for storing the TimeSynchronization cluster's `TimeZone` +
131/// `DSTOffset` lists (both `nonVolatile` quality per the Matter Core spec) as
132/// a single TLV blob. See
133/// [`TimeZoneStore`](crate::dm::clusters::time_sync::TimeZoneStore).
134pub const TIME_ZONE_KEY: u16 = CASE_RESUMPTION_KEY + 1;
135
136/// The key used for storing the Global Group Encrypted Data Message Counter's
137/// epoch boundary (a 4-byte little-endian value). Written only when a new
138/// epoch is crossed, so a restart resumes past every counter value the
139/// previous run may have used - otherwise peers tracking us in their group
140/// counter store would drop our post-restart group messages as replays.
141///
142/// NOTE: the key is reserved unconditionally (not behind the `groups`
143/// feature), so that a device's key layout never depends on which Cargo
144/// features it was built with.
145pub const GROUP_DATA_COUNTER_KEY: u16 = TIME_ZONE_KEY + 1;
146
147/// The first key past the singleton keys above - i.e. the next free slot for
148/// a *new* singleton key.
149///
150/// Only used by [`SINGLETON_KEYS_FIT`] to prove that the singleton block has
151/// not grown into [`PERSISTENT_SUBSCRIPTIONS_START`]; bump the key it is
152/// derived from whenever a singleton is added.
153const SINGLETON_KEYS_END: u16 = GROUP_DATA_COUNTER_KEY + 1;
154
155/// The first key of the range reserved for persisted subscriptions.
156///
157/// Each persisted subscription occupies its own key
158/// (`PERSISTENT_SUBSCRIPTIONS_START + slot`), so that a single record never grows
159/// the value beyond one subscribe request (already bounded to one RX packet,
160/// comfortably under the ~4 KiB per-value cap that some MCU key-value backends
161/// impose). The range runs up to (but not including)
162/// [`PERSISTENT_SUBSCRIPTIONS_END`].
163///
164/// IMPORTANT: the range is carved *downwards* from the top of the rs-matter
165/// key space, deliberately *not* derived from the singleton keys below it.
166/// Deriving it from those would mean that adding (or feature-gating) any
167/// singleton key silently shifts every persisted subscription onto a different
168/// key, so a device upgrading to a newer firmware would read another record's
169/// bytes - or would lose its subscriptions. New singleton keys therefore grow
170/// *into the gap* below this anchor, and [`SINGLETON_KEYS_FIT`] turns
171/// exhausting that gap into a compile error rather than silent corruption.
172pub const PERSISTENT_SUBSCRIPTIONS_START: u16 =
173    PERSISTENT_SUBSCRIPTIONS_END - MAX_PERSISTED_SUBSCRIPTIONS as u16;
174
175/// The first key past the range reserved for persisted subscriptions - i.e.
176/// the top of the rs-matter key space, where the vendor keys begin.
177pub const PERSISTENT_SUBSCRIPTIONS_END: u16 = VENDOR_KEYS_START;
178
179/// How many persisted subscriptions the reserved range can hold.
180///
181/// The range is explicitly bounded (rather than running open-ended down from
182/// [`PERSISTENT_SUBSCRIPTIONS_END`]) so that a `Subscriptions<N>` table sized
183/// beyond it is caught at compile time instead of quietly overwriting the
184/// singleton keys below.
185pub const MAX_PERSISTED_SUBSCRIPTIONS: usize = 2048;
186
187/// Compile-time proof that the singleton keys have not grown into the
188/// persisted-subscription range.
189///
190/// If adding a singleton key ever breaks this, do NOT make room by shrinking
191/// [`MAX_PERSISTED_SUBSCRIPTIONS`] or by moving [`VENDOR_KEYS_START`] - either
192/// relocates [`PERSISTENT_SUBSCRIPTIONS_START`], and with it every
193/// subscription an earlier firmware already persisted. That is a deliberate,
194/// breaking key-layout migration, not a constant tweak.
195// `::core::assert!` rather than the crate-wide `assert!`, which maps to
196// `defmt::assert!` under the `defmt` feature and is not const-callable.
197const SINGLETON_KEYS_FIT: () = ::core::assert!(
198    SINGLETON_KEYS_END <= PERSISTENT_SUBSCRIPTIONS_START,
199    "the rs-matter singleton keys have grown into the persisted-subscription range"
200);
201
202/// Compile-time proof that the persisted-subscription range has not moved.
203///
204/// [`PERSISTENT_SUBSCRIPTIONS_START`] is part of the on-device key layout:
205/// every subscription persisted by an earlier firmware lives at
206/// `PERSISTENT_SUBSCRIPTIONS_START + slot`. Deriving it from the two constants
207/// above keeps the intent readable, but the result must stay pinned to the
208/// value already shipped.
209const SUBSCRIPTION_RANGE_PINNED: () = ::core::assert!(
210    PERSISTENT_SUBSCRIPTIONS_START == 0x0800,
211    "the persisted-subscription range has moved - existing devices would look for their subscriptions under the wrong keys"
212);
213
214/// Force the assertions to be evaluated.
215const _: () = SINGLETON_KEYS_FIT;
216const _: () = SUBSCRIPTION_RANGE_PINNED;
217
218/// A trait representing a key-value BLOB storage.
219///
220/// NOTE: For now, the trait is deliberately modeled as non-async, so that it can be used from
221/// regular `Handler` non-async instances so as to avoid code bloat due to too much async handlers.
222///
223/// However, this might change in future once/if rustc starts to optimize the generated async code a bit better.
224pub trait KvBlobStore {
225    /// Load a BLOB with the specified key from the storage.
226    ///
227    /// # Arguments
228    /// - `key` - the key of the BLOB
229    /// - `buf` - a buffer that the `KvBlobStore` implementation might use for its own purposes
230    ///
231    /// # Returns
232    /// - `Ok(Some(&[u8]))` if the BLOB was successfully loaded,
233    /// - `Ok(None)` if the BLOB with the specified key does not exist,
234    /// - `Err` if an error occurred during loading.
235    fn load<'a>(&mut self, key: u16, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error>;
236
237    /// Store a BLOB with the specified key in the storage.
238    ///
239    /// # Arguments
240    /// - `key` - the key of the BLOB
241    /// - `data` - the data to store
242    /// - `buf` - a buffer that the `KvBlobStore` implementation might use for its own purposes
243    ///
244    /// # Returns
245    /// - `Ok(())` if the BLOB was successfully stored,
246    /// - `Err` if an error occurred during storing.
247    fn store(&mut self, key: u16, data: &[u8], buf: &mut [u8]) -> Result<(), Error>;
248
249    /// Remove a BLOB with the specified key from the storage.
250    ///
251    /// # Arguments
252    /// - `key` - the key of the BLOB
253    /// - `buf` - a buffer that the `KvBlobStore` implementation might use for its own purposes
254    ///
255    /// # Returns
256    /// - `Ok(())` if the BLOB was successfully removed or did not exist
257    /// - `Err` if an error occurred during removing.
258    fn remove(&mut self, key: u16, buf: &mut [u8]) -> Result<(), Error>;
259}
260
261impl<T> KvBlobStore for &mut T
262where
263    T: KvBlobStore,
264{
265    fn load<'a>(&mut self, key: u16, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
266        T::load(self, key, buf)
267    }
268
269    fn store(&mut self, key: u16, data: &[u8], buf: &mut [u8]) -> Result<(), Error> {
270        T::store(self, key, data, buf)
271    }
272
273    fn remove(&mut self, key: u16, buf: &mut [u8]) -> Result<(), Error> {
274        T::remove(self, key, buf)
275    }
276}
277
278impl KvBlobStore for &mut dyn KvBlobStore {
279    fn load<'a>(&mut self, key: u16, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
280        (**self).load(key, buf)
281    }
282
283    fn store(&mut self, key: u16, data: &[u8], buf: &mut [u8]) -> Result<(), Error> {
284        (**self).store(key, data, buf)
285    }
286
287    fn remove(&mut self, key: u16, buf: &mut [u8]) -> Result<(), Error> {
288        (**self).remove(key, buf)
289    }
290}
291
292/// A noop implementation of the `KvBlobStore` trait.
293pub struct DummyKvBlobStore;
294
295impl KvBlobStore for DummyKvBlobStore {
296    fn load<'a>(&mut self, _key: u16, _buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
297        Ok(None)
298    }
299
300    fn store(&mut self, _key: u16, _data: &[u8], _buf: &mut [u8]) -> Result<(), Error> {
301        Ok(())
302    }
303
304    fn remove(&mut self, _key: u16, _buf: &mut [u8]) -> Result<(), Error> {
305        Ok(())
306    }
307}
308
309/// A trait representing access to a `KvBlobStore` instance and a buffer for its use.
310pub trait KvBlobStoreAccess {
311    /// Get the `KvBlobStore` instance and buffer provided by this access.
312    fn access<F, R>(&self, f: F) -> R
313    where
314        F: FnOnce(&mut dyn KvBlobStore, &mut [u8]) -> R;
315}
316
317impl<T> KvBlobStoreAccess for &T
318where
319    T: KvBlobStoreAccess,
320{
321    fn access<F, R>(&self, f: F) -> R
322    where
323        F: FnOnce(&mut dyn KvBlobStore, &mut [u8]) -> R,
324    {
325        T::access(self, f)
326    }
327}
328
329/// Combines a (store-only) raw [`KvBlobStore`] with a scratch buffer to present a
330/// full [`KvBlobStoreAccess`].
331///
332/// This is the concrete type returned by [`Matter::kv`](crate::Matter::kv), where
333/// the buffer is owned by [`Matter`](crate::Matter). It owns the user's raw store
334/// (behind a blocking mutex for interior mutability) and borrows the buffer. The
335/// buffer lock is always taken first, then the store lock, so the two-lock order
336/// is consistent across all persistence paths (and single-threaded executors never
337/// actually block on either).
338///
339/// It can also be constructed directly (e.g. in tests, or to exercise a
340/// persistence-consuming API without a real store) by pairing a
341/// [`DummyKvBlobStore`] with a caller-owned buffer.
342pub struct SharedKvBlobStore<'a, S, const KB: usize> {
343    store: Mutex<RefCell<S>>,
344    buf: &'a Mutex<RefCell<[u8; KB]>>,
345}
346
347impl<'a, S, const KB: usize> SharedKvBlobStore<'a, S, KB> {
348    /// Create a new access object owning `store` and borrowing `buf`.
349    pub const fn new(store: S, buf: &'a Mutex<RefCell<[u8; KB]>>) -> Self {
350        Self {
351            store: Mutex::new(RefCell::new(store)),
352            buf,
353        }
354    }
355}
356
357impl<S, const KB: usize> KvBlobStoreAccess for SharedKvBlobStore<'_, S, KB>
358where
359    S: KvBlobStore,
360{
361    fn access<F, R>(&self, f: F) -> R
362    where
363        F: FnOnce(&mut dyn KvBlobStore, &mut [u8]) -> R,
364    {
365        self.buf.lock(|cell| {
366            let mut buf = cell.borrow_mut();
367
368            self.store
369                .lock(|store| f(&mut *store.borrow_mut(), &mut *buf))
370        })
371    }
372}
373
374/// A utility for persisting a value in a `KvBlobStore` instance.
375pub struct Persist<S> {
376    kvb: S,
377}
378
379impl<S> Persist<S>
380where
381    S: KvBlobStoreAccess,
382{
383    /// Create a new `Persist` instance with the given key-value store instance.
384    pub const fn new(kvb: S) -> Self {
385        Self { kvb }
386    }
387
388    /// Save a value in the storage with the specified key by calling the provided closure to serialize the value into a buffer.
389    pub fn store<F: FnOnce(&mut [u8]) -> Result<Option<usize>, Error>>(
390        &mut self,
391        key: u16,
392        f: F,
393    ) -> Result<(), Error> {
394        self.kvb.access(|kvb, buf| {
395            if !buf.is_empty() {
396                // A no-op access (e.g. a dummy store with an empty buffer) skips persistence
397                if let Some(len) = f(buf)? {
398                    let (data, buf) = buf.split_at_mut(len);
399                    kvb.store(key, data, buf)?;
400                }
401            }
402
403            Ok(())
404        })
405    }
406
407    /// Save a value that implements the `ToTLV` trait in the storage with the specified key.
408    pub fn store_tlv<T: ToTLV>(&mut self, key: u16, tlv: T) -> Result<(), Error> {
409        self.store(key, |buf| {
410            let mut wb = WriteBuf::new(buf);
411
412            tlv.to_tlv(&TLVTag::Anonymous, &mut wb)?;
413
414            Ok(Some(wb.get_tail()))
415        })
416    }
417
418    /// Remove the value with the specified key from the storage.
419    pub fn remove(&mut self, key: u16) -> Result<(), Error> {
420        self.kvb.access(|kvb, buf| {
421            if !buf.is_empty() {
422                // A no-op access (e.g. a dummy store with an empty buffer) skips persistence
423                kvb.remove(key, buf)?;
424            }
425
426            Ok(())
427        })
428    }
429
430    /// Call at the end when finished with everything else
431    /// No-op for now
432    pub fn run(self) -> Result<(), Error> {
433        // No-op for now
434
435        Ok(())
436    }
437}
438
439#[cfg(feature = "std")]
440mod fileio {
441    use std::collections::HashMap;
442    use std::fs::File;
443    use std::io::{Read, Write};
444    use std::path::{Path, PathBuf};
445
446    use crate::error::Error;
447
448    use super::KvBlobStore;
449
450    extern crate std;
451
452    /// An implementation of the `KvBlobStore` trait that stores the BLOBs in a directory.
453    ///
454    /// The BLOBs are stored in files named after the keys in the specified directory.
455    #[derive(Debug, Clone)]
456    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
457    pub struct DirKvBlobStore(
458        #[cfg_attr(feature = "defmt", defmt(Debug2Format))] std::path::PathBuf,
459    );
460
461    impl DirKvBlobStore {
462        /// Create a new `DirKvBlobStore` instance, which will persist
463        /// its settings in `<tmp-dir>/rs-matter`.
464        pub fn new_default() -> Self {
465            Self(std::env::temp_dir().join("rs-matter"))
466        }
467
468        /// Create a new `DirKvBlobStore` instance.
469        pub const fn new(path: std::path::PathBuf) -> Self {
470            Self(path)
471        }
472
473        /// Load a BLOB with the specified key from the directory.
474        pub fn load(&self, key: u16, buf: &mut [u8]) -> Result<Option<usize>, Error> {
475            let path = self.key_path(key);
476
477            match File::open(path) {
478                Ok(mut file) => {
479                    let mut offset = 0;
480
481                    loop {
482                        if offset == buf.len() {
483                            Err(crate::error::ErrorCode::NoSpace)?;
484                        }
485
486                        let len = file.read(&mut buf[offset..])?;
487
488                        if len == 0 {
489                            break;
490                        }
491
492                        offset += len;
493                    }
494
495                    let data = &buf[..offset];
496
497                    debug!("Key {}: loaded {}B ({:?})", key, data.len(), data);
498
499                    Ok(Some(data.len()))
500                }
501                Err(_) => Ok(None),
502            }
503        }
504
505        /// Store a BLOB with the specified key in the directory.
506        pub fn store(&self, key: u16, data: &[u8]) -> Result<(), Error> {
507            let path = self.key_path(key);
508
509            std::fs::create_dir_all(unwrap!(path.parent()))?;
510
511            let mut file = File::create(path)?;
512
513            file.write_all(data)?;
514
515            debug!("Key {}: stored {}B ({:?})", key, data.len(), data);
516
517            Ok(())
518        }
519
520        /// Remove a BLOB with the specified key from the directory.
521        /// If the BLOB does not exist, this method does nothing.
522        pub fn remove(&self, key: u16) -> Result<(), Error> {
523            let path = self.key_path(key);
524
525            if std::fs::remove_file(path).is_ok() {
526                debug!("Key {}: removed", key);
527            }
528
529            Ok(())
530        }
531
532        fn key_path(&self, key: u16) -> std::path::PathBuf {
533            self.0.join(format!("k_{key:04x}"))
534        }
535    }
536
537    impl Default for DirKvBlobStore {
538        fn default() -> Self {
539            Self::new_default()
540        }
541    }
542
543    impl KvBlobStore for DirKvBlobStore {
544        fn load<'a>(&mut self, key: u16, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
545            Ok(Self::load(self, key, buf)?.map(|len| &buf[..len]))
546        }
547
548        fn store(&mut self, key: u16, data: &[u8], _buf: &mut [u8]) -> Result<(), Error> {
549            Self::store(self, key, data)
550        }
551
552        fn remove(&mut self, key: u16, _buf: &mut [u8]) -> Result<(), Error> {
553            Self::remove(self, key)
554        }
555    }
556
557    /// An implementation of the `KvBlobStore` trait that stores all BLOBs in a single file.
558    ///
559    /// While the implementation is very inefficient, it is necessary when testing with the C++ SDK test harness,
560    /// as it expects all data to be persisted as a single file (`/tmp/chip_kvs`).
561    #[derive(Debug, Clone)]
562    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
563    pub struct FileKvBlobStore {
564        #[cfg_attr(feature = "defmt", defmt(Debug2Format))]
565        path: std::path::PathBuf,
566        #[cfg_attr(feature = "defmt", defmt(Debug2Format))]
567        blobs: Option<HashMap<u16, Vec<u8>>>,
568    }
569
570    impl FileKvBlobStore {
571        /// Create a new `FileKvBlobStore` instance, which will persist its settings in `/tmp/chip_kvs`.
572        pub fn new_default() -> Self {
573            Self::new(PathBuf::from("/tmp/chip_kvs"))
574        }
575
576        /// Create a new `FileKvBlobStore` instance.
577        pub const fn new(path: PathBuf) -> Self {
578            Self { path, blobs: None }
579        }
580
581        /// Load a BLOB with the specified key from the file.
582        pub fn load(&mut self, key: u16, buf: &mut [u8]) -> Result<Option<usize>, Error> {
583            self.initialize()?;
584
585            let blobs = self.blobs.as_ref().unwrap();
586
587            if let Some(blob) = blobs.get(&key) {
588                if blob.len() > buf.len() {
589                    Err(crate::error::ErrorCode::NoSpace)?;
590                }
591
592                buf[..blob.len()].copy_from_slice(blob);
593
594                Ok(Some(blob.len()))
595            } else {
596                Ok(None)
597            }
598        }
599
600        /// Store a BLOB with the specified key in the directory.
601        pub fn store(&mut self, key: u16, data: &[u8]) -> Result<(), Error> {
602            self.initialize()?;
603
604            let blobs = self.blobs.as_mut().unwrap();
605
606            blobs.insert(key, data.to_vec());
607
608            Self::save_all(&self.path, blobs)
609        }
610
611        /// Remove a BLOB with the specified key from the directory.
612        /// If the BLOB does not exist, this method does nothing.
613        pub fn remove(&mut self, key: u16) -> Result<(), Error> {
614            self.initialize()?;
615
616            let blobs = self.blobs.as_mut().unwrap();
617
618            blobs.remove(&key);
619
620            Self::save_all(&self.path, blobs)
621        }
622
623        fn initialize(&mut self) -> Result<(), Error> {
624            if self.blobs.is_none() {
625                let mut blobs = HashMap::new();
626
627                Self::load_all(&self.path, &mut blobs)?;
628
629                self.blobs = Some(blobs);
630            }
631
632            Ok(())
633        }
634
635        fn load_all(path: &Path, blobs: &mut HashMap<u16, Vec<u8>>) -> Result<(), Error> {
636            if let Ok(mut file) = File::open(path) {
637                loop {
638                    let mut key_buf = [0; 2];
639
640                    if file.read_exact(&mut key_buf).is_err() {
641                        break;
642                    }
643
644                    let key = u16::from_le_bytes(key_buf);
645
646                    let mut len_buf = [0; 4];
647
648                    file.read_exact(&mut len_buf)?;
649
650                    let len = u32::from_le_bytes(len_buf) as usize;
651
652                    let mut data = vec![0; len];
653
654                    file.read_exact(&mut data)?;
655
656                    blobs.insert(key, data);
657                }
658            }
659
660            Ok(())
661        }
662
663        fn save_all(path: &Path, blobs: &HashMap<u16, Vec<u8>>) -> Result<(), Error> {
664            let mut file = File::create(path)?;
665
666            for (key, data) in blobs {
667                file.write_all(&key.to_le_bytes())?;
668                file.write_all(&(data.len() as u32).to_le_bytes())?;
669                file.write_all(data)?;
670            }
671
672            Ok(())
673        }
674    }
675
676    impl Default for FileKvBlobStore {
677        fn default() -> Self {
678            Self::new_default()
679        }
680    }
681
682    impl KvBlobStore for FileKvBlobStore {
683        fn load<'a>(&mut self, key: u16, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
684            Ok(Self::load(self, key, buf)?.map(|len| &buf[..len]))
685        }
686
687        fn store(&mut self, key: u16, data: &[u8], _buf: &mut [u8]) -> Result<(), Error> {
688            Self::store(self, key, data)
689        }
690
691        fn remove(&mut self, key: u16, _buf: &mut [u8]) -> Result<(), Error> {
692            Self::remove(self, key)
693        }
694    }
695}