/*
 * Copyright (c) 2014-2019, 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));
    uint8_t *temporary_duid = ns_dyn_mem_alloc(16); //Support DUID-LL, DUID-LLP and DUID-UUID  by default
    if (!newEntry || !temporary_duid) {
        ns_dyn_mem_free(newEntry);
        ns_dyn_mem_free(temporary_duid);
        return NULL;
    }

    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;
    newEntry->dyn_server_duid_length = 16;
    newEntry->serverDynamic_DUID = temporary_duid;
    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 *nonTemporalPrefix, uint8_t *serverIPv6Address)
{
    uint32_t iaId;
    uint8_t *ptr;
    dhcpv6_client_server_data_t *new_entry = NULL;
    //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;
        new_entry->serverDUID.duid = NULL;
        new_entry->serverDUID.duid_length = 0;

        if (serverIPv6Address) {
            memcpy(new_entry->server_address, serverIPv6Address, 16);
            new_entry->useServerAddress = true;
        }
        if (nonTemporalPrefix) {
            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->serverDynamic_DUID);
        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) {
            if (memcmp(cur->iaNontemporalAddress.addressPrefix, prefix, 8) == 0) {
                return cur;
            }
        }
    }
    return NULL;
}

dhcpv6_client_server_data_t *libdhcpv6_nonTemporal_validate_class_pointer(void *class_ptr)
{
    ns_list_foreach(dhcpv6_client_server_data_t, cur, &dhcpv6_client_nonTemporal_list) {
        if (cur == class_ptr) {
            return cur;
        }
    }
    return NULL;
}


uint16_t libdhcpv6_duid_option_size(uint16_t duidLength)
{
    return duidLength + 6; //ID Type 2, length 2 ,Duid Type 2 + duid data
}

uint8_t libdhcpv6_duid_linktype_size(uint16_t linkType)
{
    if (linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE ||
            linkType == DHCPV6_DUID_HARDWARE_IEEE_802_NETWORKS_TYPE) {
        return 8;
    }

    return 6;
}

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_duid_options_params_t *duid)
{
    uint16_t length = duid->duid_length + 2;
    ptr = common_write_16_bit(duidRole, ptr);
    ptr = common_write_16_bit(length, ptr);
    ptr = common_write_16_bit(duid->type, ptr);
    memcpy(ptr, duid->duid, duid->duid_length);
    ptr += duid->duid_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_duid_options_params_t *targetId, dhcp_duid_options_params_t *parsedId)
{
    if (targetId->type != parsedId->type) {
        return -1;
    }

    if (targetId->duid_length != parsedId->duid_length) {
        return -1;
    }

    if (memcmp(targetId->duid, parsedId->duid, targetId->duid_length) != 0) {
        return -1;
    }
    return 0;
}

int libdhcpv6_reply_message_option_validate(dhcp_duid_options_params_t *clientId, dhcp_duid_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_duid_options_params_t *clientId, dhcp_duid_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_duid_options_params_t *clientLinkData, dhcp_duid_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_duid_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;
}

bool libdhcpv6_duid_length_validate(uint16_t duid_type, uint16_t duid_length)
{
    uint16_t min_length;
    switch (duid_type) {
        case DHCPV6_DUID_LINK_LAYER_PLUS_TIME_TYPE:
            //hardware type (16 bits) + time (32 bits) +link layer address (variable length 1 min)
            min_length = 7; //Link Time Time
            break;
        case DHCPV6_DUID_EN_TYPE: //enterprise-number (32-bits) +identifier (variable length 1 min)
            min_length = 5;
            break;
        case DHCPV6_DUID_LINK_LAYER_TYPE:
            //hardware type (16 bits)  + link layer address (variable length 1 min)
            min_length = 3; //Type 2 and MiN DUI-id 1
            break;

        case DHCPV6_DUID_UUID_TYPE:
            //UUID (128-bits)
            if (duid_length != 16) {
                return false;
            }
            min_length = 16;
            break;

        default://Unsupported type set length to inpossible
            min_length = 0xffff;
            break;
    }

    //Validate min and MAX length
    if (min_length > duid_length || min_length == 0xffff) {
        //Too short
        return false;
    }

    if (duid_length > 128 - min_length) {
        //Too Long
        return false;
    }

    return true;
}


int libdhcpv6_get_duid_by_selected_type_id_opt(uint8_t *ptr, uint16_t data_length, uint16_t type, dhcp_duid_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) {
        return -1;
    }

    if (option_msg.len < 5) {
        return -1;
    }

    uint8_t *t_ptr = option_msg.msg_ptr;
    params->type = common_read_16_bit(t_ptr);
    t_ptr += 2;
    params->duid = t_ptr;
    params->duid_length = option_msg.len - 2;
    //Validate types and lengths
    if (!libdhcpv6_duid_length_validate(params->type, params->duid_length)) {
        return -1;
    }

    return 0;
}

int libdhcpv6_get_link_address_from_duid(uint8_t *ptr, uint16_t data_length, uint16_t type, dhcp_link_options_params_t *params)
{

    if ((type != DHCPV6_DUID_LINK_LAYER_TYPE && type != DHCPV6_DUID_LINK_LAYER_PLUS_TIME_TYPE) || data_length < 8) {
        return -1;
    }

    params->link_type = common_read_16_bit(ptr);
    ptr += 2;
    data_length -= 2;
    if (type == DHCPV6_DUID_LINK_LAYER_PLUS_TIME_TYPE) {
        params->link_time = common_read_32_bit(ptr);
        ptr += 4;
        data_length -= 4;
    } else {
        params->link_time = 0;
    }
    if (libdhcpv6_duid_linktype_size(params->link_type) > data_length) {
        return -1;
    }

    params->link_id = ptr;
    return 0;
}


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;
                }
            }
        } else if (length == 0) {
            params->nonTemporalAddress = NULL;
            params->preferredValidLifeTime = 0;
            params->validLifeTime = 0;
            return 0;
        }
    }
    return -1;
}

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

    length += libdhcpv6_header_size();
    length += libdhcpv6_duid_option_size(clientDUIDLength);
    length += libdhcpv6_duid_option_size(serverDUIDLength);
    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_duid_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 clientDUIDLength, 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(clientDUIDLength);
    length += libdhcpv6_non_temporal_address_size(addressDefined);
    length += libdhcvp6_request_option_size(requestOptionCount);
    return length;
}


uint8_t *libdhcpv6_dhcp_relay_msg_write(uint8_t *ptr, uint8_t type, uint8_t hop_limit,  uint8_t *peer_addres, uint8_t *link_address)
{
    *ptr++ = type;
    *ptr++ = hop_limit;
    memcpy(ptr, link_address, 16);
    ptr += 16;
    memcpy(ptr, peer_addres, 16);
    ptr += 16;
    return ptr;
}

uint8_t *libdhcpv6_dhcp_option_header_write(uint8_t *ptr, uint16_t length)
{
    ptr = common_write_16_bit(DHCPV6_OPTION_RELAY, ptr);
    ptr = common_write_16_bit(length, ptr);
    return ptr;
}

bool libdhcpv6_relay_msg_read(uint8_t *ptr, uint16_t length, dhcpv6_relay_msg_t *relay_msg)
{
    if (length < DHCPV6_RELAY_LENGTH + 4) {
        return false;
    }
    // Relay message base first
    relay_msg->type = *ptr++;
    relay_msg->hop_limit = *ptr++;
    relay_msg->link_address = ptr;
    relay_msg->peer_address = ptr + 16;
    ptr += 32;
    //Discover
    if (libdhcpv6_message_option_discover(ptr, length - 34, DHCPV6_OPTION_RELAY, &relay_msg->relay_options) != 0) {
        return false;
    }


    return true;
}

#endif
