1use core::num::NonZeroU8;
35
36use crate::crypto::Crypto;
37use crate::dm::{
38 ArrayAttributeRead, ArrayAttributeWrite, AttrChangeNotifier, Cluster, Dataver, HandlerContext,
39 InvokeContext, LifecycleOp, ReadContext, WriteContext,
40};
41use crate::dm::{AttrId, EndptId, NodeId};
42use crate::error::{Error, ErrorCode};
43use crate::fabric::MAX_FABRICS;
44use crate::persist::{KvBlobStore, KvBlobStoreAccess, Persist, OTA_PROVIDERS_KEY};
45use crate::tlv::{FromTLV, Nullable, Octets, TLVArray, TLVBuilderParent, TLVElement, ToTLV};
46use crate::transport::exchange::Exchange;
47use crate::utils::cell::RefCell;
48use crate::utils::init::{init, Init};
49use crate::utils::storage::Vec;
50use crate::utils::sync::blocking::Mutex;
51use crate::utils::sync::Notification;
52use crate::with;
53use crate::Matter;
54
55pub use crate::dm::clusters::decl::ota_software_update_requestor::*;
56
57use crate::dm::clusters::decl::ota_software_update_provider::{
58 ApplyUpdateActionEnum, DownloadProtocolEnum, OtaSoftwareUpdateProviderClient,
59 QueryImageResponse,
60};
61use crate::dm::clusters::ota_prov::OtaApplyOutcome;
62
63const ANNOUNCED_PROVIDERS: usize = 4;
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, FromTLV, ToTLV)]
70#[cfg_attr(feature = "defmt", derive(defmt::Format))]
71pub struct Provider {
72 pub fab_idx: NonZeroU8,
74 pub node_id: NodeId,
76 pub endpoint: EndptId,
78}
79
80impl Provider {
81 pub async fn query<C, F, R>(
105 &self,
106 matter: &Matter<'_>,
107 crypto: C,
108 protocols: &[DownloadProtocolEnum],
109 current_version: Option<u32>,
110 requestor_can_consent: bool,
111 f: F,
112 ) -> Result<R, Error>
113 where
114 C: Crypto,
115 F: FnOnce(&QueryImageResponse<'_>) -> Result<R, Error>,
116 {
117 let dev = matter.dev_det();
118 let version = current_version.unwrap_or(dev.sw_ver);
119
120 let location = matter.with_state(|state| state.basic_info_settings.location.clone());
124 let location = location.as_deref();
125
126 let exchange = Exchange::initiate(matter, crypto, self.fab_idx, self.node_id).await?;
127
128 let handle = exchange
129 .ota_software_update_provider()
130 .query_image(self.endpoint, |b| {
131 let mut protos = b
132 .vendor_id(dev.vid)?
133 .product_id(dev.pid)?
134 .software_version(version)?
135 .protocols_supported()?;
136 for proto in protocols {
137 protos = protos.push(proto)?;
138 }
139 protos
140 .end()?
141 .hardware_version(Some(dev.hw_ver))?
142 .location(location)?
143 .requestor_can_consent(Some(requestor_can_consent))?
144 .metadata_for_provider(None)?
145 .end()
146 })
147 .await?;
148
149 let result = {
152 let response = handle.response()?;
153 f(&response)
154 };
155
156 handle.complete().await?;
157
158 result
159 }
160
161 pub async fn apply_update<C>(
174 &self,
175 matter: &Matter<'_>,
176 crypto: C,
177 update_token: &[u8],
178 new_version: u32,
179 ) -> Result<OtaApplyOutcome, Error>
180 where
181 C: Crypto,
182 {
183 let exchange = Exchange::initiate(matter, crypto, self.fab_idx, self.node_id).await?;
184
185 let handle = exchange
186 .ota_software_update_provider()
187 .apply_update_request(self.endpoint, |b| {
188 b.update_token(Octets(update_token))?
189 .new_version(new_version)?
190 .end()
191 })
192 .await?;
193
194 let outcome = {
195 let response = handle.response()?;
196 let delay_secs = response.delayed_action_time()?;
197
198 match response.action()? {
199 ApplyUpdateActionEnum::Proceed => OtaApplyOutcome::Proceed { delay_secs },
200 ApplyUpdateActionEnum::AwaitNextAction => OtaApplyOutcome::Await { delay_secs },
201 ApplyUpdateActionEnum::Discontinue => OtaApplyOutcome::Discontinue,
202 }
203 };
204
205 handle.complete().await?;
206
207 Ok(outcome)
208 }
209
210 pub async fn notify_applied<C>(
214 &self,
215 matter: &Matter<'_>,
216 crypto: C,
217 update_token: &[u8],
218 software_version: u32,
219 ) -> Result<(), Error>
220 where
221 C: Crypto,
222 {
223 let exchange = Exchange::initiate(matter, crypto, self.fab_idx, self.node_id).await?;
224
225 exchange
226 .ota_software_update_provider()
227 .notify_update_applied(self.endpoint, |b| {
228 b.update_token(Octets(update_token))?
229 .software_version(software_version)?
230 .end()
231 })
232 .await
233 }
234}
235
236pub struct Providers {
245 state: Mutex<RefCell<ProvidersState>>,
246 changed: Notification,
247}
248
249struct ProvidersState {
250 default: Vec<Provider, MAX_FABRICS>,
252 announced: Vec<Provider, ANNOUNCED_PROVIDERS>,
254}
255
256impl ProvidersState {
257 fn init() -> impl Init<Self> {
258 init!(Self {
259 default <- Vec::init(),
260 announced <- Vec::init(),
261 })
262 }
263}
264
265impl Providers {
266 pub const fn new() -> Self {
268 Self {
269 state: Mutex::new(RefCell::new(ProvidersState {
270 default: Vec::new(),
271 announced: Vec::new(),
272 })),
273 changed: Notification::new(),
274 }
275 }
276
277 pub fn init() -> impl Init<Self> {
279 init!(Self {
280 state <- Mutex::init(RefCell::init(ProvidersState::init())),
281 changed <- Notification::init(),
282 })
283 }
284
285 pub fn load_persist<S: KvBlobStore>(&self, mut store: S, buf: &mut [u8]) -> Result<(), Error> {
290 let Some(data) = store.load(OTA_PROVIDERS_KEY, buf)? else {
291 self.state.lock(|cell| cell.borrow_mut().default.clear());
292 return Ok(());
293 };
294
295 let loaded = Vec::<Provider, MAX_FABRICS>::from_tlv(&TLVElement::new(data))?;
296 self.state.lock(|cell| cell.borrow_mut().default = loaded);
297
298 info!("Loaded OTA provider entries from storage");
299
300 Ok(())
301 }
302
303 pub fn reset_persist<S: KvBlobStore>(&self, mut store: S, buf: &mut [u8]) -> Result<(), Error> {
310 self.state.lock(|cell| {
311 let mut state = cell.borrow_mut();
312
313 state.default.clear();
314 state.announced.clear();
315 });
316
317 store.remove(OTA_PROVIDERS_KEY, buf)
318 }
319
320 pub fn remove_for_fabric(&self, fab_idx: NonZeroU8) -> bool {
328 let removed = self.state.lock(|cell| {
329 let mut state = cell.borrow_mut();
330 let before = state.default.len() + state.announced.len();
331
332 state.default.retain(|provider| provider.fab_idx != fab_idx);
333 state
334 .announced
335 .retain(|provider| provider.fab_idx != fab_idx);
336
337 state.default.len() + state.announced.len() < before
338 });
339
340 if removed {
341 self.changed.notify();
342 }
343
344 removed
345 }
346
347 fn store_persist<C: HandlerContext>(&self, ctx: &C) -> Result<(), Error> {
349 let mut persist = Persist::new(ctx.kv());
350
351 self.state.lock(|cell| {
352 let state = cell.borrow();
353 persist.store_tlv(OTA_PROVIDERS_KEY, &state.default)
354 })?;
355
356 persist.run()
357 }
358
359 pub fn len(&self) -> usize {
361 self.state.lock(|cell| cell.borrow().default.len())
362 }
363
364 pub fn is_empty(&self) -> bool {
366 self.len() == 0
367 }
368
369 pub fn get(&self, index: usize) -> Option<Provider> {
372 self.state
373 .lock(|cell| cell.borrow().default.get(index).copied())
374 }
375
376 pub fn announced_len(&self) -> usize {
378 self.state.lock(|cell| cell.borrow().announced.len())
379 }
380
381 pub fn announced(&self, index: usize) -> Option<Provider> {
383 self.state
384 .lock(|cell| cell.borrow().announced.get(index).copied())
385 }
386
387 pub fn clear_announced(&self) {
389 self.state.lock(|cell| cell.borrow_mut().announced.clear());
390 }
391
392 pub fn take_announced(&self) -> Vec<Provider, ANNOUNCED_PROVIDERS> {
400 self.state.lock(|cell| {
401 let mut state = cell.borrow_mut();
402 let taken = state.announced.clone();
403 state.announced.clear();
404 taken
405 })
406 }
407
408 pub async fn wait_changed(&self) {
411 self.changed.wait().await;
412 }
413
414 fn replace_default<C: WriteContext>(
417 &self,
418 ctx: &C,
419 fab_idx: NonZeroU8,
420 provider: Option<Provider>,
421 ) -> Result<(), Error> {
422 self.state.lock(|cell| {
423 let mut state = cell.borrow_mut();
424 state.default.retain(|p| p.fab_idx != fab_idx);
425 if let Some(provider) = provider {
426 state
427 .default
428 .push(provider)
429 .map_err(|_| ErrorCode::ResourceExhausted)?;
430 }
431 Ok::<_, Error>(())
432 })?;
433
434 self.changed.notify();
435
436 self.store_persist(ctx)
437 }
438
439 fn add_default<C: WriteContext>(
442 &self,
443 ctx: &C,
444 fab_idx: NonZeroU8,
445 provider: Provider,
446 ) -> Result<(), Error> {
447 self.state.lock(|cell| {
448 let mut state = cell.borrow_mut();
449 if state.default.iter().any(|p| p.fab_idx == fab_idx) {
450 return Err(ErrorCode::ConstraintError.into());
451 }
452 state
453 .default
454 .push(provider)
455 .map_err(|_| ErrorCode::ResourceExhausted)?;
456 Ok::<_, Error>(())
457 })?;
458
459 self.changed.notify();
460
461 self.store_persist(ctx)
462 }
463
464 fn add_announced(&self, provider: Provider) {
467 self.state.lock(|cell| {
468 let mut state = cell.borrow_mut();
469 state
470 .announced
471 .retain(|p| !(p.fab_idx == provider.fab_idx && p.node_id == provider.node_id));
472 if state.announced.is_full() {
473 state.announced.remove(0);
474 }
475 let _ = state.announced.push(provider);
477 });
478
479 self.changed.notify();
480 }
481
482 fn render<P: TLVBuilderParent>(
484 &self,
485 fab_filter: Option<NonZeroU8>,
486 builder: ArrayAttributeRead<ProviderLocationArrayBuilder<P>, ProviderLocationBuilder<P>>,
487 ) -> Result<P, Error> {
488 self.state.lock(|cell| {
489 let state = cell.borrow();
490 let mut iter = state
491 .default
492 .iter()
493 .filter(|p| fab_filter.is_none_or(|f| p.fab_idx == f));
494
495 match builder {
496 ArrayAttributeRead::ReadAll(mut array) => {
497 for p in iter {
498 array = array
499 .push()?
500 .provider_node_id(p.node_id)?
501 .endpoint(p.endpoint)?
502 .fabric_index(Some(p.fab_idx.get()))?
503 .end()?;
504 }
505 array.end()
506 }
507 ArrayAttributeRead::ReadOne(index, item) => {
508 let Some(p) = iter.nth(index as usize) else {
509 return Err(ErrorCode::ConstraintError.into());
510 };
511 item.provider_node_id(p.node_id)?
512 .endpoint(p.endpoint)?
513 .fabric_index(Some(p.fab_idx.get()))?
514 .end()
515 }
516 ArrayAttributeRead::ReadNone(array) => array.end(),
517 }
518 })
519 }
520}
521
522impl Default for Providers {
523 fn default() -> Self {
524 Self::new()
525 }
526}
527
528pub struct OtaState {
542 endpoint_id: EndptId,
543 reported: Mutex<RefCell<Reported>>,
544}
545
546struct Reported {
547 update_state: UpdateStateEnum,
548 progress: Option<u8>,
549 update_possible: bool,
550}
551
552impl OtaState {
553 pub const fn new(endpoint_id: EndptId) -> Self {
557 Self {
558 endpoint_id,
559 reported: Mutex::new(RefCell::new(Reported {
560 update_state: UpdateStateEnum::Idle,
561 progress: None,
562 update_possible: true,
563 })),
564 }
565 }
566
567 pub fn set_update_possible(&self, notifier: &dyn AttrChangeNotifier, possible: bool) {
570 self.reported
571 .lock(|cell| cell.borrow_mut().update_possible = possible);
572
573 self.notify(notifier, AttributeId::UpdatePossible as _);
574 }
575
576 fn update_state(&self) -> UpdateStateEnum {
577 self.reported.lock(|cell| cell.borrow().update_state)
578 }
579
580 fn progress(&self) -> Option<u8> {
581 self.reported.lock(|cell| cell.borrow().progress)
582 }
583
584 fn update_possible(&self) -> bool {
585 self.reported.lock(|cell| cell.borrow().update_possible)
586 }
587
588 fn report(
590 &self,
591 notifier: &dyn AttrChangeNotifier,
592 state: UpdateStateEnum,
593 progress: Option<u8>,
594 ) {
595 self.reported.lock(|cell| {
596 let mut reported = cell.borrow_mut();
597 reported.update_state = state;
598 reported.progress = progress;
599 });
600
601 notifier.notify_cluster_changed(self.endpoint_id, FULL_CLUSTER.id);
605 }
606
607 fn notify(&self, notifier: &dyn AttrChangeNotifier, attr_id: AttrId) {
609 notifier.notify_attr_changed(self.endpoint_id, FULL_CLUSTER.id, attr_id);
610 }
611
612 pub fn initiate_update<'a>(&'a self, notifier: &'a dyn AttrChangeNotifier) -> OtaUpdate<'a> {
617 OtaUpdate {
618 state: self,
619 notifier,
620 done: false,
621 }
622 }
623}
624
625pub struct OtaUpdate<'a> {
632 state: &'a OtaState,
633 notifier: &'a dyn AttrChangeNotifier,
634 done: bool,
635}
636
637impl OtaUpdate<'_> {
638 pub fn querying(&self) {
640 self.state
641 .report(self.notifier, UpdateStateEnum::Querying, None);
642 }
643
644 pub fn downloading(&self, percent: Option<u8>) {
646 self.state
647 .report(self.notifier, UpdateStateEnum::Downloading, percent);
648 }
649
650 pub fn applying(&self) {
652 self.state
653 .report(self.notifier, UpdateStateEnum::Applying, None);
654 }
655
656 pub fn report(&self, state: UpdateStateEnum, progress: Option<u8>) {
658 self.state.report(self.notifier, state, progress);
659 }
660
661 pub fn complete(mut self) {
663 self.state
664 .report(self.notifier, UpdateStateEnum::Idle, None);
665 self.done = true;
666 }
667}
668
669impl Drop for OtaUpdate<'_> {
670 fn drop(&mut self) {
671 if !self.done {
672 self.state
674 .report(self.notifier, UpdateStateEnum::Idle, None);
675 }
676 }
677}
678
679pub struct OtaRequestorHandler<'a> {
681 dataver: Dataver,
682 providers: &'a Providers,
683 state: &'a OtaState,
684}
685
686impl<'a> OtaRequestorHandler<'a> {
687 pub const fn new(dataver: Dataver, providers: &'a Providers, state: &'a OtaState) -> Self {
690 Self {
691 dataver,
692 providers,
693 state,
694 }
695 }
696
697 pub const fn adapt(self) -> HandlerAdaptor<Self> {
699 HandlerAdaptor(self)
700 }
701}
702
703impl ClusterHandler for OtaRequestorHandler<'_> {
704 const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required));
705
706 fn dataver(&self) -> u32 {
707 self.dataver.get()
708 }
709
710 fn dataver_changed(&self) {
711 self.dataver.changed();
712 }
713
714 fn lifecycle(&self, ctx: impl HandlerContext, op: LifecycleOp) -> Result<(), Error> {
715 match op {
716 LifecycleOp::Startup => ctx
717 .kv()
718 .access(|store, buf| self.providers.load_persist(store, buf)),
719 LifecycleOp::FactoryReset => ctx
720 .kv()
721 .access(|store, buf| self.providers.reset_persist(store, buf)),
722 LifecycleOp::FabricRemoval { fab_idx } => {
723 if self.providers.remove_for_fabric(fab_idx) {
724 self.providers.store_persist(&ctx)?;
725 }
726
727 Ok(())
728 }
729 }
730 }
731
732 fn default_ota_providers<P: TLVBuilderParent>(
733 &self,
734 ctx: impl ReadContext,
735 builder: ArrayAttributeRead<ProviderLocationArrayBuilder<P>, ProviderLocationBuilder<P>>,
736 ) -> Result<P, Error> {
737 let attr = ctx.attr();
738 let fab_filter = if attr.fab_filter {
739 Some(NonZeroU8::new(attr.fab_idx).ok_or(ErrorCode::UnsupportedAccess)?)
740 } else {
741 None
742 };
743
744 self.providers.render(fab_filter, builder)
745 }
746
747 fn update_possible(&self, _ctx: impl ReadContext) -> Result<bool, Error> {
748 Ok(self.state.update_possible())
749 }
750
751 fn update_state(&self, _ctx: impl ReadContext) -> Result<UpdateStateEnum, Error> {
752 Ok(self.state.update_state())
753 }
754
755 fn update_state_progress(&self, _ctx: impl ReadContext) -> Result<Nullable<u8>, Error> {
756 Ok(self
757 .state
758 .progress()
759 .map(Nullable::some)
760 .unwrap_or_else(Nullable::none))
761 }
762
763 fn set_default_ota_providers(
764 &self,
765 ctx: impl WriteContext,
766 value: ArrayAttributeWrite<TLVArray<'_, ProviderLocation<'_>>, ProviderLocation<'_>>,
767 ) -> Result<(), Error> {
768 let fab_idx = NonZeroU8::new(ctx.attr().fab_idx).ok_or(ErrorCode::UnsupportedAccess)?;
770
771 let to_provider = |loc: &ProviderLocation<'_>| -> Result<Provider, Error> {
772 Ok(Provider {
773 fab_idx,
774 node_id: loc.provider_node_id()?,
775 endpoint: loc.endpoint()?,
776 })
777 };
778
779 match value {
780 ArrayAttributeWrite::Replace(list) => {
783 let mut iter = list.iter();
784 let first = iter.next().transpose()?;
785 if iter.next().is_some() {
786 return Err(ErrorCode::ConstraintError.into());
787 }
788
789 let parsed = first.map(|loc| to_provider(&loc)).transpose()?;
792 self.providers.replace_default(&ctx, fab_idx, parsed)?;
793 }
794 ArrayAttributeWrite::Add(loc) => {
797 self.providers
798 .add_default(&ctx, fab_idx, to_provider(&loc)?)?;
799 }
800 ArrayAttributeWrite::Update(_, _) | ArrayAttributeWrite::Remove(_) => {
803 return Err(ErrorCode::InvalidAction.into());
804 }
805 }
806
807 ctx.notify_changed();
809
810 Ok(())
811 }
812
813 fn handle_announce_ota_provider(
814 &self,
815 ctx: impl InvokeContext,
816 request: AnnounceOTAProviderRequest<'_>,
817 ) -> Result<(), Error> {
818 let fab_idx = NonZeroU8::new(ctx.cmd().fab_idx).ok_or(ErrorCode::UnsupportedAccess)?;
819
820 let provider = Provider {
821 fab_idx,
822 node_id: request.provider_node_id()?,
823 endpoint: request.endpoint()?,
824 };
825
826 self.providers.add_announced(provider);
829
830 Ok(())
831 }
832}
833
834pub fn parse_bdx_url(url: &str) -> Result<(NodeId, &str), Error> {
838 let rest = url.strip_prefix("bdx://").ok_or(ErrorCode::InvalidData)?;
839 let (node, fd) = rest.split_once('/').ok_or(ErrorCode::InvalidData)?;
840 if fd.is_empty() {
841 return Err(ErrorCode::InvalidData.into());
843 }
844 let node_id = u64::from_str_radix(node, 16).map_err(|_| ErrorCode::InvalidData)?;
845
846 Ok((node_id, fd))
847}
848
849#[cfg(test)]
850mod tests {
851 use super::*;
852
853 #[test]
854 fn parse_bdx_url_extracts_node_and_fd() {
855 let (node, fd) = parse_bdx_url("bdx://00112233AABBCCDD/my-firmware.ota").unwrap();
856 assert_eq!(node, 0x0011_2233_AABB_CCDD);
857 assert_eq!(fd, "my-firmware.ota");
858
859 assert!(parse_bdx_url("https://example.com/x").is_err());
860 assert!(parse_bdx_url("bdx://nodeid-no-slash").is_err());
861 assert!(parse_bdx_url("bdx://zzzz/fd").is_err());
862 }
863
864 #[test]
865 fn announced_dedup_evict_and_clear() {
866 let providers = Providers::new();
867 let provider = |node| Provider {
868 fab_idx: NonZeroU8::new(1).unwrap(),
869 node_id: node,
870 endpoint: 0,
871 };
872
873 providers.add_announced(provider(0xaa));
875 providers.add_announced(provider(0xaa));
876 assert_eq!(providers.announced_len(), 1);
877
878 for n in 0..(ANNOUNCED_PROVIDERS as u64 + 2) {
880 providers.add_announced(provider(0x100 + n));
881 }
882 assert_eq!(providers.announced_len(), ANNOUNCED_PROVIDERS);
883
884 providers.clear_announced();
885 assert_eq!(providers.announced_len(), 0);
886 }
887}