2 * AWS IoT Shadow V2.1.0
\r
3 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
\r
5 * Permission is hereby granted, free of charge, to any person obtaining a copy of
\r
6 * this software and associated documentation files (the "Software"), to deal in
\r
7 * the Software without restriction, including without limitation the rights to
\r
8 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
\r
9 * the Software, and to permit persons to whom the Software is furnished to do so,
\r
10 * subject to the following conditions:
\r
12 * The above copyright notice and this permission notice shall be included in all
\r
13 * copies or substantial portions of the Software.
\r
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
\r
17 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
\r
18 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
\r
19 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
\r
20 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\r
24 * @file aws_iot_shadow_subscription.c
\r
25 * @brief Implements functions for interacting with the Shadow library's
\r
26 * subscription list.
\r
29 /* The config header is always included first. */
\r
30 #include "iot_config.h"
\r
32 /* Standard includes. */
\r
35 /* Shadow internal include. */
\r
36 #include "private/aws_iot_shadow_internal.h"
\r
38 /* Error handling include. */
\r
39 #include "iot_error.h"
\r
41 /* Platform layer includes. */
\r
42 #include "platform/iot_threads.h"
\r
45 #include "iot_mqtt.h"
\r
47 /*-----------------------------------------------------------*/
\r
50 * @brief Match two #_shadowSubscription_t by Thing Name.
\r
52 * @param[in] pSubscriptionLink Pointer to the link member of a #_shadowSubscription_t
\r
53 * containing the Thing Name to check.
\r
54 * @param[in] pMatch Pointer to an `AwsIotThingName_t`.
\r
56 * @return `true` if the Thing Names match; `false` otherwise.
\r
58 static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink,
\r
61 /*-----------------------------------------------------------*/
\r
64 * @brief List of active Shadow subscriptions objects.
\r
66 IotListDouble_t _AwsIotShadowSubscriptions = { 0 };
\r
69 * @brief Protects #_AwsIotShadowSubscriptions from concurrent access.
\r
71 IotMutex_t _AwsIotShadowSubscriptionsMutex;
\r
73 /*-----------------------------------------------------------*/
\r
75 static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink,
\r
80 /* Because this function is called from a container function, the given link
\r
81 * must never be NULL. */
\r
82 AwsIotShadow_Assert( pSubscriptionLink != NULL );
\r
84 const _shadowSubscription_t * pSubscription = IotLink_Container( _shadowSubscription_t,
\r
87 const AwsIotThingName_t * pThingName = ( AwsIotThingName_t * ) pMatch;
\r
89 if( pThingName->thingNameLength == pSubscription->thingNameLength )
\r
91 /* Check for matching Thing Names. */
\r
92 match = ( strncmp( pThingName->pThingName,
\r
93 pSubscription->pThingName,
\r
94 pThingName->thingNameLength ) == 0 );
\r
100 /*-----------------------------------------------------------*/
\r
102 _shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName,
\r
103 size_t thingNameLength,
\r
104 bool createIfNotFound )
\r
106 _shadowSubscription_t * pSubscription = NULL;
\r
107 IotLink_t * pSubscriptionLink = NULL;
\r
108 AwsIotThingName_t thingName = { 0 };
\r
110 thingName.pThingName = pThingName;
\r
111 thingName.thingNameLength = thingNameLength;
\r
113 /* Search the list for an existing subscription for Thing Name. */
\r
114 pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotShadowSubscriptions ),
\r
116 _shadowSubscription_match,
\r
119 /* Check if a subscription was found. */
\r
120 if( pSubscriptionLink == NULL )
\r
122 if( createIfNotFound == true )
\r
124 /* No subscription found. Allocate a new subscription. */
\r
125 pSubscription = AwsIotShadow_MallocSubscription( sizeof( _shadowSubscription_t ) + thingNameLength );
\r
127 if( pSubscription != NULL )
\r
129 /* Clear the new subscription. */
\r
130 ( void ) memset( pSubscription, 0x00, sizeof( _shadowSubscription_t ) + thingNameLength );
\r
132 /* Set the Thing Name length and copy the Thing Name into the new subscription. */
\r
133 pSubscription->thingNameLength = thingNameLength;
\r
134 ( void ) memcpy( pSubscription->pThingName, pThingName, thingNameLength );
\r
136 /* Add the new subscription to the subscription list. */
\r
137 IotListDouble_InsertHead( &( _AwsIotShadowSubscriptions ),
\r
138 &( pSubscription->link ) );
\r
140 IotLogDebug( "Created new Shadow subscriptions object for %.*s.",
\r
146 IotLogError( "Failed to allocate memory for %.*s Shadow subscriptions.",
\r
154 IotLogDebug( "Found existing Shadow subscriptions object for %.*s.",
\r
158 pSubscription = IotLink_Container( _shadowSubscription_t, pSubscriptionLink, link );
\r
161 return pSubscription;
\r
164 /*-----------------------------------------------------------*/
\r
166 void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription,
\r
167 _shadowSubscription_t ** pRemovedSubscription )
\r
170 bool removeSubscription = true;
\r
172 IotLogDebug( "Checking if subscription object for %.*s can be removed.",
\r
173 pSubscription->thingNameLength,
\r
174 pSubscription->pThingName );
\r
176 /* Check for active operations. If any Shadow operation's subscription
\r
177 * reference count is not 0, then the subscription cannot be removed. */
\r
178 for( i = 0; i < SHADOW_OPERATION_COUNT; i++ )
\r
180 if( pSubscription->references[ i ] > 0 )
\r
182 IotLogDebug( "Reference count %ld for %.*s subscription object. "
\r
183 "Subscription cannot be removed yet.",
\r
184 ( long int ) pSubscription->references[ i ],
\r
185 pSubscription->thingNameLength,
\r
186 pSubscription->pThingName );
\r
188 removeSubscription = false;
\r
190 else if( pSubscription->references[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION )
\r
192 IotLogDebug( "Subscription object for %.*s has persistent subscriptions. "
\r
193 "Subscription will not be removed.",
\r
194 pSubscription->thingNameLength,
\r
195 pSubscription->pThingName );
\r
197 removeSubscription = false;
\r
200 if( removeSubscription == false )
\r
206 /* Check for active subscriptions. If any Shadow callbacks are active, then the
\r
207 * subscription cannot be removed. */
\r
208 if( removeSubscription == true )
\r
210 for( i = 0; i < SHADOW_CALLBACK_COUNT; i++ )
\r
212 if( pSubscription->callbacks[ i ].function != NULL )
\r
214 IotLogDebug( "Found active Shadow %s callback for %.*s subscription object. "
\r
215 "Subscription cannot be removed yet.",
\r
216 _pAwsIotShadowCallbackNames[ i ],
\r
217 pSubscription->thingNameLength,
\r
218 pSubscription->pThingName );
\r
220 removeSubscription = false;
\r
226 /* Remove the subscription if unused. */
\r
227 if( removeSubscription == true )
\r
229 /* No Shadow operation subscription references or active Shadow callbacks.
\r
230 * Remove the subscription object. */
\r
231 IotListDouble_Remove( &( pSubscription->link ) );
\r
233 IotLogDebug( "Removed subscription object for %.*s.",
\r
234 pSubscription->thingNameLength,
\r
235 pSubscription->pThingName );
\r
237 /* If the caller requested the removed subscription, set the output parameter.
\r
238 * Otherwise, free the memory used by the subscription. */
\r
239 if( pRemovedSubscription != NULL )
\r
241 *pRemovedSubscription = pSubscription;
\r
245 _AwsIotShadow_DestroySubscription( pSubscription );
\r
250 /*-----------------------------------------------------------*/
\r
252 void _AwsIotShadow_DestroySubscription( void * pData )
\r
254 _shadowSubscription_t * pSubscription = ( _shadowSubscription_t * ) pData;
\r
256 /* Free the topic buffer if allocated. */
\r
257 if( pSubscription->pTopicBuffer != NULL )
\r
259 AwsIotShadow_FreeString( pSubscription->pTopicBuffer );
\r
262 /* Free memory used by subscription. */
\r
263 AwsIotShadow_FreeSubscription( pSubscription );
\r
266 /*-----------------------------------------------------------*/
\r
268 AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOperation,
\r
269 char * pTopicBuffer,
\r
270 uint16_t operationTopicLength,
\r
271 AwsIotMqttCallbackFunction_t callback )
\r
273 IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS );
\r
274 const _shadowOperationType_t type = pOperation->type;
\r
275 _shadowSubscription_t * pSubscription = pOperation->pSubscription;
\r
276 IotMqttError_t subscriptionStatus = IOT_MQTT_STATUS_PENDING;
\r
277 AwsIotSubscriptionInfo_t subscriptionInfo = { 0 };
\r
279 /* Do nothing if this operation has persistent subscriptions. */
\r
280 if( pSubscription->references[ type ] == AWS_IOT_PERSISTENT_SUBSCRIPTION )
\r
282 IotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference "
\r
283 "count will not be incremented.",
\r
284 _pAwsIotShadowOperationNames[ type ],
\r
285 pSubscription->thingNameLength,
\r
286 pSubscription->pThingName );
\r
288 IOT_GOTO_CLEANUP();
\r
291 /* When persistent subscriptions are not present, the reference count must
\r
292 * not be negative. */
\r
293 AwsIotShadow_Assert( pSubscription->references[ type ] >= 0 );
\r
295 /* Check if there are any existing references for this operation. */
\r
296 if( pSubscription->references[ type ] == 0 )
\r
298 /* Set the parameters needed to add subscriptions. */
\r
299 subscriptionInfo.mqttConnection = pOperation->mqttConnection;
\r
300 subscriptionInfo.callbackFunction = callback;
\r
301 subscriptionInfo.timeout = _AwsIotShadowMqttTimeoutMs;
\r
302 subscriptionInfo.pTopicFilterBase = pTopicBuffer;
\r
303 subscriptionInfo.topicFilterBaseLength = operationTopicLength;
\r
305 subscriptionStatus = AwsIot_ModifySubscriptions( IotMqtt_SubscribeSync,
\r
306 &subscriptionInfo );
\r
308 /* Convert MQTT return code to Shadow return code. */
\r
309 status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( subscriptionStatus );
\r
311 if( status != AWS_IOT_SHADOW_SUCCESS )
\r
313 IOT_GOTO_CLEANUP();
\r
317 /* Increment the number of subscription references for this operation when
\r
318 * the keep subscriptions flag is not set. */
\r
319 if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 )
\r
321 ( pSubscription->references[ type ] )++;
\r
323 IotLogDebug( "Shadow %s subscriptions for %.*s now has count %d.",
\r
324 _pAwsIotShadowOperationNames[ type ],
\r
325 pSubscription->thingNameLength,
\r
326 pSubscription->pThingName,
\r
327 pSubscription->references[ type ] );
\r
329 /* Otherwise, set the persistent subscriptions flag. */
\r
332 pSubscription->references[ type ] = AWS_IOT_PERSISTENT_SUBSCRIPTION;
\r
334 IotLogDebug( "Set persistent subscriptions flag for Shadow %s of %.*s.",
\r
335 _pAwsIotShadowOperationNames[ type ],
\r
336 pSubscription->thingNameLength,
\r
337 pSubscription->pThingName );
\r
340 IOT_FUNCTION_EXIT_NO_CLEANUP();
\r
343 /*-----------------------------------------------------------*/
\r
345 void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation,
\r
346 char * pTopicBuffer,
\r
347 _shadowSubscription_t ** pRemovedSubscription )
\r
349 const _shadowOperationType_t type = pOperation->type;
\r
350 _shadowSubscription_t * pSubscription = pOperation->pSubscription;
\r
351 uint16_t operationTopicLength = 0;
\r
352 AwsIotSubscriptionInfo_t subscriptionInfo = { 0 };
\r
354 /* Do nothing if this Shadow operation has persistent subscriptions. */
\r
355 if( pSubscription->references[ type ] != AWS_IOT_PERSISTENT_SUBSCRIPTION )
\r
357 /* Decrement the number of subscription references for this operation.
\r
358 * Ensure that it's positive. */
\r
359 ( pSubscription->references[ type ] )--;
\r
360 AwsIotShadow_Assert( pSubscription->references[ type ] >= 0 );
\r
362 /* Check if the number of references has reached 0. */
\r
363 if( pSubscription->references[ type ] == 0 )
\r
365 IotLogDebug( "Reference count for %.*s %s is 0. Unsubscribing.",
\r
366 pSubscription->thingNameLength,
\r
367 pSubscription->pThingName,
\r
368 _pAwsIotShadowOperationNames[ type ] );
\r
370 /* Subscription must have a topic buffer. */
\r
371 AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL );
\r
373 /* Generate the prefix of the Shadow topic. This function will not
\r
374 * fail when given a buffer. */
\r
375 ( void ) _AwsIotShadow_GenerateShadowTopic( ( _shadowOperationType_t ) type,
\r
376 pSubscription->pThingName,
\r
377 pSubscription->thingNameLength,
\r
378 &( pSubscription->pTopicBuffer ),
\r
379 &operationTopicLength );
\r
381 /* Set the parameters needed to remove subscriptions. */
\r
382 subscriptionInfo.mqttConnection = pOperation->mqttConnection;
\r
383 subscriptionInfo.timeout = _AwsIotShadowMqttTimeoutMs;
\r
384 subscriptionInfo.pTopicFilterBase = pTopicBuffer;
\r
385 subscriptionInfo.topicFilterBaseLength = operationTopicLength;
\r
387 ( void ) AwsIot_ModifySubscriptions( IotMqtt_UnsubscribeSync,
\r
388 &subscriptionInfo );
\r
391 /* Check if this subscription should be deleted. */
\r
392 _AwsIotShadow_RemoveSubscription( pSubscription,
\r
393 pRemovedSubscription );
\r
397 IotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference "
\r
398 "count will not be decremented.",
\r
399 _pAwsIotShadowOperationNames[ type ],
\r
400 pSubscription->thingNameLength,
\r
401 pSubscription->pThingName );
\r
405 /*-----------------------------------------------------------*/
\r
407 AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnection_t mqttConnection,
\r
408 const char * pThingName,
\r
409 size_t thingNameLength,
\r
413 uint16_t operationTopicLength = 0;
\r
414 AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING;
\r
415 IotMqttError_t unsubscribeStatus = IOT_MQTT_STATUS_PENDING;
\r
416 AwsIotSubscriptionInfo_t subscriptionInfo = { 0 };
\r
417 _shadowSubscription_t * pSubscription = NULL;
\r
418 IotLink_t * pSubscriptionLink = NULL;
\r
419 AwsIotThingName_t thingName = { 0 };
\r
421 thingName.pThingName = pThingName;
\r
422 thingName.thingNameLength = thingNameLength;
\r
424 IotLogInfo( "Removing persistent subscriptions for %.*s.",
\r
428 IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) );
\r
430 /* Search the list for an existing subscription for Thing Name. */
\r
431 pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotShadowSubscriptions ),
\r
433 _shadowSubscription_match,
\r
436 /* Unsubscribe from operation subscriptions if found. */
\r
437 if( pSubscriptionLink != NULL )
\r
439 IotLogDebug( "Found subscription object for %.*s. Checking for persistent "
\r
440 "subscriptions to remove.",
\r
444 pSubscription = IotLink_Container( _shadowSubscription_t, pSubscriptionLink, link );
\r
446 for( i = 0; i < SHADOW_OPERATION_COUNT; i++ )
\r
448 if( ( flags & ( 0x1UL << i ) ) != 0 )
\r
450 IotLogDebug( "Removing %.*s %s persistent subscriptions.",
\r
453 _pAwsIotShadowOperationNames[ i ] );
\r
455 /* Subscription must have a topic buffer. */
\r
456 AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL );
\r
458 if( pSubscription->references[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION )
\r
460 /* Generate the prefix of the Shadow topic. This function will not
\r
461 * fail when given a buffer. */
\r
462 ( void ) _AwsIotShadow_GenerateShadowTopic( ( _shadowOperationType_t ) i,
\r
465 &( pSubscription->pTopicBuffer ),
\r
466 &operationTopicLength );
\r
468 /* Set the parameters needed to remove subscriptions. */
\r
469 subscriptionInfo.mqttConnection = mqttConnection;
\r
470 subscriptionInfo.timeout = _AwsIotShadowMqttTimeoutMs;
\r
471 subscriptionInfo.pTopicFilterBase = pSubscription->pTopicBuffer;
\r
472 subscriptionInfo.topicFilterBaseLength = operationTopicLength;
\r
474 unsubscribeStatus = AwsIot_ModifySubscriptions( IotMqtt_UnsubscribeSync,
\r
475 &subscriptionInfo );
\r
477 /* Convert MQTT return code to Shadow return code. */
\r
478 status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( unsubscribeStatus );
\r
480 if( status != AWS_IOT_SHADOW_SUCCESS )
\r
485 /* Clear the persistent subscriptions flag and check if the
\r
486 * subscription can be removed. */
\r
487 pSubscription->references[ i ] = 0;
\r
488 _AwsIotShadow_RemoveSubscription( pSubscription, NULL );
\r
492 IotLogDebug( "%.*s %s does not have persistent subscriptions.",
\r
495 _pAwsIotShadowOperationNames[ i ] );
\r
502 IotLogWarn( "No subscription object found for %.*s",
\r
507 IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) );
\r
512 /*-----------------------------------------------------------*/
\r