|
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 int cxBufferEof(CxBuffer const *buffer) { |
|
139 return buffer->pos >= buffer->size; |
|
140 } |
|
141 |
|
142 int cxBufferMinimumCapacity( |
|
143 CxBuffer *buffer, |
|
144 size_t newcap |
|
145 ) { |
|
146 if (newcap <= buffer->capacity) { |
|
147 return 0; |
|
148 } |
|
149 |
|
150 if (cxReallocate(buffer->allocator, |
|
151 (void **) &buffer->bytes, newcap) == 0) { |
|
152 buffer->capacity = newcap; |
|
153 return 0; |
|
154 } else { |
|
155 return -1; |
|
156 } |
|
157 } |
|
158 |
|
159 /** |
|
160 * Helps flushing data to the flush target of a buffer. |
|
161 * |
|
162 * @param buffer the buffer containing the config |
|
163 * @param space the data to flush |
|
164 * @param size the element size |
|
165 * @param nitems the number of items |
|
166 * @return the number of items flushed |
|
167 */ |
|
168 static size_t cx_buffer_write_flush_helper( |
|
169 CxBuffer *buffer, |
|
170 unsigned char const *space, |
|
171 size_t size, |
|
172 size_t nitems |
|
173 ) { |
|
174 size_t pos = 0; |
|
175 size_t remaining = nitems; |
|
176 size_t max_items = buffer->flush_blksize / size; |
|
177 while (remaining > 0) { |
|
178 size_t items = remaining > max_items ? max_items : remaining; |
|
179 size_t flushed = buffer->flush_func( |
|
180 space + pos, |
|
181 size, items, |
|
182 buffer->flush_target); |
|
183 if (flushed > 0) { |
|
184 pos += (flushed * size); |
|
185 remaining -= flushed; |
|
186 } else { |
|
187 // if no bytes can be flushed out anymore, we give up |
|
188 break; |
|
189 } |
|
190 } |
|
191 return nitems - remaining; |
|
192 } |
|
193 |
|
194 size_t cxBufferWrite( |
|
195 void const *ptr, |
|
196 size_t size, |
|
197 size_t nitems, |
|
198 CxBuffer *buffer |
|
199 ) { |
|
200 // optimize for easy case |
|
201 if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { |
|
202 memcpy(buffer->bytes + buffer->pos, ptr, nitems); |
|
203 buffer->pos += nitems; |
|
204 if (buffer->pos > buffer->size) { |
|
205 buffer->size = buffer->pos; |
|
206 } |
|
207 return nitems; |
|
208 } |
|
209 |
|
210 size_t len; |
|
211 size_t nitems_out = nitems; |
|
212 if (cx_szmul(size, nitems, &len)) { |
|
213 return 0; |
|
214 } |
|
215 size_t required = buffer->pos + len; |
|
216 if (buffer->pos > required) { |
|
217 return 0; |
|
218 } |
|
219 |
|
220 bool perform_flush = false; |
|
221 if (required > buffer->capacity) { |
|
222 if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) { |
|
223 if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) { |
|
224 perform_flush = true; |
|
225 } else { |
|
226 if (cxBufferMinimumCapacity(buffer, required)) { |
|
227 return 0; |
|
228 } |
|
229 } |
|
230 } else { |
|
231 if (buffer->flush_blkmax > 0) { |
|
232 perform_flush = true; |
|
233 } else { |
|
234 // truncate data to be written, if we can neither extend nor flush |
|
235 len = buffer->capacity - buffer->pos; |
|
236 if (size > 1) { |
|
237 len -= len % size; |
|
238 } |
|
239 nitems_out = len / size; |
|
240 } |
|
241 } |
|
242 } |
|
243 |
|
244 if (len == 0) { |
|
245 return len; |
|
246 } |
|
247 |
|
248 if (perform_flush) { |
|
249 size_t flush_max; |
|
250 if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) { |
|
251 return 0; |
|
252 } |
|
253 size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL |
|
254 ? buffer->pos |
|
255 : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos); |
|
256 if (flush_pos == buffer->pos) { |
|
257 // entire buffer has been flushed, we can reset |
|
258 buffer->size = buffer->pos = 0; |
|
259 |
|
260 size_t items_flush; // how many items can also be directly flushed |
|
261 size_t items_keep; // how many items have to be written to the buffer |
|
262 |
|
263 items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size; |
|
264 if (items_flush > 0) { |
|
265 items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size); |
|
266 // in case we could not flush everything, keep the rest |
|
267 } |
|
268 items_keep = nitems - items_flush; |
|
269 if (items_keep > 0) { |
|
270 // try again with the remaining stuff |
|
271 unsigned char const *new_ptr = ptr; |
|
272 new_ptr += items_flush * size; |
|
273 // report the directly flushed items as written plus the remaining stuff |
|
274 return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer); |
|
275 } else { |
|
276 // all items have been flushed - report them as written |
|
277 return nitems; |
|
278 } |
|
279 } else if (flush_pos == 0) { |
|
280 // nothing could be flushed at all, we immediately give up without writing any data |
|
281 return 0; |
|
282 } else { |
|
283 // we were partially successful, we shift left and try again |
|
284 cxBufferShiftLeft(buffer, flush_pos); |
|
285 return cxBufferWrite(ptr, size, nitems, buffer); |
|
286 } |
|
287 } else { |
|
288 memcpy(buffer->bytes + buffer->pos, ptr, len); |
|
289 buffer->pos += len; |
|
290 if (buffer->pos > buffer->size) { |
|
291 buffer->size = buffer->pos; |
|
292 } |
|
293 return nitems_out; |
|
294 } |
|
295 |
|
296 } |
|
297 |
|
298 int cxBufferPut( |
|
299 CxBuffer *buffer, |
|
300 int c |
|
301 ) { |
|
302 c &= 0xFF; |
|
303 unsigned char const ch = c; |
|
304 if (cxBufferWrite(&ch, 1, 1, buffer) == 1) { |
|
305 return c; |
|
306 } else { |
|
307 return EOF; |
|
308 } |
|
309 } |
|
310 |
|
311 size_t cxBufferPutString( |
|
312 CxBuffer *buffer, |
|
313 const char *str |
|
314 ) { |
|
315 return cxBufferWrite(str, 1, strlen(str), buffer); |
|
316 } |
|
317 |
|
318 size_t cxBufferRead( |
|
319 void *ptr, |
|
320 size_t size, |
|
321 size_t nitems, |
|
322 CxBuffer *buffer |
|
323 ) { |
|
324 size_t len; |
|
325 if (cx_szmul(size, nitems, &len)) { |
|
326 return 0; |
|
327 } |
|
328 if (buffer->pos + len > buffer->size) { |
|
329 len = buffer->size - buffer->pos; |
|
330 if (size > 1) len -= len % size; |
|
331 } |
|
332 |
|
333 if (len <= 0) { |
|
334 return len; |
|
335 } |
|
336 |
|
337 memcpy(ptr, buffer->bytes + buffer->pos, len); |
|
338 buffer->pos += len; |
|
339 |
|
340 return len / size; |
|
341 } |
|
342 |
|
343 int cxBufferGet(CxBuffer *buffer) { |
|
344 if (cxBufferEof(buffer)) { |
|
345 return EOF; |
|
346 } else { |
|
347 int c = buffer->bytes[buffer->pos]; |
|
348 buffer->pos++; |
|
349 return c; |
|
350 } |
|
351 } |
|
352 |
|
353 int cxBufferShiftLeft( |
|
354 CxBuffer *buffer, |
|
355 size_t shift |
|
356 ) { |
|
357 if (shift >= buffer->size) { |
|
358 buffer->pos = buffer->size = 0; |
|
359 } else { |
|
360 memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift); |
|
361 buffer->size -= shift; |
|
362 |
|
363 if (buffer->pos >= shift) { |
|
364 buffer->pos -= shift; |
|
365 } else { |
|
366 buffer->pos = 0; |
|
367 } |
|
368 } |
|
369 return 0; |
|
370 } |
|
371 |
|
372 int cxBufferShiftRight( |
|
373 CxBuffer *buffer, |
|
374 size_t shift |
|
375 ) { |
|
376 size_t req_capacity = buffer->size + shift; |
|
377 size_t movebytes; |
|
378 |
|
379 // auto extend buffer, if required and enabled |
|
380 if (buffer->capacity < req_capacity) { |
|
381 if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) { |
|
382 if (cxBufferMinimumCapacity(buffer, req_capacity)) { |
|
383 return 1; |
|
384 } |
|
385 movebytes = buffer->size; |
|
386 } else { |
|
387 movebytes = buffer->capacity - shift; |
|
388 } |
|
389 } else { |
|
390 movebytes = buffer->size; |
|
391 } |
|
392 |
|
393 memmove(buffer->bytes + shift, buffer->bytes, movebytes); |
|
394 buffer->size = shift + movebytes; |
|
395 |
|
396 buffer->pos += shift; |
|
397 if (buffer->pos > buffer->size) { |
|
398 buffer->pos = buffer->size; |
|
399 } |
|
400 |
|
401 return 0; |
|
402 } |
|
403 |
|
404 int cxBufferShift( |
|
405 CxBuffer *buffer, |
|
406 off_t shift |
|
407 ) { |
|
408 if (shift < 0) { |
|
409 return cxBufferShiftLeft(buffer, (size_t) (-shift)); |
|
410 } else if (shift > 0) { |
|
411 return cxBufferShiftRight(buffer, (size_t) shift); |
|
412 } else { |
|
413 return 0; |
|
414 } |
|
415 } |