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}