/* MIT License
*
* Copyright (c) 2024 Brad House
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_private.h"
#include "ares_array.h"
#define ARES__ARRAY_MIN 4
struct ares_array {
ares_array_destructor_t destruct;
void *arr;
size_t member_size;
size_t cnt;
size_t offset;
size_t alloc_cnt;
};
ares_array_t *ares_array_create(size_t member_size,
ares_array_destructor_t destruct)
{
ares_array_t *arr;
if (member_size == 0) {
return NULL;
}
arr = ares_malloc_zero(sizeof(*arr));
if (arr == NULL) {
return NULL;
}
arr->member_size = member_size;
arr->destruct = destruct;
return arr;
}
size_t ares_array_len(const ares_array_t *arr)
{
if (arr == NULL) {
return 0;
}
return arr->cnt;
}
void *ares_array_at(ares_array_t *arr, size_t idx)
{
if (arr == NULL || idx >= arr->cnt) {
return NULL;
}
return (unsigned char *)arr->arr + ((idx + arr->offset) * arr->member_size);
}
const void *ares_array_at_const(const ares_array_t *arr, size_t idx)
{
if (arr == NULL || idx >= arr->cnt) {
return NULL;
}
return (unsigned char *)arr->arr + ((idx + arr->offset) * arr->member_size);
}
ares_status_t ares_array_sort(ares_array_t *arr, ares_array_cmp_t cmp)
{
if (arr == NULL || cmp == NULL) {
return ARES_EFORMERR;
}
/* Nothing to sort */
if (arr->cnt < 2) {
return ARES_SUCCESS;
}
qsort((unsigned char *)arr->arr + (arr->offset * arr->member_size), arr->cnt,
arr->member_size, cmp);
return ARES_SUCCESS;
}
void ares_array_destroy(ares_array_t *arr)
{
size_t i;
if (arr == NULL) {
return;
}
if (arr->destruct != NULL) {
for (i = 0; i < arr->cnt; i++) {
arr->destruct(ares_array_at(arr, i));
}
}
ares_free(arr->arr);
ares_free(arr);
}
/* NOTE: this function operates on actual indexes, NOT indexes using the
* arr->offset */
static ares_status_t ares_array_move(ares_array_t *arr, size_t dest_idx,
size_t src_idx)
{
void *dest_ptr;
const void *src_ptr;
size_t nmembers;
if (arr == NULL || dest_idx >= arr->alloc_cnt || src_idx >= arr->alloc_cnt) {
return ARES_EFORMERR;
}
/* Nothing to do */
if (dest_idx == src_idx) {
return ARES_SUCCESS;
}
dest_ptr = (unsigned char *)arr->arr + (dest_idx * arr->member_size);
src_ptr = (unsigned char *)arr->arr + (src_idx * arr->member_size);
/* Check to make sure shifting to the right won't overflow our allocation
* boundary */
if (dest_idx > src_idx && arr->cnt + (dest_idx - src_idx) > arr->alloc_cnt) {
return ARES_EFORMERR;
}
nmembers = arr->cnt - (src_idx - arr->offset);
memmove(dest_ptr, src_ptr, nmembers * arr->member_size);
return ARES_SUCCESS;
}
void *ares_array_finish(ares_array_t *arr, size_t *num_members)
{
void *ptr;
if (arr == NULL || num_members == NULL) {
return NULL;
}
/* Make sure we move data to beginning of allocation */
if (arr->offset != 0) {
if (ares_array_move(arr, 0, arr->offset) != ARES_SUCCESS) {
return NULL;
}
arr->offset = 0;
}
ptr = arr->arr;
*num_members = arr->cnt;
ares_free(arr);
return ptr;
}
ares_status_t ares_array_set_size(ares_array_t *arr, size_t size)
{
void *temp;
size_t rounded_size;
if (arr == NULL || size == 0 || size < arr->cnt) {
return ARES_EFORMERR;
}
/* Always operate on powers of 2 */
rounded_size = ares_round_up_pow2(size);
if (rounded_size < size) {
return ARES_ENOMEM; /* rounding wrapped around */
}
size = rounded_size;
if (size < ARES__ARRAY_MIN) {
size = ARES__ARRAY_MIN;
}
/* If our allocation size is already large enough, skip */
if (size <= arr->alloc_cnt) {
return ARES_SUCCESS;
}
temp =
ares_realloc_zero_array(arr->arr, arr->alloc_cnt, size, arr->member_size);
if (temp == NULL) {
return ARES_ENOMEM;
}
arr->alloc_cnt = size;
arr->arr = temp;
return ARES_SUCCESS;
}
ares_status_t ares_array_insert_at(void **elem_ptr, ares_array_t *arr,
size_t idx)
{
void *ptr;
ares_status_t status;
if (arr == NULL) {
return ARES_EFORMERR;
}
/* Not >= since we are allowed to append to the end */
if (idx > arr->cnt) {
return ARES_EFORMERR;
}
/* Allocate more if needed */
status = ares_array_set_size(arr, arr->cnt + 1);
if (status != ARES_SUCCESS) {
return status;
}
/* Shift if we have memory but not enough room at the end */
if (arr->cnt + 1 + arr->offset > arr->alloc_cnt) {
status = ares_array_move(arr, 0, arr->offset);
if (status != ARES_SUCCESS) {
return status;
}
arr->offset = 0;
}
/* If we're inserting anywhere other than the end, we need to move some
* elements out of the way */
if (idx != arr->cnt) {
status = ares_array_move(arr, idx + arr->offset + 1, idx + arr->offset);
if (status != ARES_SUCCESS) {
return status;
}
}
/* Ok, we're guaranteed to have a gap where we need it, lets zero it out,
* and return it */
ptr = (unsigned char *)arr->arr + ((idx + arr->offset) * arr->member_size);
memset(ptr, 0, arr->member_size);
arr->cnt++;
if (elem_ptr) {
*elem_ptr = ptr;
}
return ARES_SUCCESS;
}
ares_status_t ares_array_insert_last(void **elem_ptr, ares_array_t *arr)
{
return ares_array_insert_at(elem_ptr, arr, ares_array_len(arr));
}
ares_status_t ares_array_insert_first(void **elem_ptr, ares_array_t *arr)
{
return ares_array_insert_at(elem_ptr, arr, 0);
}
ares_status_t ares_array_insertdata_at(ares_array_t *arr, size_t idx,
const void *data_ptr)
{
ares_status_t status;
void *ptr = NULL;
status = ares_array_insert_at(&ptr, arr, idx);
if (status != ARES_SUCCESS) {
return status;
}
memcpy(ptr, data_ptr, arr->member_size);
return ARES_SUCCESS;
}
ares_status_t ares_array_insertdata_last(ares_array_t *arr,
const void *data_ptr)
{
ares_status_t status;
void *ptr = NULL;
status = ares_array_insert_last(&ptr, arr);
if (status != ARES_SUCCESS) {
return status;
}
memcpy(ptr, data_ptr, arr->member_size);
return ARES_SUCCESS;
}
ares_status_t ares_array_insertdata_first(ares_array_t *arr,
const void *data_ptr)
{
ares_status_t status;
void *ptr = NULL;
status = ares_array_insert_first(&ptr, arr);
if (status != ARES_SUCCESS) {
return status;
}
memcpy(ptr, data_ptr, arr->member_size);
return ARES_SUCCESS;
}
void *ares_array_first(ares_array_t *arr)
{
return ares_array_at(arr, 0);
}
void *ares_array_last(ares_array_t *arr)
{
size_t cnt = ares_array_len(arr);
if (cnt == 0) {
return NULL;
}
return ares_array_at(arr, cnt - 1);
}
const void *ares_array_first_const(const ares_array_t *arr)
{
return ares_array_at_const(arr, 0);
}
const void *ares_array_last_const(const ares_array_t *arr)
{
size_t cnt = ares_array_len(arr);
if (cnt == 0) {
return NULL;
}
return ares_array_at_const(arr, cnt - 1);
}
ares_status_t ares_array_claim_at(void *dest, size_t dest_size,
ares_array_t *arr, size_t idx)
{
ares_status_t status;
if (arr == NULL || idx >= arr->cnt) {
return ARES_EFORMERR;
}
if (dest != NULL && dest_size < arr->member_size) {
return ARES_EFORMERR;
}
if (dest) {
memcpy(dest, ares_array_at(arr, idx), arr->member_size);
}
if (idx == 0) {
/* Optimization, if first element, just increment offset, makes removing a
* lot from the start quick */
arr->offset++;
} else if (idx != arr->cnt - 1) {
/* Must shift entire array if removing an element from the middle. Does
* nothing if removing last element other than decrement count. */
status = ares_array_move(arr, idx + arr->offset, idx + arr->offset + 1);
if (status != ARES_SUCCESS) {
return status;
}
}
arr->cnt--;
/* When empty, reset offset so a later insert doesn't compact from an
* out-of-range index (offset can reach alloc_cnt after front claims).
* This also protects ares_array_finish(), which has the same
* move(0, offset) and would otherwise fail on an offset == alloc_cnt
* array. */
if (arr->cnt == 0) {
arr->offset = 0;
}
return ARES_SUCCESS;
}
ares_status_t ares_array_remove_at(ares_array_t *arr, size_t idx)
{
void *ptr = ares_array_at(arr, idx);
if (arr == NULL || ptr == NULL) {
return ARES_EFORMERR;
}
if (arr->destruct != NULL) {
arr->destruct(ptr);
}
return ares_array_claim_at(NULL, 0, arr, idx);
}
ares_status_t ares_array_remove_first(ares_array_t *arr)
{
return ares_array_remove_at(arr, 0);
}
ares_status_t ares_array_remove_last(ares_array_t *arr)
{
size_t cnt = ares_array_len(arr);
if (cnt == 0) {
return ARES_EFORMERR;
}
return ares_array_remove_at(arr, cnt - 1);
}