| |
1 /* |
| |
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
| |
3 * |
| |
4 * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. |
| |
5 * |
| |
6 * Redistribution and use in source and binary forms, with or without |
| |
7 * modification, are permitted provided that the following conditions are met: |
| |
8 * |
| |
9 * 1. Redistributions of source code must retain the above copyright |
| |
10 * notice, this list of conditions and the following disclaimer. |
| |
11 * |
| |
12 * 2. Redistributions in binary form must reproduce the above copyright |
| |
13 * notice, this list of conditions and the following disclaimer in the |
| |
14 * documentation and/or other materials provided with the distribution. |
| |
15 * |
| |
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| |
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| |
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| |
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| |
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| |
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| |
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| |
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| |
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| |
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| |
26 * POSSIBILITY OF SUCH DAMAGE. |
| |
27 */ |
| |
28 |
| |
29 #include "cx/buffer.h" |
| |
30 #include "cx/utils.h" |
| |
31 |
| |
32 #include <stdio.h> |
| |
33 #include <string.h> |
| |
34 |
| |
35 int cxBufferInit( |
| |
36 CxBuffer *buffer, |
| |
37 void *space, |
| |
38 size_t capacity, |
| |
39 CxAllocator const *allocator, |
| |
40 int flags |
| |
41 ) { |
| |
42 if (allocator == NULL) allocator = cxDefaultAllocator; |
| |
43 buffer->allocator = allocator; |
| |
44 buffer->flags = flags; |
| |
45 if (!space) { |
| |
46 buffer->bytes = cxMalloc(allocator, capacity); |
| |
47 if (buffer->bytes == NULL) { |
| |
48 return 1; |
| |
49 } |
| |
50 buffer->flags |= CX_BUFFER_FREE_CONTENTS; |
| |
51 } else { |
| |
52 buffer->bytes = space; |
| |
53 } |
| |
54 buffer->capacity = capacity; |
| |
55 buffer->size = 0; |
| |
56 buffer->pos = 0; |
| |
57 |
| |
58 buffer->flush_func = NULL; |
| |
59 buffer->flush_target = NULL; |
| |
60 buffer->flush_blkmax = 0; |
| |
61 buffer->flush_blksize = 4096; |
| |
62 buffer->flush_threshold = SIZE_MAX; |
| |
63 |
| |
64 return 0; |
| |
65 } |
| |
66 |
| |
67 void cxBufferDestroy(CxBuffer *buffer) { |
| |
68 if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { |
| |
69 cxFree(buffer->allocator, buffer->bytes); |
| |
70 } |
| |
71 } |
| |
72 |
| |
73 CxBuffer *cxBufferCreate( |
| |
74 void *space, |
| |
75 size_t capacity, |
| |
76 CxAllocator const *allocator, |
| |
77 int flags |
| |
78 ) { |
| |
79 CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer)); |
| |
80 if (buf == NULL) return NULL; |
| |
81 if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) { |
| |
82 return buf; |
| |
83 } else { |
| |
84 cxFree(allocator, buf); |
| |
85 return NULL; |
| |
86 } |
| |
87 } |
| |
88 |
| |
89 void cxBufferFree(CxBuffer *buffer) { |
| |
90 if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { |
| |
91 cxFree(buffer->allocator, buffer->bytes); |
| |
92 } |
| |
93 cxFree(buffer->allocator, buffer); |
| |
94 } |
| |
95 |
| |
96 int cxBufferSeek( |
| |
97 CxBuffer *buffer, |
| |
98 off_t offset, |
| |
99 int whence |
| |
100 ) { |
| |
101 size_t npos; |
| |
102 switch (whence) { |
| |
103 case SEEK_CUR: |
| |
104 npos = buffer->pos; |
| |
105 break; |
| |
106 case SEEK_END: |
| |
107 npos = buffer->size; |
| |
108 break; |
| |
109 case SEEK_SET: |
| |
110 npos = 0; |
| |
111 break; |
| |
112 default: |
| |
113 return -1; |
| |
114 } |
| |
115 |
| |
116 size_t opos = npos; |
| |
117 npos += offset; |
| |
118 |
| |
119 if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) { |
| |
120 return -1; |
| |
121 } |
| |
122 |
| |
123 if (npos >= buffer->size) { |
| |
124 return -1; |
| |
125 } else { |
| |
126 buffer->pos = npos; |
| |
127 return 0; |
| |
128 } |
| |
129 |
| |
130 } |
| |
131 |
| |
132 void cxBufferClear(CxBuffer *buffer) { |
| |
133 memset(buffer->bytes, 0, buffer->size); |
| |
134 buffer->size = 0; |
| |
135 buffer->pos = 0; |
| |
136 } |
| |
137 |
| |
138 void cxBufferReset(CxBuffer *buffer) { |
| |
139 buffer->size = 0; |
| |
140 buffer->pos = 0; |
| |
141 } |
| |
142 |
| |
143 int cxBufferEof(CxBuffer const *buffer) { |
| |
144 return buffer->pos >= buffer->size; |
| |
145 } |
| |
146 |
| |
147 int cxBufferMinimumCapacity( |
| |
148 CxBuffer *buffer, |
| |
149 size_t newcap |
| |
150 ) { |
| |
151 if (newcap <= buffer->capacity) { |
| |
152 return 0; |
| |
153 } |
| |
154 |
| |
155 if (cxReallocate(buffer->allocator, |
| |
156 (void **) &buffer->bytes, newcap) == 0) { |
| |
157 buffer->capacity = newcap; |
| |
158 return 0; |
| |
159 } else { |
| |
160 return -1; |
| |
161 } |
| |
162 } |
| |
163 |
| |
164 /** |
| |
165 * Helps flushing data to the flush target of a buffer. |
| |
166 * |
| |
167 * @param buffer the buffer containing the config |
| |
168 * @param space the data to flush |
| |
169 * @param size the element size |
| |
170 * @param nitems the number of items |
| |
171 * @return the number of items flushed |
| |
172 */ |
| |
173 static size_t cx_buffer_write_flush_helper( |
| |
174 CxBuffer *buffer, |
| |
175 unsigned char const *space, |
| |
176 size_t size, |
| |
177 size_t nitems |
| |
178 ) { |
| |
179 size_t pos = 0; |
| |
180 size_t remaining = nitems; |
| |
181 size_t max_items = buffer->flush_blksize / size; |
| |
182 while (remaining > 0) { |
| |
183 size_t items = remaining > max_items ? max_items : remaining; |
| |
184 size_t flushed = buffer->flush_func( |
| |
185 space + pos, |
| |
186 size, items, |
| |
187 buffer->flush_target); |
| |
188 if (flushed > 0) { |
| |
189 pos += (flushed * size); |
| |
190 remaining -= flushed; |
| |
191 } else { |
| |
192 // if no bytes can be flushed out anymore, we give up |
| |
193 break; |
| |
194 } |
| |
195 } |
| |
196 return nitems - remaining; |
| |
197 } |
| |
198 |
| |
199 size_t cxBufferWrite( |
| |
200 void const *ptr, |
| |
201 size_t size, |
| |
202 size_t nitems, |
| |
203 CxBuffer *buffer |
| |
204 ) { |
| |
205 // optimize for easy case |
| |
206 if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { |
| |
207 memcpy(buffer->bytes + buffer->pos, ptr, nitems); |
| |
208 buffer->pos += nitems; |
| |
209 if (buffer->pos > buffer->size) { |
| |
210 buffer->size = buffer->pos; |
| |
211 } |
| |
212 return nitems; |
| |
213 } |
| |
214 |
| |
215 size_t len; |
| |
216 size_t nitems_out = nitems; |
| |
217 if (cx_szmul(size, nitems, &len)) { |
| |
218 return 0; |
| |
219 } |
| |
220 size_t required = buffer->pos + len; |
| |
221 if (buffer->pos > required) { |
| |
222 return 0; |
| |
223 } |
| |
224 |
| |
225 bool perform_flush = false; |
| |
226 if (required > buffer->capacity) { |
| |
227 if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) { |
| |
228 if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) { |
| |
229 perform_flush = true; |
| |
230 } else { |
| |
231 if (cxBufferMinimumCapacity(buffer, required)) { |
| |
232 return 0; |
| |
233 } |
| |
234 } |
| |
235 } else { |
| |
236 if (buffer->flush_blkmax > 0) { |
| |
237 perform_flush = true; |
| |
238 } else { |
| |
239 // truncate data to be written, if we can neither extend nor flush |
| |
240 len = buffer->capacity - buffer->pos; |
| |
241 if (size > 1) { |
| |
242 len -= len % size; |
| |
243 } |
| |
244 nitems_out = len / size; |
| |
245 } |
| |
246 } |
| |
247 } |
| |
248 |
| |
249 if (len == 0) { |
| |
250 return len; |
| |
251 } |
| |
252 |
| |
253 if (perform_flush) { |
| |
254 size_t flush_max; |
| |
255 if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) { |
| |
256 return 0; |
| |
257 } |
| |
258 size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL |
| |
259 ? buffer->pos |
| |
260 : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos); |
| |
261 if (flush_pos == buffer->pos) { |
| |
262 // entire buffer has been flushed, we can reset |
| |
263 buffer->size = buffer->pos = 0; |
| |
264 |
| |
265 size_t items_flush; // how many items can also be directly flushed |
| |
266 size_t items_keep; // how many items have to be written to the buffer |
| |
267 |
| |
268 items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size; |
| |
269 if (items_flush > 0) { |
| |
270 items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size); |
| |
271 // in case we could not flush everything, keep the rest |
| |
272 } |
| |
273 items_keep = nitems - items_flush; |
| |
274 if (items_keep > 0) { |
| |
275 // try again with the remaining stuff |
| |
276 unsigned char const *new_ptr = ptr; |
| |
277 new_ptr += items_flush * size; |
| |
278 // report the directly flushed items as written plus the remaining stuff |
| |
279 return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer); |
| |
280 } else { |
| |
281 // all items have been flushed - report them as written |
| |
282 return nitems; |
| |
283 } |
| |
284 } else if (flush_pos == 0) { |
| |
285 // nothing could be flushed at all, we immediately give up without writing any data |
| |
286 return 0; |
| |
287 } else { |
| |
288 // we were partially successful, we shift left and try again |
| |
289 cxBufferShiftLeft(buffer, flush_pos); |
| |
290 return cxBufferWrite(ptr, size, nitems, buffer); |
| |
291 } |
| |
292 } else { |
| |
293 memcpy(buffer->bytes + buffer->pos, ptr, len); |
| |
294 buffer->pos += len; |
| |
295 if (buffer->pos > buffer->size) { |
| |
296 buffer->size = buffer->pos; |
| |
297 } |
| |
298 return nitems_out; |
| |
299 } |
| |
300 |
| |
301 } |
| |
302 |
| |
303 int cxBufferPut( |
| |
304 CxBuffer *buffer, |
| |
305 int c |
| |
306 ) { |
| |
307 c &= 0xFF; |
| |
308 unsigned char const ch = c; |
| |
309 if (cxBufferWrite(&ch, 1, 1, buffer) == 1) { |
| |
310 return c; |
| |
311 } else { |
| |
312 return EOF; |
| |
313 } |
| |
314 } |
| |
315 |
| |
316 size_t cxBufferPutString( |
| |
317 CxBuffer *buffer, |
| |
318 const char *str |
| |
319 ) { |
| |
320 return cxBufferWrite(str, 1, strlen(str), buffer); |
| |
321 } |
| |
322 |
| |
323 size_t cxBufferRead( |
| |
324 void *ptr, |
| |
325 size_t size, |
| |
326 size_t nitems, |
| |
327 CxBuffer *buffer |
| |
328 ) { |
| |
329 size_t len; |
| |
330 if (cx_szmul(size, nitems, &len)) { |
| |
331 return 0; |
| |
332 } |
| |
333 if (buffer->pos + len > buffer->size) { |
| |
334 len = buffer->size - buffer->pos; |
| |
335 if (size > 1) len -= len % size; |
| |
336 } |
| |
337 |
| |
338 if (len <= 0) { |
| |
339 return len; |
| |
340 } |
| |
341 |
| |
342 memcpy(ptr, buffer->bytes + buffer->pos, len); |
| |
343 buffer->pos += len; |
| |
344 |
| |
345 return len / size; |
| |
346 } |
| |
347 |
| |
348 int cxBufferGet(CxBuffer *buffer) { |
| |
349 if (cxBufferEof(buffer)) { |
| |
350 return EOF; |
| |
351 } else { |
| |
352 int c = buffer->bytes[buffer->pos]; |
| |
353 buffer->pos++; |
| |
354 return c; |
| |
355 } |
| |
356 } |
| |
357 |
| |
358 int cxBufferShiftLeft( |
| |
359 CxBuffer *buffer, |
| |
360 size_t shift |
| |
361 ) { |
| |
362 if (shift >= buffer->size) { |
| |
363 buffer->pos = buffer->size = 0; |
| |
364 } else { |
| |
365 memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift); |
| |
366 buffer->size -= shift; |
| |
367 |
| |
368 if (buffer->pos >= shift) { |
| |
369 buffer->pos -= shift; |
| |
370 } else { |
| |
371 buffer->pos = 0; |
| |
372 } |
| |
373 } |
| |
374 return 0; |
| |
375 } |
| |
376 |
| |
377 int cxBufferShiftRight( |
| |
378 CxBuffer *buffer, |
| |
379 size_t shift |
| |
380 ) { |
| |
381 size_t req_capacity = buffer->size + shift; |
| |
382 size_t movebytes; |
| |
383 |
| |
384 // auto extend buffer, if required and enabled |
| |
385 if (buffer->capacity < req_capacity) { |
| |
386 if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) { |
| |
387 if (cxBufferMinimumCapacity(buffer, req_capacity)) { |
| |
388 return 1; |
| |
389 } |
| |
390 movebytes = buffer->size; |
| |
391 } else { |
| |
392 movebytes = buffer->capacity - shift; |
| |
393 } |
| |
394 } else { |
| |
395 movebytes = buffer->size; |
| |
396 } |
| |
397 |
| |
398 memmove(buffer->bytes + shift, buffer->bytes, movebytes); |
| |
399 buffer->size = shift + movebytes; |
| |
400 |
| |
401 buffer->pos += shift; |
| |
402 if (buffer->pos > buffer->size) { |
| |
403 buffer->pos = buffer->size; |
| |
404 } |
| |
405 |
| |
406 return 0; |
| |
407 } |
| |
408 |
| |
409 int cxBufferShift( |
| |
410 CxBuffer *buffer, |
| |
411 off_t shift |
| |
412 ) { |
| |
413 if (shift < 0) { |
| |
414 return cxBufferShiftLeft(buffer, (size_t) (-shift)); |
| |
415 } else if (shift > 0) { |
| |
416 return cxBufferShiftRight(buffer, (size_t) shift); |
| |
417 } else { |
| |
418 return 0; |
| |
419 } |
| |
420 } |