diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c4ba431 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.27) + +project(allcing C) + +set(CMAKE_C_STANDARD 23) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + + +# ---- thirdparty modules +include(CTest) + + +# ---- common config +add_library(allcing_config INTERFACE) +target_include_directories(allcing_config + INTERFACE + ${PROJECT_SOURCE_DIR} + ${NF7_GENERATED_DIR} +) +target_compile_options(allcing_config INTERFACE + $<$: + /W4 + $<$:/WX> + > + $<$>: + -Wall -Wextra -Wpedantic + -Wno-gnu-zero-variadic-macro-arguments + $<$:-Werror> + > +) + + +# ---- library header +add_library(allcing INTERFACE) +target_sources(allcing INTERFACE allcing.h) +target_link_libraries(allcing INTERFACE allcing_config) + + +# ---- test binary +if (BUILD_TESTING) + add_executable(allcing_test) + target_sources(allcing_test PRIVATE test.c) + target_link_libraries(allcing_test PRIVATE allcing) + add_test(NAME allcing_test COMMAND $) +endif() diff --git a/allcing.h b/allcing.h new file mode 100644 index 0000000..b9d57df --- /dev/null +++ b/allcing.h @@ -0,0 +1,72 @@ +// No copyright +#pragma once + +#include +#include +#include +#include + + +#define ACG_CHUNK_BLOB 0x00 + +#define ACG_CHUNK_BEGIN 0x01 +#define ACG_CHUNK_END 0x02 +#define ACG_CHUNK_CHECK 0x03 + + +static inline uint64_t acg_util_fullbits(uint8_t bits) { + return (((uint64_t) 1U) << bits) - 1U; +} +static inline uint64_t acg_util_umin(uint64_t a, uint64_t b) { + return a > b? b: a; +} + + +struct acg_stream { + uint8_t* buffer; + uint64_t size_bytes; + + uint64_t cursor_bits; +}; + +static inline bool acg_stream_write_bool(struct acg_stream* s, bool v) { + if (s->size_bytes <= s->cursor_bits) { + return false; + } + uint64_t* const c = &s->cursor_bits; + s->buffer[*c/8U] |= (!!v) << (*c%8U); + ++*c; + return true; +} +static inline bool acg_stream_write_uint(struct acg_stream* s, uint64_t v, uint8_t unit_bits) { + assert(unit_bits >= 2U); + assert(unit_bits <= 65U); + + uint8_t* const buf = s->buffer; + uint64_t* const c = &s->cursor_bits; + + const uint8_t unit_data_bits = unit_bits - 1U; + while (v) { + const uint64_t next_v = v >> unit_data_bits; + acg_stream_write_bool(s, !!next_v); + + const uint8_t frag_bits = acg_util_umin(unit_data_bits, (8U - *c%8U)%8U); + if (frag_bits > 0U) { + buf[*c/8U] |= (v & acg_util_fullbits(frag_bits)) << (*c%8U); + *c += frag_bits; + } + + const uint8_t remain_bits = unit_data_bits - frag_bits; + const uint64_t v_part = (v >> frag_bits) & acg_util_fullbits(remain_bits); + memcpy(&buf[*c/8U], &v_part, (remain_bits + 7U) / 8U); + *c += remain_bits; + + v = next_v; + } + return true; +} +static inline bool acg_stream_write_int(struct acg_stream* s, int64_t v, uint8_t unit_bits) { + assert(unit_bits > 0); + const uint64_t uv = v >= 0? ((uint64_t) v): (~((uint64_t) -v) + 1); + return acg_stream_write_uint(s, uv, unit_bits); +} diff --git a/test.c b/test.c new file mode 100644 index 0000000..a148d9f --- /dev/null +++ b/test.c @@ -0,0 +1,68 @@ +#include "allcing.h" + +#ifdef NDEBUG +# undef NDEBUG // this test uses assert() macro +#endif + +#include +#include +#include +#include + + +int main(int, char**) { +# define DEFINE_STREAM(name, offset) \ + struct acg_stream s = { \ + .buffer = (uint8_t[1024U]) {0U}, \ + .size_bytes = 1024U, \ + .cursor_bits = (offset), \ + } +# define STREAM(size, offset) \ + (struct acg_stream) { \ + .buffer = (uint8_t[size]) {0U}, \ + .size_bytes = (size), \ + .cursor_bits = (offset), \ + } + + /* acg_stream_write_bool / value=true */ { + struct acg_stream s = STREAM(1024U, 0U); + assert(acg_stream_write_bool(&s, true)); + assert(s.buffer[0U] == 0x1U); + assert(s.cursor_bits == 1U); + } + /* acg_stream_write_bool / value=false */ { + struct acg_stream s = STREAM(1024U, 0U); + assert(acg_stream_write_bool(&s, false)); + assert(s.buffer[0U] == 0x0U); + assert(s.cursor_bits == 1U); + } + + /* acg_stream_write_uint / ends in a single byte */ { + struct acg_stream s = STREAM(1024U, 0U); + assert(acg_stream_write_uint(&s, 7U, 4U)); + assert(s.buffer[0U] == 0x0EU); + assert(s.cursor_bits == 4U); + } + /* acg_stream_write_uint / ends in a single byte with offset */ { + struct acg_stream s = STREAM(1024U, 1U); + assert(acg_stream_write_uint(&s, 7U, 4U)); + assert(s.buffer[0U] == 0x1CU); + assert(s.cursor_bits == 5U); + } + /* acg_stream_write_uint / separated data bits */ { + struct acg_stream s = STREAM(1024U, 6U); + assert(acg_stream_write_uint(&s, 7U, 4U)); + assert(s.buffer[0U] == 0x80U); + assert(s.buffer[1U] == 0x03U); + assert(s.cursor_bits == 10U); + } + /* acg_stream_write_uint / separated cont flag */ { + struct acg_stream s = STREAM(1024U, 7U); + assert(acg_stream_write_uint(&s, 7U, 4U)); + assert(s.buffer[0U] == 0x00U); + assert(s.buffer[1U] == 0x07U); + assert(s.cursor_bits == 11U); + } + + return EXIT_SUCCESS; +}