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 | */ |
36 | struct 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 | |
45 | static 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 | */ |
58 | static KTX_error_code |
59 | ktxMem_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 | */ |
81 | static KTX_error_code |
82 | ktxMem_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 | */ |
103 | static void |
104 | ktxMem_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 | */ |
127 | static KTX_error_code |
128 | ktxMem_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 | */ |
151 | static void |
152 | ktxMem_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 | */ |
167 | static void |
168 | ktxMem_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 | */ |
186 | static KTX_error_code |
187 | ktxMem_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 | */ |
238 | static |
239 | KTX_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 | */ |
274 | static |
275 | KTX_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 | */ |
312 | static |
313 | KTX_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 | */ |
357 | static |
358 | KTX_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 | */ |
383 | static |
384 | KTX_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 | */ |
414 | KTX_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 | */ |
436 | static |
437 | KTX_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 | */ |
452 | void |
453 | ktxMemStream_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 | */ |
482 | KTX_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 | */ |
517 | KTX_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 | */ |
553 | void |
554 | ktxMemStream_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 | |