42
42
// TODO Expose a method onto the delegate to make that configurable.
43
43
constexpr uint32_t kMaxBdxBlockSize = 1024 ;
44
44
constexpr uint32_t kMaxBDXURILen = 256 ;
45
+
46
+ // Since the BDX timeout is 5 minutes and we are starting this after query image is available and before the BDX init comes,
47
+ // we just double the timeout to give enough time for the BDX init to come in a reasonable amount of time.
48
+ constexpr System::Clock::Timeout kBdxInitReceivedTimeout = System::Clock::Seconds16(10 * 60 );
49
+
50
+ // Time in seconds after which the requestor should retry calling query image if busy status is receieved
51
+ constexpr uint32_t kDelayedActionTimeSeconds = 120 ;
52
+
45
53
constexpr System::Clock::Timeout kBdxTimeout = System::Clock::Seconds16(5 * 60 ); // OTA Spec mandates >= 5 minutes
46
54
constexpr System::Clock::Timeout kBdxPollIntervalMs = System::Clock::Milliseconds32(50 );
47
55
constexpr bdx::TransferRole kBdxRole = bdx::TransferRole::kSender ;
@@ -89,13 +97,12 @@ CHIP_ERROR Shutdown()
89
97
VerifyOrReturnError (mExchangeMgr != nullptr , CHIP_ERROR_INCORRECT_STATE);
90
98
91
99
mExchangeMgr ->UnregisterUnsolicitedMessageHandlerForProtocol (Protocols::BDX::Id);
100
+ ResetState ();
92
101
93
102
mExchangeMgr = nullptr ;
94
103
mSystemLayer = nullptr ;
95
104
mDelegateNotificationQueue = nil ;
96
105
97
- ResetState ();
98
-
99
106
return CHIP_NO_ERROR;
100
107
}
101
108
@@ -120,7 +127,39 @@ void SetDelegate(id<MTROTAProviderDelegate> delegate, dispatch_queue_t delegateN
120
127
}
121
128
}
122
129
130
+ void ResetState ()
131
+ {
132
+ assertChipStackLockedByCurrentThread ();
133
+ if (mSystemLayer ) {
134
+ mSystemLayer ->CancelTimer (HandleBdxInitReceivedTimeoutExpired, this );
135
+ }
136
+ // TODO: Check if this can be removed. It seems like we can close the exchange context and reset transfer regardless.
137
+ if (!mInitialized ) {
138
+ return ;
139
+ }
140
+ Responder::ResetTransfer ();
141
+ ++mTransferGeneration ;
142
+ mFabricIndex .ClearValue ();
143
+ mNodeId .ClearValue ();
144
+
145
+ if (mExchangeCtx != nullptr ) {
146
+ mExchangeCtx ->Close ();
147
+ mExchangeCtx = nullptr ;
148
+ }
149
+
150
+ mInitialized = false ;
151
+ }
152
+
123
153
private:
154
+ /* *
155
+ * Timer callback called when we don't receive a BDX init within a reasonable time after a successful QueryImage response.
156
+ */
157
+ static void HandleBdxInitReceivedTimeoutExpired (chip::System::Layer * systemLayer, void * state)
158
+ {
159
+ VerifyOrReturn (state != nullptr );
160
+ static_cast <BdxOTASender *>(state)->ResetState ();
161
+ }
162
+
124
163
CHIP_ERROR OnMessageToSend (TransferSession::OutputEvent & event)
125
164
{
126
165
assertChipStackLockedByCurrentThread ();
@@ -137,12 +176,41 @@ CHIP_ERROR OnMessageToSend(TransferSession::OutputEvent & event)
137
176
}
138
177
139
178
auto & msgTypeData = event.msgTypeData ;
140
- return mExchangeCtx ->SendMessage (msgTypeData.ProtocolId , msgTypeData.MessageType , std::move (event.MsgData ), sendFlags);
179
+ // If there's an error sending the message, close the exchange and call ResetState.
180
+ // TODO: If we can remove the !mInitialized check in ResetState(), just calling ResetState() will suffice here.
181
+ CHIP_ERROR err
182
+ = mExchangeCtx ->SendMessage (msgTypeData.ProtocolId , msgTypeData.MessageType , std::move (event.MsgData ), sendFlags);
183
+ if (err != CHIP_NO_ERROR) {
184
+ mExchangeCtx ->Close ();
185
+ mExchangeCtx = nullptr ;
186
+ ResetState ();
187
+ } else if (event.msgTypeData .HasMessageType (Protocols::SecureChannel::MsgType::StatusReport)) {
188
+ // If the send was successful for a status report, since we are not expecting a response the exchange context is
189
+ // already closed. We need to null out the reference to avoid having a dangling pointer.
190
+ mExchangeCtx = nullptr ;
191
+ ResetState ();
192
+ }
193
+ return err;
194
+ }
195
+
196
+ bdx::StatusCode GetBdxStatusCodeFromChipError (CHIP_ERROR err)
197
+ {
198
+ if (err == CHIP_ERROR_INCORRECT_STATE) {
199
+ return bdx::StatusCode::kUnexpectedMessage ;
200
+ }
201
+ if (err == CHIP_ERROR_INVALID_ARGUMENT) {
202
+ return bdx::StatusCode::kBadMessageContents ;
203
+ }
204
+ return bdx::StatusCode::kUnknown ;
141
205
}
142
206
143
207
CHIP_ERROR OnTransferSessionBegin (TransferSession::OutputEvent & event)
144
208
{
145
209
assertChipStackLockedByCurrentThread ();
210
+ // Once we receive the BDX init, cancel the BDX Init timeout and start the BDX session
211
+ if (mSystemLayer ) {
212
+ mSystemLayer ->CancelTimer (HandleBdxInitReceivedTimeoutExpired, this );
213
+ }
146
214
147
215
VerifyOrReturnError (mFabricIndex .HasValue (), CHIP_ERROR_INCORRECT_STATE);
148
216
VerifyOrReturnError (mNodeId .HasValue (), CHIP_ERROR_INCORRECT_STATE);
@@ -169,8 +237,9 @@ CHIP_ERROR OnTransferSessionBegin(TransferSession::OutputEvent & event)
169
237
}
170
238
171
239
if (error != nil ) {
172
- LogErrorOnFailure ([MTRError errorToCHIPErrorCode: error]);
173
- LogErrorOnFailure (mTransfer .AbortTransfer (bdx::StatusCode::kUnknown ));
240
+ CHIP_ERROR err = [MTRError errorToCHIPErrorCode: error];
241
+ LogErrorOnFailure (err);
242
+ LogErrorOnFailure (mTransfer .AbortTransfer (GetBdxStatusCodeFromChipError (err)));
174
243
return ;
175
244
}
176
245
@@ -328,6 +397,9 @@ void HandleTransferSessionOutput(TransferSession::OutputEvent & event) override
328
397
switch (event.EventType ) {
329
398
case TransferSession::OutputEventType::kInitReceived :
330
399
err = OnTransferSessionBegin (event);
400
+ if (err != CHIP_NO_ERROR) {
401
+ LogErrorOnFailure (mTransfer .AbortTransfer (GetBdxStatusCodeFromChipError (err)));
402
+ }
331
403
break ;
332
404
case TransferSession::OutputEventType::kStatusReceived :
333
405
ChipLogError (BDX, " Got StatusReport %x" , static_cast <uint16_t >(event.statusData .statusCode ));
@@ -370,6 +442,14 @@ CHIP_ERROR ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId)
370
442
ResetState ();
371
443
}
372
444
445
+ // Start a timer to track whether we receive a BDX init after a successful query image in a reasonable amount of time
446
+ CHIP_ERROR err = mSystemLayer ->StartTimer (kBdxInitReceivedTimeout , HandleBdxInitReceivedTimeoutExpired, this );
447
+ LogErrorOnFailure (err);
448
+
449
+ // The caller of this method maps CHIP_ERROR to specific BDX Status Codes (see GetBdxStatusCodeFromChipError)
450
+ // and those are used by the BDX session to prepare the status report.
451
+ VerifyOrReturnError (err == CHIP_NO_ERROR, CHIP_ERROR_INCORRECT_STATE);
452
+
373
453
mFabricIndex .SetValue (fabricIndex);
374
454
mNodeId .SetValue (nodeId);
375
455
@@ -378,27 +458,6 @@ CHIP_ERROR ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId)
378
458
return CHIP_NO_ERROR;
379
459
}
380
460
381
- void ResetState ()
382
- {
383
- assertChipStackLockedByCurrentThread ();
384
-
385
- if (!mInitialized ) {
386
- return ;
387
- }
388
-
389
- Responder::ResetTransfer ();
390
- ++mTransferGeneration ;
391
- mFabricIndex .ClearValue ();
392
- mNodeId .ClearValue ();
393
-
394
- if (mExchangeCtx != nullptr ) {
395
- mExchangeCtx ->Close ();
396
- mExchangeCtx = nullptr ;
397
- }
398
-
399
- mInitialized = false ;
400
- }
401
-
402
461
bool mInitialized = false ;
403
462
Optional<FabricIndex> mFabricIndex ;
404
463
Optional<NodeId> mNodeId ;
@@ -549,63 +608,81 @@ bool GetPeerNodeInfo(CommandHandler * commandHandler, const ConcreteCommandPath
549
608
__block CommandHandler::Handle handle (commandObj);
550
609
__block ConcreteCommandPath cachedCommandPath (commandPath.mEndpointId , commandPath.mClusterId , commandPath.mCommandId );
551
610
552
- auto completionHandler
553
- = ^( MTROTASoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, NSError * _Nullable error) {
554
- [controller
555
- asyncDispatchToMatterQueue: ^() {
556
- assertChipStackLockedByCurrentThread ();
611
+ auto completionHandler = ^(
612
+ MTROTASoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, NSError * _Nullable error) {
613
+ [controller
614
+ asyncDispatchToMatterQueue: ^() {
615
+ assertChipStackLockedByCurrentThread ();
557
616
558
- CommandHandler * handler = EnsureValidState (handle, cachedCommandPath, " QueryImage" , data, error);
559
- VerifyOrReturn (handler != nullptr );
617
+ CommandHandler * handler = EnsureValidState (handle, cachedCommandPath, " QueryImage" , data, error);
618
+ VerifyOrReturn (handler != nullptr );
560
619
561
- ChipLogDetail (Controller, " QueryImage: application responded with: %s" ,
562
- [[data description ] cStringUsingEncoding: NSUTF8StringEncoding]);
620
+ ChipLogDetail (Controller, " QueryImage: application responded with: %s" ,
621
+ [[data description ] cStringUsingEncoding: NSUTF8StringEncoding]);
563
622
564
- Commands::QueryImageResponse::Type response;
565
- ConvertFromQueryImageResponseParms (data, response);
566
-
567
- auto hasUpdate = [data.status isEqual: @(MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable)];
568
- auto isBDXProtocolSupported = [commandParams.protocolsSupported
569
- containsObject: @(MTROtaSoftwareUpdateProviderOTADownloadProtocolBDXSynchronous)];
570
-
571
- if (hasUpdate && isBDXProtocolSupported) {
572
- auto fabricIndex = handler->GetSubjectDescriptor ().fabricIndex ;
573
- auto nodeId = handler->GetSubjectDescriptor ().subject ;
574
- CHIP_ERROR err = gOtaSender .PrepareForTransfer (fabricIndex, nodeId);
575
- if (CHIP_NO_ERROR != err) {
576
- LogErrorOnFailure (err);
577
- handler->AddStatus (cachedCommandPath, Protocols::InteractionModel::Status::Failure);
578
- handle.Release ();
579
- return ;
580
- }
581
-
582
- auto targetNodeId
583
- = handler->GetExchangeContext ()->GetSessionHandle ()->AsSecureSession ()->GetLocalScopedNodeId ();
584
-
585
- char uriBuffer[kMaxBDXURILen ];
586
- MutableCharSpan uri (uriBuffer);
587
- err = bdx::MakeURI (targetNodeId.GetNodeId (), AsCharSpan (data.imageURI ), uri);
588
- if (CHIP_NO_ERROR != err) {
589
- LogErrorOnFailure (err);
590
- handler->AddStatus (cachedCommandPath, Protocols::InteractionModel::Status::Failure);
591
- handle.Release ();
592
- return ;
593
- }
594
-
595
- response.imageURI .SetValue (uri);
596
- handler->AddResponse (cachedCommandPath, response);
597
- handle.Release ();
598
- return ;
599
- }
623
+ Commands::QueryImageResponse::Type response;
624
+ ConvertFromQueryImageResponseParms (data, response);
600
625
601
- handler-> AddResponse (cachedCommandPath, response) ;
602
- handle. Release ();
603
- }
626
+ auto hasUpdate = [data.status isEqual: @(MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable)] ;
627
+ auto isBDXProtocolSupported = [commandParams.protocolsSupported
628
+ containsObject: @(MTROtaSoftwareUpdateProviderOTADownloadProtocolBDXSynchronous)];
604
629
605
- errorHandler: ^(NSError *) {
606
- // Not much we can do here
607
- }];
608
- };
630
+ if (hasUpdate && isBDXProtocolSupported) {
631
+ auto fabricIndex = handler->GetSubjectDescriptor ().fabricIndex ;
632
+ auto nodeId = handler->GetSubjectDescriptor ().subject ;
633
+ CHIP_ERROR err = gOtaSender .PrepareForTransfer (fabricIndex, nodeId);
634
+ if (CHIP_NO_ERROR != err) {
635
+ LogErrorOnFailure (err);
636
+ if (err == CHIP_ERROR_BUSY) {
637
+ Commands::QueryImageResponse::Type busyResponse;
638
+ busyResponse.status = static_cast <OTAQueryStatus>(MTROTASoftwareUpdateProviderOTAQueryStatusBusy);
639
+ busyResponse.delayedActionTime .SetValue (response.delayedActionTime .ValueOr (kDelayedActionTimeSeconds ));
640
+ handler->AddResponse (cachedCommandPath, busyResponse);
641
+ handle.Release ();
642
+ return ;
643
+ }
644
+ handler->AddStatus (cachedCommandPath, Protocols::InteractionModel::Status::Failure);
645
+ handle.Release ();
646
+ gOtaSender .ResetState ();
647
+ return ;
648
+ }
649
+ auto targetNodeId
650
+ = handler->GetExchangeContext ()->GetSessionHandle ()->AsSecureSession ()->GetLocalScopedNodeId ();
651
+
652
+ char uriBuffer[kMaxBDXURILen ];
653
+ MutableCharSpan uri (uriBuffer);
654
+ err = bdx::MakeURI (targetNodeId.GetNodeId (), AsCharSpan (data.imageURI ), uri);
655
+ if (CHIP_NO_ERROR != err) {
656
+ LogErrorOnFailure (err);
657
+ handler->AddStatus (cachedCommandPath, Protocols::InteractionModel::Status::Failure);
658
+ handle.Release ();
659
+ gOtaSender .ResetState ();
660
+ return ;
661
+ }
662
+
663
+ response.imageURI .SetValue (uri);
664
+ handler->AddResponse (cachedCommandPath, response);
665
+ handle.Release ();
666
+ return ;
667
+ }
668
+ if (!hasUpdate) {
669
+ // Send whatever error response our delegate decided on.
670
+ handler->AddResponse (cachedCommandPath, response);
671
+ } else {
672
+ // We must have isBDXProtocolSupported false. Send the corresponding error status.
673
+ Commands::QueryImageResponse::Type protocolNotSupportedResponse;
674
+ protocolNotSupportedResponse.status
675
+ = static_cast <OTAQueryStatus>(MTROTASoftwareUpdateProviderOTAQueryStatusDownloadProtocolNotSupported);
676
+ handler->AddResponse (cachedCommandPath, protocolNotSupportedResponse);
677
+ }
678
+ handle.Release ();
679
+ gOtaSender .ResetState ();
680
+ }
681
+
682
+ errorHandler: ^(NSError *) {
683
+ // Not much we can do here
684
+ }];
685
+ };
609
686
610
687
auto strongDelegate = mDelegate ;
611
688
dispatch_async (mDelegateNotificationQueue , ^{
0 commit comments