/*
 * tlc.v -- tag-length-value
 *
 * (c) Copyright IBM Corporation 2018.
 *
 * Author: Stefan Berger <stefanb@us.ibm.com>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the names of the IBM Corporation nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include "tlv.h"
#include "logging.h"
#include "sys_dependencies.h"

#include <string.h>

#include <libtpms/tpm_library.h>
#include <libtpms/tpm_memory.h>
#include <libtpms/tpm_error.h>

void
tlv_data_free(tlv_data *td, size_t td_len)
{
    size_t i;

    for (i = 0; i < td_len; i++) {
        if (!td[i].is_const_ptr)
            free(td[i].u.ptr);
        memset(&td[i], 0, sizeof(*td));
    }
}

/*
 * tlv_data_append: append data in tlv_data array to a buffer
 * @buffer: pointer to a pointer to a buffer or NULL if new buffer
 * @buffer_len: length of existing buffer; will hold size of new buffer on return
 * @td: array of tlv_data
 * @td_len: length of td array
 */
TPM_RESULT
tlv_data_append(unsigned char **buffer, uint32_t *buffer_len,
                tlv_data *td, size_t td_len)
{
    size_t i;
    tlv_header tlv;
    uint64_t totlen;
    uint64_t addlen = 0;
    unsigned char *ptr;
    unsigned char *tmp;

    for (i = 0; i < td_len; i++)
        addlen += sizeof(tlv) + td[i].tlv.length;

    if (*buffer)
        totlen = *buffer_len + addlen;
    else
        totlen = addlen;

    if (totlen > 0xffffffff) {
        /* can only happen if tlv.length or *buffer_len were excessive */
        logprintf(STDERR_FILENO, "%s: Excessive buffer size error.\n", __func__);
        return TPM_FAIL;
    }

    tmp = realloc(*buffer, (size_t)totlen);
    if (!tmp) {
         logprintf(STDERR_FILENO, "Could not allocate %u bytes.\n", totlen);
         return TPM_FAIL;
    }
    *buffer = tmp;

    ptr = *buffer + *buffer_len;
    *buffer_len = totlen;

    for (i = 0; i < td_len; i++) {
        tlv.tag = htobe16(td[i].tlv.tag);
        tlv.length = htobe32(td[i].tlv.length);

        memcpy(ptr, &tlv, sizeof(tlv));
        ptr += sizeof(tlv);

        if (td[i].is_const_ptr)
            memcpy(ptr, td[i].u.const_ptr, td[i].tlv.length);
        else
            memcpy(ptr, td[i].u.ptr, td[i].tlv.length);
        ptr += td[i].tlv.length;
    }

    return 0;
}

/* tlv_data_find_tag: in a byte stream that starts with a tlv_header,
                      find a tlv_header with a given tag
 * @buffer: the buffer to search; must start with a tlv_header
 * @buffer_len: the length of the buffer
 * @tag: the tag to search for
 * @td: tlv_data pointer to receive the result in,
 *
 * Returns NULL if nothing was found, the pointer to the data corresponding
 * to the tag otherwise.
 */
const unsigned char *
tlv_data_find_tag(const unsigned char *buffer, uint32_t buffer_len,
                  uint16_t tag, tlv_data *td)
{
    uint64_t offset = 0; /* uint64_t to prevent integer overflow */

    while (offset < buffer_len) {
        if (offset + sizeof(td->tlv) > buffer_len)
            return NULL;

        memcpy(&td->tlv, buffer + offset, sizeof(td->tlv));
        offset += sizeof(td->tlv);

        td->tlv.length = be32toh(td->tlv.length);
        if (offset + td->tlv.length > buffer_len)
            return NULL;

        td->tlv.tag = be16toh(td->tlv.tag);
        if (td->tlv.tag == tag) {
            td->is_const_ptr = true;
            td->u.const_ptr = &buffer[offset];
            return buffer;
        }
        offset += td->tlv.length;
    }
    return NULL;
}

