/*
 * AWS IoT Common V1.0.0
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @file aws_iot_parser.c
 * @brief Parses topics for Thing Name and status.
 */

/* The config header is always included first. */
#include "iot_config.h"

/* Standard includes. */
#include <string.h>

/* AWS IoT include. */
#include "aws_iot.h"

/* Error handling include. */
#include "iot_error.h"

/* AWS Parser include. */
#include "aws_iot_doc_parser.h"

/**
 * @brief Minimum allowed topic length for an AWS IoT status topic.
 *
 * Topics must contain at least:
 * - The common prefix
 * - The suffix "/accepted" or "/rejected"
 * - 1 character for the Thing Name
 * - 2 characters for the operation name and the enclosing slashes
 */
#define MINIMUM_TOPIC_NAME_LENGTH                   \
    ( uint16_t ) ( AWS_IOT_TOPIC_PREFIX_LENGTH +    \
                   AWS_IOT_ACCEPTED_SUFFIX_LENGTH + \
                   1 + 2 )

/**
 * @brief The longest client token accepted by AWS IoT service, per AWS IoT
 * service limits.
 */
#define MAX_CLIENT_TOKEN_LENGTH    ( 64 )

/*-----------------------------------------------------------*/

bool AwsIot_GetClientToken( const char * pJsonDocument,
                            size_t jsonDocumentLength,
                            const char ** pClientToken,
                            size_t * pClientTokenLength )
{
    /* Extract the client token from the JSON document. */
    bool status = AwsIotDocParser_FindValue( pJsonDocument,
                                             jsonDocumentLength,
                                             AWS_IOT_CLIENT_TOKEN_KEY,
                                             AWS_IOT_CLIENT_TOKEN_KEY_LENGTH,
                                             pClientToken,
                                             pClientTokenLength );

    if( status == true )
    {
        /* Check that the length of the client token is valid. */
        if( ( *pClientTokenLength < 2 ) ||
            ( *pClientTokenLength > MAX_CLIENT_TOKEN_LENGTH ) )
        {
            status = false;
        }
    }

    return status;
}

/*-----------------------------------------------------------*/

bool AwsIot_ParseThingName( const char * pTopicName,
                            uint16_t topicNameLength,
                            const char ** pThingName,
                            size_t * pThingNameLength )
{
    IOT_FUNCTION_ENTRY( bool, true );
    const char * pThingNameStart = NULL;
    size_t thingNameLength = 0;

    /* Check that the topic name is at least as long as the minimum allowed. */
    if( topicNameLength < MINIMUM_TOPIC_NAME_LENGTH )
    {
        IOT_SET_AND_GOTO_CLEANUP( false );
    }

    /* Check that the given topic starts with the common prefix. */
    if( strncmp( AWS_IOT_TOPIC_PREFIX,
                 pTopicName,
                 AWS_IOT_TOPIC_PREFIX_LENGTH ) != 0 )
    {
        IOT_SET_AND_GOTO_CLEANUP( false );
    }

    /* The Thing Name starts immediately after the topic prefix. */
    pThingNameStart = pTopicName + AWS_IOT_TOPIC_PREFIX_LENGTH;

    /* Calculate the length of the Thing Name, which is terminated with a '/'. */
    while( ( thingNameLength + AWS_IOT_TOPIC_PREFIX_LENGTH < ( size_t ) topicNameLength ) &&
           ( pThingNameStart[ thingNameLength ] != '/' ) )
    {
        thingNameLength++;
    }

    /* The end of the topic name was reached without finding a '/'. The topic
     * name is invalid. */
    if( thingNameLength + AWS_IOT_TOPIC_PREFIX_LENGTH >= ( size_t ) topicNameLength )
    {
        IOT_SET_AND_GOTO_CLEANUP( false );
    }

    /* Set the output parameters. */
    *pThingName = pThingNameStart;
    *pThingNameLength = thingNameLength;

    IOT_FUNCTION_EXIT_NO_CLEANUP();
}

/*-----------------------------------------------------------*/

AwsIotStatus_t AwsIot_ParseStatus( const char * pTopicName,
                                   uint16_t topicNameLength )
{
    IOT_FUNCTION_ENTRY( AwsIotStatus_t, AWS_IOT_UNKNOWN );
    const char * pSuffixStart = NULL;

    /* Both 'accepted' and  'rejected' topics are of the same length
     * The below is a defensive check at run time to ensure that.
     */
    Iot_DefaultAssert( AWS_IOT_ACCEPTED_SUFFIX_LENGTH == AWS_IOT_REJECTED_SUFFIX_LENGTH );

    /* Check that the status topic name is at least as long as the
     * "accepted" suffix. This length check will be good for rejected also
     * as both are of 8 characters in length. */
    if( topicNameLength > AWS_IOT_ACCEPTED_SUFFIX_LENGTH )
    {
        /* Calculate where the "accepted" suffix should start. */
        pSuffixStart = pTopicName + topicNameLength - AWS_IOT_ACCEPTED_SUFFIX_LENGTH;

        /* Check if the end of the status topic name is "/accepted". */
        if( strncmp( pSuffixStart,
                     AWS_IOT_ACCEPTED_SUFFIX,
                     AWS_IOT_ACCEPTED_SUFFIX_LENGTH ) == 0 )
        {
            IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_ACCEPTED );
        }

        /* Check if the end of the status topic name is "/rejected". */
        if( strncmp( pSuffixStart,
                     AWS_IOT_REJECTED_SUFFIX,
                     AWS_IOT_REJECTED_SUFFIX_LENGTH ) == 0 )
        {
            IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_REJECTED );
        }
    }

    IOT_FUNCTION_EXIT_NO_CLEANUP();
}

/*-----------------------------------------------------------*/
