| 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 26 * POSSIBILITY OF SUCH DAMAGE. |
26 * POSSIBILITY OF SUCH DAMAGE. |
| 27 */ |
27 */ |
| 28 |
28 |
| 29 #include "cx/buffer.h" |
29 #include "cx/buffer.h" |
| 30 #include "cx/utils.h" |
|
| 31 |
30 |
| 32 #include <stdio.h> |
31 #include <stdio.h> |
| 33 #include <string.h> |
32 #include <string.h> |
| |
33 #include <errno.h> |
| |
34 |
| |
35 static int buffer_copy_on_write(CxBuffer* buffer) { |
| |
36 if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0; |
| |
37 void *newspace = cxMalloc(buffer->allocator, buffer->capacity); |
| |
38 if (NULL == newspace) return -1; |
| |
39 memcpy(newspace, buffer->space, buffer->size); |
| |
40 buffer->space = newspace; |
| |
41 buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE; |
| |
42 buffer->flags |= CX_BUFFER_FREE_CONTENTS; |
| |
43 return 0; |
| |
44 } |
| 34 |
45 |
| 35 int cxBufferInit( |
46 int cxBufferInit( |
| 36 CxBuffer *buffer, |
47 CxBuffer *buffer, |
| 37 void *space, |
48 void *space, |
| 38 size_t capacity, |
49 size_t capacity, |
| 39 CxAllocator const *allocator, |
50 const CxAllocator *allocator, |
| 40 int flags |
51 int flags |
| 41 ) { |
52 ) { |
| 42 if (allocator == NULL) allocator = cxDefaultAllocator; |
53 if (allocator == NULL) { |
| |
54 allocator = cxDefaultAllocator; |
| |
55 } |
| |
56 if (flags & CX_BUFFER_COPY_ON_EXTEND) { |
| |
57 flags |= CX_BUFFER_AUTO_EXTEND; |
| |
58 } |
| 43 buffer->allocator = allocator; |
59 buffer->allocator = allocator; |
| 44 buffer->flags = flags; |
60 buffer->flags = flags; |
| 45 if (!space) { |
61 if (!space) { |
| 46 buffer->bytes = cxMalloc(allocator, capacity); |
62 buffer->bytes = cxMalloc(allocator, capacity); |
| 47 if (buffer->bytes == NULL) { |
63 if (buffer->bytes == NULL) { |
| 48 return 1; |
64 return -1; // LCOV_EXCL_LINE |
| 49 } |
65 } |
| 50 buffer->flags |= CX_BUFFER_FREE_CONTENTS; |
66 buffer->flags |= CX_BUFFER_FREE_CONTENTS; |
| 51 } else { |
67 } else { |
| 52 buffer->bytes = space; |
68 buffer->bytes = space; |
| 53 } |
69 } |
| 63 |
79 |
| 64 return 0; |
80 return 0; |
| 65 } |
81 } |
| 66 |
82 |
| 67 void cxBufferDestroy(CxBuffer *buffer) { |
83 void cxBufferDestroy(CxBuffer *buffer) { |
| 68 if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { |
84 if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { |
| 69 cxFree(buffer->allocator, buffer->bytes); |
85 cxFree(buffer->allocator, buffer->bytes); |
| 70 } |
86 } |
| |
87 memset(buffer, 0, sizeof(CxBuffer)); |
| 71 } |
88 } |
| 72 |
89 |
| 73 CxBuffer *cxBufferCreate( |
90 CxBuffer *cxBufferCreate( |
| 74 void *space, |
91 void *space, |
| 75 size_t capacity, |
92 size_t capacity, |
| 76 CxAllocator const *allocator, |
93 const CxAllocator *allocator, |
| 77 int flags |
94 int flags |
| 78 ) { |
95 ) { |
| |
96 if (allocator == NULL) { |
| |
97 allocator = cxDefaultAllocator; |
| |
98 } |
| 79 CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer)); |
99 CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer)); |
| 80 if (buf == NULL) return NULL; |
100 if (buf == NULL) return NULL; |
| 81 if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) { |
101 if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) { |
| 82 return buf; |
102 return buf; |
| 83 } else { |
103 } else { |
| |
104 // LCOV_EXCL_START |
| 84 cxFree(allocator, buf); |
105 cxFree(allocator, buf); |
| 85 return NULL; |
106 return NULL; |
| |
107 // LCOV_EXCL_STOP |
| 86 } |
108 } |
| 87 } |
109 } |
| 88 |
110 |
| 89 void cxBufferFree(CxBuffer *buffer) { |
111 void cxBufferFree(CxBuffer *buffer) { |
| 90 if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { |
112 if (buffer == NULL) return; |
| 91 cxFree(buffer->allocator, buffer->bytes); |
113 const CxAllocator *allocator = buffer->allocator; |
| 92 } |
114 cxBufferDestroy(buffer); |
| 93 cxFree(buffer->allocator, buffer); |
115 cxFree(allocator, buffer); |
| 94 } |
116 } |
| 95 |
117 |
| 96 int cxBufferSeek( |
118 int cxBufferSeek( |
| 97 CxBuffer *buffer, |
119 CxBuffer *buffer, |
| 98 off_t offset, |
120 off_t offset, |
| 115 |
137 |
| 116 size_t opos = npos; |
138 size_t opos = npos; |
| 117 npos += offset; |
139 npos += offset; |
| 118 |
140 |
| 119 if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) { |
141 if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) { |
| |
142 errno = EOVERFLOW; |
| 120 return -1; |
143 return -1; |
| 121 } |
144 } |
| 122 |
145 |
| 123 if (npos >= buffer->size) { |
146 if (npos > buffer->size) { |
| 124 return -1; |
147 return -1; |
| 125 } else { |
148 } else { |
| 126 buffer->pos = npos; |
149 buffer->pos = npos; |
| 127 return 0; |
150 return 0; |
| 128 } |
151 } |
| 129 |
152 |
| 130 } |
153 } |
| 131 |
154 |
| 132 void cxBufferClear(CxBuffer *buffer) { |
155 void cxBufferClear(CxBuffer *buffer) { |
| 133 memset(buffer->bytes, 0, buffer->size); |
156 if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) { |
| |
157 memset(buffer->bytes, 0, buffer->size); |
| |
158 } |
| 134 buffer->size = 0; |
159 buffer->size = 0; |
| 135 buffer->pos = 0; |
160 buffer->pos = 0; |
| 136 } |
161 } |
| 137 |
162 |
| 138 void cxBufferReset(CxBuffer *buffer) { |
163 void cxBufferReset(CxBuffer *buffer) { |
| 139 buffer->size = 0; |
164 buffer->size = 0; |
| 140 buffer->pos = 0; |
165 buffer->pos = 0; |
| 141 } |
166 } |
| 142 |
167 |
| 143 int cxBufferEof(CxBuffer const *buffer) { |
168 bool cxBufferEof(const CxBuffer *buffer) { |
| 144 return buffer->pos >= buffer->size; |
169 return buffer->pos >= buffer->size; |
| 145 } |
170 } |
| 146 |
171 |
| 147 int cxBufferMinimumCapacity( |
172 int cxBufferMinimumCapacity( |
| 148 CxBuffer *buffer, |
173 CxBuffer *buffer, |
| 150 ) { |
175 ) { |
| 151 if (newcap <= buffer->capacity) { |
176 if (newcap <= buffer->capacity) { |
| 152 return 0; |
177 return 0; |
| 153 } |
178 } |
| 154 |
179 |
| 155 if (cxReallocate(buffer->allocator, |
180 const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; |
| |
181 if (buffer->flags & force_copy_flags) { |
| |
182 void *newspace = cxMalloc(buffer->allocator, newcap); |
| |
183 if (NULL == newspace) return -1; |
| |
184 memcpy(newspace, buffer->space, buffer->size); |
| |
185 buffer->space = newspace; |
| |
186 buffer->capacity = newcap; |
| |
187 buffer->flags &= ~force_copy_flags; |
| |
188 buffer->flags |= CX_BUFFER_FREE_CONTENTS; |
| |
189 return 0; |
| |
190 } else if (cxReallocate(buffer->allocator, |
| 156 (void **) &buffer->bytes, newcap) == 0) { |
191 (void **) &buffer->bytes, newcap) == 0) { |
| 157 buffer->capacity = newcap; |
192 buffer->capacity = newcap; |
| 158 return 0; |
193 return 0; |
| 159 } else { |
194 } else { |
| 160 return -1; |
195 return -1; // LCOV_EXCL_LINE |
| 161 } |
196 } |
| 162 } |
197 } |
| 163 |
198 |
| 164 /** |
199 /** |
| 165 * Helps flushing data to the flush target of a buffer. |
200 * Helps flushing data to the flush target of a buffer. |
| 170 * @param nitems the number of items |
205 * @param nitems the number of items |
| 171 * @return the number of items flushed |
206 * @return the number of items flushed |
| 172 */ |
207 */ |
| 173 static size_t cx_buffer_write_flush_helper( |
208 static size_t cx_buffer_write_flush_helper( |
| 174 CxBuffer *buffer, |
209 CxBuffer *buffer, |
| 175 unsigned char const *space, |
210 const unsigned char *space, |
| 176 size_t size, |
211 size_t size, |
| 177 size_t nitems |
212 size_t nitems |
| 178 ) { |
213 ) { |
| 179 size_t pos = 0; |
214 size_t pos = 0; |
| 180 size_t remaining = nitems; |
215 size_t remaining = nitems; |
| 195 } |
230 } |
| 196 return nitems - remaining; |
231 return nitems - remaining; |
| 197 } |
232 } |
| 198 |
233 |
| 199 size_t cxBufferWrite( |
234 size_t cxBufferWrite( |
| 200 void const *ptr, |
235 const void *ptr, |
| 201 size_t size, |
236 size_t size, |
| 202 size_t nitems, |
237 size_t nitems, |
| 203 CxBuffer *buffer |
238 CxBuffer *buffer |
| 204 ) { |
239 ) { |
| 205 // optimize for easy case |
240 // optimize for easy case |
| 206 if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { |
241 if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { |
| |
242 if (buffer_copy_on_write(buffer)) return 0; |
| 207 memcpy(buffer->bytes + buffer->pos, ptr, nitems); |
243 memcpy(buffer->bytes + buffer->pos, ptr, nitems); |
| 208 buffer->pos += nitems; |
244 buffer->pos += nitems; |
| 209 if (buffer->pos > buffer->size) { |
245 if (buffer->pos > buffer->size) { |
| 210 buffer->size = buffer->pos; |
246 buffer->size = buffer->pos; |
| 211 } |
247 } |
| 213 } |
249 } |
| 214 |
250 |
| 215 size_t len; |
251 size_t len; |
| 216 size_t nitems_out = nitems; |
252 size_t nitems_out = nitems; |
| 217 if (cx_szmul(size, nitems, &len)) { |
253 if (cx_szmul(size, nitems, &len)) { |
| |
254 errno = EOVERFLOW; |
| 218 return 0; |
255 return 0; |
| 219 } |
256 } |
| 220 size_t required = buffer->pos + len; |
257 size_t required = buffer->pos + len; |
| 221 if (buffer->pos > required) { |
258 if (buffer->pos > required) { |
| 222 return 0; |
259 return 0; |
| 223 } |
260 } |
| 224 |
261 |
| 225 bool perform_flush = false; |
262 bool perform_flush = false; |
| 226 if (required > buffer->capacity) { |
263 if (required > buffer->capacity) { |
| 227 if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) { |
264 if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { |
| 228 if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) { |
265 if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) { |
| 229 perform_flush = true; |
266 perform_flush = true; |
| 230 } else { |
267 } else { |
| 231 if (cxBufferMinimumCapacity(buffer, required)) { |
268 if (cxBufferMinimumCapacity(buffer, required)) { |
| 232 return 0; |
269 return 0; // LCOV_EXCL_LINE |
| 233 } |
270 } |
| 234 } |
271 } |
| 235 } else { |
272 } else { |
| 236 if (buffer->flush_blkmax > 0) { |
273 if (buffer->flush_blkmax > 0) { |
| 237 perform_flush = true; |
274 perform_flush = true; |
| 251 } |
288 } |
| 252 |
289 |
| 253 if (perform_flush) { |
290 if (perform_flush) { |
| 254 size_t flush_max; |
291 size_t flush_max; |
| 255 if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) { |
292 if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) { |
| |
293 errno = EOVERFLOW; |
| 256 return 0; |
294 return 0; |
| 257 } |
295 } |
| 258 size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL |
296 size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL |
| 259 ? buffer->pos |
297 ? buffer->pos |
| 260 : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos); |
298 : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos); |
| 271 // in case we could not flush everything, keep the rest |
309 // in case we could not flush everything, keep the rest |
| 272 } |
310 } |
| 273 items_keep = nitems - items_flush; |
311 items_keep = nitems - items_flush; |
| 274 if (items_keep > 0) { |
312 if (items_keep > 0) { |
| 275 // try again with the remaining stuff |
313 // try again with the remaining stuff |
| 276 unsigned char const *new_ptr = ptr; |
314 const unsigned char *new_ptr = ptr; |
| 277 new_ptr += items_flush * size; |
315 new_ptr += items_flush * size; |
| 278 // report the directly flushed items as written plus the remaining stuff |
316 // report the directly flushed items as written plus the remaining stuff |
| 279 return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer); |
317 return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer); |
| 280 } else { |
318 } else { |
| 281 // all items have been flushed - report them as written |
319 // all items have been flushed - report them as written |
| 288 // we were partially successful, we shift left and try again |
326 // we were partially successful, we shift left and try again |
| 289 cxBufferShiftLeft(buffer, flush_pos); |
327 cxBufferShiftLeft(buffer, flush_pos); |
| 290 return cxBufferWrite(ptr, size, nitems, buffer); |
328 return cxBufferWrite(ptr, size, nitems, buffer); |
| 291 } |
329 } |
| 292 } else { |
330 } else { |
| |
331 if (buffer_copy_on_write(buffer)) return 0; |
| 293 memcpy(buffer->bytes + buffer->pos, ptr, len); |
332 memcpy(buffer->bytes + buffer->pos, ptr, len); |
| 294 buffer->pos += len; |
333 buffer->pos += len; |
| 295 if (buffer->pos > buffer->size) { |
334 if (buffer->pos > buffer->size) { |
| 296 buffer->size = buffer->pos; |
335 buffer->size = buffer->pos; |
| 297 } |
336 } |
| 298 return nitems_out; |
337 return nitems_out; |
| 299 } |
338 } |
| 300 |
339 |
| 301 } |
340 } |
| 302 |
341 |
| |
342 size_t cxBufferAppend( |
| |
343 const void *ptr, |
| |
344 size_t size, |
| |
345 size_t nitems, |
| |
346 CxBuffer *buffer |
| |
347 ) { |
| |
348 size_t pos = buffer->pos; |
| |
349 buffer->pos = buffer->size; |
| |
350 size_t written = cxBufferWrite(ptr, size, nitems, buffer); |
| |
351 buffer->pos = pos; |
| |
352 return written; |
| |
353 } |
| |
354 |
| 303 int cxBufferPut( |
355 int cxBufferPut( |
| 304 CxBuffer *buffer, |
356 CxBuffer *buffer, |
| 305 int c |
357 int c |
| 306 ) { |
358 ) { |
| 307 c &= 0xFF; |
359 c &= 0xFF; |
| 308 unsigned char const ch = c; |
360 unsigned char const ch = c; |
| 309 if (cxBufferWrite(&ch, 1, 1, buffer) == 1) { |
361 if (cxBufferWrite(&ch, 1, 1, buffer) == 1) { |
| 310 return c; |
362 return c; |
| 311 } else { |
363 } else { |
| 312 return EOF; |
364 return EOF; |
| |
365 } |
| |
366 } |
| |
367 |
| |
368 int cxBufferTerminate(CxBuffer *buffer) { |
| |
369 bool success = 0 == cxBufferPut(buffer, 0); |
| |
370 if (success) { |
| |
371 buffer->pos--; |
| |
372 buffer->size--; |
| |
373 return 0; |
| |
374 } else { |
| |
375 return -1; |
| 313 } |
376 } |
| 314 } |
377 } |
| 315 |
378 |
| 316 size_t cxBufferPutString( |
379 size_t cxBufferPutString( |
| 317 CxBuffer *buffer, |
380 CxBuffer *buffer, |
| 326 size_t nitems, |
389 size_t nitems, |
| 327 CxBuffer *buffer |
390 CxBuffer *buffer |
| 328 ) { |
391 ) { |
| 329 size_t len; |
392 size_t len; |
| 330 if (cx_szmul(size, nitems, &len)) { |
393 if (cx_szmul(size, nitems, &len)) { |
| |
394 errno = EOVERFLOW; |
| 331 return 0; |
395 return 0; |
| 332 } |
396 } |
| 333 if (buffer->pos + len > buffer->size) { |
397 if (buffer->pos + len > buffer->size) { |
| 334 len = buffer->size - buffer->pos; |
398 len = buffer->size - buffer->pos; |
| 335 if (size > 1) len -= len % size; |
399 if (size > 1) len -= len % size; |
| 360 size_t shift |
424 size_t shift |
| 361 ) { |
425 ) { |
| 362 if (shift >= buffer->size) { |
426 if (shift >= buffer->size) { |
| 363 buffer->pos = buffer->size = 0; |
427 buffer->pos = buffer->size = 0; |
| 364 } else { |
428 } else { |
| |
429 if (buffer_copy_on_write(buffer)) return -1; |
| 365 memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift); |
430 memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift); |
| 366 buffer->size -= shift; |
431 buffer->size -= shift; |
| 367 |
432 |
| 368 if (buffer->pos >= shift) { |
433 if (buffer->pos >= shift) { |
| 369 buffer->pos -= shift; |
434 buffer->pos -= shift; |
| 376 |
441 |
| 377 int cxBufferShiftRight( |
442 int cxBufferShiftRight( |
| 378 CxBuffer *buffer, |
443 CxBuffer *buffer, |
| 379 size_t shift |
444 size_t shift |
| 380 ) { |
445 ) { |
| |
446 if (buffer->size > SIZE_MAX - shift) { |
| |
447 errno = EOVERFLOW; |
| |
448 return -1; |
| |
449 } |
| 381 size_t req_capacity = buffer->size + shift; |
450 size_t req_capacity = buffer->size + shift; |
| 382 size_t movebytes; |
451 size_t movebytes; |
| 383 |
452 |
| 384 // auto extend buffer, if required and enabled |
453 // auto extend buffer, if required and enabled |
| 385 if (buffer->capacity < req_capacity) { |
454 if (buffer->capacity < req_capacity) { |
| 386 if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) { |
455 if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { |
| 387 if (cxBufferMinimumCapacity(buffer, req_capacity)) { |
456 if (cxBufferMinimumCapacity(buffer, req_capacity)) { |
| 388 return 1; |
457 return -1; // LCOV_EXCL_LINE |
| 389 } |
458 } |
| 390 movebytes = buffer->size; |
459 movebytes = buffer->size; |
| 391 } else { |
460 } else { |
| 392 movebytes = buffer->capacity - shift; |
461 movebytes = buffer->capacity - shift; |
| 393 } |
462 } |
| 394 } else { |
463 } else { |
| 395 movebytes = buffer->size; |
464 movebytes = buffer->size; |
| 396 } |
465 } |
| 397 |
466 |
| 398 memmove(buffer->bytes + shift, buffer->bytes, movebytes); |
467 if (movebytes > 0) { |
| 399 buffer->size = shift + movebytes; |
468 if (buffer_copy_on_write(buffer)) return -1; |
| |
469 memmove(buffer->bytes + shift, buffer->bytes, movebytes); |
| |
470 buffer->size = shift + movebytes; |
| |
471 } |
| 400 |
472 |
| 401 buffer->pos += shift; |
473 buffer->pos += shift; |
| 402 if (buffer->pos > buffer->size) { |
474 if (buffer->pos > buffer->size) { |
| 403 buffer->pos = buffer->size; |
475 buffer->pos = buffer->size; |
| 404 } |
476 } |