1use core::num::NonZeroU8;
35
36use crate::crypto::Crypto;
37use crate::dm::{
38 ArrayAttributeRead, ArrayAttributeWrite, AttrChangeNotifier, Cluster, Dataver, InvokeContext,
39 ReadContext, WriteContext,
40};
41use crate::dm::{AttrId, EndptId, NodeId};
42use crate::error::{Error, ErrorCode};
43use crate::fabric::MAX_FABRICS;
44use crate::persist::{KvBlobStore, 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::new(),
282 })
283 }
284
285 pub async fn load_persist<S: KvBlobStore>(
288 &self,
289 mut store: S,
290 buf: &mut [u8],
291 ) -> Result<(), Error> {
292 let Some(data) = store.load(OTA_PROVIDERS_KEY, buf)? else {
293 self.state.lock(|cell| cell.borrow_mut().default.clear());
294 return Ok(());
295 };
296
297 let loaded = Vec::<Provider, MAX_FABRICS>::from_tlv(&TLVElement::new(data))?;
298 self.state.lock(|cell| cell.borrow_mut().default = loaded);
299
300 info!("Loaded OTA provider entries from storage");
301
302 Ok(())
303 }
304
305 fn store_persist<C: WriteContext>(&self, ctx: &C) -> Result<(), Error> {
307 let mut persist = Persist::new(ctx.kv());
308
309 self.state.lock(|cell| {
310 let state = cell.borrow();
311 persist.store_tlv(OTA_PROVIDERS_KEY, &state.default)
312 })?;
313
314 persist.run()
315 }
316
317 pub fn len(&self) -> usize {
319 self.state.lock(|cell| cell.borrow().default.len())
320 }
321
322 pub fn is_empty(&self) -> bool {
324 self.len() == 0
325 }
326
327 pub fn get(&self, index: usize) -> Option<Provider> {
330 self.state
331 .lock(|cell| cell.borrow().default.get(index).copied())
332 }
333
334 pub fn announced_len(&self) -> usize {
336 self.state.lock(|cell| cell.borrow().announced.len())
337 }
338
339 pub fn announced(&self, index: usize) -> Option<Provider> {
341 self.state
342 .lock(|cell| cell.borrow().announced.get(index).copied())
343 }
344
345 pub fn clear_announced(&self) {
347 self.state.lock(|cell| cell.borrow_mut().announced.clear());
348 }
349
350 pub fn take_announced(&self) -> Vec<Provider, ANNOUNCED_PROVIDERS> {
358 self.state.lock(|cell| {
359 let mut state = cell.borrow_mut();
360 let taken = state.announced.clone();
361 state.announced.clear();
362 taken
363 })
364 }
365
366 pub async fn wait_changed(&self) {
369 self.changed.wait().await;
370 }
371
372 fn replace_default<C: WriteContext>(
375 &self,
376 ctx: &C,
377 fab_idx: NonZeroU8,
378 provider: Option<Provider>,
379 ) -> Result<(), Error> {
380 self.state.lock(|cell| {
381 let mut state = cell.borrow_mut();
382 state.default.retain(|p| p.fab_idx != fab_idx);
383 if let Some(provider) = provider {
384 state
385 .default
386 .push(provider)
387 .map_err(|_| ErrorCode::ResourceExhausted)?;
388 }
389 Ok::<_, Error>(())
390 })?;
391
392 self.changed.notify();
393
394 self.store_persist(ctx)
395 }
396
397 fn add_default<C: WriteContext>(
400 &self,
401 ctx: &C,
402 fab_idx: NonZeroU8,
403 provider: Provider,
404 ) -> Result<(), Error> {
405 self.state.lock(|cell| {
406 let mut state = cell.borrow_mut();
407 if state.default.iter().any(|p| p.fab_idx == fab_idx) {
408 return Err(ErrorCode::ConstraintError.into());
409 }
410 state
411 .default
412 .push(provider)
413 .map_err(|_| ErrorCode::ResourceExhausted)?;
414 Ok::<_, Error>(())
415 })?;
416
417 self.changed.notify();
418
419 self.store_persist(ctx)
420 }
421
422 fn add_announced(&self, provider: Provider) {
425 self.state.lock(|cell| {
426 let mut state = cell.borrow_mut();
427 state
428 .announced
429 .retain(|p| !(p.fab_idx == provider.fab_idx && p.node_id == provider.node_id));
430 if state.announced.is_full() {
431 state.announced.remove(0);
432 }
433 let _ = state.announced.push(provider);
435 });
436
437 self.changed.notify();
438 }
439
440 fn render<P: TLVBuilderParent>(
442 &self,
443 fab_filter: Option<NonZeroU8>,
444 builder: ArrayAttributeRead<ProviderLocationArrayBuilder<P>, ProviderLocationBuilder<P>>,
445 ) -> Result<P, Error> {
446 self.state.lock(|cell| {
447 let state = cell.borrow();
448 let mut iter = state
449 .default
450 .iter()
451 .filter(|p| fab_filter.is_none_or(|f| p.fab_idx == f));
452
453 match builder {
454 ArrayAttributeRead::ReadAll(mut array) => {
455 for p in iter {
456 array = array
457 .push()?
458 .provider_node_id(p.node_id)?
459 .endpoint(p.endpoint)?
460 .fabric_index(Some(p.fab_idx.get()))?
461 .end()?;
462 }
463 array.end()
464 }
465 ArrayAttributeRead::ReadOne(index, item) => {
466 let Some(p) = iter.nth(index as usize) else {
467 return Err(ErrorCode::ConstraintError.into());
468 };
469 item.provider_node_id(p.node_id)?
470 .endpoint(p.endpoint)?
471 .fabric_index(Some(p.fab_idx.get()))?
472 .end()
473 }
474 ArrayAttributeRead::ReadNone(array) => array.end(),
475 }
476 })
477 }
478}
479
480impl Default for Providers {
481 fn default() -> Self {
482 Self::new()
483 }
484}
485
486pub struct OtaState {
500 endpoint_id: EndptId,
501 reported: Mutex<RefCell<Reported>>,
502}
503
504struct Reported {
505 update_state: UpdateStateEnum,
506 progress: Option<u8>,
507 update_possible: bool,
508}
509
510impl OtaState {
511 pub const fn new(endpoint_id: EndptId) -> Self {
515 Self {
516 endpoint_id,
517 reported: Mutex::new(RefCell::new(Reported {
518 update_state: UpdateStateEnum::Idle,
519 progress: None,
520 update_possible: true,
521 })),
522 }
523 }
524
525 pub fn set_update_possible(&self, notifier: &dyn AttrChangeNotifier, possible: bool) {
528 self.reported
529 .lock(|cell| cell.borrow_mut().update_possible = possible);
530
531 self.notify(notifier, AttributeId::UpdatePossible as _);
532 }
533
534 fn update_state(&self) -> UpdateStateEnum {
535 self.reported.lock(|cell| cell.borrow().update_state)
536 }
537
538 fn progress(&self) -> Option<u8> {
539 self.reported.lock(|cell| cell.borrow().progress)
540 }
541
542 fn update_possible(&self) -> bool {
543 self.reported.lock(|cell| cell.borrow().update_possible)
544 }
545
546 fn report(
548 &self,
549 notifier: &dyn AttrChangeNotifier,
550 state: UpdateStateEnum,
551 progress: Option<u8>,
552 ) {
553 self.reported.lock(|cell| {
554 let mut reported = cell.borrow_mut();
555 reported.update_state = state;
556 reported.progress = progress;
557 });
558
559 notifier.notify_cluster_changed(self.endpoint_id, FULL_CLUSTER.id);
563 }
564
565 fn notify(&self, notifier: &dyn AttrChangeNotifier, attr_id: AttrId) {
567 notifier.notify_attr_changed(self.endpoint_id, FULL_CLUSTER.id, attr_id);
568 }
569
570 pub fn initiate_update<'a>(&'a self, notifier: &'a dyn AttrChangeNotifier) -> OtaUpdate<'a> {
575 OtaUpdate {
576 state: self,
577 notifier,
578 done: false,
579 }
580 }
581}
582
583pub struct OtaUpdate<'a> {
590 state: &'a OtaState,
591 notifier: &'a dyn AttrChangeNotifier,
592 done: bool,
593}
594
595impl OtaUpdate<'_> {
596 pub fn querying(&self) {
598 self.state
599 .report(self.notifier, UpdateStateEnum::Querying, None);
600 }
601
602 pub fn downloading(&self, percent: Option<u8>) {
604 self.state
605 .report(self.notifier, UpdateStateEnum::Downloading, percent);
606 }
607
608 pub fn applying(&self) {
610 self.state
611 .report(self.notifier, UpdateStateEnum::Applying, None);
612 }
613
614 pub fn report(&self, state: UpdateStateEnum, progress: Option<u8>) {
616 self.state.report(self.notifier, state, progress);
617 }
618
619 pub fn complete(mut self) {
621 self.state
622 .report(self.notifier, UpdateStateEnum::Idle, None);
623 self.done = true;
624 }
625}
626
627impl Drop for OtaUpdate<'_> {
628 fn drop(&mut self) {
629 if !self.done {
630 self.state
632 .report(self.notifier, UpdateStateEnum::Idle, None);
633 }
634 }
635}
636
637pub struct OtaRequestorHandler<'a> {
639 dataver: Dataver,
640 providers: &'a Providers,
641 state: &'a OtaState,
642}
643
644impl<'a> OtaRequestorHandler<'a> {
645 pub const fn new(dataver: Dataver, providers: &'a Providers, state: &'a OtaState) -> Self {
648 Self {
649 dataver,
650 providers,
651 state,
652 }
653 }
654
655 pub const fn adapt(self) -> HandlerAdaptor<Self> {
657 HandlerAdaptor(self)
658 }
659}
660
661impl ClusterHandler for OtaRequestorHandler<'_> {
662 const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required));
663
664 fn dataver(&self) -> u32 {
665 self.dataver.get()
666 }
667
668 fn dataver_changed(&self) {
669 self.dataver.changed();
670 }
671
672 fn default_ota_providers<P: TLVBuilderParent>(
673 &self,
674 ctx: impl ReadContext,
675 builder: ArrayAttributeRead<ProviderLocationArrayBuilder<P>, ProviderLocationBuilder<P>>,
676 ) -> Result<P, Error> {
677 let attr = ctx.attr();
678 let fab_filter = if attr.fab_filter {
679 Some(NonZeroU8::new(attr.fab_idx).ok_or(ErrorCode::UnsupportedAccess)?)
680 } else {
681 None
682 };
683
684 self.providers.render(fab_filter, builder)
685 }
686
687 fn update_possible(&self, _ctx: impl ReadContext) -> Result<bool, Error> {
688 Ok(self.state.update_possible())
689 }
690
691 fn update_state(&self, _ctx: impl ReadContext) -> Result<UpdateStateEnum, Error> {
692 Ok(self.state.update_state())
693 }
694
695 fn update_state_progress(&self, _ctx: impl ReadContext) -> Result<Nullable<u8>, Error> {
696 Ok(self
697 .state
698 .progress()
699 .map(Nullable::some)
700 .unwrap_or_else(Nullable::none))
701 }
702
703 fn set_default_ota_providers(
704 &self,
705 ctx: impl WriteContext,
706 value: ArrayAttributeWrite<TLVArray<'_, ProviderLocation<'_>>, ProviderLocation<'_>>,
707 ) -> Result<(), Error> {
708 let fab_idx = NonZeroU8::new(ctx.attr().fab_idx).ok_or(ErrorCode::UnsupportedAccess)?;
710
711 let to_provider = |loc: &ProviderLocation<'_>| -> Result<Provider, Error> {
712 Ok(Provider {
713 fab_idx,
714 node_id: loc.provider_node_id()?,
715 endpoint: loc.endpoint()?,
716 })
717 };
718
719 match value {
720 ArrayAttributeWrite::Replace(list) => {
723 let mut iter = list.iter();
724 let first = iter.next().transpose()?;
725 if iter.next().is_some() {
726 return Err(ErrorCode::ConstraintError.into());
727 }
728
729 let parsed = first.map(|loc| to_provider(&loc)).transpose()?;
732 self.providers.replace_default(&ctx, fab_idx, parsed)?;
733 }
734 ArrayAttributeWrite::Add(loc) => {
737 self.providers
738 .add_default(&ctx, fab_idx, to_provider(&loc)?)?;
739 }
740 ArrayAttributeWrite::Update(_, _) | ArrayAttributeWrite::Remove(_) => {
743 return Err(ErrorCode::InvalidAction.into());
744 }
745 }
746
747 ctx.notify_changed();
749
750 Ok(())
751 }
752
753 fn handle_announce_ota_provider(
754 &self,
755 ctx: impl InvokeContext,
756 request: AnnounceOTAProviderRequest<'_>,
757 ) -> Result<(), Error> {
758 let fab_idx = NonZeroU8::new(ctx.cmd().fab_idx).ok_or(ErrorCode::UnsupportedAccess)?;
759
760 let provider = Provider {
761 fab_idx,
762 node_id: request.provider_node_id()?,
763 endpoint: request.endpoint()?,
764 };
765
766 self.providers.add_announced(provider);
769
770 Ok(())
771 }
772}
773
774pub fn parse_bdx_url(url: &str) -> Result<(NodeId, &str), Error> {
778 let rest = url.strip_prefix("bdx://").ok_or(ErrorCode::InvalidData)?;
779 let (node, fd) = rest.split_once('/').ok_or(ErrorCode::InvalidData)?;
780 if fd.is_empty() {
781 return Err(ErrorCode::InvalidData.into());
783 }
784 let node_id = u64::from_str_radix(node, 16).map_err(|_| ErrorCode::InvalidData)?;
785
786 Ok((node_id, fd))
787}
788
789#[cfg(test)]
790mod tests {
791 use super::*;
792
793 #[test]
794 fn parse_bdx_url_extracts_node_and_fd() {
795 let (node, fd) = parse_bdx_url("bdx://00112233AABBCCDD/my-firmware.ota").unwrap();
796 assert_eq!(node, 0x0011_2233_AABB_CCDD);
797 assert_eq!(fd, "my-firmware.ota");
798
799 assert!(parse_bdx_url("https://example.com/x").is_err());
800 assert!(parse_bdx_url("bdx://nodeid-no-slash").is_err());
801 assert!(parse_bdx_url("bdx://zzzz/fd").is_err());
802 }
803
804 #[test]
805 fn announced_dedup_evict_and_clear() {
806 let providers = Providers::new();
807 let provider = |node| Provider {
808 fab_idx: NonZeroU8::new(1).unwrap(),
809 node_id: node,
810 endpoint: 0,
811 };
812
813 providers.add_announced(provider(0xaa));
815 providers.add_announced(provider(0xaa));
816 assert_eq!(providers.announced_len(), 1);
817
818 for n in 0..(ANNOUNCED_PROVIDERS as u64 + 2) {
820 providers.add_announced(provider(0x100 + n));
821 }
822 assert_eq!(providers.announced_len(), ANNOUNCED_PROVIDERS);
823
824 providers.clear_announced();
825 assert_eq!(providers.announced_len(), 0);
826 }
827}