Skip to main content

rs_matter/dm/clusters/
basic_info.rs

1/*
2 *
3 *    Copyright (c) 2022-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 contains the implementation of the Basic Information cluster and its handler.
19
20use core::str::FromStr;
21
22use crate::dm::{Cluster, Dataver, InvokeContext, ReadContext, WriteContext};
23use crate::error::{Error, ErrorCode};
24use crate::fabric::MAX_FABRICS;
25use crate::persist::{KvBlobStore, Persist, BASIC_INFO_KEY};
26use crate::tlv::{FromTLV, Nullable, TLVBuilderParent, TLVElement, ToTLV, Utf8StrBuilder};
27use crate::transport::exchange::Exchange;
28use crate::transport::session::MAX_SESSIONS;
29use crate::utils::bitflags::bitflags;
30use crate::utils::init::{init, Init};
31use crate::{except, with};
32
33pub use crate::dm::clusters::decl::basic_information::*;
34pub use crate::dm::clusters::decl::general_commissioning::RegulatoryLocationTypeEnum;
35
36/// The default Matter App Clusters specification version
37///
38/// Currently set to V1.5.1.0
39pub const DEFAULT_MATTER_SPEC_VERSION: u32 = 0x01050100;
40
41/// The default Matter Data Model revision
42///
43/// Currently set to V19, which was released with Matter Core spec V1.4.2
44pub const DEFAULT_DATA_MODEL_REVISION: u16 = 19;
45
46/// The default maximum number of paths that can be included in an Invoke request
47///
48/// Set to 5, which is enough to support typical batched invokes while
49/// keeping the in-memory CommandRef tracking buffer in `dm::invoke()` small.
50pub const DEFAULT_MAX_PATHS_PER_INVOKE: u16 = 5;
51
52bitflags! {
53    #[repr(transparent)]
54    #[derive(Default)]
55    #[cfg_attr(not(feature = "defmt"), derive(Debug, Copy, Clone, Eq, PartialEq, Hash))]
56    pub struct PairingHintFlags: u32 {
57        /// Power Cycle False The Device will automatically enter Commissioning Mode upon
58        /// power cycle (unplug/replug, remove/re-insert batteries).
59        /// This bit SHALL be set to 1 for devices using Standard Commissioning Flow,
60        /// and set to 0 otherwise.
61        const POWER_CYCLE = 0x0000_0001;
62        /// This SHALL be set to 1 for devices requiring Custom Commissioning
63        /// Flow before they can be available for Commissioning by any Commissioner.
64        /// For such a flow, the user SHOULD be sent to the URL specified in the
65        /// CommissioningCustomFlowUrl of the DeviceModel schema entry indexed by the
66        /// Vendor ID and Product ID (e.g., as found in the announcement) in the
67        /// Distributed Compliance Ledger.
68        const DEV_MANUFACTURER_URL = 0x0000_0002;
69        /// The Device has been commissioned. Any Administrator that commissioned the
70        /// device provides a user interface that may be used to put the device
71        /// into Commissioning Mode.
72        const ADMINISTRATOR = 0x0000_0004;
73        /// The settings menu on the Device provides instructions to put it
74        /// into Commissioning Mode.
75        const SETTINGS_MENU = 0x0000_0008;
76        /// The PI key/value pair describes a custom way to put the Device into
77        /// Commissioning Mode. This Custom Instruction option is NOT recommended
78        /// for use by a Device that does not have knowledge of the user's language preference.
79        const CUSTOM_INSTRUCTION = 0x0000_0010;
80        /// The Device Manual provides special instructions to put the Device
81        /// into Commissioning Mode (see "UserManualUrl" in the Core Spec).
82        /// This is a catchall option to capture user interactions that are not codified by
83        /// other options in this flags type.
84        const DEVICE_MANUAL = 0x0000_0020;
85        /// The Device will enter Commissioning Mode when reset button is pressed.
86        const PRESS_RESET_BUTTON = 0x0000_0040;
87        /// The Device will enter Commissioning Mode when reset button is pressed when applying power to it.
88        const PRESS_RESET_BUTTON_WITH_POWER = 0x0000_0080;
89        /// The Device will enter Commissioning Mode when reset button is pressed for N seconds.
90        /// The exact value of N SHALL be made available via PI key.
91        const PRESS_RESET_BUTTON_FOR_N_SECONDS = 0x0000_0100;
92        /// The Device will enter Commissioning Mode when reset button is pressed until associated light blinks.
93        /// Information on color of light MAY be made available via PI key.
94        const PRESS_RESET_BUTTON_UNTIL_LIGHT_BLINKS = 0x0000_0200;
95        /// The Device will enter Commissioning Mode when reset button is pressed for N seconds
96        /// when applying power to it. The exact value of N SHALL be made available via PI key.
97        const PRESS_RESET_BUTTON_FOR_N_SECONDS_WITH_POWER = 0x0000_0400;
98        /// The Device will enter Commissioning Mode when reset button is pressed until associated
99        /// light blinks when applying power to the Device. Information on color of light MAY be
100        /// made available via PI key.
101        const PRESS_RESET_BUTTON_UNTIL_LIGHT_BLINKS_WITH_POWER = 0x0000_0800;
102        /// The Device will enter Commissioning Mode when reset button is pressed N times
103        /// with maximum 1 second between each press. The exact value of N SHALL be made available via PI key.
104        const PRESS_RESET_BUTTON_N_TIMES = 0x0000_1000;
105        /// The Device will enter Commissioning Mode when setup button is pressed.
106        const PRESS_SETUP_BUTTON = 0x0000_2000;
107        /// The Device will enter Commissioning Mode when setup button is pressed when applying power to it.
108        const PRESS_SETUP_BUTTON_WITH_POWER = 0x0000_4000;
109        /// The Device will enter Commissioning Mode when setup button is pressed for N seconds.
110        /// The exact value of N SHALL be made available via PI key.
111        const PRESS_SETUP_BUTTON_FOR_N_SECONDS = 0x0000_8000;
112        /// The Device will enter Commissioning Mode when setup button is pressed until associated
113        /// light blinks. Information on color of light MAY be made available via PI key.
114        const PRESS_SETUP_BUTTON_UNTIL_LIGHT_BLINKS = 0x0001_0000;
115        /// The Device will enter Commissioning Mode when setup button is pressed for N seconds
116        /// when applying power to it. The exact value of N SHALL be made available via PI key.
117        const PRESS_SETUP_BUTTON_FOR_N_SECONDS_WITH_POWER = 0x0002_0000;
118        /// The Device will enter Commissioning Mode when setup button is pressed until associated
119        /// light blinks when applying power to the Device. Information on color of light MAY be
120        /// made available via PI key.
121        const PRESS_SETUP_BUTTON_UNTIL_LIGHT_BLINKS_WITH_POWER = 0x0004_0000;
122        /// The Device will enter Commissioning Mode when setup button is pressed N times with
123        /// maximum 1 second between each press. The exact value of N SHALL be made available via PI key.
124        const PRESS_SETUP_BUTTON_N_TIMES = 0x0008_0000;
125    }
126}
127
128/// Basic information which is immutable
129/// (i.e. valid for the lifetime of the device firmware)
130///
131/// Note that some of the fields will be reported only if their corresponding optional attributes are enabled.
132///
133/// By default, `BasicInfoHandler::CLUSTER` enables ALL optional attributes except `reachable` which is only valid for
134/// bridged devices.
135#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
136#[cfg_attr(feature = "defmt", derive(defmt::Format))]
137pub struct BasicInfoConfig<'a> {
138    /// Vendor name (up to 32 characters)
139    pub vendor_name: &'a str,
140    /// Vendor ID
141    pub vid: u16,
142    /// Product name (up to 32 characters)
143    pub product_name: &'a str,
144    /// Product ID
145    pub pid: u16,
146    /// Hardware version
147    pub hw_ver: u16,
148    /// Hardware version string (up to 64 characters)
149    pub hw_ver_str: &'a str,
150    /// Software version
151    pub sw_ver: u32,
152    /// Software version string (up to 64 characters)
153    pub sw_ver_str: &'a str,
154    /// Manufacturing date (up to 16 characters)
155    pub manufacturing_date: &'a str,
156    /// Part number (up to 32 characters)
157    pub part_number: &'a str,
158    /// Product URL (up to 256 characters)
159    pub product_url: &'a str,
160    /// Product label (up to 64 characters)
161    pub product_label: &'a str,
162    /// Serial number (up to 32 characters)
163    pub serial_no: &'a str,
164    /// Unique ID (up to 64 characters)
165    pub unique_id: &'a str,
166    /// Capability Minima
167    pub capability_minima: CapabilityMinima,
168    /// Product Appearance
169    pub product_appearance: ProductAppearance,
170    /// Specification Version
171    pub specification_version: u32,
172    /// Data Model Revision
173    pub data_model_revision: u16,
174    /// Max Paths Per Invoke
175    pub max_paths_per_invoke: u16,
176    /// Device Name
177    ///
178    /// Not a real attribute; used in the mDNS commissioning advertisement
179    pub device_name: &'a str,
180    /// Device Type
181    ///
182    /// Not a real attribute; used in the mDNS commissioning advertisement
183    pub device_type: Option<u16>,
184    /// Pairing Hint
185    ///
186    /// Not a real attribute; used in the mDNS commissioning advertisement
187    pub pairing_hint: PairingHintFlags,
188    /// Pairing Instruction
189    ///
190    /// Not a real attribute; used in the mDNS commissioning advertisement
191    pub pairing_instruction: &'a str,
192    /// Session Active Interval in ms
193    /// If not specified, defaults to 300
194    ///
195    /// Per the Matter Core Spec, the value is a 32-bit unsigned integer and
196    /// SHALL NOT exceed 3,600,000 (1 hour in milliseconds).
197    ///
198    /// Not a real attribute, just used to configure the session timeouts
199    pub sai: Option<u32>,
200    /// Session Idle Interval in ms
201    /// If not specified, defaults to 5000
202    ///
203    /// Per the Matter Core Spec, the value is a 32-bit unsigned integer and
204    /// SHALL NOT exceed 3,600,000 (1 hour in milliseconds).
205    ///
206    /// Not a real attribute, just used to configure the session timeouts
207    pub sii: Option<u32>,
208    /// Whether the device supports TCP transport.
209    ///
210    /// Not a real attribute; advertised via the `T` TXT record key in mDNS.
211    /// Per the Matter Core Spec, `T` is a bitmap: bit 2 (value 4)
212    /// indicates TCP server support. Required for large payloads such as WebRTC SDP
213    /// exchanges and camera snapshots.
214    pub tcp_supported: bool,
215}
216
217impl BasicInfoConfig<'_> {
218    pub const fn new() -> Self {
219        Self {
220            vid: 0,
221            pid: 0,
222            hw_ver: 0,
223            hw_ver_str: "",
224            sw_ver: 0,
225            sw_ver_str: "",
226            serial_no: "",
227            product_name: "",
228            vendor_name: "",
229            manufacturing_date: "",
230            part_number: "",
231            product_url: "",
232            product_label: "",
233            unique_id: "",
234            capability_minima: CapabilityMinima::new(),
235            product_appearance: ProductAppearance::new(),
236            specification_version: DEFAULT_MATTER_SPEC_VERSION,
237            data_model_revision: DEFAULT_DATA_MODEL_REVISION,
238            max_paths_per_invoke: DEFAULT_MAX_PATHS_PER_INVOKE,
239            device_name: "",
240            device_type: None,
241            pairing_hint: PairingHintFlags::empty(),
242            pairing_instruction: "",
243            sai: None,
244            sii: None,
245            tcp_supported: false,
246        }
247    }
248}
249
250impl Default for BasicInfoConfig<'_> {
251    fn default() -> Self {
252        Self::new()
253    }
254}
255
256/// Capability Minima as reported in the Basic Information cluster
257#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
258#[cfg_attr(feature = "defmt", derive(defmt::Format))]
259pub struct CapabilityMinima {
260    /// Maximum CASE sessions per fabric
261    pub case_sessions_per_fabric: u16,
262    /// Maximum subscriptions per fabric
263    pub subscriptions_per_fabric: u16,
264}
265
266/// The Matter spec mandates `CapabilityMinima.SubscriptionsPerFabric >= 3`.
267/// rs-matter sizes its default subscription table as `MAX_FABRICS * 3` (see
268/// [`DEFAULT_MAX_SUBSCRIPTIONS`](crate::im::subscriptions::DEFAULT_MAX_SUBSCRIPTIONS)),
269/// so this per-fabric minimum is what the device guarantees.
270const SUBSCRIPTIONS_PER_FABRIC: u16 = 3;
271
272impl CapabilityMinima {
273    /// Create a default instance of `CapabilityMinima`, with CASE sessions per
274    /// fabric derived from the session table and the spec-minimum subscriptions
275    /// per fabric.
276    pub const fn new() -> Self {
277        Self {
278            case_sessions_per_fabric: (MAX_SESSIONS / MAX_FABRICS) as _,
279            subscriptions_per_fabric: SUBSCRIPTIONS_PER_FABRIC,
280        }
281    }
282}
283
284impl Default for CapabilityMinima {
285    fn default() -> Self {
286        Self::new()
287    }
288}
289
290/// Product Appearance as reported in the Basic Information cluster
291#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
292#[cfg_attr(feature = "defmt", derive(defmt::Format))]
293pub struct ProductAppearance {
294    /// Product finish type
295    pub finish: ProductFinishEnum,
296    /// Product primary color
297    pub color: Option<ColorEnum>,
298}
299
300impl ProductAppearance {
301    /// Create a default instance of `ProductAppearance`,
302    /// with `Other` finish and no color.
303    pub const fn new() -> Self {
304        Self {
305            finish: ProductFinishEnum::Other,
306            color: None,
307        }
308    }
309}
310
311impl Default for ProductAppearance {
312    fn default() -> Self {
313        Self::new()
314    }
315}
316
317/// Mutable basic information
318#[derive(Debug, Clone, Eq, PartialEq, Hash, ToTLV, FromTLV)]
319#[cfg_attr(feature = "defmt", derive(defmt::Format))]
320pub struct BasicInfoSettings {
321    pub node_label: heapless::String<32>, // Max node-label as per the spec
322    pub location: Option<heapless::String<2>>, // Max location as per the spec
323    pub location_type: RegulatoryLocationTypeEnum,
324    pub local_config_disabled: bool,
325    /// `BasicInformation::ConfigurationVersion` (Matter Core Spec).
326    /// Non-volatile, monotonically increasing, minimum 1.
327    /// Bumped by application code via
328    /// `InteractionModel::bump_configuration_version` whenever the node's
329    /// fixed-quality surface (Server/Parts list, device types, software
330    /// version, …) changes — see Matter Core Spec.
331    pub configuration_version: u32,
332}
333
334impl BasicInfoSettings {
335    /// Create a new instance of `BasicInfoSettings`
336    pub const fn new() -> Self {
337        Self {
338            node_label: heapless::String::new(),
339            location: None,
340            location_type: RegulatoryLocationTypeEnum::IndoorOutdoor,
341            local_config_disabled: false,
342            // Spec fallback for `ConfigurationVersion` is 1 (`min 1`,
343            // Core Spec).
344            configuration_version: 1,
345        }
346    }
347
348    /// Return an in-place initializer for `BasicInfoSettings`
349    pub fn init() -> impl Init<Self> {
350        init!(Self {
351            node_label: heapless::String::new(),
352            location: None,
353            location_type: RegulatoryLocationTypeEnum::IndoorOutdoor,
354            local_config_disabled: false,
355            configuration_version: 1,
356        })
357    }
358
359    /// Resets the basic info to initial values
360    ///
361    /// # Arguments
362    /// - `flag_changed`: whether to mark the basic info settings as changed
363    pub fn reset(&mut self) {
364        self.node_label.clear();
365        self.location = None;
366        self.local_config_disabled = false;
367        self.configuration_version = 1;
368    }
369
370    /// Bump `ConfigurationVersion` by one and return the new value.
371    ///
372    /// Saturates at `u32::MAX` (the spec gives no wrap semantics, so
373    /// staying at the max is safer than rolling over to 0 which would
374    /// violate the `min 1` constraint).
375    ///
376    /// This routine only mutates the in-memory value. Persistence and
377    /// subscriber notification are the caller's responsibility — use
378    /// `InteractionModel::bump_configuration_version` for the full
379    /// "bump + persist + notify + dataver-bump" pass.
380    pub fn bump_configuration_version(&mut self) -> u32 {
381        self.configuration_version = self.configuration_version.saturating_add(1);
382        self.configuration_version
383    }
384
385    pub fn set_location(&mut self, location: &str) {
386        if location == "XX" {
387            self.location = None;
388        } else {
389            self.location = Some(unwrap!(heapless::String::<2>::from_str(location)));
390        }
391    }
392
393    /// Remove all basic info settings from the provided BLOB store as well as from memory
394    ///
395    /// # Arguments
396    /// - `store`: the BLOB store to remove the settings from
397    /// - `buf`: a temporary buffer to use for removing the settings
398    pub fn reset_persist<S: KvBlobStore>(
399        &mut self,
400        mut store: S,
401        buf: &mut [u8],
402    ) -> Result<(), Error> {
403        self.reset();
404
405        store.remove(BASIC_INFO_KEY, buf)?;
406
407        info!("Removed basic info settings from storage");
408
409        Ok(())
410    }
411
412    /// Load basic info settings from the provided byte slice
413    pub fn load(&mut self, data: &[u8]) -> Result<(), Error> {
414        let info = Self::from_tlv(&TLVElement::new(data))?;
415
416        self.node_label = info.node_label;
417        self.location = info.location;
418        self.location_type = info.location_type;
419        self.local_config_disabled = info.local_config_disabled;
420        self.configuration_version = info.configuration_version;
421
422        Ok(())
423    }
424
425    /// Load all basic info settings from the provided BLOB store
426    ///
427    /// # Arguments
428    /// - `store`: the BLOB store to load the fabrics from
429    /// - `buf`: a temporary buffer to use for loading the fabrics
430    pub fn load_persist<S: KvBlobStore>(
431        &mut self,
432        mut store: S,
433        buf: &mut [u8],
434    ) -> Result<(), Error> {
435        self.reset();
436
437        if let Some(data) = store.load(BASIC_INFO_KEY, buf)? {
438            self.load(data)?;
439
440            info!("Loaded basic info settings from storage");
441        }
442
443        Ok(())
444    }
445}
446
447impl Default for BasicInfoSettings {
448    fn default() -> Self {
449        Self::new()
450    }
451}
452
453/// The system implementation of a handler for the Basic Information Matter cluster.
454#[derive(Clone, Debug)]
455#[cfg_attr(feature = "defmt", derive(defmt::Format))]
456pub struct BasicInfoHandler(Dataver);
457
458impl BasicInfoHandler {
459    /// Create a new instance of `BasicInfoHandler` with the given `Dataver`
460    pub fn new(dataver: Dataver) -> Self {
461        Self(dataver)
462    }
463
464    /// Adapt the handler instance to the generic `rs-matter` `Handler` trait
465    pub const fn adapt(self) -> HandlerAdaptor<Self> {
466        HandlerAdaptor(self)
467    }
468
469    fn config<'a>(exchange: &'a Exchange) -> &'a BasicInfoConfig<'a> {
470        exchange.matter().dev_det()
471    }
472
473    fn with_settings<F, R>(exchange: &Exchange, f: F) -> Result<R, Error>
474    where
475        F: FnOnce(&mut BasicInfoSettings) -> Result<R, Error>,
476    {
477        exchange.with_state(|state| f(&mut state.basic_info_settings))
478    }
479}
480
481impl ClusterHandler for BasicInfoHandler {
482    const CLUSTER: Cluster<'static> = FULL_CLUSTER
483        // Hide `Reachable` (TODO) and `ConfigurationVersion` from the default
484        // metadata. `ConfigurationVersion` is provisional in Matter 1.5 and
485        // upstream's 1.5 dataset (CHIP commit faf4d09ad1, "Remove
486        // configuration version from 1.5 branch") explicitly excludes it from
487        // `BasicInformation`'s `AttributeList`. The
488        // `BasicInformation`/`BasicInfoSettings` plumbing for it stays in
489        // place — read handler, persisted settings field, and the
490        // `Matter::bump_configuration_version` / `InteractionModel::bump_configuration_version`
491        // entry points — so a user that supplies their own cluster metadata
492        // (i.e. one that drops `ConfigurationVersion` from `except!`) gets a
493        // working implementation out of the box.
494        .with_attrs(except!(
495            AttributeId::Reachable | AttributeId::ConfigurationVersion
496        ))
497        .with_cmds(with!());
498
499    fn dataver(&self) -> u32 {
500        self.0.get()
501    }
502
503    fn dataver_changed(&self) {
504        self.0.changed();
505    }
506
507    fn data_model_revision(&self, ctx: impl ReadContext) -> Result<u16, Error> {
508        Ok(Self::config(ctx.exchange()).data_model_revision)
509    }
510
511    fn vendor_id(&self, ctx: impl ReadContext) -> Result<u16, Error> {
512        Ok(Self::config(ctx.exchange()).vid)
513    }
514
515    fn vendor_name<P: TLVBuilderParent>(
516        &self,
517        ctx: impl ReadContext,
518        out: Utf8StrBuilder<P>,
519    ) -> Result<P, Error> {
520        out.set(Self::config(ctx.exchange()).vendor_name)
521    }
522
523    fn product_id(&self, ctx: impl ReadContext) -> Result<u16, Error> {
524        Ok(Self::config(ctx.exchange()).pid)
525    }
526
527    fn product_name<P: TLVBuilderParent>(
528        &self,
529        ctx: impl ReadContext,
530        out: Utf8StrBuilder<P>,
531    ) -> Result<P, Error> {
532        out.set(Self::config(ctx.exchange()).product_name)
533    }
534
535    fn hardware_version(&self, ctx: impl ReadContext) -> Result<u16, Error> {
536        Ok(Self::config(ctx.exchange()).hw_ver)
537    }
538
539    fn hardware_version_string<P: TLVBuilderParent>(
540        &self,
541        ctx: impl ReadContext,
542        out: Utf8StrBuilder<P>,
543    ) -> Result<P, Error> {
544        out.set(Self::config(ctx.exchange()).hw_ver_str)
545    }
546
547    fn software_version(&self, ctx: impl ReadContext) -> Result<u32, Error> {
548        Ok(Self::config(ctx.exchange()).sw_ver)
549    }
550
551    fn software_version_string<P: TLVBuilderParent>(
552        &self,
553        ctx: impl ReadContext,
554        out: Utf8StrBuilder<P>,
555    ) -> Result<P, Error> {
556        out.set(Self::config(ctx.exchange()).sw_ver_str)
557    }
558
559    fn node_label<P: TLVBuilderParent>(
560        &self,
561        ctx: impl ReadContext,
562        out: Utf8StrBuilder<P>,
563    ) -> Result<P, Error> {
564        Self::with_settings(ctx.exchange(), |settings| {
565            out.set(settings.node_label.as_str())
566        })
567    }
568
569    fn set_node_label(&self, ctx: impl WriteContext, label: &str) -> Result<(), Error> {
570        if label.len() > 32 {
571            return Err(ErrorCode::ConstraintError.into());
572        }
573
574        let mut persist = Persist::new(ctx.kv());
575
576        Self::with_settings(ctx.exchange(), |settings| {
577            settings.node_label.clear();
578            settings
579                .node_label
580                .push_str(label)
581                .map_err(|_| ErrorCode::ConstraintError)?;
582
583            persist.store_tlv(BASIC_INFO_KEY, &*settings)
584        })?;
585
586        persist.run()
587    }
588
589    fn location<P: TLVBuilderParent>(
590        &self,
591        ctx: impl ReadContext,
592        out: Utf8StrBuilder<P>,
593    ) -> Result<P, Error> {
594        Self::with_settings(ctx.exchange(), |settings| {
595            out.set(settings.location.as_ref().map_or("XX", |loc| loc.as_str()))
596        })
597    }
598
599    fn set_location(&self, ctx: impl WriteContext, location: &str) -> Result<(), Error> {
600        if location.len() != 2 {
601            return Err(ErrorCode::ConstraintError.into());
602        }
603
604        let mut persist = Persist::new(ctx.kv());
605
606        Self::with_settings(ctx.exchange(), |settings| {
607            settings.set_location(location);
608
609            persist.store_tlv(BASIC_INFO_KEY, &*settings)
610        })?;
611
612        persist.run()
613    }
614
615    fn capability_minima<P: TLVBuilderParent>(
616        &self,
617        ctx: impl ReadContext,
618        builder: CapabilityMinimaStructBuilder<P>,
619    ) -> Result<P, Error> {
620        let cm = Self::config(ctx.exchange()).capability_minima;
621
622        builder
623            .case_sessions_per_fabric(cm.case_sessions_per_fabric)?
624            .subscriptions_per_fabric(cm.subscriptions_per_fabric)?
625            .end()
626    }
627
628    fn specification_version(&self, ctx: impl ReadContext) -> Result<u32, Error> {
629        Ok(Self::config(ctx.exchange()).specification_version)
630    }
631
632    fn max_paths_per_invoke(&self, ctx: impl ReadContext) -> Result<u16, Error> {
633        Ok(Self::config(ctx.exchange()).max_paths_per_invoke)
634    }
635
636    fn configuration_version(&self, ctx: impl ReadContext) -> Result<u32, Error> {
637        // Non-volatile, runtime-mutable. Lives in `BasicInfoSettings`,
638        // bumped via `InteractionModel::bump_configuration_version`.
639        Self::with_settings(
640            ctx.exchange(),
641            |settings| Ok(settings.configuration_version),
642        )
643    }
644
645    fn handle_mfg_specific_ping(&self, _ctx: impl InvokeContext) -> Result<(), Error> {
646        Err(ErrorCode::CommandNotFound.into())
647    }
648
649    fn manufacturing_date<P: TLVBuilderParent>(
650        &self,
651        ctx: impl ReadContext,
652        builder: Utf8StrBuilder<P>,
653    ) -> Result<P, Error> {
654        builder.set(Self::config(ctx.exchange()).manufacturing_date)
655    }
656
657    fn part_number<P: TLVBuilderParent>(
658        &self,
659        ctx: impl ReadContext,
660        builder: Utf8StrBuilder<P>,
661    ) -> Result<P, Error> {
662        builder.set(Self::config(ctx.exchange()).part_number)
663    }
664
665    fn product_url<P: TLVBuilderParent>(
666        &self,
667        ctx: impl ReadContext,
668        builder: Utf8StrBuilder<P>,
669    ) -> Result<P, Error> {
670        builder.set(Self::config(ctx.exchange()).product_url)
671    }
672
673    fn product_label<P: TLVBuilderParent>(
674        &self,
675        ctx: impl ReadContext,
676        builder: Utf8StrBuilder<P>,
677    ) -> Result<P, Error> {
678        builder.set(Self::config(ctx.exchange()).product_label)
679    }
680
681    fn serial_number<P: TLVBuilderParent>(
682        &self,
683        ctx: impl ReadContext,
684        builder: Utf8StrBuilder<P>,
685    ) -> Result<P, Error> {
686        builder.set(Self::config(ctx.exchange()).serial_no)
687    }
688
689    fn local_config_disabled(&self, ctx: impl ReadContext) -> Result<bool, Error> {
690        Self::with_settings(
691            ctx.exchange(),
692            |settings| Ok(settings.local_config_disabled),
693        )
694    }
695
696    fn set_local_config_disabled(&self, ctx: impl WriteContext, value: bool) -> Result<(), Error> {
697        let mut persist = Persist::new(ctx.kv());
698
699        Self::with_settings(ctx.exchange(), |settings| {
700            settings.local_config_disabled = value;
701
702            persist.store_tlv(BASIC_INFO_KEY, &*settings)
703        })?;
704
705        persist.run()
706    }
707
708    fn unique_id<P: TLVBuilderParent>(
709        &self,
710        ctx: impl ReadContext,
711        builder: Utf8StrBuilder<P>,
712    ) -> Result<P, Error> {
713        builder.set(Self::config(ctx.exchange()).unique_id)
714    }
715
716    fn product_appearance<P: TLVBuilderParent>(
717        &self,
718        ctx: impl ReadContext,
719        builder: ProductAppearanceStructBuilder<P>,
720    ) -> Result<P, Error> {
721        let appearance = Self::config(ctx.exchange()).product_appearance;
722
723        builder
724            .finish(appearance.finish)?
725            .primary_color(Nullable::new(appearance.color))?
726            .end()
727    }
728}