/* -*- tab-width: 4; -*- */1/* vi: set sw=2 ts=4 expandtab: */23/*4* Copyright 2010-2020 The Khronos Group Inc.5* SPDX-License-Identifier: Apache-2.06*/78/**9* @file10* @~English11*12* @brief Implementation of ktxStream for memory.13*14* @author Maksim Kolesin, Under Development15* @author Georg Kolling, Imagination Technology16* @author Mark Callow, HI Corporation17*/1819#include <assert.h>20#include <string.h>21#include <stdlib.h>2223#include "ktx.h"24#include "ktxint.h"25#include "memstream.h"2627/**28* @brief Default allocation size for a ktxMemStream.29*/30#define KTX_MEM_DEFAULT_ALLOCATED_SIZE 2563132/**33* @brief Structure to store information about data allocated for ktxMemStream.34*/35struct ktxMem36{37const ktx_uint8_t* robytes;/*!< pointer to read-only data */38ktx_uint8_t* bytes; /*!< pointer to rw data. */39ktx_size_t alloc_size; /*!< allocated size of the memory block. */40ktx_size_t used_size; /*!< bytes used. Effectively the write position. */41ktx_off_t pos; /*!< read/write position. */42};4344static KTX_error_code ktxMem_expand(ktxMem* pMem, const ktx_size_t size);4546/**47* @brief Initialize a ktxMem struct for read-write.48*49* Memory for the stream data is allocated internally but the50* caller is responsible for freeing the memory. A pointer to51* the memory can be obtained with ktxMem_getdata().52*53* @sa ktxMem_getdata.54*55* @param [in] pMem pointer to the @c ktxMem to initialize.56*/57static KTX_error_code58ktxMem_construct(ktxMem* pMem)59{60pMem->pos = 0;61pMem->alloc_size = 0;62pMem->robytes = 0;63pMem->bytes = 0;64pMem->used_size = 0;65return ktxMem_expand(pMem, KTX_MEM_DEFAULT_ALLOCATED_SIZE);66}6768/**69* @brief Create & initialize a ktxMem struct for read-write.70*71* @sa ktxMem_construct.72*73* @param [in,out] ppMem pointer to the location in which to return74* a pointer to the newly created @c ktxMem.75*76* @return KTX_SUCCESS on success, KTX_OUT_OF_MEMORY on error.77*78* @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient pMemory.79*/80static KTX_error_code81ktxMem_create(ktxMem** ppMem)82{83ktxMem* pNewMem = (ktxMem*)malloc(sizeof(ktxMem));84if (pNewMem) {85KTX_error_code result = ktxMem_construct(pNewMem);86if (result == KTX_SUCCESS)87*ppMem = pNewMem;88return result;89}90else {91return KTX_OUT_OF_MEMORY;92}93}9495/**96* @brief Initialize a ktxMem struct for read-only.97*98* @param [in] pMem pointer to the @c ktxMem to initialize.99* @param [in] bytes pointer to the data to be read.100* @param [in] numBytes number of bytes of data.101*/102static void103ktxMem_construct_ro(ktxMem* pMem, const void* bytes, ktx_size_t numBytes)104{105pMem->pos = 0;106pMem->robytes = bytes;107pMem->bytes = 0;108pMem->used_size = numBytes;109pMem->alloc_size = numBytes;110}111112/**113* @brief Create & initialize a ktxMem struct for read-only.114*115* @sa ktxMem_construct.116*117* @param [in,out] ppMem pointer to the location in which to return118* a pointer to the newly created @c ktxMem.119* @param [in] bytes pointer to the data to be read.120* @param [in] numBytes number of bytes of data.121*122* @return KTX_SUCCESS on success, KTX_OUT_OF_MEMORY on error.123*124* @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient pMemory.125*/126static KTX_error_code127ktxMem_create_ro(ktxMem** ppMem, const void* bytes, ktx_size_t numBytes)128{129ktxMem* pNewMem = (ktxMem*)malloc(sizeof(ktxMem));130if (pNewMem) {131ktxMem_construct_ro(pNewMem, bytes, numBytes);132*ppMem = pNewMem;133return KTX_SUCCESS;134}135else {136return KTX_OUT_OF_MEMORY;137}138}139140/*141* ktxMem_destruct not needed as ktxMem_construct caller is reponsible142* for freeing the data written.143*/144145/**146* @brief Free the memory of a struct ktxMem.147*148* @param pMem pointer to ktxMem to free.149*/150static void151ktxMem_destroy(ktxMem* pMem, ktx_bool_t freeData)152{153assert(pMem != NULL);154if (freeData) {155free(pMem->bytes);156}157free(pMem);158}159160#ifdef KTXMEM_CLEAR_USED161/**162* @brief Clear the data of a memory stream.163*164* @param pMem pointer to ktxMem to clear.165*/166static void167ktxMem_clear(ktxMem* pMem)168{169assert(pMem != NULL);170memset(pMem, 0, sizeof(ktxMem));171}172#endif173174/**175* @~English176* @brief Expand a ktxMem to fit to a new size.177*178* @param [in] pMem pointer to ktxMem struct to expand.179* @param [in] newsize minimum new size required.180*181* @return KTX_SUCCESS on success, KTX_OUT_OF_MEMORY on error.182*183* @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient pMemory.184*/185static KTX_error_code186ktxMem_expand(ktxMem *pMem, const ktx_size_t newsize)187{188ktx_size_t new_alloc_size;189190assert(pMem != NULL && newsize != 0);191192new_alloc_size = pMem->alloc_size == 0 ?193KTX_MEM_DEFAULT_ALLOCATED_SIZE : pMem->alloc_size;194while (new_alloc_size < newsize) {195ktx_size_t alloc_size = new_alloc_size;196new_alloc_size <<= 1;197if (new_alloc_size < alloc_size) {198/* Overflow. Set to maximum size. newsize can't be larger. */199new_alloc_size = (ktx_size_t)-1L;200}201}202203if (new_alloc_size == pMem->alloc_size)204return KTX_SUCCESS;205206if (!pMem->bytes)207pMem->bytes = (ktx_uint8_t*)malloc(new_alloc_size);208else209pMem->bytes = (ktx_uint8_t*)realloc(pMem->bytes, new_alloc_size);210211if (!pMem->bytes)212{213pMem->alloc_size = 0;214pMem->used_size = 0;215return KTX_OUT_OF_MEMORY;216}217218pMem->alloc_size = new_alloc_size;219return KTX_SUCCESS;220}221222/**223* @~English224* @brief Read bytes from a ktxMemStream.225*226* @param [in] str pointer to ktxMem struct, converted to a void*, that227* specifies an input stream.228* @param [in,out] dst pointer to memory where to copy read bytes.229* @param [in,out] count pointer to number of bytes to read.230*231* @return KTX_SUCCESS on success, KTX_INVALID_VALUE on error.232*233* @exception KTX_INVALID_VALUE @p str or @p dst is @c NULL or @p str->data is234* @c NULL.235* @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.236*/237static238KTX_error_code ktxMemStream_read(ktxStream* str, void* dst, const ktx_size_t count)239{240ktxMem* mem;241ktx_off_t newpos;242const ktx_uint8_t* bytes;243244245if (!str || (mem = str->data.mem)== 0)246return KTX_INVALID_VALUE;247248newpos = mem->pos + count;249/* The first clause checks for overflow. */250if (newpos < mem->pos || (ktx_size_t)newpos > mem->used_size)251return KTX_FILE_UNEXPECTED_EOF;252253bytes = mem->robytes ? mem->robytes : mem->bytes;254memcpy(dst, bytes + mem->pos, count);255mem->pos = newpos;256257return KTX_SUCCESS;258}259260/**261* @~English262* @brief Skip bytes in a ktxMemStream.263*264* @param [in] str pointer to the ktxStream on which to operate.265* @param [in] count number of bytes to skip.266*267* @return KTX_SUCCESS on success, KTX_INVALID_VALUE on error.268*269* @exception KTX_INVALID_VALUE @p str or @p mem is @c NULL or sufficient270* data is not available in ktxMem.271* @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.272*/273static274KTX_error_code ktxMemStream_skip(ktxStream* str, const ktx_size_t count)275{276ktxMem* mem;277ktx_off_t newpos;278279if (!str || (mem = str->data.mem) == 0)280return KTX_INVALID_VALUE;281282newpos = mem->pos + count;283/* The first clause checks for overflow. */284if (newpos < mem->pos || (ktx_size_t)newpos > mem->used_size)285return KTX_FILE_UNEXPECTED_EOF;286287mem->pos = newpos;288289return KTX_SUCCESS;290}291292/**293* @~English294* @brief Write bytes to a ktxMemStream.295*296* @param [out] str pointer to the ktxStream that specifies the destination.297* @param [in] src pointer to the array of elements to be written,298* converted to a const void*.299* @param [in] size size in bytes of each element to be written.300* @param [in] count number of elements, each one with a @p size of size301* bytes.302*303* @return KTX_SUCCESS on success, other KTX_* enum values on error.304*305* @exception KTX_FILE_OVERFLOW write would result in file exceeding the306* maximum permissible size.307* @exception KTX_INVALID_OPERATION @p str is a read-only stream.308* @exception KTX_INVALID_VALUE @p dst is @c NULL or @p mem is @c NULL.309* @exception KTX_OUT_OF_MEMORY See ktxMem_expand() for causes.310*/311static312KTX_error_code ktxMemStream_write(ktxStream* str, const void* src,313const ktx_size_t size, const ktx_size_t count)314{315ktxMem* mem;316KTX_error_code rc = KTX_SUCCESS;317ktx_size_t new_size;318319if (!str || (mem = str->data.mem) == 0)320return KTX_INVALID_VALUE;321322if (mem->robytes)323return KTX_INVALID_OPERATION; /* read-only */324325new_size = mem->pos + (size*count);326//if (new_size < mem->used_size)327if ((ktx_off_t)new_size < mem->pos)328return KTX_FILE_OVERFLOW;329330if (mem->alloc_size < new_size) {331rc = ktxMem_expand(mem, new_size);332if (rc != KTX_SUCCESS)333return rc;334}335336memcpy(mem->bytes + mem->pos, src, size*count);337mem->pos += size*count;338if (mem->pos > (ktx_off_t)mem->used_size)339mem->used_size = mem->pos;340341342return KTX_SUCCESS;343}344345/**346* @~English347* @brief Get the current read/write position in a ktxMemStream.348*349* @param [in] str pointer to the ktxStream to query.350* @param [in,out] off pointer to variable to receive the offset value.351*352* @return KTX_SUCCESS on success, other KTX_* enum values on error.353*354* @exception KTX_INVALID_VALUE @p str or @p pos is @c NULL.355*/356static357KTX_error_code ktxMemStream_getpos(ktxStream* str, ktx_off_t* const pos)358{359if (!str || !pos)360return KTX_INVALID_VALUE;361362assert(str->type == eStreamTypeMemory);363364*pos = str->data.mem->pos;365return KTX_SUCCESS;366}367368/**369* @~English370* @brief Set the current read/write position in a ktxMemStream.371*372* Offset of 0 is the start of the file.373*374* @param [in] str pointer to the ktxStream whose r/w position is to be set.375* @param [in] off pointer to the offset value to set.376*377* @return KTX_SUCCESS on success, other KTX_* enum values on error.378*379* @exception KTX_INVALID_VALUE @p str is @c NULL.380* @exception KTX_INVALID_OPERATION @p pos > size of the allocated memory.381*/382static383KTX_error_code ktxMemStream_setpos(ktxStream* str, ktx_off_t pos)384{385if (!str)386return KTX_INVALID_VALUE;387388assert(str->type == eStreamTypeMemory);389390if (pos < 0 || (ktx_size_t)pos > str->data.mem->alloc_size)391return KTX_INVALID_OPERATION;392393str->data.mem->pos = pos;394return KTX_SUCCESS;395}396397/**398* @~English399* @brief Get a pointer to a ktxMemStream's data.400*401* Gets a pointer to data that has been written to the stream. Returned402* pointer will be 0 if stream is read-only.403*404* @param [in] str pointer to the ktxStream whose data pointer is to405* be queried.406* @param [in,out] ppBytes pointer to a variable in which the data pointer407* will be written.408*409* @return KTX_SUCCESS on success, other KTX_* enum values on error.410*411* @exception KTX_INVALID_VALUE @p str or @p ppBytes is @c NULL.412*/413KTX_error_code ktxMemStream_getdata(ktxStream* str, ktx_uint8_t** ppBytes)414{415if (!str || !ppBytes)416return KTX_INVALID_VALUE;417418assert(str->type == eStreamTypeMemory);419420*ppBytes = str->data.mem->bytes;421return KTX_SUCCESS;422}423424/**425* @~English426* @brief Get the size of a ktxMemStream in bytes.427*428* @param [in] str pointer to the ktxStream whose size is to be queried.429* @param [in,out] size pointer to a variable in which size will be written.430*431* @return KTX_SUCCESS on success, other KTX_* enum values on error.432*433* @exception KTX_INVALID_VALUE @p str or @p pSize is @c NULL.434*/435static436KTX_error_code ktxMemStream_getsize(ktxStream* str, ktx_size_t* pSize)437{438if (!str || !pSize)439return KTX_INVALID_VALUE;440441assert(str->type == eStreamTypeMemory);442443*pSize = str->data.mem->used_size;444return KTX_SUCCESS;445}446447/**448* @~English449* @brief Setup ktxMemStream function pointers.450*/451void452ktxMemStream_setup(ktxStream* str)453{454str->type = eStreamTypeMemory;455str->read = ktxMemStream_read;456str->skip = ktxMemStream_skip;457str->write = ktxMemStream_write;458str->getpos = ktxMemStream_getpos;459str->setpos = ktxMemStream_setpos;460str->getsize = ktxMemStream_getsize;461str->destruct = ktxMemStream_destruct;462}463464/**465* @~English466* @brief Initialize a read-write ktxMemStream.467*468* Memory is allocated as data is written. The caller of this is469* responsible for freeing this memory unless @a freeOnDestruct470* is not KTX_FALSE.471*472* @param [in] str pointer to a ktxStream struct to initialize.473* @param [in] freeOnDestruct If not KTX_FALSE memory holding the data will474* be freed by the destructor.475*476* @return KTX_SUCCESS on success, other KTX_* enum values on error.477*478* @exception KTX_INVALID_VALUE @p str is @c NULL.479* @exception KTX_OUT_OF_MEMORY system failed to allocate sufficient memory.480*/481KTX_error_code ktxMemStream_construct(ktxStream* str,482ktx_bool_t freeOnDestruct)483{484ktxMem* mem;485KTX_error_code result = KTX_SUCCESS;486487if (!str)488return KTX_INVALID_VALUE;489490result = ktxMem_create(&mem);491492if (KTX_SUCCESS == result) {493str->data.mem = mem;494ktxMemStream_setup(str);495str->closeOnDestruct = freeOnDestruct;496}497498return result;499}500501/**502* @~English503* @brief Initialize a read-only ktxMemStream.504*505* @param [in] str pointer to a ktxStream struct to initialize.506* @param [in] bytes pointer to an array of bytes containing the data.507* @param [in] numBytes size of array of data for ktxMemStream.508*509* @return KTX_SUCCESS on success, other KTX_* enum values on error.510*511* @exception KTX_INVALID_VALUE @p str or @p mem is @c NULL or @p numBytes512* is 0.513* or @p size is less than 0.514* @exception KTX_OUT_OF_MEMORY system failed to allocate sufficient memory.515*/516KTX_error_code ktxMemStream_construct_ro(ktxStream* str,517const ktx_uint8_t* bytes,518const ktx_size_t numBytes)519{520ktxMem* mem;521KTX_error_code result = KTX_SUCCESS;522523if (!str || !bytes || numBytes == 0)524return KTX_INVALID_VALUE;525526result = ktxMem_create_ro(&mem, bytes, numBytes);527528if (KTX_SUCCESS == result) {529str->data.mem = mem;530ktxMemStream_setup(str);531str->closeOnDestruct = KTX_FALSE;532}533534return result;535}536537/**538* @~English539* @brief Free the memory used by a ktxMemStream.540*541* This only frees the memory used to store the data written to the stream,542* if the @c freeOnDestruct parameter to ktxMemStream_construct() was not543* @c KTX_FALSE. Otherwise it is the responsibility of the caller of544* ktxMemStream_construct() and a pointer to this memory should be retrieved545* using ktxMemStream_getdata() before calling this function.546*547* @sa ktxMemStream_construct, ktxMemStream_getdata.548*549* @param [in] str pointer to the ktxStream whose memory is550* to be freed.551*/552void553ktxMemStream_destruct(ktxStream* str)554{555assert(str && str->type == eStreamTypeMemory);556557ktxMem_destroy(str->data.mem, str->closeOnDestruct);558str->data.mem = NULL;559}560561562563