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 iot_mqtt_validate.c
\r
25 * @brief Implements functions that validate the structs of the MQTT library.
\r
28 /* The config header is always included first. */
\r
29 #include "iot_config.h"
\r
31 /* Error handling include. */
\r
32 #include "iot_error.h"
\r
34 /* MQTT internal include. */
\r
35 #include "private/iot_mqtt_internal.h"
\r
38 * @brief Check that an #IotMqttPublishInfo_t is valid.
\r
40 * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to
\r
41 * an AWS IoT MQTT server.
\r
42 * @param[in] maximumPayloadLength Maximum payload length.
\r
43 * @param[in] pPublishTypeDescription String describing the publish type.
\r
44 * @param[in] pPublishInfo The #IotMqttPublishInfo_t to validate.
\r
46 * @return `true` if `pPublishInfo` is valid; `false` otherwise.
\r
48 static bool _validatePublish( bool awsIotMqttMode,
\r
49 size_t maximumPayloadLength,
\r
50 const char * pPublishTypeDescription,
\r
51 const IotMqttPublishInfo_t * pPublishInfo );
\r
53 /*-----------------------------------------------------------*/
\r
55 bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo )
\r
57 IOT_FUNCTION_ENTRY( bool, true );
\r
58 uint16_t maxClientIdLength = MQTT_SERVER_MAX_CLIENTID_LENGTH;
\r
59 bool enforceMaxClientIdLength = false;
\r
61 /* Check for NULL. */
\r
62 if( pConnectInfo == NULL )
\r
64 IotLogError( "MQTT connection information cannot be NULL." );
\r
66 IOT_SET_AND_GOTO_CLEANUP( false );
\r
73 /* Check that a client identifier was set. */
\r
74 if( pConnectInfo->pClientIdentifier == NULL )
\r
76 IotLogError( "Client identifier must be set." );
\r
78 IOT_SET_AND_GOTO_CLEANUP( false );
\r
85 /* Check for a zero-length client identifier. Zero-length client identifiers
\r
86 * are not allowed with clean sessions. */
\r
87 if( pConnectInfo->clientIdentifierLength == 0 )
\r
89 IotLogWarn( "A zero-length client identifier was provided." );
\r
91 if( pConnectInfo->cleanSession == true )
\r
93 IotLogError( "A zero-length client identifier cannot be used with a clean session." );
\r
95 IOT_SET_AND_GOTO_CLEANUP( false );
\r
107 /* Check that the number of persistent session subscriptions is valid. */
\r
108 if( pConnectInfo->pPreviousSubscriptions != NULL )
\r
110 if( _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE,
\r
111 pConnectInfo->awsIotMqttMode,
\r
112 pConnectInfo->pPreviousSubscriptions,
\r
113 pConnectInfo->previousSubscriptionCount ) == false )
\r
115 IOT_SET_AND_GOTO_CLEANUP( false );
\r
127 /* If will info is provided, check that it is valid. */
\r
128 if( pConnectInfo->pWillInfo != NULL )
\r
130 if( _IotMqtt_ValidateLwtPublish( pConnectInfo->awsIotMqttMode,
\r
131 pConnectInfo->pWillInfo ) == false )
\r
133 IOT_SET_AND_GOTO_CLEANUP( false );
\r
145 /* The AWS IoT MQTT service enforces a client ID length limit. */
\r
146 if( pConnectInfo->awsIotMqttMode == true )
\r
148 maxClientIdLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH;
\r
149 enforceMaxClientIdLength = true;
\r
152 if( pConnectInfo->clientIdentifierLength > maxClientIdLength )
\r
154 if( enforceMaxClientIdLength == false )
\r
156 IotLogWarn( "A client identifier length of %hu is longer than %hu, "
\r
158 "the longest client identifier a server must accept.",
\r
159 pConnectInfo->clientIdentifierLength,
\r
160 maxClientIdLength );
\r
164 IotLogError( "A client identifier length of %hu exceeds the "
\r
165 "maximum supported length of %hu.",
\r
166 pConnectInfo->clientIdentifierLength,
\r
167 maxClientIdLength );
\r
169 IOT_SET_AND_GOTO_CLEANUP( false );
\r
177 IOT_FUNCTION_EXIT_NO_CLEANUP();
\r
180 /*-----------------------------------------------------------*/
\r
181 static bool _validatePublish( bool awsIotMqttMode,
\r
182 size_t maximumPayloadLength,
\r
183 const char * pPublishTypeDescription,
\r
184 const IotMqttPublishInfo_t * pPublishInfo )
\r
186 IOT_FUNCTION_ENTRY( bool, true );
\r
188 /* Check for NULL. */
\r
189 if( pPublishInfo == NULL )
\r
191 IotLogError( "Publish information cannot be NULL." );
\r
193 IOT_SET_AND_GOTO_CLEANUP( false );
\r
200 /* Check topic name for NULL or zero-length. */
\r
201 if( pPublishInfo->pTopicName == NULL )
\r
203 IotLogError( "Publish topic name must be set." );
\r
210 if( pPublishInfo->topicNameLength == 0 )
\r
212 IotLogError( "Publish topic name length cannot be 0." );
\r
214 IOT_SET_AND_GOTO_CLEANUP( false );
\r
221 if( pPublishInfo->payloadLength != 0 )
\r
223 if( pPublishInfo->payloadLength > maximumPayloadLength )
\r
225 IotLogError( "%s payload size of %zu exceeds maximum length of %zu.",
\r
226 pPublishTypeDescription,
\r
227 pPublishInfo->payloadLength,
\r
228 maximumPayloadLength );
\r
230 IOT_SET_AND_GOTO_CLEANUP( false );
\r
234 if( pPublishInfo->pPayload == NULL )
\r
236 IotLogError( "Nonzero payload length cannot have a NULL payload." );
\r
238 IOT_SET_AND_GOTO_CLEANUP( false );
\r
251 /* Check for a valid QoS. */
\r
252 if( pPublishInfo->qos != IOT_MQTT_QOS_0 )
\r
254 if( pPublishInfo->qos != IOT_MQTT_QOS_1 )
\r
256 IotLogError( "Publish QoS must be either 0 or 1." );
\r
258 IOT_SET_AND_GOTO_CLEANUP( false );
\r
270 /* Check the retry parameters. */
\r
271 if( pPublishInfo->retryLimit > 0 )
\r
273 if( pPublishInfo->retryMs == 0 )
\r
275 IotLogError( "Publish retry time must be positive." );
\r
277 IOT_SET_AND_GOTO_CLEANUP( false );
\r
289 /* Check for compatibility with AWS IoT MQTT server. */
\r
290 if( awsIotMqttMode == true )
\r
292 /* Check for retained message. */
\r
293 if( pPublishInfo->retain == true )
\r
295 IotLogError( "AWS IoT does not support retained publish messages." );
\r
297 IOT_SET_AND_GOTO_CLEANUP( false );
\r
304 /* Check topic name length. */
\r
305 if( pPublishInfo->topicNameLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH )
\r
307 IotLogError( "AWS IoT does not support topic names longer than %d bytes.",
\r
308 AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH );
\r
310 IOT_SET_AND_GOTO_CLEANUP( false );
\r
322 IOT_FUNCTION_EXIT_NO_CLEANUP();
\r
325 /*-----------------------------------------------------------*/
\r
327 bool _IotMqtt_ValidatePublish( bool awsIotMqttMode,
\r
328 const IotMqttPublishInfo_t * pPublishInfo )
\r
330 size_t maximumPayloadLength = MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH;
\r
332 if( awsIotMqttMode == true )
\r
334 maximumPayloadLength = AWS_IOT_MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH;
\r
341 return _validatePublish( awsIotMqttMode,
\r
342 maximumPayloadLength,
\r
347 /*-----------------------------------------------------------*/
\r
349 bool _IotMqtt_ValidateLwtPublish( bool awsIotMqttMode,
\r
350 const IotMqttPublishInfo_t * pLwtPublishInfo )
\r
352 return _validatePublish( awsIotMqttMode,
\r
353 MQTT_SERVER_MAX_LWT_PAYLOAD_LENGTH,
\r
358 /*-----------------------------------------------------------*/
\r
360 bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation )
\r
362 IOT_FUNCTION_ENTRY( bool, true );
\r
364 /* Check for NULL. */
\r
365 if( operation == NULL )
\r
367 IotLogError( "Operation reference cannot be NULL." );
\r
369 IOT_SET_AND_GOTO_CLEANUP( false );
\r
376 /* Check that reference is waitable. */
\r
377 if( ( operation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) != IOT_MQTT_FLAG_WAITABLE )
\r
379 IotLogError( "Operation is not waitable." );
\r
381 IOT_SET_AND_GOTO_CLEANUP( false );
\r
388 IOT_FUNCTION_EXIT_NO_CLEANUP();
\r
391 /*-----------------------------------------------------------*/
\r
393 bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation,
\r
394 bool awsIotMqttMode,
\r
395 const IotMqttSubscription_t * pListStart,
\r
398 IOT_FUNCTION_ENTRY( bool, true );
\r
401 const IotMqttSubscription_t * pListElement = NULL;
\r
403 /* Operation must be either subscribe or unsubscribe. */
\r
404 IotMqtt_Assert( ( operation == IOT_MQTT_SUBSCRIBE ) ||
\r
405 ( operation == IOT_MQTT_UNSUBSCRIBE ) );
\r
407 /* Check for empty list. */
\r
408 if( pListStart == NULL )
\r
410 IotLogError( "Subscription list pointer cannot be NULL." );
\r
412 IOT_SET_AND_GOTO_CLEANUP( false );
\r
419 if( listSize == 0 )
\r
421 IotLogError( "Empty subscription list." );
\r
423 IOT_SET_AND_GOTO_CLEANUP( false );
\r
430 /* AWS IoT supports at most 8 topic filters in a single SUBSCRIBE packet. */
\r
431 if( awsIotMqttMode == true )
\r
433 if( listSize > AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE )
\r
435 IotLogError( "AWS IoT does not support more than %d topic filters per "
\r
436 "subscription request.",
\r
437 AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE );
\r
439 IOT_SET_AND_GOTO_CLEANUP( false );
\r
451 for( i = 0; i < listSize; i++ )
\r
453 pListElement = &( pListStart[ i ] );
\r
455 /* Check for a valid QoS and callback function when subscribing. */
\r
456 if( operation == IOT_MQTT_SUBSCRIBE )
\r
458 if( pListElement->qos != IOT_MQTT_QOS_0 )
\r
460 if( pListElement->qos != IOT_MQTT_QOS_1 )
\r
462 IotLogError( "Subscription QoS must be either 0 or 1." );
\r
464 IOT_SET_AND_GOTO_CLEANUP( false );
\r
476 if( pListElement->callback.function == NULL )
\r
478 IotLogError( "Callback function must be set." );
\r
480 IOT_SET_AND_GOTO_CLEANUP( false );
\r
492 /* Check subscription topic filter. */
\r
493 if( pListElement->pTopicFilter == NULL )
\r
495 IotLogError( "Subscription topic filter cannot be NULL." );
\r
497 IOT_SET_AND_GOTO_CLEANUP( false );
\r
504 if( pListElement->topicFilterLength == 0 )
\r
506 IotLogError( "Subscription topic filter length cannot be 0." );
\r
508 IOT_SET_AND_GOTO_CLEANUP( false );
\r
515 /* Check for compatibility with AWS IoT MQTT server. */
\r
516 if( awsIotMqttMode == true )
\r
518 /* Check topic filter length. */
\r
519 if( pListElement->topicFilterLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH )
\r
521 IotLogError( "AWS IoT does not support topic filters longer than %d bytes.",
\r
522 AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH );
\r
524 IOT_SET_AND_GOTO_CLEANUP( false );
\r
536 /* Check that the wildcards '+' and '#' are being used correctly. */
\r
537 for( j = 0; j < pListElement->topicFilterLength; j++ )
\r
539 switch( pListElement->pTopicFilter[ j ] )
\r
541 /* Check that the single level wildcard '+' is used correctly. */
\r
544 /* Unless '+' is the first character in the filter, it must be preceded by '/'. */
\r
547 if( pListElement->pTopicFilter[ j - 1 ] != '/' )
\r
549 IotLogError( "Invalid topic filter %.*s -- '+' must be preceded by '/'.",
\r
550 pListElement->topicFilterLength,
\r
551 pListElement->pTopicFilter );
\r
553 IOT_SET_AND_GOTO_CLEANUP( false );
\r
565 /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */
\r
566 if( j < pListElement->topicFilterLength - 1 )
\r
568 if( pListElement->pTopicFilter[ j + 1 ] != '/' )
\r
570 IotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.",
\r
571 pListElement->topicFilterLength,
\r
572 pListElement->pTopicFilter );
\r
574 IOT_SET_AND_GOTO_CLEANUP( false );
\r
588 /* Check that the multi-level wildcard '#' is used correctly. */
\r
591 /* '#' must be the last character in the filter. */
\r
592 if( j != pListElement->topicFilterLength - 1 )
\r
594 IotLogError( "Invalid topic filter %.*s -- '#' must be the last character.",
\r
595 pListElement->topicFilterLength,
\r
596 pListElement->pTopicFilter );
\r
598 IOT_SET_AND_GOTO_CLEANUP( false );
\r
605 /* Unless '#' is standalone, it must be preceded by '/'. */
\r
606 if( pListElement->topicFilterLength > 1 )
\r
608 if( pListElement->pTopicFilter[ j - 1 ] != '/' )
\r
610 IotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.",
\r
611 pListElement->topicFilterLength,
\r
612 pListElement->pTopicFilter );
\r
614 IOT_SET_AND_GOTO_CLEANUP( false );
\r
634 IOT_FUNCTION_EXIT_NO_CLEANUP();
\r
637 /*-----------------------------------------------------------*/
\r