Endian-independent binary IO utilities for C
at main 12 kB view raw
1#ifndef __kf_bini__ 2#define __kf_bini__ 3 4/* 5bini.h: endian-independent binary IO. 6License: BSD 3-Clause. See EOF for license text. 7 8Version: 1.1 9 10Changelog: 11 1.0 (Dec 28, 2025): Initial version. 12 1.1 (Dec 30, 2025): Use an offset instead of shifting the 13 byte buffer so carelessly. _bini_grow will shift the 14 buffer on-demand instead so as to conserve CPU time. 15*/ 16 17/* Used to add functions for version-specific types. */ 18#define bini_c 1989 19#ifdef __STDC_VERSION__ 20# undef bini_c 21# if __STDC_VERSION__ == 199901 22# define bini_c 1999 23# elif __STDC_VERSION__ == 201112 24# define bini_c 2011 25# elif __STDC_VERSION__ == 201710 26# define bini_c 2017 27# elif __STDC_VERSION__ == 202000 || __STDC_VERSION__ == 202311 28# define bini_c 2023 29# else 30# warning "bini.h: unknown C version. Falling back to C89." 31# define bini_c 1989 32# endif 33#endif 34 35 36#include <stdint.h> 37#include <stddef.h> 38#include <stdarg.h> 39#include <stdio.h> 40#if bini_c >= 1999 41# include <stdbool.h> 42#endif 43 44 45#ifndef BINI_BUFSIZ 46/* Starting size for bini_stream buffers. */ 47# define BINI_BUFSIZ 8192 48#endif 49 50#ifndef BINI_STRBUFSIZ 51/* Starting size for bini_*print buffers. */ 52# define BINI_STRBUFSIZ 1024 53#endif 54 55 56/* Execute the `x` macro for each type that bini uses. 57 Excludes string types (char *). */ 58#ifndef BINI_X 59# if bini_c >= 1999 60# define BINI_X(x) \ 61 x(char, c) \ 62 x(short, s) \ 63 x(int, i) \ 64 x(long, l) \ 65 x(long long, ll) \ 66 x(unsigned char, cu) \ 67 x(unsigned short, su) \ 68 x(unsigned int, iu) \ 69 x(unsigned long, lu) \ 70 x(unsigned long long, llu) \ 71 x(float, f) \ 72 x(double, d) \ 73 x(bool, b) 74# else 75# define BINI_X(x) \ 76 x(char, c) \ 77 x(short, s) \ 78 x(int, i) \ 79 x(long, l) \ 80 x(unsigned char, cu) \ 81 x(unsigned short, su) \ 82 x(unsigned int, iu) \ 83 x(unsigned long, lu) \ 84 x(float, f) \ 85 x(double, d) 86# endif 87#endif 88 89 90enum { BINI_ERR, BINI_OK }; 91enum { BINI_BIGENDIAN, BINI_LITTLEENDIAN }; 92enum 93{ 94 /* Read values from the end of the buffer. */ 95 BINI_STACK, 96 /* Read values from the start of the buffer. */ 97 BINI_STREAM 98}; 99 100 101/* Represents a binary IO stream. */ 102struct bini_stream 103{ 104 /* Read mode for the stream. Should be either BINI_STACK or BINI_STREAM. */ 105 int mode; 106 /* Offset from the start of the buffer in bytes. Only used for BINI_STREAM. */ 107 size_t off; 108 size_t cap; 109 size_t len; 110 uint8_t *buffer; 111}; 112 113 114int bini_getendian(void); 115 116 117/* Initialize a bini_stream, allocating a buffer and 118 setting each field to its respective defaults. Also 119 see bini_new(). Free data using bini_end(). */ 120int bini_init(struct bini_stream *); 121/* Create a new bini_stream, allocating a starting buffer 122 of BINI_BUFSIZ bytes. To use your own buffer, initialize 123 your own bini_stream and call bini_init() to fill in the 124 necessary fields. Free data using bini_close(). */ 125struct bini_stream *bini_new(void); 126/* End a bini_stream, flushing it if need be and freeing 127 its buffer. */ 128void bini_end(struct bini_stream *); 129/* End a bini_stream, flushing it if need be, freeing its 130 buffer, and frees the provided variable as well. */ 131void bini_close(struct bini_stream *bs); 132 133/* Shift a binary stream, resetting the offset and shifting the array. */ 134void bini_shift(struct bini_stream *); 135/* Flush a binary stream, clearing its buffer. */ 136int bini_flush(struct bini_stream *); 137/* Dump the binary stream's buffer to fp. */ 138void bini_dump(FILE *fp, struct bini_stream *bs); 139 140/* Read data from a FILE into a binary stream. */ 141size_t bini_fread(struct bini_stream *bs, size_t size, size_t n, FILE *fp); 142/* Write data to a FILE into a binary stream. This will 143 NOT consume data from the stream! Stack/stream mode does 144 not affect this function. 145 To consume data, use `bs->len -= bini_fwrite(bs, ...);` */ 146size_t bini_fwrite(struct bini_stream *bs, size_t size, size_t n, FILE *fp); 147 148 149#define _bini_rwx(_type, _name) \ 150 /* Write a `typeof(v)` to the stream. */ \ 151 void bini_w##_name(struct bini_stream *bs, _type v); \ 152 /* Read a `typeof(v)` from the stream. */ \ 153 _type bini_r##_name(struct bini_stream *bs); 154BINI_X(_bini_rwx) 155#undef _bini_rwx 156 157void bini_wstrn(struct bini_stream *, const char *, size_t); 158void bini_wstr(struct bini_stream *, const char *); 159 160size_t bini_rstr(struct bini_stream *, char *); 161 162#if 0 /* unimplemented */ 163/* Write to a binary stream. */ 164int bini_vprint(struct bini_stream *bs, char *fmt, va_list va); 165/* Write to a binary stream. */ 166int bini_print(struct bini_stream *bs, char *fmt, ...); 167 168/* Read from a binary stream. */ 169int bini_vscan(struct bini_stream *bs, char *fmt, va_list va); 170/* Read from a binary stream. */ 171int bini_scan(struct bini_stream *bs, char *fmt, ...); 172#endif 173 174 175/* Aliased names that are more akin to libc names. */ 176#if defined(bini_libc_names) 177 178typedef struct bini_stream bstream; 179 180# define bnew bini_new 181# define binit bini_init 182# define bend bini_end 183# define bclose bini_close 184 185# define bflush bini_flush 186# define bdump bini_dump 187 188# define bputc bini_wc 189# define bputs bini_ws 190# define bputi bini_wi 191# define bputl bini_wl 192# define bputll bini_wll 193# define bputcu bini_wcu 194# define bputsu bini_wsu 195# define bputiu bini_wiu 196# define bputlu bini_wlu 197# define bputllu bini_wllu 198# define bputf bini_wf 199# define bputd bini_wd 200# define bputb bini_wb 201# define bputstr bini_wstr 202# define bputstrn bini_wstrn 203 204# define breadc bini_rc 205# define breads bini_rs 206# define breadi bini_ri 207# define breadl bini_rl 208# define breadll bini_rll 209# define breadcu bini_rcu 210# define breadsu bini_rsu 211# define breadiu bini_riu 212# define breadlu bini_rlu 213# define breadllu bini_rllu 214# define breadf bini_rf 215# define breadd bini_rd 216# define breadb bini_rb 217# define breadstr bini_rstr 218# define breadstrn bini_rstrn 219 220#if 0 221# define bfprint bini_vprint 222# define bprint bini_print 223# define bvscan bini_vscan 224# define bscan bini_scan 225#endif 226 227#endif 228 229 230#if defined(bini_impl) 231 232 233#include <stdlib.h> 234#include <string.h> 235 236 237static 238void _bini_dbg(char *fmt, ...) 239{ 240# if bini_dbg 241 va_list ap; 242 va_start(ap, fmt); 243 vfprintf(stderr, fmt, ap); 244 va_end(ap); 245# else 246 (void)fmt; 247# endif 248} 249 250 251int bini_getendian(void) 252{ 253 const int n = 1; 254 if (*(char *)&n == 1) 255 return BINI_LITTLEENDIAN; 256 return BINI_BIGENDIAN; 257} 258 259 260int bini_init(struct bini_stream *bs) 261{ 262 bs->buffer = calloc(BINI_BUFSIZ, 1); 263 if (!bs->buffer) 264 return BINI_ERR; 265 bs->off = 0; 266 bs->len = 0; 267 bs->cap = BINI_BUFSIZ; 268 bs->mode = BINI_STREAM; 269 return BINI_OK; 270} 271 272struct bini_stream *bini_new(void) 273{ 274 struct bini_stream *bs = malloc(sizeof(struct bini_stream)); 275 bini_init(bs); 276 return bs; 277} 278 279void bini_end(struct bini_stream *bs) 280{ 281 bini_flush(bs); 282 free(bs->buffer); 283} 284 285void bini_close(struct bini_stream *bs) 286{ 287 bini_end(bs); 288 free(bs); 289} 290 291static 292#if bini_c >= 1999 293inline 294#endif 295void _bini_shiftleft(struct bini_stream *bs, size_t n) 296{ 297 size_t i; 298 _bini_dbg("shift %d: (len=%lu)\n", n, bs->len); 299 for (i = 0 ; i < bs->len ; i++) 300 { 301 if (i+n >= bs->len) 302 { 303 _bini_dbg(" %d: [%d] (out of bounds) to [%d]\n", n, i+n, i); 304 bs->buffer[i] = 0; 305 continue; 306 } 307 _bini_dbg(" %d: [%d] (%02x '%c') to [%d]\n", 308 n, 309 i+n, 310 bs->buffer[i+n], 311 bs->buffer[i+n], 312 i); 313 bs->buffer[i] = bs->buffer[i+n]; 314 } 315} 316 317void bini_shift(struct bini_stream *bs) 318{ 319 _bini_shiftleft(bs, bs->off); 320 bs->off = 0; 321} 322 323int bini_flush(struct bini_stream *bs) 324{ 325 size_t i; 326 for (i = 0 ; i < bs->len ; i++) 327 bs->buffer[i] = 0; 328 bs->len = 0; 329 return BINI_OK; 330} 331 332void bini_dump(FILE *fp, struct bini_stream *bs) 333{ 334 size_t i; 335 fprintf(fp, "bini buffer dump:\n"); 336 fprintf(fp, "buffer: %lu/%lu bytes\n", bs->len, bs->cap); 337 fprintf(fp, "---\n"); 338 for (i = 0 ; i < bs->len ; i++) 339 fprintf(fp, ". %02x '%c'\n", bs->buffer[i], bs->buffer[i]); 340 fprintf(fp, "---\n"); 341} 342 343static 344#if bini_c >= 1999 345inline 346#endif 347int _bini_grow(struct bini_stream *bs, size_t n) 348{ 349 uint8_t *newbuf; 350 351 if (bs->off + bs->len + n < bs->cap) 352 return BINI_OK; 353 354 /* Shifting the array could give enough space to 355 fit `n`, avoiding the need to realloc. */ 356 bini_shift(bs); 357 358 if (bs->len + n < bs->cap) 359 return BINI_OK; 360 361 while (bs->len + n >= bs->cap) 362 { 363 newbuf = realloc(bs->buffer, bs->cap * 2); 364 if (!newbuf) 365 return BINI_ERR; 366 bs->buffer = newbuf; 367 bs->cap *= 2; 368 } 369 370 return BINI_OK; 371} 372 373size_t bini_fread(struct bini_stream *bs, size_t size, size_t n, FILE *fp) 374{ 375 size_t nread; 376 _bini_grow(bs, size*n); 377 nread = fread(bs->buffer, size, n, fp); 378 bs->len += nread; 379 return nread; 380} 381 382size_t bini_fwrite(struct bini_stream *bs, size_t size, size_t n, FILE *fp) 383{ 384 return fwrite(bs->buffer, size, n, fp); 385} 386 387#define _bini_rwx(_type, _name) \ 388 void bini_w##_name(struct bini_stream *bs, _type v) \ 389 { \ 390 size_t i; \ 391 uint8_t *bytes; \ 392 int l = bini_getendian() == BINI_LITTLEENDIAN; \ 393 _bini_dbg("%s: %d (%lu bytes)\n", __FUNCTION__, *(int *)&v, sizeof(v)); \ 394 _bini_grow(bs, sizeof(v)); \ 395 bytes = (uint8_t *)&v; \ 396 for (i = 0 ; i < sizeof(v) ; i++) \ 397 { \ 398 _bini_dbg("%s: byte index [%lu] (= %02x '%c') to buffer index %lu\n", \ 399 __FUNCTION__, \ 400 l ? sizeof(v)-i-1 : i, \ 401 bytes[l ? sizeof(v)-i-1 : i], \ 402 bytes[l ? sizeof(v)-i-1 : i], \ 403 bs->off+bs->len+i); \ 404 bs->buffer[bs->off+bs->len+i] = bytes[l ? sizeof(v)-i-1 : i]; \ 405 } \ 406 bs->len += i; \ 407 _bini_dbg("-> %s: %d (%lu bytes)\n", __FUNCTION__, *(int *)bytes, sizeof(v)); \ 408 } \ 409 _type bini_r##_name(struct bini_stream *bs) \ 410 { \ 411 size_t i; \ 412 _type v; \ 413 uint8_t *bytes = (uint8_t *)&v; \ 414 int l = bini_getendian() == BINI_LITTLEENDIAN; \ 415 int m = bs->mode == BINI_STREAM; \ 416 for (i = 0 ; i < sizeof(v) ; i++) \ 417 { \ 418 const size_t p = bs->off + (m ? i : (bs->len-sizeof(v)+i)); \ 419 _bini_dbg("%s: byte index [%lu] from buffer index %lu (= %02x '%c')\n", \ 420 __FUNCTION__, \ 421 l ? sizeof(v)-i-1 : i, \ 422 p, \ 423 bs->buffer[p], \ 424 bs->buffer[p]); \ 425 bytes[l ? sizeof(v)-i-1 : i] = bs->buffer[p]; \ 426 } \ 427 if (m) \ 428 { \ 429 bs->off += i; \ 430 /* _bini_shiftleft(bs, i); */ \ 431 } \ 432 bs->len -= i; \ 433 return v; \ 434 } 435BINI_X(_bini_rwx) 436#undef _bini_rwx 437 438 439void bini_wstrn(struct bini_stream *bs, const char *str, size_t n) 440{ 441 bini_wlu(bs, n); 442 _bini_grow(bs, n); 443 while (str) 444 { 445 bini_wc(bs, *str); 446 str++; 447 } 448} 449 450void bini_wstr(struct bini_stream *bs, const char *str) 451{ 452 size_t i, n = strlen(str); 453 char *s = (char *)str; 454 bini_wlu(bs, n); 455 _bini_grow(bs, n); 456 for (i = 0 ; i < n ; i++) 457 bini_wc(bs, s[i]); 458} 459 460size_t bini_rstr(struct bini_stream *bs, char *str) 461{ 462 size_t i, n = bini_rlu(bs); 463 for (i = 0 ; i < n ; i++) 464 str[i] = bini_rc(bs); 465 return n; 466} 467 468/* 469Bini license: BSD 3-Clause. 470 471Copyright 2025 Emmeline Coats 472 473Redistribution and use in source and binary forms, with or without 474modification, are permitted provided that the following conditions are met: 475 4761. Redistributions of source code must retain the above copyright notice, this 477 list of conditions and the following disclaimer. 478 4792. Redistributions in binary form must reproduce the above copyright notice, 480 this list of conditions and the following disclaimer in the documentation 481 and/or other materials provided with the distribution. 482 4833. Neither the name of the copyright holder nor the names of its contributors 484 may be used to endorse or promote products derived from this software 485 without specific prior written permission. 486 487THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND 488ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 489WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 490DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 491FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 492DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 493SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 494CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 495OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 496OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 497*/ 498 499#endif /* bini_impl */ 500#endif /* __bini__ */