1/* -*- tab-width: 4; -*- */
2/* vi: set sw=2 ts=4 expandtab: */
3
4/*
5 * Copyright 2010-2020 The Khronos Group Inc.
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9/**
10 * @file
11 * @~English
12 *
13 * @brief Implementation of ktxStream for memory.
14 *
15 * @author Maksim Kolesin, Under Development
16 * @author Georg Kolling, Imagination Technology
17 * @author Mark Callow, HI Corporation
18 */
19
20#include <assert.h>
21#include <string.h>
22#include <stdlib.h>
23
24#include "ktx.h"
25#include "ktxint.h"
26#include "memstream.h"
27
28/**
29* @brief Default allocation size for a ktxMemStream.
30*/
31#define KTX_MEM_DEFAULT_ALLOCATED_SIZE 256
32
33/**
34 * @brief Structure to store information about data allocated for ktxMemStream.
35 */
36struct ktxMem
37{
38 const ktx_uint8_t* robytes;/*!< pointer to read-only data */
39 ktx_uint8_t* bytes; /*!< pointer to rw data. */
40 ktx_size_t alloc_size; /*!< allocated size of the memory block. */
41 ktx_size_t used_size; /*!< bytes used. Effectively the write position. */
42 ktx_off_t pos; /*!< read/write position. */
43};
44
45static KTX_error_code ktxMem_expand(ktxMem* pMem, const ktx_size_t size);
46
47/**
48 * @brief Initialize a ktxMem struct for read-write.
49 *
50 * Memory for the stream data is allocated internally but the
51 * caller is responsible for freeing the memory. A pointer to
52 * the memory can be obtained with ktxMem_getdata().
53 *
54 * @sa ktxMem_getdata.
55 *
56 * @param [in] pMem pointer to the @c ktxMem to initialize.
57 */
58static KTX_error_code
59ktxMem_construct(ktxMem* pMem)
60{
61 pMem->pos = 0;
62 pMem->alloc_size = 0;
63 pMem->robytes = 0;
64 pMem->bytes = 0;
65 pMem->used_size = 0;
66 return ktxMem_expand(pMem, KTX_MEM_DEFAULT_ALLOCATED_SIZE);
67}
68
69/**
70 * @brief Create & initialize a ktxMem struct for read-write.
71 *
72 * @sa ktxMem_construct.
73 *
74 * @param [in,out] ppMem pointer to the location in which to return
75 * a pointer to the newly created @c ktxMem.
76 *
77 * @return KTX_SUCCESS on success, KTX_OUT_OF_MEMORY on error.
78 *
79 * @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient pMemory.
80 */
81static KTX_error_code
82ktxMem_create(ktxMem** ppMem)
83{
84 ktxMem* pNewMem = (ktxMem*)malloc(sizeof(ktxMem));
85 if (pNewMem) {
86 KTX_error_code result = ktxMem_construct(pNewMem);
87 if (result == KTX_SUCCESS)
88 *ppMem = pNewMem;
89 return result;
90 }
91 else {
92 return KTX_OUT_OF_MEMORY;
93 }
94}
95
96/**
97 * @brief Initialize a ktxMem struct for read-only.
98 *
99 * @param [in] pMem pointer to the @c ktxMem to initialize.
100 * @param [in] bytes pointer to the data to be read.
101 * @param [in] numBytes number of bytes of data.
102 */
103static void
104ktxMem_construct_ro(ktxMem* pMem, const void* bytes, ktx_size_t numBytes)
105{
106 pMem->pos = 0;
107 pMem->robytes = bytes;
108 pMem->bytes = 0;
109 pMem->used_size = numBytes;
110 pMem->alloc_size = numBytes;
111}
112
113/**
114 * @brief Create & initialize a ktxMem struct for read-only.
115 *
116 * @sa ktxMem_construct.
117 *
118 * @param [in,out] ppMem pointer to the location in which to return
119 * a pointer to the newly created @c ktxMem.
120 * @param [in] bytes pointer to the data to be read.
121 * @param [in] numBytes number of bytes of data.
122 *
123 * @return KTX_SUCCESS on success, KTX_OUT_OF_MEMORY on error.
124 *
125 * @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient pMemory.
126 */
127static KTX_error_code
128ktxMem_create_ro(ktxMem** ppMem, const void* bytes, ktx_size_t numBytes)
129{
130 ktxMem* pNewMem = (ktxMem*)malloc(sizeof(ktxMem));
131 if (pNewMem) {
132 ktxMem_construct_ro(pNewMem, bytes, numBytes);
133 *ppMem = pNewMem;
134 return KTX_SUCCESS;
135 }
136 else {
137 return KTX_OUT_OF_MEMORY;
138 }
139}
140
141/*
142 * ktxMem_destruct not needed as ktxMem_construct caller is reponsible
143 * for freeing the data written.
144 */
145
146/**
147 * @brief Free the memory of a struct ktxMem.
148 *
149 * @param pMem pointer to ktxMem to free.
150 */
151static void
152ktxMem_destroy(ktxMem* pMem, ktx_bool_t freeData)
153{
154 assert(pMem != NULL);
155 if (freeData) {
156 free(pMem->bytes);
157 }
158 free(pMem);
159}
160
161#ifdef KTXMEM_CLEAR_USED
162/**
163 * @brief Clear the data of a memory stream.
164 *
165 * @param pMem pointer to ktxMem to clear.
166 */
167static void
168ktxMem_clear(ktxMem* pMem)
169{
170 assert(pMem != NULL);
171 memset(pMem, 0, sizeof(ktxMem));
172}
173#endif
174
175/**
176 * @~English
177 * @brief Expand a ktxMem to fit to a new size.
178 *
179 * @param [in] pMem pointer to ktxMem struct to expand.
180 * @param [in] newsize minimum new size required.
181 *
182 * @return KTX_SUCCESS on success, KTX_OUT_OF_MEMORY on error.
183 *
184 * @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient pMemory.
185 */
186static KTX_error_code
187ktxMem_expand(ktxMem *pMem, const ktx_size_t newsize)
188{
189 ktx_size_t new_alloc_size;
190
191 assert(pMem != NULL && newsize != 0);
192
193 new_alloc_size = pMem->alloc_size == 0 ?
194 KTX_MEM_DEFAULT_ALLOCATED_SIZE : pMem->alloc_size;
195 while (new_alloc_size < newsize) {
196 ktx_size_t alloc_size = new_alloc_size;
197 new_alloc_size <<= 1;
198 if (new_alloc_size < alloc_size) {
199 /* Overflow. Set to maximum size. newsize can't be larger. */
200 new_alloc_size = (ktx_size_t)-1L;
201 }
202 }
203
204 if (new_alloc_size == pMem->alloc_size)
205 return KTX_SUCCESS;
206
207 if (!pMem->bytes)
208 pMem->bytes = (ktx_uint8_t*)malloc(new_alloc_size);
209 else
210 pMem->bytes = (ktx_uint8_t*)realloc(pMem->bytes, new_alloc_size);
211
212 if (!pMem->bytes)
213 {
214 pMem->alloc_size = 0;
215 pMem->used_size = 0;
216 return KTX_OUT_OF_MEMORY;
217 }
218
219 pMem->alloc_size = new_alloc_size;
220 return KTX_SUCCESS;
221}
222
223/**
224 * @~English
225 * @brief Read bytes from a ktxMemStream.
226 *
227 * @param [in] str pointer to ktxMem struct, converted to a void*, that
228 * specifies an input stream.
229 * @param [in,out] dst pointer to memory where to copy read bytes.
230 * @param [in,out] count pointer to number of bytes to read.
231 *
232 * @return KTX_SUCCESS on success, KTX_INVALID_VALUE on error.
233 *
234 * @exception KTX_INVALID_VALUE @p str or @p dst is @c NULL or @p str->data is
235 * @c NULL.
236 * @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.
237 */
238static
239KTX_error_code ktxMemStream_read(ktxStream* str, void* dst, const ktx_size_t count)
240{
241 ktxMem* mem;
242 ktx_off_t newpos;
243 const ktx_uint8_t* bytes;
244
245
246 if (!str || (mem = str->data.mem)== 0)
247 return KTX_INVALID_VALUE;
248
249 newpos = mem->pos + count;
250 /* The first clause checks for overflow. */
251 if (newpos < mem->pos || (ktx_uint32_t)newpos > mem->used_size)
252 return KTX_FILE_UNEXPECTED_EOF;
253
254 bytes = mem->robytes ? mem->robytes : mem->bytes;
255 memcpy(dst, bytes + mem->pos, count);
256 mem->pos = newpos;
257
258 return KTX_SUCCESS;
259}
260
261/**
262 * @~English
263 * @brief Skip bytes in a ktxMemStream.
264 *
265 * @param [in] str pointer to the ktxStream on which to operate.
266 * @param [in] count number of bytes to skip.
267 *
268 * @return KTX_SUCCESS on success, KTX_INVALID_VALUE on error.
269 *
270 * @exception KTX_INVALID_VALUE @p str or @p mem is @c NULL or sufficient
271 * data is not available in ktxMem.
272 * @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.
273 */
274static
275KTX_error_code ktxMemStream_skip(ktxStream* str, const ktx_size_t count)
276{
277 ktxMem* mem;
278 ktx_off_t newpos;
279
280 if (!str || (mem = str->data.mem) == 0)
281 return KTX_INVALID_VALUE;
282
283 newpos = mem->pos + count;
284 /* The first clause checks for overflow. */
285 if (newpos < mem->pos || (ktx_uint32_t)newpos > mem->used_size)
286 return KTX_FILE_UNEXPECTED_EOF;
287
288 mem->pos = newpos;
289
290 return KTX_SUCCESS;
291}
292
293/**
294 * @~English
295 * @brief Write bytes to a ktxMemStream.
296 *
297 * @param [out] str pointer to the ktxStream that specifies the destination.
298 * @param [in] src pointer to the array of elements to be written,
299 * converted to a const void*.
300 * @param [in] size size in bytes of each element to be written.
301 * @param [in] count number of elements, each one with a @p size of size
302 * bytes.
303 *
304 * @return KTX_SUCCESS on success, other KTX_* enum values on error.
305 *
306 * @exception KTX_FILE_OVERFLOW write would result in file exceeding the
307 * maximum permissible size.
308 * @exception KTX_INVALID_OPERATION @p str is a read-only stream.
309 * @exception KTX_INVALID_VALUE @p dst is @c NULL or @p mem is @c NULL.
310 * @exception KTX_OUT_OF_MEMORY See ktxMem_expand() for causes.
311 */
312static
313KTX_error_code ktxMemStream_write(ktxStream* str, const void* src,
314 const ktx_size_t size, const ktx_size_t count)
315{
316 ktxMem* mem;
317 KTX_error_code rc = KTX_SUCCESS;
318 ktx_size_t new_size;
319
320 if (!str || (mem = str->data.mem) == 0)
321 return KTX_INVALID_VALUE;
322
323 if (mem->robytes)
324 return KTX_INVALID_OPERATION; /* read-only */
325
326 new_size = mem->pos + (size*count);
327 //if (new_size < mem->used_size)
328 if ((ktx_off_t)new_size < mem->pos)
329 return KTX_FILE_OVERFLOW;
330
331 if (mem->alloc_size < new_size) {
332 rc = ktxMem_expand(mem, new_size);
333 if (rc != KTX_SUCCESS)
334 return rc;
335 }
336
337 memcpy(mem->bytes + mem->pos, src, size*count);
338 mem->pos += size*count;
339 if (mem->pos > (ktx_off_t)mem->used_size)
340 mem->used_size = mem->pos;
341
342
343 return KTX_SUCCESS;
344}
345
346/**
347 * @~English
348 * @brief Get the current read/write position in a ktxMemStream.
349 *
350 * @param [in] str pointer to the ktxStream to query.
351 * @param [in,out] off pointer to variable to receive the offset value.
352 *
353 * @return KTX_SUCCESS on success, other KTX_* enum values on error.
354 *
355 * @exception KTX_INVALID_VALUE @p str or @p pos is @c NULL.
356 */
357static
358KTX_error_code ktxMemStream_getpos(ktxStream* str, ktx_off_t* const pos)
359{
360 if (!str || !pos)
361 return KTX_INVALID_VALUE;
362
363 assert(str->type == eStreamTypeMemory);
364
365 *pos = str->data.mem->pos;
366 return KTX_SUCCESS;
367}
368
369/**
370 * @~English
371 * @brief Set the current read/write position in a ktxMemStream.
372 *
373 * Offset of 0 is the start of the file.
374 *
375 * @param [in] str pointer to the ktxStream whose r/w position is to be set.
376 * @param [in] off pointer to the offset value to set.
377 *
378 * @return KTX_SUCCESS on success, other KTX_* enum values on error.
379 *
380 * @exception KTX_INVALID_VALUE @p str is @c NULL.
381 * @exception KTX_INVALID_OPERATION @p pos > size of the allocated memory.
382 */
383static
384KTX_error_code ktxMemStream_setpos(ktxStream* str, ktx_off_t pos)
385{
386 if (!str)
387 return KTX_INVALID_VALUE;
388
389 assert(str->type == eStreamTypeMemory);
390
391 if (pos > (ktx_off_t)str->data.mem->alloc_size)
392 return KTX_INVALID_OPERATION;
393
394 str->data.mem->pos = pos;
395 return KTX_SUCCESS;
396}
397
398/**
399 * @~English
400 * @brief Get a pointer to a ktxMemStream's data.
401 *
402 * Gets a pointer to data that has been written to the stream. Returned
403 * pointer will be 0 if stream is read-only.
404 *
405 * @param [in] str pointer to the ktxStream whose data pointer is to
406 * be queried.
407 * @param [in,out] ppBytes pointer to a variable in which the data pointer
408 * will be written.
409 *
410 * @return KTX_SUCCESS on success, other KTX_* enum values on error.
411 *
412 * @exception KTX_INVALID_VALUE @p str or @p ppBytes is @c NULL.
413 */
414KTX_error_code ktxMemStream_getdata(ktxStream* str, ktx_uint8_t** ppBytes)
415{
416 if (!str || !ppBytes)
417 return KTX_INVALID_VALUE;
418
419 assert(str->type == eStreamTypeMemory);
420
421 *ppBytes = str->data.mem->bytes;
422 return KTX_SUCCESS;
423}
424
425/**
426 * @~English
427 * @brief Get the size of a ktxMemStream in bytes.
428 *
429 * @param [in] str pointer to the ktxStream whose size is to be queried.
430 * @param [in,out] size pointer to a variable in which size will be written.
431 *
432 * @return KTX_SUCCESS on success, other KTX_* enum values on error.
433 *
434 * @exception KTX_INVALID_VALUE @p str or @p pSize is @c NULL.
435 */
436static
437KTX_error_code ktxMemStream_getsize(ktxStream* str, ktx_size_t* pSize)
438{
439 if (!str || !pSize)
440 return KTX_INVALID_VALUE;
441
442 assert(str->type == eStreamTypeMemory);
443
444 *pSize = str->data.mem->used_size;
445 return KTX_SUCCESS;
446}
447
448/**
449 * @~English
450 * @brief Setup ktxMemStream function pointers.
451 */
452void
453ktxMemStream_setup(ktxStream* str)
454{
455 str->type = eStreamTypeMemory;
456 str->read = ktxMemStream_read;
457 str->skip = ktxMemStream_skip;
458 str->write = ktxMemStream_write;
459 str->getpos = ktxMemStream_getpos;
460 str->setpos = ktxMemStream_setpos;
461 str->getsize = ktxMemStream_getsize;
462 str->destruct = ktxMemStream_destruct;
463}
464
465/**
466 * @~English
467 * @brief Initialize a read-write ktxMemStream.
468 *
469 * Memory is allocated as data is written. The caller of this is
470 * responsible for freeing this memory unless @a freeOnDestruct
471 * is not KTX_FALSE.
472 *
473 * @param [in] str pointer to a ktxStream struct to initialize.
474 * @param [in] freeOnDestruct If not KTX_FALSE memory holding the data will
475 * be freed by the destructor.
476 *
477 * @return KTX_SUCCESS on success, other KTX_* enum values on error.
478 *
479 * @exception KTX_INVALID_VALUE @p str is @c NULL.
480 * @exception KTX_OUT_OF_MEMORY system failed to allocate sufficient memory.
481 */
482KTX_error_code ktxMemStream_construct(ktxStream* str,
483 ktx_bool_t freeOnDestruct)
484{
485 ktxMem* mem;
486 KTX_error_code result = KTX_SUCCESS;
487
488 if (!str)
489 return KTX_INVALID_VALUE;
490
491 result = ktxMem_create(&mem);
492
493 if (KTX_SUCCESS == result) {
494 str->data.mem = mem;
495 ktxMemStream_setup(str);
496 str->closeOnDestruct = freeOnDestruct;
497 }
498
499 return result;
500}
501
502/**
503 * @~English
504 * @brief Initialize a read-only ktxMemStream.
505 *
506 * @param [in] str pointer to a ktxStream struct to initialize.
507 * @param [in] bytes pointer to an array of bytes containing the data.
508 * @param [in] numBytes size of array of data for ktxMemStream.
509 *
510 * @return KTX_SUCCESS on success, other KTX_* enum values on error.
511 *
512 * @exception KTX_INVALID_VALUE @p str or @p mem is @c NULL or @p numBytes
513 * is 0.
514 * or @p size is less than 0.
515 * @exception KTX_OUT_OF_MEMORY system failed to allocate sufficient memory.
516 */
517KTX_error_code ktxMemStream_construct_ro(ktxStream* str,
518 const ktx_uint8_t* bytes,
519 const ktx_size_t numBytes)
520{
521 ktxMem* mem;
522 KTX_error_code result = KTX_SUCCESS;
523
524 if (!str || !bytes || numBytes == 0)
525 return KTX_INVALID_VALUE;
526
527 result = ktxMem_create_ro(&mem, bytes, numBytes);
528
529 if (KTX_SUCCESS == result) {
530 str->data.mem = mem;
531 ktxMemStream_setup(str);
532 str->closeOnDestruct = KTX_FALSE;
533 }
534
535 return result;
536}
537
538/**
539 * @~English
540 * @brief Free the memory used by a ktxMemStream.
541 *
542 * This only frees the memory used to store the data written to the stream,
543 * if the @c freeOnDestruct parameter to ktxMemStream_construct() was not
544 * @c KTX_FALSE. Otherwise it is the responsibility of the caller of
545 * ktxMemStream_construct() and a pointer to this memory should be retrieved
546 * using ktxMemStream_getdata() before calling this function.
547 *
548 * @sa ktxMemStream_construct, ktxMemStream_getdata.
549 *
550 * @param [in] str pointer to the ktxStream whose memory is
551 * to be freed.
552 */
553void
554ktxMemStream_destruct(ktxStream* str)
555{
556 assert(str && str->type == eStreamTypeMemory);
557
558 ktxMem_destroy(str->data.mem, str->closeOnDestruct);
559 str->data.mem = NULL;
560}
561
562