/********************************************************************************/
/*                                                                              */
/*                              Safe Storage Buffer                             */
/*                           Written by Ken Goldman                             */
/*                     IBM Thomas J. Watson Research Center                     */
/*            $Id: tpm_store.c 4668 2012-01-25 21:16:48Z kgoldman $             */
/*                                                                              */
/* (c) Copyright IBM Corporation 2006, 2010.					*/
/*										*/
/* 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.		*/
/********************************************************************************/

/* Generally useful utilities to serialize structures to a stream */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "tpm_commands.h"
#include "tpm_constants.h"
#include "tpm_crypto.h"
#include "tpm_debug.h"
#include "tpm_error.h"
#include "tpm_memory.h"
#include "tpm_process.h"

#include "tpm_store.h"

/*
  ->buffer;             beginning of buffer
  ->buffer_current;     first empty position in buffer
  ->buffer_end;         one past last valid position in buffer
*/

/* local prototypes */

static void       TPM_Sbuffer_AdjustParamSize(TPM_STORE_BUFFER *sbuffer);
static TPM_RESULT TPM_Sbuffer_AdjustReturnCode(TPM_STORE_BUFFER *sbuffer, TPM_RESULT returnCode);


/* TPM_Sbuffer_Init() sets up a new serialize buffer.  It should be called before the first use. */

void TPM_Sbuffer_Init(TPM_STORE_BUFFER *sbuffer)
{
    sbuffer->buffer = NULL;
    sbuffer->buffer_current = NULL;
    sbuffer->buffer_end = NULL;
}

/* TPM_Sbuffer_Load() loads TPM_STORE_BUFFER that has been serialized using
   TPM_Sbuffer_AppendAsSizedBuffer(), as a size plus stream.
*/

TPM_RESULT TPM_Sbuffer_Load(TPM_STORE_BUFFER *sbuffer,
                            unsigned char **stream,
                            uint32_t *stream_size)
{
    TPM_RESULT  rc = 0;
    uint32_t length;

    /* get the length of the stream to be loaded */
    if (rc == 0) {
        rc = TPM_Load32(&length, stream, stream_size);
    }
    /* check stream_size */
    if (rc == 0) {
        if (*stream_size < length) {
            printf("TPM_Sbuffer_Load: Error, stream_size %u less than %u\n",
                   *stream_size, length);
            rc = TPM_BAD_PARAM_SIZE;
        }
    }
    if (rc == 0) {
        rc = TPM_Sbuffer_Append(sbuffer, *stream, length);
        *stream += length;
        *stream_size -= length;
    }    
    return rc;
}

/* TPM_Sbuffer_Store() cannot simply store the elements, as they are pointers.  Rather, the
   TPM_Sbuffer_AppendAsSizedBuffer() function is used.
*/

/* TPM_Sbuffer_Delete() frees an existing buffer and reinitializes it.  It must be called when a
   TPM_STORE_BUFFER is no longer required, to avoid a memory leak.  The buffer can be reused, but in
   that case TPM_Sbuffer_Clear would be a better choice. */

void TPM_Sbuffer_Delete(TPM_STORE_BUFFER *sbuffer)
{
    free(sbuffer->buffer);
    TPM_Sbuffer_Init(sbuffer);
}

/* TPM_Sbuffer_Clear() removes all data from an existing buffer, allowing reuse.  Memory is NOT
   freed. */

void TPM_Sbuffer_Clear(TPM_STORE_BUFFER *sbuffer)
{
    sbuffer->buffer_current = sbuffer->buffer;
    return;
}

/* TPM_Sbuffer_Get() gets the resulting byte buffer and its size. */

void TPM_Sbuffer_Get(TPM_STORE_BUFFER *sbuffer,
                     const unsigned char **buffer,
                     uint32_t *length)
{
    *length = sbuffer->buffer_current - sbuffer->buffer;
    *buffer = sbuffer->buffer;
    return;
}

/* TPM_Sbuffer_GetAll() gets the resulting byte buffer and its size, as well as the total size. */

void TPM_Sbuffer_GetAll(TPM_STORE_BUFFER *sbuffer,
			unsigned char **buffer,
			uint32_t *length,
			uint32_t *total)
{
    *length = sbuffer->buffer_current - sbuffer->buffer;
    *total = sbuffer->buffer_end - sbuffer->buffer;
    *buffer = sbuffer->buffer;
    return;
}

/* TPM_Sbuffer_Set() creates a TPM_STORE_BUFFER from

   'buffer' - pointer to a buffer that was allocated (can be NULL)

   'total' - the total number of allocated bytes (ignored if buffer is NULL)

   'length' - the number of valid bytes in buffer (ignored if buffer is NULL, can be 0, cannot be
   greater than total.
*/

TPM_RESULT TPM_Sbuffer_Set(TPM_STORE_BUFFER *sbuffer,
			   unsigned char *buffer,
			   const uint32_t length,
			   const uint32_t total)
{
    TPM_RESULT rc = 0;

    if (rc == 0) {
	if (sbuffer == NULL) {
	    printf("TPM_Sbuffer_Set: Error (fatal), sbuffer is NULL\n");
	    rc = TPM_FAIL;
	}
    }
    if (rc == 0) {
	if (buffer != NULL) {
	    if (rc == 0) {
		if (length > total) {
		    printf("TPM_Sbuffer_Set: Error (fatal), length %u > total %u\n",
			   length, total);
		    rc = TPM_FAIL;
		}
	    }
	    if (rc == 0) {
		sbuffer->buffer = buffer;
		sbuffer->buffer_current = buffer + length;
		sbuffer->buffer_end = buffer + total;
	    }
	}
	else {	/* buffer == NULL */
	    sbuffer->buffer = NULL;
	    sbuffer->buffer_current = NULL;
	    sbuffer->buffer_end = NULL;
	}
    }
    return rc;
}

/* TPM_Sbuffer_Append() is the basic function to append 'data' of size 'data_length' to the
   TPM_STORE_BUFFER

   Returns 0 if success, TPM_SIZE if the buffer cannot be allocated.
*/

TPM_RESULT TPM_Sbuffer_Append(TPM_STORE_BUFFER *sbuffer,
                              const unsigned char *data,
                              size_t data_length)
{
    TPM_RESULT  rc = 0;
    size_t free_length;         /* length of free bytes in current buffer */
    size_t current_size;        /* size of current buffer */
    size_t current_length;      /* bytes in current buffer */
    size_t new_size;            /* size of new buffer */
    
    /* can data fit? */
    if (rc == 0) {
        /* cast safe as end is always greater than current */
        free_length = (size_t)(sbuffer->buffer_end - sbuffer->buffer_current);
        /* if data cannot fit in buffer as sized */
        if (free_length < data_length) {
            /* This test will fail long before the add uint32_t overflow */
            if (rc == 0) {
                /* cast safe as current is always greater than start */
                current_length = (size_t)(sbuffer->buffer_current - sbuffer->buffer);
                if ((current_length + data_length) > TPM_ALLOC_MAX) {
                    printf("TPM_Sbuffer_Append: "
                           "Error, size %lu + %lu greater than maximum allowed\n",
                           (unsigned long)current_length, (unsigned long)data_length);
                    rc = TPM_SIZE;
                }
            }
            if (rc == 0) {
                /* cast safe as end is always greater than start */
                current_size = (size_t)(sbuffer->buffer_end - sbuffer->buffer);
                /* optimize realloc's by rounding up data_length to the next increment */
                new_size = current_size +       /* currently used */
                           ((((data_length - 1)/TPM_STORE_BUFFER_INCREMENT) + 1) *
                            TPM_STORE_BUFFER_INCREMENT);
                /* but not greater than maximum buffer size */
                if (new_size > TPM_ALLOC_MAX) {
                    new_size = TPM_ALLOC_MAX;
                }
                printf("   TPM_Sbuffer_Append: data_length %lu, growing from %lu to %lu\n",
                       (unsigned long)data_length,
                       (unsigned long)current_size,
                       (unsigned long)new_size);
                rc = TPM_Realloc(&(sbuffer->buffer), new_size);
            }
            if (rc == 0) {
                sbuffer->buffer_end = sbuffer->buffer + new_size;       /* end */
                sbuffer->buffer_current = sbuffer->buffer + current_length; /* new empty position */
            }
        }
    }
    /* append the data */
    if (rc == 0) {
        memcpy(sbuffer->buffer_current, data, data_length);
        sbuffer->buffer_current += data_length;
    }
    return rc;
}

/* TPM_Sbuffer_Append8() is a special append that appends a uint8_t
 */

TPM_RESULT TPM_Sbuffer_Append8(TPM_STORE_BUFFER *sbuffer, uint8_t data)
{
    TPM_RESULT  rc = 0;

    rc = TPM_Sbuffer_Append(sbuffer, (const unsigned char *)(&data), sizeof(uint8_t));
    return rc;
}

/* TPM_Sbuffer_Append16() is a special append that converts a uint16_t to big endian (network byte
   order) and appends. */

TPM_RESULT TPM_Sbuffer_Append16(TPM_STORE_BUFFER *sbuffer, uint16_t data)
{
    TPM_RESULT  rc = 0;

    uint16_t ndata = htons(data);
    rc = TPM_Sbuffer_Append(sbuffer, (const unsigned char *)(&ndata), sizeof(uint16_t));
    return rc;
}

/* TPM_Sbuffer_Append32() is a special append that converts a uint32_t to big endian (network byte
   order) and appends. */

TPM_RESULT TPM_Sbuffer_Append32(TPM_STORE_BUFFER *sbuffer, uint32_t data)
{
    TPM_RESULT  rc = 0;

    uint32_t ndata = htonl(data);
    rc = TPM_Sbuffer_Append(sbuffer, (const unsigned char *)(&ndata), sizeof(uint32_t));
    return rc;
}

/* TPM_Sbuffer_AppendAsSizedBuffer() appends the source to the destination using the
   TPM_SIZED_BUFFER idiom.  That is, for a uint32_t size is stored.  Then the data is stored.

   Use this function when the stream is not self-describing and a size must be prepended.
*/

TPM_RESULT TPM_Sbuffer_AppendAsSizedBuffer(TPM_STORE_BUFFER *destSbuffer,
                                           TPM_STORE_BUFFER *srcSbuffer)
{
    TPM_RESULT  rc = 0;
    const unsigned char *buffer;
    uint32_t length;
    
    if (rc == 0) {
        TPM_Sbuffer_Get(srcSbuffer, &buffer, &length);
        rc = TPM_Sbuffer_Append32(destSbuffer, length);
    }
    if (rc == 0) {
        rc = TPM_Sbuffer_Append(destSbuffer, buffer, length);
    }
    return rc;
}

/* TPM_Sbuffer_AppendSBuffer() appends the source to the destination.  The size is not prepended, so
   the stream must be self-describing.
*/

TPM_RESULT TPM_Sbuffer_AppendSBuffer(TPM_STORE_BUFFER *destSbuffer,
                                     TPM_STORE_BUFFER *srcSbuffer)
{
    TPM_RESULT  rc = 0;
    const unsigned char *buffer;
    uint32_t length;
    
    if (rc == 0) {
        TPM_Sbuffer_Get(srcSbuffer, &buffer, &length);
        rc = TPM_Sbuffer_Append(destSbuffer, buffer, length);
    }
    return rc;
}

/* TPM_Sbuffer_StoreInitialResponse() is a special purpose append specific to a TPM response.

   It appends the first 3 standard response parameters:
        - response_tag
        - parameter size
        - return code
        
   For some TPM commands, this is the entire response.  Other times, additional parameters 
   will be appended.  See TPM_Sbuffer_StoreFinalResponse().

   Returns:
        0 success
        TPM_SIZE response could not fit in buffer
*/

TPM_RESULT TPM_Sbuffer_StoreInitialResponse(TPM_STORE_BUFFER *response,
                                            TPM_TAG request_tag,
                                            TPM_RESULT returnCode)
{
    TPM_RESULT  rc = 0;
    TPM_TAG     response_tag;
    
    printf(" TPM_Sbuffer_StoreInitialResponse: returnCode %08x\n", returnCode);
    if (rc == 0) {
        if (request_tag == TPM_TAG_RQU_COMMAND) {
            response_tag = TPM_TAG_RSP_COMMAND;
        }
        else if (request_tag == TPM_TAG_RQU_AUTH1_COMMAND) {
            response_tag = TPM_TAG_RSP_AUTH1_COMMAND;
        }
        else if (request_tag == TPM_TAG_RQU_AUTH2_COMMAND) {
            response_tag = TPM_TAG_RSP_AUTH2_COMMAND;
        }
        /* input tag error, returnCode is handled by caller TPM_CheckRequestTag() */
        else {
            response_tag = TPM_TAG_RSP_COMMAND;
        }
    }
    /* tag */
    if (rc == 0) {
        rc = TPM_Sbuffer_Append16(response, response_tag);
    }
    /* paramSize */
    if (rc == 0) {
        rc = TPM_Sbuffer_Append32(response,
				  sizeof(TPM_TAG) + sizeof(uint32_t) + sizeof(TPM_RESULT));
    }
    /* returnCode */    
    if (rc == 0) {
        rc = TPM_Sbuffer_Append32(response, returnCode);
    }
    return rc;
}

/* TPM_Sbuffer_StoreFinalResponse() is a special purpose append specific to a TPM response.

   It is used after TPM_Sbuffer_StoreInitialResponse() and all additional parameters are appended.

   1 - If the additional parameters were successfully appended, this function adjusts the
   preliminary parameter size set by TPM_Sbuffer_StoreInitialResponse() to reflect the additional
   appends.

   2 - If there was a failure during the additional appends, this function adjusts the return code
   and removes the additional appends.
*/

TPM_RESULT TPM_Sbuffer_StoreFinalResponse(TPM_STORE_BUFFER *sbuffer,
                                          TPM_RESULT returnCode,
                                          tpm_state_t *tpm_state)
{
    TPM_RESULT  	rc = 0;
    const unsigned char *buffer;
    uint32_t 		length;

    printf(" TPM_Sbuffer_StoreFinalResponse: returnCode %08x\n", returnCode);
    /* determine whether the response would exceed the output buffer size */
    TPM_Sbuffer_Get(sbuffer, &buffer, &length);
    if (length > TPM12_GetBufferSize()) {
	printf("TPM_Sbuffer_StoreFinalResponse: Error, response buffer %u exceeds max %u\n",
	       length, TPM12_GetBufferSize());
	returnCode = TPM_SIZE;
    }
    if (returnCode == TPM_SUCCESS) {
        TPM_Sbuffer_AdjustParamSize(sbuffer);
    }
    else {
        /* TPM_FAIL is reserved for "should never occur" errors that indicate a software or hardware
           failure */
        if ((returnCode == TPM_FAIL) && (tpm_state != NULL)) {
	    printf("  TPM_Sbuffer_StoreFinalResponse: Set testState to %u \n",
		   TPM_TEST_STATE_FAILURE);
            tpm_state->testState = TPM_TEST_STATE_FAILURE;
        }
        rc = TPM_Sbuffer_AdjustReturnCode(sbuffer, returnCode);
    }
    return rc;
}

/* TPM_Sbuffer_AdjustParamSize() is a special purpose function to go back and adjust the response
   paramSize after the response buffer is complete
*/

static void TPM_Sbuffer_AdjustParamSize(TPM_STORE_BUFFER *sbuffer)
{
    uint32_t paramSize;   /* the correct paramsize */
    uint32_t nParamSize;  /* the correct paramsize, in network byte order */
    uint32_t paramSizeOffset;
    
    /* actual size */
    paramSize = sbuffer->buffer_current - sbuffer->buffer;
    paramSizeOffset = sizeof(TPM_TAG);
    nParamSize = htonl(paramSize);      /* network byte order */
    /* overwrite the original size */
    memcpy(sbuffer->buffer + paramSizeOffset, &nParamSize, sizeof(uint32_t));
    return;
}

/* TPM_Sbuffer_AdjustReturnCode() is a special function to go back and adjust the response tag and
   returnCode if there was a failure while appending the rest of the parameters.

   This should never fail, because sbuffer was allocated during TPM_Sbuffer_StoreInitialResponse().
*/

static TPM_RESULT TPM_Sbuffer_AdjustReturnCode(TPM_STORE_BUFFER *sbuffer, TPM_RESULT returnCode)
{
    TPM_RESULT  rc = 0;
    
    if (rc == 0) {
        /* erase the previous result without freeing the buffer */
        sbuffer->buffer_current = sbuffer->buffer;
        /* error tag */
        rc = TPM_Sbuffer_Append16(sbuffer, TPM_TAG_RSP_COMMAND);
    }
    /* paramSize */
    if (rc == 0) {
        rc = TPM_Sbuffer_Append32(sbuffer, sizeof(TPM_TAG) + sizeof(uint32_t) + sizeof(TPM_RESULT));
    }
    /* returnCode */    
    if (rc == 0) {
        rc = TPM_Sbuffer_Append32(sbuffer, returnCode);
    }
    return rc;
}

/* Test appending to the TPM_STORE_BUFFER up to the limit */

TPM_RESULT TPM_Sbuffer_Test(void)
{
    TPM_RESULT  rc = 0;
    TPM_STORE_BUFFER sbuffer;
    size_t total_count;
    unsigned char count;
    unsigned char data[256];    /* dummy data */
    
    printf(" TPM_Sbuffer_Test:\n");
    TPM_Sbuffer_Init(&sbuffer);
    total_count = 0;
    while ((total_count != TPM_ALLOC_MAX) && rc == 0) {
        if (rc == 0) {
            rc = TPM_Random(&count, 1);
        }
        if (rc == 0) {
            printf(" TPM_Sbuffer_Test: count %u\n", count);
            /* last time through */
            if (total_count + count > TPM_ALLOC_MAX) {
                count = TPM_ALLOC_MAX - total_count;
            }
            rc = TPM_Sbuffer_Append(&sbuffer,data, count);
        }
        if (rc == 0) {
            total_count += count;
        }
        printf(" TPM_Sbuffer_Test: total_count %lu\n", (unsigned long)total_count);
    }
    TPM_Sbuffer_Delete(&sbuffer);
    return rc;
}

/* type to byte stream */
void STORE32(unsigned char *buffer, unsigned int offset, uint32_t value)
{
    buffer[offset + 0] = value >> 24;
    buffer[offset + 1] = value >> 16;
    buffer[offset + 2] = value >>  8;
    buffer[offset + 3] = value >>  0;
}

void STORE16(unsigned char *buffer, unsigned int offset, uint16_t value)
{
    buffer[offset + 0] = value >> 8;
    buffer[offset + 1] = value >> 0;
}

void STORE8(unsigned char *buffer, unsigned int offset, uint8_t value)

{
    buffer[offset + 0] = value >> 0;
}

/* TPM_Bitmap_Load() is a safe loading of a TPM_BOOL from a bitmap.

   If 'pos' is >= 32, the function fails.
   TPM_BOOL is TRUE. if The bit at pos is set
   'pos' is incremented after the load.
*/
   
TPM_RESULT TPM_Bitmap_Load(TPM_BOOL *tpm_bool,
			   uint32_t tpm_bitmap,
			   uint32_t *pos)
{
    TPM_RESULT  rc = 0;
    
    if (rc == 0) {
        if ((*pos) >= (sizeof(uint32_t) * CHAR_BIT)) {
            printf("TPM_Bitmap_Load: Error (fatal), loading from position %u\n", *pos);
            rc = TPM_FAIL;      /* should never occur */
        }
    }   
    if (rc == 0) {
        *tpm_bool = (tpm_bitmap & (1 << (*pos))) != 0;
	(*pos)++;
    }
    return rc;
}
 
/* TPM_Bitmap_Store() is a safe storing of a TPM_BOOL into a bitmap.

   If 'pos' is >= 32, the function fails.
   The bit at pos is set if the TPM_BOOL is TRUE.
   'pos' is incremented after the store.
*/
   
TPM_RESULT TPM_Bitmap_Store(uint32_t *tpm_bitmap,
			    TPM_BOOL tpm_bool,
			    uint32_t *pos)
{
    TPM_RESULT  rc = 0;
    
    if (rc == 0) {
        if ((*pos) >= (sizeof(uint32_t) * CHAR_BIT)) {
            printf("TPM_Bitmap_Store: Error (fatal), storing to position %u\n", *pos);
            rc = TPM_FAIL;      /* should never occur */
        }
    }   
    if (rc == 0) {
        if (tpm_bool) {
            *tpm_bitmap |= (1 << (*pos));
        }
        (*pos)++;
    }   
    return rc;
}
 
