From 12bb8b9b716147717bc667a8ac94dd34b5461331 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 1 Oct 2021 16:01:48 +0200 Subject: [PATCH] Squashed 'src/c-siphash/' content from commit eb87a9c4a5b0 git-subtree-dir: src/c-siphash git-subtree-split: eb87a9c4a5b0441ede073597253e1d0b7785e6be --- .editorconfig | 11 ++ .github/workflows/ci.yml | 21 ++++ .gitmodules | 3 + AUTHORS | 37 ++++++ NEWS.md | 11 ++ README.md | 53 +++++++++ meson.build | 19 +++ src/c-siphash.c | 245 +++++++++++++++++++++++++++++++++++++++ src/c-siphash.h | 60 ++++++++++ src/libcsiphash.sym | 9 ++ src/meson.build | 64 ++++++++++ src/test-api.c | 31 +++++ src/test-basic.c | 120 +++++++++++++++++++ subprojects/c-stdaux | 1 + 14 files changed, 685 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml create mode 100644 .gitmodules create mode 100644 AUTHORS create mode 100644 NEWS.md create mode 100644 README.md create mode 100644 meson.build create mode 100644 src/c-siphash.c create mode 100644 src/c-siphash.h create mode 100644 src/libcsiphash.sym create mode 100644 src/meson.build create mode 100644 src/test-api.c create mode 100644 src/test-basic.c create mode 160000 subprojects/c-stdaux diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..b10bb4f3f8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.{c,h}] +indent_style = space +indent_size = 8 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..b40abf690f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: Continuous Integration + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + ci: + name: CI with Default Configuration + runs-on: ubuntu-latest + + steps: + - name: Fetch Sources + uses: actions/checkout@v2 + - name: Run through C-Util CI + uses: c-util/automation/src/ci-c-util@v1 + with: + m32: 1 + valgrind: 1 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..a86b29fd1c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "subprojects/c-stdaux"] + path = subprojects/c-stdaux + url = https://github.com/c-util/c-stdaux.git diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000000..b59660c5ee --- /dev/null +++ b/AUTHORS @@ -0,0 +1,37 @@ +LICENSE: + This project is dual-licensed under both the Apache License, Version + 2.0, and the GNU Lesser General Public License, Version 2.1+. + +AUTHORS-ASL: + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +AUTHORS-LGPL: + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; If not, see . + +COPYRIGHT: (ordered alphabetically) + Copyright (C) 2015-2019 Red Hat, Inc. + +AUTHORS: (ordered alphabetically) + David Rheinsberg + Tom Gundersen diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000000..0110711ff7 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,11 @@ +# c-siphash - Streaming-capable SipHash Implementation + +## CHANGES WITH 1: + + * Initial release of c-siphash. + + * TBD + + Contributions from: TBD + + - TBD, YYYY-MM-DD diff --git a/README.md b/README.md new file mode 100644 index 0000000000..ba09041be4 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +c-siphash +========= + +Streaming-capable SipHash Implementation + +The c-siphash project is a standalone implementation of SipHash in Standard +ISO-C11. It provides a streaming-capable API to compute data hashes according +to the SipHash algorithm. For API documentation, see the c-siphash.h header +file, as well as the docbook comments for each function. + +### Project + + * **Website**: + * **Bug Tracker**: + +### Requirements + +The requirements for this project are: + + * `libc` (e.g., `glibc >= 2.16`) + +At build-time, the following software is required: + + * `meson >= 0.41` + * `pkg-config >= 0.29` + +### Build + +The meson build-system is used for this project. Contact upstream +documentation for detailed help. In most situations the following +commands are sufficient to build and install from source: + +```sh +mkdir build +cd build +meson setup .. +ninja +meson test +ninja install +``` + +No custom configuration options are available. + +### Repository: + + - **web**: + - **https**: `https://github.com/c-util/c-siphash.git` + - **ssh**: `git@github.com:c-util/c-siphash.git` + +### License: + + - **Apache-2.0** OR **LGPL-2.1-or-later** + - See AUTHORS file for details. diff --git a/meson.build b/meson.build new file mode 100644 index 0000000000..2b07b019b3 --- /dev/null +++ b/meson.build @@ -0,0 +1,19 @@ +project( + 'c-siphash', + 'c', + version: '1', + license: 'Apache', + default_options: [ + 'c_std=c11' + ], +) +project_description = 'Streaming-capable SipHash Implementation' + +add_project_arguments('-D_GNU_SOURCE', language: 'c') +mod_pkgconfig = import('pkgconfig') + +sub_cstdaux = subproject('c-stdaux') + +dep_cstdaux = sub_cstdaux.get_variable('libcstdaux_dep') + +subdir('src') diff --git a/src/c-siphash.c b/src/c-siphash.c new file mode 100644 index 0000000000..720e403a43 --- /dev/null +++ b/src/c-siphash.c @@ -0,0 +1,245 @@ +/* + * SipHash Implementation + * + * For highlevel documentation of the API see the header file and the docbook + * comments. This implementation is based on the reference implementation of + * SipHash, written by Jean-Philippe Aumasson and Daniel J. Bernstein, and + * released to the Public Domain. + * + * So far, only SipHash24 is implemented, since there was no need for other + * parameters. However, adjusted c_siphash_append_X() and + * C_siphash_finalize_Y() can be easily provided, if required. + */ + +#include +#include +#include +#include "c-siphash.h" + +static inline uint64_t c_siphash_read_le64(const uint8_t bytes[8]) { + return ((uint64_t) bytes[0]) | + (((uint64_t) bytes[1]) << 8) | + (((uint64_t) bytes[2]) << 16) | + (((uint64_t) bytes[3]) << 24) | + (((uint64_t) bytes[4]) << 32) | + (((uint64_t) bytes[5]) << 40) | + (((uint64_t) bytes[6]) << 48) | + (((uint64_t) bytes[7]) << 56); +} + +static inline uint64_t c_siphash_rotate_left(uint64_t x, uint8_t b) { + return (x << b) | (x >> (64 - b)); +} + +static inline void c_siphash_sipround(CSipHash *state) { + state->v0 += state->v1; + state->v1 = c_siphash_rotate_left(state->v1, 13); + state->v1 ^= state->v0; + state->v0 = c_siphash_rotate_left(state->v0, 32); + state->v2 += state->v3; + state->v3 = c_siphash_rotate_left(state->v3, 16); + state->v3 ^= state->v2; + state->v0 += state->v3; + state->v3 = c_siphash_rotate_left(state->v3, 21); + state->v3 ^= state->v0; + state->v2 += state->v1; + state->v1 = c_siphash_rotate_left(state->v1, 17); + state->v1 ^= state->v2; + state->v2 = c_siphash_rotate_left(state->v2, 32); +} + +/** + * c_siphash_init() - initialize siphash context + * @state: context object + * @seed: 128bit seed + * + * This initializes the siphash state context. Once initialized, it can be used + * to hash arbitrary input. To feed data into it, use c_siphash_append(). To get + * the final hash, use c_siphash_finalize(). + * + * Note that the siphash context does not allocate state. There is no need to + * deserialize it before releasing its backing memory. + * + * The hashes generated by this context change depending on the seed. Every + * user is highly inclined to provide their unique seed. If no stable hashes + * are needed, a random seed will do fine. + * + * Right now, only SipHash24 is supported. Other SipHash parameters can be + * easily added if required. + */ +_c_public_ void c_siphash_init(CSipHash *state, const uint8_t seed[16]) { + uint64_t k0, k1; + + k0 = c_siphash_read_le64(seed); + k1 = c_siphash_read_le64(seed + 8); + + *state = (CSipHash) { + /* + * Default seed is taken from the reference implementation + * of SipHash24 ("somepseudorandomlygeneratedbytes"). Callers + * are still recommended to provide proper seeds themselves. + */ + .v0 = 0x736f6d6570736575ULL ^ k0, + .v1 = 0x646f72616e646f6dULL ^ k1, + .v2 = 0x6c7967656e657261ULL ^ k0, + .v3 = 0x7465646279746573ULL ^ k1, + .padding = 0, + .n_bytes = 0, + }; +} + +/** + * c_siphash_append() - hash stream of data + * @state: context object + * @bytes: array of input bytes + * @n_bytes: number of input bytes + * + * This feeds an array of bytes into the SipHash state machine. This is a + * streaming-capable API. That is, the resulting hash is the same, regardless + * of the way you chunk the input. + * This function simply feeds the given bytes into the SipHash state machine. + * It does not produce a final hash. You can call this function many times to + * append more data. To retrieve the final hash, call c_siphash_finalize(). + * + * Note that this implementation works best when used with chunk-sizes of + * multiples of 64bit (8-bytes). This is not a requirement, though. + */ +_c_public_ void c_siphash_append(CSipHash *state, const uint8_t *bytes, size_t n_bytes) { + const uint8_t *end = bytes + n_bytes; + size_t left = state->n_bytes & 7; + uint64_t m; + + state->n_bytes += n_bytes; + + /* + * SipHash operates on 64bit chunks. If the previous blob was not a + * multiple of 64bit in length, we must operate on single bytes. + */ + if (left > 0) { + for ( ; bytes < end && left < 8; ++bytes, ++left) + state->padding |= ((uint64_t) *bytes) << (left * 8); + + if (bytes == end && left < 8) + return; + + state->v3 ^= state->padding; + c_siphash_sipround(state); + c_siphash_sipround(state); + state->v0 ^= state->padding; + + state->padding = 0; + } + + end -= (state->n_bytes % sizeof(uint64_t)); + + /* + * We are now guaranteed to be at a 64bit state boundary. Hence, we can + * operate in 64bit chunks on all input. This is much faster than the + * one-byte-at-a-time loop. + */ + for ( ; bytes < end; bytes += 8) { + m = c_siphash_read_le64(bytes); + + state->v3 ^= m; + c_siphash_sipround(state); + c_siphash_sipround(state); + state->v0 ^= m; + } + + /* + * Now that we hashed as much 64bit chunks as possible, we need to + * remember the remaining trailing bytes. Keep them in @padding so the + * next round (or the finalizer) get access to them. + */ + left = state->n_bytes & 7; + switch (left) { + case 7: + state->padding |= ((uint64_t) bytes[6]) << 48; + /* fallthrough */ + case 6: + state->padding |= ((uint64_t) bytes[5]) << 40; + /* fallthrough */ + case 5: + state->padding |= ((uint64_t) bytes[4]) << 32; + /* fallthrough */ + case 4: + state->padding |= ((uint64_t) bytes[3]) << 24; + /* fallthrough */ + case 3: + state->padding |= ((uint64_t) bytes[2]) << 16; + /* fallthrough */ + case 2: + state->padding |= ((uint64_t) bytes[1]) << 8; + /* fallthrough */ + case 1: + state->padding |= ((uint64_t) bytes[0]); + /* fallthrough */ + case 0: + break; + } +} + +/** + * c_siphash_finalize() - finalize hash + * @state: context object + * + * This produces the final SipHash24 hash value for the given SipHash state. + * That is, it produces a hash value corresponding to the SipHash24 hash value + * of the concatenated byte-array passed into @state via c_siphash_append(). + * + * Note that @state has an invalid state after this function returns. To reuse + * it for another hash, you must call c_siphash_init() again. If you don't need + * the object, anymore, you can release it any time. There is no need to + * destroy the object explicitly. + * + * Return: 64bit hash value + */ +_c_public_ uint64_t c_siphash_finalize(CSipHash *state) { + uint64_t b; + + b = state->padding | (((uint64_t) state->n_bytes) << 56); + + state->v3 ^= b; + c_siphash_sipround(state); + c_siphash_sipround(state); + state->v0 ^= b; + + state->v2 ^= 0xff; + + c_siphash_sipround(state); + c_siphash_sipround(state); + c_siphash_sipround(state); + c_siphash_sipround(state); + + return state->v0 ^ state->v1 ^ state->v2 ^ state->v3; +} + +/** + * c_siphash_hash() - hash data blob + * @seed: 128bit seed + * @bytes: byte array to hash + * @n_bytes: number of bytes to hash + * + * This produces the SipHash24 hash value for the input @bytes / @n_bytes, + * using the seed provided as @seed. + * + * This is functionally equivalent to: + * + * CSipHash state; + * c_siphash_init(&state, seed); + * c_siphash_append(&state, bytes, n_bytes); + * return c_siphash_finalize(&state); + * + * Unlike the streaming API, this is a one-shot call suitable for any data that + * is available in-memory at the same time. + * + * Return: 64bit hash value + */ +_c_public_ uint64_t c_siphash_hash(const uint8_t seed[16], const uint8_t *bytes, size_t n_bytes) { + CSipHash state; + + c_siphash_init(&state, seed); + c_siphash_append(&state, bytes, n_bytes); + + return c_siphash_finalize(&state); +} diff --git a/src/c-siphash.h b/src/c-siphash.h new file mode 100644 index 0000000000..c0cfc1e084 --- /dev/null +++ b/src/c-siphash.h @@ -0,0 +1,60 @@ +#pragma once + +/** + * Streaming-capable SipHash Implementation + * + * This library provides a SipHash API, that is fully implemented in ISO-C11 + * and has no external dependencies. The library performs no memory allocation, + * and provides a streaming API where data to be hashed can be appended + * piecemeal. + * + * A streaming-capable hash state is represented by the "CSipHash" structure, + * which should be initialized with a unique seed before use. If streaming + * capabilities are not required, c_siphash_hash() provides a simple one-shot + * API. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct CSipHash CSipHash; + +/** + * struct CSipHash - SipHash state object + * @v0-@v3: internal state + * @padding: pending bytes that were not a multiple of 8 + * @n_bytes: number of hashed bytes + * + * The state of an inflight hash is represenetd by a CSipHash object. Before + * hashing, it must be initialized with c_siphash_init(), providing a unique + * random hash seed. Data is hashed by appending it to the state object, using + * c_siphash_append(). Finally, the hash is read out by calling + * c_siphash_finalize(). + * + * This state object has no allocated resources. It is safe to release its + * backing memory without any further action. + */ +struct CSipHash { + uint64_t v0; + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t padding; + size_t n_bytes; +}; + +#define C_SIPHASH_NULL {} + +void c_siphash_init(CSipHash *state, const uint8_t seed[16]); +void c_siphash_append(CSipHash *state, const uint8_t *bytes, size_t n_bytes); +uint64_t c_siphash_finalize(CSipHash *state); + +uint64_t c_siphash_hash(const uint8_t seed[16], const uint8_t *bytes, size_t n_bytes); + +#ifdef __cplusplus +} +#endif diff --git a/src/libcsiphash.sym b/src/libcsiphash.sym new file mode 100644 index 0000000000..5ab16d6115 --- /dev/null +++ b/src/libcsiphash.sym @@ -0,0 +1,9 @@ +LIBCSIPHASH_1 { +global: + c_siphash_init; + c_siphash_append; + c_siphash_finalize; + c_siphash_hash; +local: + *; +}; diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000000..7ee9c601e7 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,64 @@ +# +# target: libcsiphash.so +# + +libcsiphash_symfile = join_paths(meson.current_source_dir(), 'libcsiphash.sym') + +libcsiphash_deps = [ + dep_cstdaux, +] + +libcsiphash_private = static_library( + 'csiphash-private', + [ + 'c-siphash.c', + ], + c_args: [ + '-fvisibility=hidden', + '-fno-common', + ], + dependencies: libcsiphash_deps, + pic: true, +) + +libcsiphash_shared = shared_library( + 'csiphash', + objects: libcsiphash_private.extract_all_objects(), + dependencies: libcsiphash_deps, + install: not meson.is_subproject(), + soversion: 0, + link_depends: libcsiphash_symfile, + link_args: [ + '-Wl,--no-undefined', + '-Wl,--version-script=@0@'.format(libcsiphash_symfile), + ], +) + +libcsiphash_dep = declare_dependency( + include_directories: include_directories('.'), + link_with: libcsiphash_private, + dependencies: libcsiphash_deps, + version: meson.project_version(), +) + +if not meson.is_subproject() + install_headers('c-siphash.h') + + mod_pkgconfig.generate( + libraries: libcsiphash_shared, + version: meson.project_version(), + name: 'libcsiphash', + filebase: 'libcsiphash', + description: project_description, + ) +endif + +# +# target: test-* +# + +test_api = executable('test-api', ['test-api.c'], link_with: libcsiphash_shared) +test('API Symbol Visibility', test_api) + +test_basic = executable('test-basic', ['test-basic.c'], dependencies: libcsiphash_dep) +test('Basic API Behavior', test_basic) diff --git a/src/test-api.c b/src/test-api.c new file mode 100644 index 0000000000..8075a17ff4 --- /dev/null +++ b/src/test-api.c @@ -0,0 +1,31 @@ +/* + * Tests for Public API + * This test, unlikely the others, is linked against the real, distributed, + * shared library. Its sole purpose is to test for symbol availability. + */ + +#undef NDEBUG +#include +#include +#include +#include +#include "c-siphash.h" + +static void test_api(void) { + CSipHash state = C_SIPHASH_NULL; + uint8_t seed[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + uint64_t hash1, hash2; + + c_siphash_init(&state, seed); + c_siphash_append(&state, NULL, 0); + hash1 = c_siphash_finalize(&state); + assert(hash1 == 12552310112479190712ULL); + + hash2 = c_siphash_hash(seed, NULL, 0); + assert(hash1 == hash2); +} + +int main(int argc, char **argv) { + test_api(); + return 0; +} diff --git a/src/test-basic.c b/src/test-basic.c new file mode 100644 index 0000000000..6b80d0cdfa --- /dev/null +++ b/src/test-basic.c @@ -0,0 +1,120 @@ +/* + * Tests for Basic Hash Operations + * This test does some basic hash operations and verifies their correctness. It + * breaks up the data to be hashed in various ways to make sure it is stable. + */ + +#undef NDEBUG +#include +#include +#include +#include +#include +#include "c-siphash.h" + +/* See https://131002.net/siphash/siphash.pdf, Appendix A. */ +static void do_reference_test(const uint8_t *in, size_t len, const uint8_t *key) { + CSipHash state = {}; + uint64_t out; + unsigned i, j; + + /* verify the internal state as given in the above paper */ + c_siphash_init(&state, key); + c_assert(state.v0 == 0x7469686173716475); + c_assert(state.v1 == 0x6b617f6d656e6665); + c_assert(state.v2 == 0x6b7f62616d677361); + c_assert(state.v3 == 0x7b6b696e727e6c7b); + c_siphash_append(&state, in, len); + c_assert(state.v0 == 0x4a017198de0a59e0); + c_assert(state.v1 == 0x0d52f6f62a4f59a4); + c_assert(state.v2 == 0x634cb3577b01fd3d); + c_assert(state.v3 == 0xa5224d6f55c7d9c8); + out = c_siphash_finalize(&state); + c_assert(out == 0xa129ca6149be45e5); + c_assert(state.v0 == 0xf6bcd53893fecff1); + c_assert(state.v1 == 0x54b9964c7ea0d937); + c_assert(state.v2 == 0x1b38329c099bb55a); + c_assert(state.v3 == 0x1814bb89ad7be679); + + /* verify that decomposing the input in three chunks gives the + same result */ + for (i = 0; i < len; i++) { + for (j = i; j < len; j++) { + c_siphash_init(&state, key); + c_siphash_append(&state, in, i); + c_siphash_append(&state, &in[i], j - i); + c_siphash_append(&state, &in[j], len - j); + out = c_siphash_finalize(&state); + c_assert(out == 0xa129ca6149be45e5); + } + } + + /* verify c_siphash_hash() produces the same result */ + c_assert(out == c_siphash_hash(key, in, len)); +} + +static void test_reference(void) { + + const uint8_t in[15] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e }; + const uint8_t key[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; + uint8_t in_buf[20]; + + /* Test with same input but different alignments. */ + memcpy(in_buf, in, sizeof(in)); + do_reference_test(in_buf, sizeof(in), key); + memcpy(in_buf + 1, in, sizeof(in)); + do_reference_test(in_buf + 1, sizeof(in), key); + memcpy(in_buf + 2, in, sizeof(in)); + do_reference_test(in_buf + 2, sizeof(in), key); + memcpy(in_buf + 4, in, sizeof(in)); + do_reference_test(in_buf + 4, sizeof(in), key); +} + +static void test_short_hashes(void) { + const uint8_t one[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 }; + const uint8_t key[16] = { 0x22, 0x24, 0x41, 0x22, 0x55, 0x77, 0x88, 0x07, + 0x23, 0x09, 0x23, 0x14, 0x0c, 0x33, 0x0e, 0x0f}; + uint8_t two[sizeof one] = {}; + + CSipHash state1 = {}, state2 = {}; + unsigned i, j; + + c_siphash_init(&state1, key); + c_siphash_init(&state2, key); + + /* hashing 1, 2, 3, 4, 5, ..., 16 bytes, with the byte after the buffer different */ + for (i = 1; i <= sizeof one; i++) { + c_siphash_append(&state1, one, i); + + two[i-1] = one[i-1]; + c_siphash_append(&state2, two, i); + + c_assert(memcmp(&state1, &state2, sizeof state1) == 0); + } + + /* hashing n and 1, n and 2, n and 3, ..., n-1 and 1, n-2 and 2, ... */ + for (i = sizeof one; i > 0; i--) { + memset(two, 0, sizeof(two)); + + for (j = 1; j <= sizeof one; j++) { + c_siphash_append(&state1, one, i); + c_siphash_append(&state1, one, j); + + c_siphash_append(&state2, one, i); + two[j-1] = one[j-1]; + c_siphash_append(&state2, two, j); + + c_assert(memcmp(&state1, &state2, sizeof state1) == 0); + } + } +} + +int main(int argc, char *argv[]) { + test_reference(); + test_short_hashes(); + + return 0; +} diff --git a/subprojects/c-stdaux b/subprojects/c-stdaux new file mode 160000 index 0000000000..c5f166d02f --- /dev/null +++ b/subprojects/c-stdaux @@ -0,0 +1 @@ +Subproject commit c5f166d02ff68af5cdcbad1bdcea2cb134e34ce4