/*
 * Copyright (c) 2014-2017, Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * \file libDHCPv6.c
 * \brief Add short description about this file!!!
 *
 */
#include "nsconfig.h"
#include <string.h>
#include <ns_types.h>
#include "ns_trace.h"
#include "common_functions.h"
#include "libDHCPv6/libDHCPv6.h"
#include "randLIB.h"
#include "nsdynmemLIB.h"
#ifdef HAVE_DHCPV6
#define TRACE_GROUP "dhcp"


static NS_LARGE NS_LIST_DEFINE(dhcpv6_client_nonTemporal_list, dhcpv6_client_server_data_t, link);

//Allocate
static dhcpv6_client_server_data_t *libdhcvp6_nontemporalAddress_entry_allocate(void)
{
    dhcpv6_client_server_data_t *newEntry = ns_dyn_mem_alloc(sizeof(dhcpv6_client_server_data_t));
    if (newEntry) {
        newEntry->T0 = 0;
        newEntry->T1 = 0;
        newEntry->reNewTimer = 0;
        newEntry->iaNonTemporalStructValid = false;
        newEntry->GlobalAddress = false;
        newEntry->useServerAddress = false;
        newEntry->iaNontemporalAddress.preferredTime = 0;
        newEntry->iaNontemporalAddress.validLifetime = 0;
    }
    return newEntry;
}

static uint32_t libdhcpv6_IAID_generate(void)
{
    uint32_t iaId;
    bool notUnique = true;
    while (notUnique) {
        notUnique = false;
        iaId = randLIB_get_32bit();
        if (libdhcpv6_nonTemporal_entry_get_by_iaid(iaId)) {
            notUnique = true;
        }

    }
    return iaId;
}

static uint32_t libdhcpv6_Tx_timer_generate(uint32_t lifetime, bool T1_get)
{
    uint32_t timeout = lifetime;

    if (T1_get) {
        timeout = (timeout >> 1);
    } else {
        timeout = (timeout >> 2);
        timeout = (timeout * 3);
    }

    return timeout;
}

uint32_t libdhcpv6_txid_get(void)
{
    uint32_t transaction_id = randLIB_get_32bit();
    transaction_id &= 0x00ffffff;
    return transaction_id;
}

uint32_t libdhcpv6_renew_time_define(dhcpv6_client_server_data_t *addresInfo)
{
    uint32_t renewTimer = 0xffffffff;

    if (addresInfo->iaNontemporalAddress.preferredTime < renewTimer) {
        renewTimer = addresInfo->iaNontemporalAddress.preferredTime;
    }

    if (renewTimer ==  0xffffffff) {
        //Check T1
        renewTimer = 0;
    } else if (renewTimer < 100) {
        renewTimer = 100;
    }

    if (addresInfo->T0 == 0) {
        //Calculate
        if (renewTimer != 0) {
            addresInfo->T0 = libdhcpv6_Tx_timer_generate(renewTimer, true);
            addresInfo->T1 = libdhcpv6_Tx_timer_generate(renewTimer, false);
        } else {
            addresInfo->T0 = 0xffffffff;
            addresInfo->T1 = 0xffffffff;
        }
    }

    //Calculate Renew Time
    if (addresInfo->T0 != 0xffffffff) {
        renewTimer = addresInfo->T0;
    } else {
        renewTimer = 0;
    }

    return renewTimer;
}

dhcpv6_client_server_data_t *libdhcvp6_nontemporalAddress_server_data_allocate(int8_t interfaceId, uint8_t instanceId, uint8_t *duiId, uint16_t duiLinkType, uint8_t *nonTemporalPrefix, uint8_t *serverIPv6Address)
{
    uint32_t iaId;
    dhcpv6_client_server_data_t *new_entry = NULL;
    if (duiId) {
        //allocate new Entry
        iaId = libdhcpv6_IAID_generate();
        new_entry = libdhcvp6_nontemporalAddress_entry_allocate();
        if (new_entry) {
            new_entry->IAID = iaId;
            new_entry->interfaceId = interfaceId;
            new_entry->instanceId = instanceId;
            //save Cliet ID
            memcpy(new_entry->clientId, duiId, 8);
            new_entry->clientLinkIdType = duiLinkType;
            if (serverIPv6Address) {
                memcpy(new_entry->server_address, serverIPv6Address, 16);
                new_entry->useServerAddress = true;
            }
            if (nonTemporalPrefix) {
                uint8_t *ptr = new_entry->iaNontemporalAddress.addressPrefix;
                memcpy(ptr, nonTemporalPrefix, 8);
                memset((ptr + 8), 0, 8);
                new_entry->iaNonTemporalStructValid = true;
                new_entry->iaNontemporalAddress.preferredTime = 0;
                new_entry->iaNontemporalAddress.validLifetime = 0;
            }
            ns_list_add_to_end(&dhcpv6_client_nonTemporal_list, new_entry);
        }
    }
    return new_entry;
}

void libdhcvp6_nontemporalAddress_server_data_free(dhcpv6_client_server_data_t *removedEntry)
{
    if (removedEntry) {
        ns_list_remove(&dhcpv6_client_nonTemporal_list, removedEntry);
        ns_dyn_mem_free(removedEntry);
    }
}

dhcpv6_client_server_data_t *libdhcpv6_nonTemporal_entry_get_by_instance(uint8_t instanceId)
{
    ns_list_foreach(dhcpv6_client_server_data_t, cur, &dhcpv6_client_nonTemporal_list) {
        if (cur->instanceId == instanceId) {
            return cur;
        }
    }
    return NULL;
}
uint8_t libdhcpv6_nonTemporal_entry_get_unique_instance_id(void)
{
    uint8_t unique_id = 1;
    ns_list_foreach(dhcpv6_client_server_data_t, cur, &dhcpv6_client_nonTemporal_list) {
        if (cur->instanceId == unique_id) {
            unique_id++;
            cur = ns_list_get_first(&dhcpv6_client_nonTemporal_list);
        }
    }
    return unique_id;
}

dhcpv6_client_server_data_t *libdhcpv6_nonTemporal_entry_get_by_iaid(uint32_t iaId)
{
    ns_list_foreach(dhcpv6_client_server_data_t, cur, &dhcpv6_client_nonTemporal_list) {
        if (cur->IAID == iaId) {
            return cur;
        }
    }
    return NULL;
}

dhcpv6_client_server_data_t *libdhcpv6_nonTemporal_entry_get_by_transactionId(uint32_t txId)
{
    ns_list_foreach(dhcpv6_client_server_data_t, cur, &dhcpv6_client_nonTemporal_list) {
        if (cur->transActionId == txId) {
            return cur;
        }
    }
    return NULL;
}

dhcpv6_client_server_data_t *libdhcpv6_nonTemporal_entry_get_by_prefix(int8_t interfaceId, uint8_t *prefix)
{
    ns_list_foreach(dhcpv6_client_server_data_t, cur, &dhcpv6_client_nonTemporal_list) {
        if ((cur->interfaceId == interfaceId) && cur->iaNonTemporalStructValid) {
            if (memcmp(cur->iaNontemporalAddress.addressPrefix, prefix , 8) == 0) {
                return cur;
            }
        }
    }
    return NULL;
}

uint16_t libdhcpv6_duid_option_size(uint16_t linkType)
{
    uint16_t length = 8; // Type & Length header part *2
    if (linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE) {
        length += 8;
    } else {
        length += 6;
    }

    return length;
}

uint16_t libdhcpv6_client_data_option_size(uint16_t linkType)
{
    uint16_t optionLength = 4;
    optionLength += libdhcpv6_duid_option_size(linkType);
    optionLength += libdhcpv6_ia_address_option_size();
    optionLength += libdhcpv6_client_last_transaction_time_option_size();
    return optionLength;
}

uint16_t libdhcvp6_request_option_size(uint8_t optionCnt)
{
    uint16_t optionLength = 4;
    optionLength += 2 * optionCnt;
    return optionLength;
}

uint16_t libdhcpv6_non_temporal_address_size(bool addressDefined)
{
    uint16_t optionLength = 16;
    if (addressDefined) {
        optionLength += libdhcpv6_ia_address_option_size();
    }
    return optionLength;
}

int libdhcpv6_message_malformed_check(uint8_t *ptr, uint16_t data_len)
{
    uint8_t *dptr;
    uint16_t length;
    if (data_len > 4) {
        dptr = ptr + 4; //Skip Type & TXID
        data_len -= 4;
        while (data_len) {
            if (data_len >= 4) {

                length = common_read_16_bit(dptr + 2); //Skip Type
                dptr += 4;
                data_len -= 4;
                if (data_len >= length) {
                    data_len -= length;
                    dptr += length;
                }
            } else {
                return -1;
            }
        }
    }
    return 0;
}


/**
 * This Function write dhcpv6 basic header
 *
 * \param ptr pointer where header will be writed
 * \param msgType dhcpv6 message type
 * \param transActionId 24-bit unique Trasnaction ID
 *
 * return incremented pointer after write
 */
uint8_t *libdhcpv6_header_write(uint8_t *ptr, uint8_t msgType, uint32_t transActionId)
{
    *ptr++ = msgType;
    ptr = common_write_24_bit(transActionId, ptr);
    return ptr;
}

uint8_t *libdhcpv6_elapsed_time_option_write(uint8_t *ptr, uint16_t elapsedTime)
{
    //Elapsed time
    ptr = common_write_16_bit(DHCPV6_ELAPSED_TIME_OPTION, ptr);
    ptr = common_write_16_bit(DHCPV6_ELAPSED_TIME_OPTION_LEN, ptr);
    ptr = common_write_16_bit(elapsedTime, ptr);
    return ptr;
}

uint8_t *libdhcpv6_rapid_commit_option_write(uint8_t *ptr)
{
    ptr = common_write_16_bit(DHCPV6_OPTION_RAPID_COMMIT, ptr);
    ptr = common_write_16_bit(DHCPV6_OPTION_RAPID_COMMIT_LEN, ptr);
    return ptr;
}

uint8_t *libdhcvp6_vendor_specific_option_write(uint8_t *ptr, uint8_t *data, uint16_t dataLength)
{
    ptr = common_write_16_bit(DHCPV6_OPTION_VENDOR_SPECIFIC_INFO, ptr);
    ptr = common_write_16_bit(dataLength, ptr);
    memcpy(ptr, data, dataLength);
    ptr += dataLength;
    return ptr;
}

uint8_t *libdhcvp6_request_option_write(uint8_t *ptr, uint8_t optionCnt, uint16_t *optionPtr)
{
    uint16_t optionLength = libdhcvp6_request_option_size(optionCnt);
    ptr = common_write_16_bit(DHCPV6_OPTION_REQUEST_OPTION, ptr);
    ptr = common_write_16_bit((optionLength - 4), ptr);
    while (optionCnt) {
        ptr = common_write_16_bit(*optionPtr++, ptr);
        optionCnt--;
    }
    return ptr;
}

uint8_t *libdhcpv6_duid_option_write(uint8_t *ptr, uint16_t duidRole, const dhcp_link_options_params_t *duid)
{
    uint16_t length = libdhcpv6_duid_option_size(duid->linkType);

    length -= 4; //Cut normal option header out
    ptr = common_write_16_bit(duidRole, ptr);
    ptr = common_write_16_bit(length, ptr);
    ptr = common_write_16_bit(DHCPV6_DUID_LINK_LAYER_TYPE, ptr);
    ptr = common_write_16_bit(duid->linkType, ptr);
    length -= 4; //Cut normal option header out
    memcpy(ptr, duid->linkID, length);
    ptr += length;
    return ptr;
}

uint8_t *libdhcpv6_prefix_delegation_info_option_write(uint8_t *ptr, uint32_t iaId)
{
    ptr = common_write_16_bit(DHCPV6_OPTION_IA_PREFIX_DELEGATION, ptr);
    ptr = common_write_16_bit(DHCPV6_OPTION_IA_PREFIX_DELEGATION_MIN_LENGTH, ptr);
    ptr = common_write_32_bit(iaId, ptr);
    ptr = common_write_32_bit(0, ptr); //T1
    ptr = common_write_32_bit(0, ptr);//T2
    return ptr;
}

uint8_t *libdhcpv6_identity_association_option_write(uint8_t *ptr, uint32_t iaID, uint32_t TimerT1, uint32_t TimerT2, bool withAddress)
{
    uint16_t optionMsgLen = libdhcpv6_non_temporal_address_size(withAddress);

    ptr = common_write_16_bit(DHCPV6_IDENTITY_ASSOCIATION_OPTION, ptr);
    ptr = common_write_16_bit((optionMsgLen - 4), ptr);

    ptr = common_write_32_bit(iaID, ptr); //iaId
    ptr = common_write_32_bit(TimerT1, ptr); //T1
    ptr = common_write_32_bit(TimerT2, ptr);//T2
    return ptr;
}

uint8_t *libdhcpv6_identity_association_option_write_with_status(uint8_t *ptr, uint32_t iaID, uint32_t TimerT1, uint32_t TimerT2, uint16_t status)
{
    uint16_t optionMsgLen = libdhcpv6_non_temporal_address_size(false);

    optionMsgLen += 6; // add status option length

    ptr = common_write_16_bit(DHCPV6_IDENTITY_ASSOCIATION_OPTION, ptr);
    ptr = common_write_16_bit((optionMsgLen - 4), ptr);

    ptr = common_write_32_bit(iaID, ptr); //iaId
    ptr = common_write_32_bit(TimerT1, ptr); //T1
    ptr = common_write_32_bit(TimerT2, ptr);//T2
    ptr = libdhcpv6_status_code_write(ptr, status);
    return ptr;
}

uint8_t *libdhcpv6_ia_address_option_write(uint8_t *ptr, const uint8_t *addressPtr, uint32_t preferredValidLifeTime, uint32_t validLifeTime)
{
    ptr = common_write_16_bit(DHCPV6_IA_ADDRESS_OPTION, ptr);
    ptr = common_write_16_bit(DHCPV6_IA_ADDRESS_OPTION_LEN, ptr);
    memcpy(ptr, addressPtr, 16);
    ptr += 16;
    ptr = common_write_32_bit(preferredValidLifeTime, ptr); //Preferred
    ptr = common_write_32_bit(validLifeTime, ptr);//Valid
    return ptr;
}

uint8_t *libdhcpv6_status_code_write(uint8_t *ptr, uint16_t statusCode)
{
    ptr = common_write_16_bit(DHCPV6_STATUS_CODE_OPTION, ptr);
    ptr = common_write_16_bit(DHCPV6_STATUS_CODE_OPTION_LEN, ptr);
    ptr = common_write_16_bit(statusCode, ptr);
    return ptr;
}

uint8_t *libdhcpv6_client_last_transaction_time_option_write(uint8_t *ptr, uint32_t last_transaction_Time)
{

    uint16_t Length = libdhcpv6_client_last_transaction_time_option_size();
    ptr = common_write_16_bit(DHCPV6_OPTION_CLT_TIME, ptr);
    ptr = common_write_16_bit((Length - 4), ptr);
    ptr = common_write_32_bit(last_transaction_Time, ptr); //SET Last time we heard from this child either from mle or data packets.
    return ptr;
}

int libdhcpv6_message_option_discover(uint8_t *ptr, uint16_t data_len, uint16_t discovered_type, dhcp_options_msg_t *option_info)
{
    uint8_t *dptr;
    uint16_t type, length;
    dptr = ptr;
    if( data_len < 4 ){
        tr_warn("libdhcpv6_message_option_discover() data_len<4");
        return -1;
    }
    while (data_len >= 4) {
        type = common_read_16_bit(dptr);
        dptr += 2;
        length = common_read_16_bit(dptr);
        dptr += 2;
        data_len -= 4;
        if (data_len >= length) {
            if (type == discovered_type) {
                option_info->len = length;
                option_info->type = type;
                option_info->msg_ptr = dptr;
                return 0;
            }
            data_len -= length;
            dptr += length;
        } else {
            tr_warn("libdhcpv6_message_option_discover() data_len<length=%"PRIu16, length);
            break;
        }
    }
    return -1;
}

int libdhcpv6_compare_DUID(dhcp_link_options_params_t *targetId, dhcp_link_options_params_t *parsedId)
{
    if (targetId->linkType == parsedId->linkType) {
        uint8_t cmpLen;
        if (targetId->linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE) {
            //Compare Current Interface EUID64
            cmpLen = 8;
        } else {
            cmpLen = 6;
        }
        if (memcmp(targetId->linkID, parsedId->linkID, cmpLen) == 0) {
            return 0;
        }
    }
    return -1;
}

int libdhcpv6_reply_message_option_validate(dhcp_link_options_params_t *clientId, dhcp_link_options_params_t *serverId, dhcp_ia_non_temporal_params_t *dhcp_ia_non_temporal_params, uint8_t *ptr, uint16_t data_length)
{
    /**
     * Solication Message Should Include Next Options:
     *  - DHCPV6_SERVER_ID_OPTION
     *  - DHCPV6_CLIENT_ID_OPTION
     *  - DHCPV6_IDENTITY_ASSOCIATION_OPTION
     *
     */
    /** Verify Client ID */
    if (libdhcpv6_get_duid_by_selected_type_id_opt(ptr, data_length, DHCPV6_CLIENT_ID_OPTION , clientId) != 0) {
        return -1;
    }

    if (libdhcpv6_get_duid_by_selected_type_id_opt(ptr, data_length, DHCPV6_SERVER_ID_OPTION, serverId) != 0) {
        return -1;
    }

    if (libdhcpv6_get_IA_address(ptr, data_length, dhcp_ia_non_temporal_params) != 0) {
        return -1;
    }

    return 0;
}

int libdhcpv6_advertisment_message_option_validate(dhcp_link_options_params_t *clientId, dhcp_link_options_params_t *serverId, dhcp_ia_non_temporal_params_t *dhcp_ia_non_temporal_params, uint8_t *ptr, uint16_t data_length)
{
    /**
     * Solication Message Should Include Next Options:
     *  - DHCPV6_SERVER_ID_OPTION
     *  - DHCPV6_CLIENT_ID_OPTION
     *  - DHCPV6_IDENTITY_ASSOCIATION_OPTION
     *
     */
    /** Verify Client ID to own EUID64 */
    if (libdhcpv6_get_duid_by_selected_type_id_opt(ptr, data_length, DHCPV6_CLIENT_ID_OPTION , clientId) != 0) {
        return -1;
    }

    if (libdhcpv6_get_duid_by_selected_type_id_opt(ptr, data_length, DHCPV6_SERVER_ID_OPTION, serverId) != 0) {
        return -1;
    }

    if (libdhcpv6_get_IA_address(ptr, data_length, dhcp_ia_non_temporal_params) != 0) {
        return -1;
    }

    return 0;
}
#ifdef HAVE_DHCPV6_SERVER
int libdhcpv6_renew_message_options_validate(uint8_t *ptr, uint16_t data_length, dhcp_link_options_params_t *clientLinkData, dhcp_link_options_params_t *serverLinkData, dhcp_ia_non_temporal_params_t *dhcp_ia_non_temporal_params)
{

    /**
         * Renew Message Should Include Next Options:
         *  - DHCPV6_ELAPSED_TIME_OPTION
         *  - DHCPV6_CLIENT_ID_OPTION
         *  - DHCPV6_SERVER_ID_OPTION
         *  - DHCPV6_IDENTITY_ASSOCIATION_OPTION
         *  - DHCPV6_OPTION_REQUEST_OPTION
         * Optionally:
         *  - DHCPV6_OPTION_REQUEST_RAPID_COMMIT
         */

    /** Verify First DHCPV6_ELAPSED_TIME_OPTION */
    if (libdhcpv6_time_elapsed_option_at_packet(ptr, data_length) == false) {
        return -1;
    }
    /** Verify DHCPV6_CLIENT_ID_OPTION */
    if (libdhcpv6_get_duid_by_selected_type_id_opt(ptr, data_length, DHCPV6_CLIENT_ID_OPTION, clientLinkData) != 0) {
        return -1;
    }
    /** Verify DHCPV6_SERVER_ID_OPTION */
    if (libdhcpv6_get_duid_by_selected_type_id_opt(ptr, data_length, DHCPV6_SERVER_ID_OPTION, serverLinkData) != 0) {
        return -1;
    }

    /** Verify DHCPV6_IDENTITY_ASSOCIATION_OPTION */
    if (libdhcpv6_get_IA_address(ptr, data_length, dhcp_ia_non_temporal_params) != 0) {
        return -1;
    }

    return 0;
}



int libdhcpv6_solication_message_options_validate(uint8_t *ptr, uint16_t data_length, dhcp_link_options_params_t *clientLink, dhcp_ia_non_temporal_params_t *dhcp_ia_non_temporal_params)
{
    /**
     * Solication Message Should Include Next Options:
     *  - DHCPV6_ELAPSED_TIME_OPTION
     *  - DHCPV6_CLIENT_ID_OPTION
     *  - DHCPV6_IDENTITY_ASSOCIATION_OPTION
     *  - DHCPV6_OPTION_REQUEST_OPTION
     * Optionally:
     *  - DHCPV6_OPTION_REQUEST_RAPID_COMMIT
     */
    /** Verify First DHCPV6_ELAPSED_TIME_OPTION */

    if (libdhcpv6_time_elapsed_option_at_packet(ptr, data_length) == false) {
        return -1;
    }
    /** Verify DHCPV6_CLIENT_ID_OPTION */
    if (libdhcpv6_get_duid_by_selected_type_id_opt(ptr, data_length, DHCPV6_CLIENT_ID_OPTION, clientLink) != 0) {
        return -1;
    }


    /** Verify DHCPV6_IDENTITY_ASSOCIATION_OPTION */
    if (libdhcpv6_get_IA_address(ptr, data_length, dhcp_ia_non_temporal_params) != 0) {
        return -1;
    }

    return 0;
}
#endif

bool libdhcpv6_time_elapsed_option_at_packet(uint8_t *ptr, uint16_t length)
{
    bool retVal = false;
    dhcp_options_msg_t option_msg;
    /** Verify First DHCPV6_ELAPSED_TIME_OPTION */

    if (libdhcpv6_message_option_discover(ptr, length, DHCPV6_ELAPSED_TIME_OPTION, &option_msg) == 0) {
        if (option_msg.len == DHCPV6_ELAPSED_TIME_OPTION_LEN) {
            retVal = true;
        }
    }
    return retVal;
}

bool libdhcpv6_rapid_commit_option_at_packet(uint8_t *ptr, uint16_t length)
{
    bool retVal = false;
    dhcp_options_msg_t option_msg;
    if (libdhcpv6_message_option_discover(ptr, length, DHCPV6_OPTION_RAPID_COMMIT, &option_msg) == 0) {
        retVal = true;
    }
    return retVal;
}

int libdhcpv6_get_duid_by_selected_type_id_opt(uint8_t *ptr, uint16_t data_length, uint16_t type , dhcp_link_options_params_t *params)
{
    dhcp_options_msg_t option_msg;

    /** Verify DHCPV6_CLIENT_ID_OPTION */
    if (libdhcpv6_message_option_discover(ptr, data_length, type, &option_msg) == 0) {
        if (option_msg.len >= DHCPV6_SERVER_ID_MAC48_OPTION_LEN) {
            uint8_t *t_ptr = option_msg.msg_ptr;
            type = common_read_16_bit(t_ptr);
            t_ptr += 2;
            params->linkType = common_read_16_bit(t_ptr);
            t_ptr += 2;
            if (type == DHCPV6_DUID_LINK_LAYER_TYPE) {
                params->linkID = t_ptr;
                if ((params->linkType == DHCPV6_DUID_HARDWARE_EUI48_TYPE) && (option_msg.len == DHCPV6_SERVER_ID_MAC48_OPTION_LEN)) {
                    return 0;
                } else if ((params->linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE) && (option_msg.len == DHCPV6_SERVER_ID_MAC64_OPTION_LEN)) {
                    return 0;
                }
            }
        }
    }

    return -1;
}

int libdhcpv6_get_IA_address(uint8_t *ptr, uint16_t data_length, dhcp_ia_non_temporal_params_t *params)
{
    dhcp_options_msg_t option_msg;
    uint16_t status_code = 0;

    if (libdhcpv6_message_option_discover(ptr, data_length, DHCPV6_STATUS_CODE_OPTION, &option_msg) == 0) {
        if (option_msg.len >= DHCPV6_STATUS_CODE_OPTION_LEN) {
            status_code = common_read_16_bit(option_msg.msg_ptr);
            if (status_code == DHCPV6_STATUS_NO_ADDR_AVAILABLE_CODE) {
                return -1;
            }
        }
    }

    if (libdhcpv6_message_option_discover(ptr, data_length, DHCPV6_IDENTITY_ASSOCIATION_OPTION, &option_msg) == 0) {
        if (option_msg.len < DHCPV6_IDENTITY_ASSOCIATION_OPTION_MIN_LEN) {
            return -1;
        }
        uint8_t *t_ptr;
        uint16_t length;
        t_ptr = option_msg.msg_ptr;
        length = (option_msg.len - 12);
        params->iaId = common_read_32_bit(t_ptr);
        t_ptr += 4;
        params->T0 = common_read_32_bit(t_ptr);
        t_ptr += 4;
        params->T1 = common_read_32_bit(t_ptr);
        t_ptr += 4;

        if(length > 4) {
            if (libdhcpv6_message_option_discover(t_ptr, length, DHCPV6_STATUS_CODE_OPTION, &option_msg) == 0) {
                if (option_msg.len >= DHCPV6_STATUS_CODE_OPTION_LEN) {
                    status_code = common_read_16_bit(option_msg.msg_ptr);
                    if (status_code != 0) {
                        return -1;
                    }
                }
            }

            if (libdhcpv6_message_option_discover(t_ptr, length, DHCPV6_IA_ADDRESS_OPTION, &option_msg) == 0) {
                if (option_msg.len >= DHCPV6_IA_ADDRESS_OPTION_LEN) {
                    t_ptr = option_msg.msg_ptr;
                    params->nonTemporalAddress = t_ptr;
                    t_ptr += 16;
                    params->preferredValidLifeTime = common_read_32_bit(t_ptr);
                    t_ptr += 4;
                    params->validLifeTime = common_read_32_bit(t_ptr);
                    return 0;
                }
            }
        }
    }
    return -1;
}

uint16_t libdhcpv6_address_request_message_len(uint16_t clientLinkType, uint16_t serverLinkType, uint8_t requstOptionCnt)
{
    uint16_t length = 0;
    length += libdhcpv6_header_size();
    length += libdhcpv6_elapsed_time_option_size();
    length += libdhcpv6_duid_option_size(clientLinkType);
    length += libdhcpv6_duid_option_size(serverLinkType);
    length += libdhcvp6_request_option_size(requstOptionCnt);
    length += libdhcpv6_rapid_commit_option_size();
    length += libdhcpv6_non_temporal_address_size(true);
    return length;
}
#ifdef HAVE_DHCPV6_SERVER
uint16_t libdhcpv6_address_reply_message_len(uint16_t clientLinkType, uint16_t serverLinkType, uint16_t vendorDataLen, bool rapidCommon, bool status)
{
    uint16_t length = 0;

    length += libdhcpv6_header_size();
    length += libdhcpv6_duid_option_size(clientLinkType);
    length += libdhcpv6_duid_option_size(serverLinkType);
    if (rapidCommon) {
        length += libdhcpv6_rapid_commit_option_size();
    }

    if (vendorDataLen) {
        length += (vendorDataLen + 4);
    }

    if (status) {
        length += libdhcpv6_non_temporal_address_size(true);
    } else {
        length += libdhcpv6_non_temporal_address_size(false);
        length += libdhcpv6_status_option_size();
    }

    return length;
}
#endif

uint8_t *libdhcpv6_generic_nontemporal_address_message_write(uint8_t *ptr, dhcpv6_solication_base_packet_s *packet, dhcpv6_ia_non_temporal_address_s *nonTemporalAddress, dhcp_link_options_params_t *serverLink)
{
    bool add_address = false;
    if (nonTemporalAddress) {
        add_address = true;
    }
    //Start Build Packet
    ptr = libdhcpv6_header_write(ptr, packet->messageType, packet->transActionId);
    //Elapsed time
    ptr = libdhcpv6_elapsed_time_option_write(ptr, 0);
    //Client Identifier
    ptr = libdhcpv6_duid_option_write(ptr, DHCPV6_CLIENT_ID_OPTION, &packet->clientDUID); //16
    //SET Server ID if It is defined
    if (serverLink) {
        ptr = libdhcpv6_duid_option_write(ptr, DHCPV6_SERVER_ID_OPTION, serverLink);
    }

    //SET Server ID
    ptr = libdhcpv6_rapid_commit_option_write(ptr);
    //Request Option
    ptr = libdhcvp6_request_option_write(ptr, packet->requestedOptionCnt, packet->requestedOptionList);
    //CLient Identity Association

    ptr = libdhcpv6_identity_association_option_write(ptr, packet->iaID, packet->timerT0, packet->timerT1, add_address);
    if (add_address) {
        ptr = libdhcpv6_ia_address_option_write(ptr, nonTemporalAddress->requestedAddress, nonTemporalAddress->preferredLifeTime, nonTemporalAddress->validLifeTime);
    }

    return ptr;
}


uint8_t *libdhcpv6_reply_message_write(uint8_t *ptr, dhcpv6_reply_packet_s *replyPacket, dhcpv6_ia_non_temporal_address_s *nonTemporalAddress, dhcpv6_vendor_data_packet_s *vendorData)
{
    ptr = libdhcpv6_header_write(ptr, DHCPV6_REPLY_TYPE, replyPacket->transaction_ID);
    ptr = libdhcpv6_duid_option_write(ptr, DHCPV6_SERVER_ID_OPTION, &replyPacket->serverDUID); //16
    ptr = libdhcpv6_duid_option_write(ptr, DHCPV6_CLIENT_ID_OPTION, &replyPacket->clientDUID); //16

    if (nonTemporalAddress) {
        ptr = libdhcpv6_identity_association_option_write(ptr, replyPacket->iaId, replyPacket->T0, replyPacket->T1, true);
        ptr = libdhcpv6_ia_address_option_write(ptr, nonTemporalAddress->requestedAddress, nonTemporalAddress->preferredLifeTime, nonTemporalAddress->validLifeTime);
    } else {
        ptr = libdhcpv6_identity_association_option_write_with_status(ptr, replyPacket->iaId, replyPacket->T0, replyPacket->T1, DHCPV6_STATUS_NO_ADDR_AVAILABLE_CODE);
    }

    if (vendorData) {
        ptr = libdhcvp6_vendor_specific_option_write(ptr, vendorData->vendorData, vendorData->vendorDataLength);
    }

    if (replyPacket->rapidCommit) {
        ptr = libdhcpv6_rapid_commit_option_write(ptr);
    }

    return ptr;
}

uint16_t libdhcpv6_solication_message_length(uint16_t clientLinkType, bool addressDefined, uint8_t requestOptionCount)
{
    uint16_t length = 0;
    length += libdhcpv6_header_size();
    length += libdhcpv6_elapsed_time_option_size();
    length += libdhcpv6_rapid_commit_option_size();
    length += libdhcpv6_duid_option_size(clientLinkType);
    length += libdhcpv6_non_temporal_address_size(addressDefined);
    length += libdhcvp6_request_option_size(requestOptionCount);
    return length;
}

#endif
