universe@483: /* universe@483: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. universe@483: * universe@483: * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. universe@483: * universe@483: * Redistribution and use in source and binary forms, with or without universe@483: * modification, are permitted provided that the following conditions are met: universe@483: * universe@483: * 1. Redistributions of source code must retain the above copyright universe@483: * notice, this list of conditions and the following disclaimer. universe@483: * universe@483: * 2. Redistributions in binary form must reproduce the above copyright universe@483: * notice, this list of conditions and the following disclaimer in the universe@483: * documentation and/or other materials provided with the distribution. universe@483: * universe@483: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" universe@483: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE universe@483: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE universe@483: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE universe@483: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR universe@483: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF universe@483: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS universe@483: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN universe@483: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) universe@483: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE universe@483: * POSSIBILITY OF SUCH DAMAGE. universe@483: */ universe@483: universe@483: #include "cx/buffer.h" universe@483: #include "cx/utils.h" universe@483: universe@530: #include universe@483: #include universe@483: universe@501: int cxBufferInit( universe@501: CxBuffer *buffer, universe@483: void *space, universe@483: size_t capacity, universe@529: CxAllocator const *allocator, universe@483: int flags universe@483: ) { universe@673: if (allocator == NULL) allocator = cxDefaultAllocator; universe@501: buffer->allocator = allocator; universe@501: buffer->flags = flags; universe@501: if (!space) { universe@501: buffer->bytes = cxMalloc(allocator, capacity); universe@501: if (buffer->bytes == NULL) { universe@501: return 1; universe@483: } universe@501: buffer->flags |= CX_BUFFER_FREE_CONTENTS; universe@501: } else { universe@501: buffer->bytes = space; universe@501: } universe@501: buffer->capacity = capacity; universe@501: buffer->size = 0; universe@539: buffer->pos = 0; universe@483: universe@539: buffer->flush_func = NULL; universe@541: buffer->flush_target = NULL; universe@539: buffer->flush_blkmax = 0; universe@539: buffer->flush_blksize = 4096; universe@539: buffer->flush_threshold = SIZE_MAX; universe@483: universe@501: return 0; universe@483: } universe@483: universe@500: void cxBufferDestroy(CxBuffer *buffer) { universe@483: if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { universe@501: cxFree(buffer->allocator, buffer->bytes); universe@483: } universe@483: } universe@483: universe@683: CxBuffer *cxBufferCreate( universe@683: void *space, universe@683: size_t capacity, universe@683: CxAllocator const *allocator, universe@683: int flags universe@683: ) { universe@683: CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer)); universe@683: if (buf == NULL) return NULL; universe@683: if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) { universe@683: return buf; universe@683: } else { universe@683: cxFree(allocator, buf); universe@683: return NULL; universe@683: } universe@683: } universe@683: universe@683: void cxBufferFree(CxBuffer *buffer) { universe@683: if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { universe@683: cxFree(buffer->allocator, buffer->bytes); universe@683: } universe@683: cxFree(buffer->allocator, buffer); universe@683: } universe@683: universe@483: int cxBufferSeek( universe@500: CxBuffer *buffer, universe@483: off_t offset, universe@483: int whence universe@483: ) { universe@483: size_t npos; universe@483: switch (whence) { universe@483: case SEEK_CUR: universe@483: npos = buffer->pos; universe@483: break; universe@483: case SEEK_END: universe@483: npos = buffer->size; universe@483: break; universe@483: case SEEK_SET: universe@483: npos = 0; universe@483: break; universe@483: default: universe@483: return -1; universe@483: } universe@483: universe@483: size_t opos = npos; universe@483: npos += offset; universe@483: universe@483: if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) { universe@483: return -1; universe@483: } universe@483: universe@483: if (npos >= buffer->size) { universe@483: return -1; universe@483: } else { universe@483: buffer->pos = npos; universe@483: return 0; universe@483: } universe@483: universe@483: } universe@483: universe@529: void cxBufferClear(CxBuffer *buffer) { universe@529: memset(buffer->bytes, 0, buffer->size); universe@529: buffer->size = 0; universe@529: buffer->pos = 0; universe@529: } universe@529: universe@761: void cxBufferReset(CxBuffer *buffer) { universe@761: buffer->size = 0; universe@761: buffer->pos = 0; universe@761: } universe@761: universe@529: int cxBufferEof(CxBuffer const *buffer) { universe@483: return buffer->pos >= buffer->size; universe@483: } universe@483: universe@483: int cxBufferMinimumCapacity( universe@500: CxBuffer *buffer, universe@532: size_t newcap universe@483: ) { universe@532: if (newcap <= buffer->capacity) { universe@532: return 0; universe@483: } universe@483: universe@536: if (cxReallocate(buffer->allocator, universe@536: (void **) &buffer->bytes, newcap) == 0) { universe@483: buffer->capacity = newcap; universe@533: return 0; universe@483: } else { universe@483: return -1; universe@483: } universe@483: } universe@483: universe@544: /** universe@544: * Helps flushing data to the flush target of a buffer. universe@544: * universe@544: * @param buffer the buffer containing the config universe@544: * @param space the data to flush universe@544: * @param size the element size universe@544: * @param nitems the number of items universe@544: * @return the number of items flushed universe@544: */ universe@544: static size_t cx_buffer_write_flush_helper( universe@544: CxBuffer *buffer, universe@544: unsigned char const *space, universe@544: size_t size, universe@544: size_t nitems universe@544: ) { universe@544: size_t pos = 0; universe@544: size_t remaining = nitems; universe@544: size_t max_items = buffer->flush_blksize / size; universe@544: while (remaining > 0) { universe@544: size_t items = remaining > max_items ? max_items : remaining; universe@544: size_t flushed = buffer->flush_func( universe@544: space + pos, universe@544: size, items, universe@544: buffer->flush_target); universe@544: if (flushed > 0) { universe@544: pos += (flushed * size); universe@544: remaining -= flushed; universe@544: } else { universe@544: // if no bytes can be flushed out anymore, we give up universe@544: break; universe@544: } universe@544: } universe@544: return nitems - remaining; universe@544: } universe@544: universe@483: size_t cxBufferWrite( universe@489: void const *ptr, universe@483: size_t size, universe@483: size_t nitems, universe@500: CxBuffer *buffer universe@483: ) { universe@543: // optimize for easy case universe@543: if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { universe@543: memcpy(buffer->bytes + buffer->pos, ptr, nitems); universe@543: buffer->pos += nitems; universe@543: if (buffer->pos > buffer->size) { universe@543: buffer->size = buffer->pos; universe@543: } universe@543: return nitems; universe@543: } universe@543: universe@483: size_t len; universe@544: size_t nitems_out = nitems; universe@483: if (cx_szmul(size, nitems, &len)) { universe@483: return 0; universe@483: } universe@483: size_t required = buffer->pos + len; universe@483: if (buffer->pos > required) { universe@483: return 0; universe@483: } universe@483: universe@539: bool perform_flush = false; universe@483: if (required > buffer->capacity) { universe@539: if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) { universe@539: if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) { universe@539: perform_flush = true; universe@539: } else { universe@539: if (cxBufferMinimumCapacity(buffer, required)) { universe@539: return 0; universe@539: } universe@483: } universe@483: } else { universe@539: if (buffer->flush_blkmax > 0) { universe@539: perform_flush = true; universe@539: } else { universe@539: // truncate data to be written, if we can neither extend nor flush universe@539: len = buffer->capacity - buffer->pos; universe@539: if (size > 1) { universe@539: len -= len % size; universe@539: } universe@544: nitems_out = len / size; universe@483: } universe@483: } universe@483: } universe@483: universe@483: if (len == 0) { universe@483: return len; universe@483: } universe@483: universe@539: if (perform_flush) { universe@544: size_t flush_max; universe@544: if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) { universe@544: return 0; universe@544: } universe@544: size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL universe@544: ? buffer->pos universe@544: : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos); universe@544: if (flush_pos == buffer->pos) { universe@544: // entire buffer has been flushed, we can reset universe@544: buffer->size = buffer->pos = 0; universe@544: universe@544: size_t items_flush; // how many items can also be directly flushed universe@544: size_t items_keep; // how many items have to be written to the buffer universe@544: universe@544: items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size; universe@544: if (items_flush > 0) { universe@544: items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size); universe@544: // in case we could not flush everything, keep the rest universe@544: } universe@544: items_keep = nitems - items_flush; universe@544: if (items_keep > 0) { universe@544: // try again with the remaining stuff universe@544: unsigned char const *new_ptr = ptr; universe@544: new_ptr += items_flush * size; universe@567: // report the directly flushed items as written plus the remaining stuff universe@567: return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer); universe@544: } else { universe@544: // all items have been flushed - report them as written universe@544: return nitems; universe@544: } universe@544: } else if (flush_pos == 0) { universe@544: // nothing could be flushed at all, we immediately give up without writing any data universe@544: return 0; universe@544: } else { universe@567: // we were partially successful, we shift left and try again universe@544: cxBufferShiftLeft(buffer, flush_pos); universe@544: return cxBufferWrite(ptr, size, nitems, buffer); universe@544: } universe@539: } else { universe@539: memcpy(buffer->bytes + buffer->pos, ptr, len); universe@539: buffer->pos += len; universe@539: if (buffer->pos > buffer->size) { universe@539: buffer->size = buffer->pos; universe@539: } universe@544: return nitems_out; universe@483: } universe@483: universe@483: } universe@483: universe@538: int cxBufferPut( universe@538: CxBuffer *buffer, universe@538: int c universe@538: ) { universe@538: c &= 0xFF; universe@538: unsigned char const ch = c; universe@538: if (cxBufferWrite(&ch, 1, 1, buffer) == 1) { universe@538: return c; universe@538: } else { universe@538: return EOF; universe@538: } universe@538: } universe@538: universe@538: size_t cxBufferPutString( universe@538: CxBuffer *buffer, universe@538: const char *str universe@538: ) { universe@538: return cxBufferWrite(str, 1, strlen(str), buffer); universe@538: } universe@538: universe@483: size_t cxBufferRead( universe@483: void *ptr, universe@483: size_t size, universe@483: size_t nitems, universe@500: CxBuffer *buffer universe@483: ) { universe@483: size_t len; universe@483: if (cx_szmul(size, nitems, &len)) { universe@483: return 0; universe@483: } universe@483: if (buffer->pos + len > buffer->size) { universe@483: len = buffer->size - buffer->pos; universe@483: if (size > 1) len -= len % size; universe@483: } universe@483: universe@483: if (len <= 0) { universe@483: return len; universe@483: } universe@483: universe@483: memcpy(ptr, buffer->bytes + buffer->pos, len); universe@483: buffer->pos += len; universe@483: universe@483: return len / size; universe@483: } universe@483: universe@500: int cxBufferGet(CxBuffer *buffer) { universe@483: if (cxBufferEof(buffer)) { universe@483: return EOF; universe@483: } else { universe@483: int c = buffer->bytes[buffer->pos]; universe@483: buffer->pos++; universe@483: return c; universe@483: } universe@483: } universe@483: universe@483: int cxBufferShiftLeft( universe@500: CxBuffer *buffer, universe@483: size_t shift universe@483: ) { universe@483: if (shift >= buffer->size) { universe@483: buffer->pos = buffer->size = 0; universe@483: } else { universe@483: memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift); universe@483: buffer->size -= shift; universe@483: universe@483: if (buffer->pos >= shift) { universe@483: buffer->pos -= shift; universe@483: } else { universe@483: buffer->pos = 0; universe@483: } universe@483: } universe@483: return 0; universe@483: } universe@483: universe@483: int cxBufferShiftRight( universe@500: CxBuffer *buffer, universe@483: size_t shift universe@483: ) { universe@483: size_t req_capacity = buffer->size + shift; universe@483: size_t movebytes; universe@483: universe@483: // auto extend buffer, if required and enabled universe@483: if (buffer->capacity < req_capacity) { universe@483: if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) { universe@483: if (cxBufferMinimumCapacity(buffer, req_capacity)) { universe@483: return 1; universe@483: } universe@483: movebytes = buffer->size; universe@483: } else { universe@483: movebytes = buffer->capacity - shift; universe@483: } universe@483: } else { universe@483: movebytes = buffer->size; universe@483: } universe@483: universe@483: memmove(buffer->bytes + shift, buffer->bytes, movebytes); universe@483: buffer->size = shift + movebytes; universe@483: universe@483: buffer->pos += shift; universe@483: if (buffer->pos > buffer->size) { universe@483: buffer->pos = buffer->size; universe@483: } universe@483: universe@483: return 0; universe@483: } universe@483: universe@483: int cxBufferShift( universe@500: CxBuffer *buffer, universe@483: off_t shift universe@483: ) { universe@483: if (shift < 0) { universe@483: return cxBufferShiftLeft(buffer, (size_t) (-shift)); universe@483: } else if (shift > 0) { universe@483: return cxBufferShiftRight(buffer, (size_t) shift); universe@483: } else { universe@483: return 0; universe@483: } universe@483: }