1 /* MIT License
2 *
3 * Copyright (c) 2024 Brad House
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 *
24 * SPDX-License-Identifier: MIT
25 */
26 #include "ares_private.h"
27 #include "ares_array.h"
28
29 #define ARES__ARRAY_MIN 4
30
31 struct ares_array {
32 ares_array_destructor_t destruct;
33 void *arr;
34 size_t member_size;
35 size_t cnt;
36 size_t offset;
37 size_t alloc_cnt;
38 };
39
ares_array_create(size_t member_size,ares_array_destructor_t destruct)40 ares_array_t *ares_array_create(size_t member_size,
41 ares_array_destructor_t destruct)
42 {
43 ares_array_t *arr;
44
45 if (member_size == 0) {
46 return NULL;
47 }
48
49 arr = ares_malloc_zero(sizeof(*arr));
50 if (arr == NULL) {
51 return NULL;
52 }
53
54 arr->member_size = member_size;
55 arr->destruct = destruct;
56 return arr;
57 }
58
ares_array_len(const ares_array_t * arr)59 size_t ares_array_len(const ares_array_t *arr)
60 {
61 if (arr == NULL) {
62 return 0;
63 }
64 return arr->cnt;
65 }
66
ares_array_at(ares_array_t * arr,size_t idx)67 void *ares_array_at(ares_array_t *arr, size_t idx)
68 {
69 if (arr == NULL || idx >= arr->cnt) {
70 return NULL;
71 }
72 return (unsigned char *)arr->arr + ((idx + arr->offset) * arr->member_size);
73 }
74
ares_array_at_const(const ares_array_t * arr,size_t idx)75 const void *ares_array_at_const(const ares_array_t *arr, size_t idx)
76 {
77 if (arr == NULL || idx >= arr->cnt) {
78 return NULL;
79 }
80 return (unsigned char *)arr->arr + ((idx + arr->offset) * arr->member_size);
81 }
82
ares_array_sort(ares_array_t * arr,ares_array_cmp_t cmp)83 ares_status_t ares_array_sort(ares_array_t *arr, ares_array_cmp_t cmp)
84 {
85 if (arr == NULL || cmp == NULL) {
86 return ARES_EFORMERR;
87 }
88
89 /* Nothing to sort */
90 if (arr->cnt < 2) {
91 return ARES_SUCCESS;
92 }
93
94 qsort((unsigned char *)arr->arr + (arr->offset * arr->member_size), arr->cnt,
95 arr->member_size, cmp);
96 return ARES_SUCCESS;
97 }
98
ares_array_destroy(ares_array_t * arr)99 void ares_array_destroy(ares_array_t *arr)
100 {
101 size_t i;
102
103 if (arr == NULL) {
104 return;
105 }
106
107 if (arr->destruct != NULL) {
108 for (i = 0; i < arr->cnt; i++) {
109 arr->destruct(ares_array_at(arr, i));
110 }
111 }
112
113 ares_free(arr->arr);
114 ares_free(arr);
115 }
116
117 /* NOTE: this function operates on actual indexes, NOT indexes using the
118 * arr->offset */
ares_array_move(ares_array_t * arr,size_t dest_idx,size_t src_idx)119 static ares_status_t ares_array_move(ares_array_t *arr, size_t dest_idx,
120 size_t src_idx)
121 {
122 void *dest_ptr;
123 const void *src_ptr;
124 size_t nmembers;
125
126 if (arr == NULL || dest_idx >= arr->alloc_cnt || src_idx >= arr->alloc_cnt) {
127 return ARES_EFORMERR;
128 }
129
130 /* Nothing to do */
131 if (dest_idx == src_idx) {
132 return ARES_SUCCESS;
133 }
134
135 dest_ptr = (unsigned char *)arr->arr + (dest_idx * arr->member_size);
136 src_ptr = (unsigned char *)arr->arr + (src_idx * arr->member_size);
137
138 /* Check to make sure shifting to the right won't overflow our allocation
139 * boundary */
140 if (dest_idx > src_idx && arr->cnt + (dest_idx - src_idx) > arr->alloc_cnt) {
141 return ARES_EFORMERR;
142 }
143
144 nmembers = arr->cnt - (src_idx - arr->offset);
145 memmove(dest_ptr, src_ptr, nmembers * arr->member_size);
146
147 return ARES_SUCCESS;
148 }
149
ares_array_finish(ares_array_t * arr,size_t * num_members)150 void *ares_array_finish(ares_array_t *arr, size_t *num_members)
151 {
152 void *ptr;
153
154 if (arr == NULL || num_members == NULL) {
155 return NULL;
156 }
157
158 /* Make sure we move data to beginning of allocation */
159 if (arr->offset != 0) {
160 if (ares_array_move(arr, 0, arr->offset) != ARES_SUCCESS) {
161 return NULL;
162 }
163 arr->offset = 0;
164 }
165
166 ptr = arr->arr;
167 *num_members = arr->cnt;
168 ares_free(arr);
169 return ptr;
170 }
171
ares_array_set_size(ares_array_t * arr,size_t size)172 ares_status_t ares_array_set_size(ares_array_t *arr, size_t size)
173 {
174 void *temp;
175
176 if (arr == NULL || size == 0 || size < arr->cnt) {
177 return ARES_EFORMERR;
178 }
179
180 /* Always operate on powers of 2 */
181 size = ares_round_up_pow2(size);
182
183 if (size < ARES__ARRAY_MIN) {
184 size = ARES__ARRAY_MIN;
185 }
186
187 /* If our allocation size is already large enough, skip */
188 if (size <= arr->alloc_cnt) {
189 return ARES_SUCCESS;
190 }
191
192 temp = ares_realloc_zero(arr->arr, arr->alloc_cnt * arr->member_size,
193 size * arr->member_size);
194 if (temp == NULL) {
195 return ARES_ENOMEM;
196 }
197 arr->alloc_cnt = size;
198 arr->arr = temp;
199 return ARES_SUCCESS;
200 }
201
ares_array_insert_at(void ** elem_ptr,ares_array_t * arr,size_t idx)202 ares_status_t ares_array_insert_at(void **elem_ptr, ares_array_t *arr,
203 size_t idx)
204 {
205 void *ptr;
206 ares_status_t status;
207
208 if (arr == NULL) {
209 return ARES_EFORMERR;
210 }
211
212 /* Not >= since we are allowed to append to the end */
213 if (idx > arr->cnt) {
214 return ARES_EFORMERR;
215 }
216
217 /* Allocate more if needed */
218 status = ares_array_set_size(arr, arr->cnt + 1);
219 if (status != ARES_SUCCESS) {
220 return status;
221 }
222
223 /* Shift if we have memory but not enough room at the end */
224 if (arr->cnt + 1 + arr->offset > arr->alloc_cnt) {
225 status = ares_array_move(arr, 0, arr->offset);
226 if (status != ARES_SUCCESS) {
227 return status;
228 }
229 arr->offset = 0;
230 }
231
232 /* If we're inserting anywhere other than the end, we need to move some
233 * elements out of the way */
234 if (idx != arr->cnt) {
235 status = ares_array_move(arr, idx + arr->offset + 1, idx + arr->offset);
236 if (status != ARES_SUCCESS) {
237 return status;
238 }
239 }
240
241 /* Ok, we're guaranteed to have a gap where we need it, lets zero it out,
242 * and return it */
243 ptr = (unsigned char *)arr->arr + ((idx + arr->offset) * arr->member_size);
244 memset(ptr, 0, arr->member_size);
245 arr->cnt++;
246
247 if (elem_ptr) {
248 *elem_ptr = ptr;
249 }
250
251 return ARES_SUCCESS;
252 }
253
ares_array_insert_last(void ** elem_ptr,ares_array_t * arr)254 ares_status_t ares_array_insert_last(void **elem_ptr, ares_array_t *arr)
255 {
256 return ares_array_insert_at(elem_ptr, arr, ares_array_len(arr));
257 }
258
ares_array_insert_first(void ** elem_ptr,ares_array_t * arr)259 ares_status_t ares_array_insert_first(void **elem_ptr, ares_array_t *arr)
260 {
261 return ares_array_insert_at(elem_ptr, arr, 0);
262 }
263
ares_array_insertdata_at(ares_array_t * arr,size_t idx,const void * data_ptr)264 ares_status_t ares_array_insertdata_at(ares_array_t *arr, size_t idx,
265 const void *data_ptr)
266 {
267 ares_status_t status;
268 void *ptr = NULL;
269
270 status = ares_array_insert_at(&ptr, arr, idx);
271 if (status != ARES_SUCCESS) {
272 return status;
273 }
274 memcpy(ptr, data_ptr, arr->member_size);
275 return ARES_SUCCESS;
276 }
277
ares_array_insertdata_last(ares_array_t * arr,const void * data_ptr)278 ares_status_t ares_array_insertdata_last(ares_array_t *arr,
279 const void *data_ptr)
280 {
281 ares_status_t status;
282 void *ptr = NULL;
283
284 status = ares_array_insert_last(&ptr, arr);
285 if (status != ARES_SUCCESS) {
286 return status;
287 }
288 memcpy(ptr, data_ptr, arr->member_size);
289 return ARES_SUCCESS;
290 }
291
ares_array_insertdata_first(ares_array_t * arr,const void * data_ptr)292 ares_status_t ares_array_insertdata_first(ares_array_t *arr,
293 const void *data_ptr)
294 {
295 ares_status_t status;
296 void *ptr = NULL;
297
298 status = ares_array_insert_last(&ptr, arr);
299 if (status != ARES_SUCCESS) {
300 return status;
301 }
302 memcpy(ptr, data_ptr, arr->member_size);
303 return ARES_SUCCESS;
304 }
305
ares_array_first(ares_array_t * arr)306 void *ares_array_first(ares_array_t *arr)
307 {
308 return ares_array_at(arr, 0);
309 }
310
ares_array_last(ares_array_t * arr)311 void *ares_array_last(ares_array_t *arr)
312 {
313 size_t cnt = ares_array_len(arr);
314 if (cnt == 0) {
315 return NULL;
316 }
317 return ares_array_at(arr, cnt - 1);
318 }
319
ares_array_first_const(const ares_array_t * arr)320 const void *ares_array_first_const(const ares_array_t *arr)
321 {
322 return ares_array_at_const(arr, 0);
323 }
324
ares_array_last_const(const ares_array_t * arr)325 const void *ares_array_last_const(const ares_array_t *arr)
326 {
327 size_t cnt = ares_array_len(arr);
328 if (cnt == 0) {
329 return NULL;
330 }
331 return ares_array_at_const(arr, cnt - 1);
332 }
333
ares_array_claim_at(void * dest,size_t dest_size,ares_array_t * arr,size_t idx)334 ares_status_t ares_array_claim_at(void *dest, size_t dest_size,
335 ares_array_t *arr, size_t idx)
336 {
337 ares_status_t status;
338
339 if (arr == NULL || idx >= arr->cnt) {
340 return ARES_EFORMERR;
341 }
342
343 if (dest != NULL && dest_size < arr->member_size) {
344 return ARES_EFORMERR;
345 }
346
347 if (dest) {
348 memcpy(dest, ares_array_at(arr, idx), arr->member_size);
349 }
350
351 if (idx == 0) {
352 /* Optimization, if first element, just increment offset, makes removing a
353 * lot from the start quick */
354 arr->offset++;
355 } else if (idx != arr->cnt - 1) {
356 /* Must shift entire array if removing an element from the middle. Does
357 * nothing if removing last element other than decrement count. */
358 status = ares_array_move(arr, idx + arr->offset, idx + arr->offset + 1);
359 if (status != ARES_SUCCESS) {
360 return status;
361 }
362 }
363
364 arr->cnt--;
365 return ARES_SUCCESS;
366 }
367
ares_array_remove_at(ares_array_t * arr,size_t idx)368 ares_status_t ares_array_remove_at(ares_array_t *arr, size_t idx)
369 {
370 void *ptr = ares_array_at(arr, idx);
371 if (arr == NULL || ptr == NULL) {
372 return ARES_EFORMERR;
373 }
374
375 if (arr->destruct != NULL) {
376 arr->destruct(ptr);
377 }
378
379 return ares_array_claim_at(NULL, 0, arr, idx);
380 }
381
ares_array_remove_first(ares_array_t * arr)382 ares_status_t ares_array_remove_first(ares_array_t *arr)
383 {
384 return ares_array_remove_at(arr, 0);
385 }
386
ares_array_remove_last(ares_array_t * arr)387 ares_status_t ares_array_remove_last(ares_array_t *arr)
388 {
389 size_t cnt = ares_array_len(arr);
390 if (cnt == 0) {
391 return ARES_EFORMERR;
392 }
393 return ares_array_remove_at(arr, cnt - 1);
394 }
395